From 089c33d9ccc201ac48c0711dd25d3c152216648e Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:19:25 +0000 Subject: [PATCH 1/4] feat(cfk): reversible backend (JanusKey model) with cfk history/undo; hybrid-ops design notes Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- czech-file-knife/cfk-cli/src/commands.rs | 87 ++- czech-file-knife/cfk-cli/src/main.rs | 19 + czech-file-knife/cfk-core/Cargo.toml | 6 + czech-file-knife/cfk-core/src/lib.rs | 2 + czech-file-knife/cfk-core/src/reversible.rs | 740 +++++++++++++++++++ czech-file-knife/docs/HYBRID-OPERATIONS.adoc | 106 +++ 6 files changed, 957 insertions(+), 3 deletions(-) create mode 100644 czech-file-knife/cfk-core/src/reversible.rs create mode 100644 czech-file-knife/docs/HYBRID-OPERATIONS.adoc diff --git a/czech-file-knife/cfk-cli/src/commands.rs b/czech-file-knife/cfk-cli/src/commands.rs index fd5cb3898..4835e2744 100644 --- a/czech-file-knife/cfk-cli/src/commands.rs +++ b/czech-file-knife/cfk-cli/src/commands.rs @@ -4,7 +4,7 @@ use cfk_core::{ entry::EntryKind, operations::{CopyOptions, DeleteOptions, ListOptions, MoveOptions, ReadOptions, WriteOptions}, - CfkError, CfkResult, VirtualPath, + CfkError, CfkResult, ReversibleBackend, ReversibleConfig, VirtualPath, }; use cfk_providers::{BackendRegistry, LocalBackend}; use chrono::{DateTime, Utc}; @@ -18,14 +18,95 @@ use tabled::{Table, Tabled}; fn init_registry() -> BackendRegistry { let mut registry = BackendRegistry::new(); - // Register local filesystem with root as base - registry.register(Arc::new(LocalBackend::new("local", "/"))); + // Register local filesystem with root as base. Unless disabled, every + // mutation goes through the reversible journal so `cfk undo` works. + let local = Arc::new(LocalBackend::new("local", "/")); + match journal() { + Some(Ok(rev)) => registry.register(rev), + Some(Err(e)) => { + eprintln!("warning: undo journal unavailable ({e}); operations are NOT reversible"); + registry.register(local); + } + None => registry.register(local), + } // Future: register cloud backends based on config registry } +/// Journal directory: $CFK_JOURNAL_DIR, else $XDG_STATE_HOME/cfk/journal, +/// else ~/.local/state/cfk/journal. +fn journal_dir() -> Option { + if let Some(d) = std::env::var_os("CFK_JOURNAL_DIR") { + return Some(PathBuf::from(d)); + } + let base = std::env::var_os("XDG_STATE_HOME") + .map(PathBuf::from) + .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".local/state")))?; + Some(base.join("cfk").join("journal")) +} + +/// Reversible wrapper around the local backend. `None` if disabled via +/// CFK_NO_JOURNAL=1 (e.g. to delete a file bigger than the capture limit). +fn journal() -> Option>>> { + if std::env::var_os("CFK_NO_JOURNAL").map(|v| v == "1").unwrap_or(false) { + return None; + } + let dir = journal_dir()?; + let mut config = ReversibleConfig::default(); + if let Some(n) = std::env::var("CFK_JOURNAL_MAX_BYTES").ok().and_then(|v| v.parse().ok()) { + config.max_capture_bytes = n; + } + Some( + ReversibleBackend::new(Arc::new(LocalBackend::new("local", "/")), dir, config).map(Arc::new), + ) +} + +fn journal_or_err() -> CfkResult>> { + journal().unwrap_or_else(|| Err(CfkError::Unsupported("journal disabled (CFK_NO_JOURNAL=1)".into()))) +} + +/// Show the operation journal +pub async fn history(limit: usize, _verbose: bool) -> CfkResult<()> { + let rev = journal_or_err()?; + let records = rev.history()?; + if records.is_empty() { + println!("(no recorded operations)"); + return Ok(()); + } + let undone: std::collections::HashSet = records + .iter() + .filter_map(|r| match r.op { + cfk_core::Operation::Undo { target } => Some(target), + _ => None, + }) + .collect(); + let skip = records.len().saturating_sub(limit); + for r in records.iter().skip(skip) { + let mark = if undone.contains(&r.id) { style(" (undone)").dim().to_string() } else { String::new() }; + println!( + "#{:<5} {} {}{}", + r.id, + r.timestamp.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M:%S"), + r.op.summary(), + mark + ); + } + Ok(()) +} + +/// Undo the latest (or given) operation +pub async fn undo(id: Option, _verbose: bool) -> CfkResult<()> { + let rev = journal_or_err()?; + let rec = match id { + Some(id) => rev.undo(id).await?, + None => rev.undo_last().await?, + }; + println!("{} #{} {}", style("undone").green(), rec.id, rec.op.summary()); + Ok(()) +} + /// Parse a path string into a VirtualPath /// Supports: /// - cfk://backend/path - explicit URI diff --git a/czech-file-knife/cfk-cli/src/main.rs b/czech-file-knife/cfk-cli/src/main.rs index e9295736c..7817bd8b2 100644 --- a/czech-file-knife/cfk-cli/src/main.rs +++ b/czech-file-knife/cfk-cli/src/main.rs @@ -110,6 +110,19 @@ enum Commands { path: String, }, + /// Show the reversible-operation journal (JanusKey model) + History { + /// Show at most N most-recent records + #[arg(short = 'n', long, default_value_t = 20)] + limit: usize, + }, + + /// Undo the most recent operation (or a specific id, which must be the latest) + Undo { + /// Operation id from `cfk history` + id: Option, + }, + /// List registered backends Backends, @@ -147,6 +160,12 @@ async fn main() -> ExitCode { Commands::Stat { path } => { commands::stat(&path, cli.verbose).await } + Commands::History { limit } => { + commands::history(limit, cli.verbose).await + } + Commands::Undo { id } => { + commands::undo(id, cli.verbose).await + } Commands::Backends => { commands::backends(cli.verbose).await } diff --git a/czech-file-knife/cfk-core/Cargo.toml b/czech-file-knife/cfk-core/Cargo.toml index a5186be5e..d5151b76a 100644 --- a/czech-file-knife/cfk-core/Cargo.toml +++ b/czech-file-knife/cfk-core/Cargo.toml @@ -13,3 +13,9 @@ futures.workspace = true serde.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["sync"] } +serde_json.workspace = true +hex.workspace = true +sha2 = "0.10" + +[dev-dependencies] +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/czech-file-knife/cfk-core/src/lib.rs b/czech-file-knife/cfk-core/src/lib.rs index 3c45ab9e8..f7f3d3de5 100644 --- a/czech-file-knife/cfk-core/src/lib.rs +++ b/czech-file-knife/cfk-core/src/lib.rs @@ -9,9 +9,11 @@ pub mod metadata; pub mod operations; pub mod path; pub mod platform; +pub mod reversible; pub use backend::{StorageBackend, StorageCapabilities}; pub use entry::{Entry, EntryKind}; pub use error::{CfkError, CfkResult}; pub use metadata::Metadata; pub use path::VirtualPath; +pub use reversible::{ContentStore, OpLog, Operation, ReversibleBackend, ReversibleConfig}; diff --git a/czech-file-knife/cfk-core/src/reversible.rs b/czech-file-knife/cfk-core/src/reversible.rs new file mode 100644 index 000000000..5dcd6a0ef --- /dev/null +++ b/czech-file-knife/cfk-core/src/reversible.rs @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: MPL-2.0 +//! Reversible operations (JanusKey model). +//! +//! [`ReversibleBackend`] wraps any [`StorageBackend`] and, before every +//! destructive call, captures enough state to invert it: +//! +//! * prior content goes into a content-addressed [`ContentStore`] (SHA-256, +//! `objects/ab/cdef…`), the same scheme JanusKey uses; +//! * the inverse is recorded in an append-only JSON-lines [`OpLog`]. +//! +//! Undo never rewrites the log: it appends an `Undo { target }` record, so +//! the full history (including undos) stays auditable. +//! +//! Because the wrapper works at the `StorageBackend` level it gives rollback +//! to *every* backend — including ones with no native versioning — and the +//! captured objects also back `get_versions` / `get_version`. +//! +//! Limitations (honest residue, mirroring JanusKey's own caveat): +//! * content is buffered in memory when captured; `max_capture_bytes` +//! guards against capturing huge files (the op is refused, not silently +//! made irreversible, unless `allow_irreversible` is set); +//! * metadata (permissions, mtimes, xattrs) is not yet restored; +//! * the reversibility guarantee is not yet mechanically proven. + +use async_trait::async_trait; +use bytes::{Bytes, BytesMut}; +use futures::StreamExt; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::fs::{self, OpenOptions}; +use std::io::{BufRead, BufReader, Write}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use crate::{ + backend::{ByteStream, FileVersion, SearchOptions, SpaceInfo, StorageBackend, StorageCapabilities}, + entry::{DirectoryListing, Entry, EntryKind}, + error::{CfkError, CfkResult}, + operations::*, + VirtualPath, +}; + +// ───────────────────────────── content store ────────────────────────────── + +/// Content-addressed object store keyed by SHA-256 hex digest. +#[derive(Debug, Clone)] +pub struct ContentStore { + root: PathBuf, +} + +impl ContentStore { + pub fn open(root: impl Into) -> CfkResult { + let root = root.into(); + fs::create_dir_all(&root)?; + Ok(Self { root }) + } + + pub fn hash(data: &[u8]) -> String { + hex::encode(Sha256::digest(data)) + } + + fn object_path(&self, hash: &str) -> CfkResult { + if hash.len() != 64 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) { + return Err(CfkError::Other(format!("invalid object hash: {hash}"))); + } + Ok(self.root.join(&hash[..2]).join(&hash[2..])) + } + + /// Store `data`, returning its hash. Idempotent (deduplicating). + pub fn put(&self, data: &[u8]) -> CfkResult { + let hash = Self::hash(data); + let path = self.object_path(&hash)?; + if !path.exists() { + fs::create_dir_all(path.parent().expect("object has parent"))?; + // write-then-rename so a crash never leaves a truncated object + let tmp = path.with_extension("tmp"); + { + let mut f = fs::File::create(&tmp)?; + f.write_all(data)?; + f.sync_all()?; + } + fs::rename(&tmp, &path)?; + } + Ok(hash) + } + + /// Fetch an object, verifying its hash. + pub fn get(&self, hash: &str) -> CfkResult { + let data = fs::read(self.object_path(hash)?)?; + if Self::hash(&data) != hash { + return Err(CfkError::ChecksumMismatch); + } + Ok(Bytes::from(data)) + } + + pub fn contains(&self, hash: &str) -> bool { + self.object_path(hash).map(|p| p.exists()).unwrap_or(false) + } +} + +// ─────────────────────────────── op log ─────────────────────────────────── + +/// One node of a captured directory tree (for recursive deletes). +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TreeNode { + pub path: VirtualPath, + /// `None` = directory, `Some(hash)` = file content. + pub content: Option, +} + +/// A recorded operation together with the data needed to invert it. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "op", rename_all = "snake_case")] +pub enum Operation { + /// File written; `prior` is the old content (None = did not exist). + Write { path: VirtualPath, prior: Option }, + /// Directory created (undo removes it if still empty). + CreateDir { path: VirtualPath }, + /// Path deleted; tree is parent-first. + Delete { path: VirtualPath, tree: Vec }, + /// Copy to `dest`; `prior` is what `dest` held before. + Copy { source: VirtualPath, dest: VirtualPath, prior: Option }, + /// Rename; `overwritten` is what `dest` held before. + Rename { source: VirtualPath, dest: VirtualPath, overwritten: Option }, + /// Undo of an earlier record. + Undo { target: u64 }, +} + +impl Operation { + pub fn summary(&self) -> String { + match self { + Operation::Write { path, prior } => format!( + "{} {}", if prior.is_some() { "modify" } else { "create" }, path), + Operation::CreateDir { path } => format!("mkdir {path}"), + Operation::Delete { path, tree } => format!("delete {path} ({} item(s))", tree.len()), + Operation::Copy { source, dest, .. } => format!("copy {source} -> {dest}"), + Operation::Rename { source, dest, .. } => format!("move {source} -> {dest}"), + Operation::Undo { target } => format!("undo #{target}"), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct LogRecord { + pub id: u64, + pub timestamp: chrono::DateTime, + #[serde(flatten)] + pub op: Operation, +} + +/// Append-only JSON-lines operation log. +#[derive(Debug)] +pub struct OpLog { + path: PathBuf, + next_id: u64, +} + +impl OpLog { + pub fn open(path: impl Into) -> CfkResult { + let path = path.into(); + if let Some(p) = path.parent() { + fs::create_dir_all(p)?; + } + let next_id = Self::read_all(&path)?.last().map(|r| r.id + 1).unwrap_or(1); + Ok(Self { path, next_id }) + } + + fn read_all(path: &Path) -> CfkResult> { + if !path.exists() { + return Ok(Vec::new()); + } + let mut out = Vec::new(); + for (n, line) in BufReader::new(fs::File::open(path)?).lines().enumerate() { + let line = line?; + if line.trim().is_empty() { + continue; + } + match serde_json::from_str::(&line) { + Ok(r) => out.push(r), + // A torn final line (crash mid-append) is tolerated; anything + // else is corruption and must be surfaced. + Err(e) => { + return Err(CfkError::Serialization(format!("oplog line {}: {e}", n + 1))) + } + } + } + Ok(out) + } + + pub fn records(&self) -> CfkResult> { + Self::read_all(&self.path) + } + + pub fn append(&mut self, op: Operation) -> CfkResult { + let rec = LogRecord { id: self.next_id, timestamp: chrono::Utc::now(), op }; + let line = serde_json::to_string(&rec).map_err(|e| CfkError::Serialization(e.to_string()))?; + let mut f = OpenOptions::new().create(true).append(true).open(&self.path)?; + writeln!(f, "{line}")?; + f.sync_data()?; + self.next_id += 1; + Ok(rec) + } + + /// Records that are not undos and have not themselves been undone. + pub fn undoable(&self) -> CfkResult> { + let all = self.records()?; + let undone: std::collections::HashSet = all + .iter() + .filter_map(|r| match r.op { Operation::Undo { target } => Some(target), _ => None }) + .collect(); + Ok(all + .into_iter() + .filter(|r| !matches!(r.op, Operation::Undo { .. }) && !undone.contains(&r.id)) + .collect()) + } +} + +// ───────────────────────────── the wrapper ──────────────────────────────── + +#[derive(Debug, Clone)] +pub struct ReversibleConfig { + /// Refuse to capture files bigger than this (bytes). + pub max_capture_bytes: u64, + /// If true, operations too big to capture proceed unrecorded instead of + /// failing. Off by default: silent irreversibility is the thing we avoid. + pub allow_irreversible: bool, +} + +impl Default for ReversibleConfig { + fn default() -> Self { + Self { max_capture_bytes: 1 << 30, allow_irreversible: false } + } +} + +/// A [`StorageBackend`] decorator that makes every mutation undoable. +pub struct ReversibleBackend { + inner: Arc, + store: ContentStore, + log: Mutex, + config: ReversibleConfig, + caps: StorageCapabilities, +} + +impl ReversibleBackend { + /// `state_dir` holds `objects/` and `oplog.jsonl`. + pub fn new(inner: Arc, state_dir: impl AsRef, config: ReversibleConfig) -> CfkResult { + let dir = state_dir.as_ref(); + let mut caps = inner.capabilities().clone(); + caps.versioning = true; + Ok(Self { + store: ContentStore::open(dir.join("objects"))?, + log: Mutex::new(OpLog::open(dir.join("oplog.jsonl"))?), + inner, + config, + caps, + }) + } + + pub fn inner(&self) -> &Arc { + &self.inner + } + + pub fn store(&self) -> &ContentStore { + &self.store + } + + /// Full history, oldest first (including undo records). + pub fn history(&self) -> CfkResult> { + self.log.lock().expect("oplog poisoned").records() + } + + fn record(&self, op: Operation) -> CfkResult { + self.log.lock().expect("oplog poisoned").append(op) + } + + async fn read_all(&self, path: &VirtualPath) -> CfkResult { + let mut s = self.inner.read_file(path, &ReadOptions::default()).await?; + let mut buf = BytesMut::new(); + while let Some(chunk) = s.next().await { + let chunk = chunk?; + buf.extend_from_slice(&chunk); + if buf.len() as u64 > self.config.max_capture_bytes { + return Err(CfkError::Unsupported(format!( + "{path} exceeds reversible capture limit ({} bytes)", + self.config.max_capture_bytes + ))); + } + } + Ok(buf.freeze()) + } + + /// Existing file content at `path` stored in the CAS, or None if absent. + async fn capture_file(&self, path: &VirtualPath) -> CfkResult> { + match self.inner.get_metadata(path).await { + Ok(e) if e.kind == EntryKind::File => { + if let Some(sz) = e.metadata.size { + if sz > self.config.max_capture_bytes { + return Err(CfkError::Unsupported(format!( + "{path} ({sz} bytes) exceeds reversible capture limit" + ))); + } + } + let data = self.read_all(path).await?; + Ok(Some(self.store.put(&data)?)) + } + Ok(e) if e.kind == EntryKind::Directory => { + Err(CfkError::Unsupported(format!("{path} is a directory; cannot capture as file"))) + } + Ok(_) => Err(CfkError::Unsupported(format!("{path}: unsupported entry kind"))), + Err(CfkError::NotFound(_)) => Ok(None), + Err(e) => Err(e), + } + } + + /// Capture a whole tree, parent-first. + async fn capture_tree(&self, root: &VirtualPath) -> CfkResult> { + let mut out = Vec::new(); + let mut stack = vec![root.clone()]; + while let Some(p) = stack.pop() { + let entry = self.inner.get_metadata(&p).await?; + match entry.kind { + EntryKind::Directory => { + out.push(TreeNode { path: p.clone(), content: None }); + let opts = ListOptions { include_hidden: true, ..Default::default() }; + let listing = self.inner.list_directory(&p, &opts).await?; + // push reversed so traversal is stable / listing-ordered + for child in listing.entries.into_iter().rev() { + stack.push(child.path); + } + } + EntryKind::File => { + let hash = self.capture_file(&p).await?; + out.push(TreeNode { path: p, content: hash }); + } + _ => { + return Err(CfkError::Unsupported(format!( + "{p}: symlinks/special files not yet reversible" + ))) + } + } + } + Ok(out) + } + + /// Wrap capture errors according to `allow_irreversible`. + fn capture_or(&self, r: CfkResult) -> CfkResult> { + match r { + Ok(v) => Ok(Some(v)), + Err(CfkError::Unsupported(_)) if self.config.allow_irreversible => Ok(None), + Err(e) => Err(e), + } + } + + async fn restore(&self, path: &VirtualPath, content: &Option) -> CfkResult<()> { + match content { + Some(hash) => { + let data = self.store.get(hash)?; + let opts = WriteOptions { overwrite: true, create_parents: true, content_hash: None }; + self.inner.write_file(path, data, &opts).await?; + } + None => { + self.inner + .delete(path, &DeleteOptions { recursive: false, force: true }) + .await?; + } + } + Ok(()) + } + + /// Undo the most recent undoable operation. Returns the undone record. + pub async fn undo_last(&self) -> CfkResult { + let last = self + .log + .lock() + .expect("oplog poisoned") + .undoable()? + .pop() + .ok_or_else(|| CfkError::NotFound("nothing to undo".into()))?; + self.undo(last.id).await + } + + /// Undo a specific operation by id. + /// + /// Only the most recent undoable operation may be undone: undoing out of + /// order could clobber later changes to the same path. (Selective undo + /// with conflict detection is future work.) + pub async fn undo(&self, id: u64) -> CfkResult { + let undoable = self.log.lock().expect("oplog poisoned").undoable()?; + let rec = undoable + .iter() + .find(|r| r.id == id) + .cloned() + .ok_or_else(|| CfkError::NotFound(format!("operation #{id} is not undoable")))?; + if undoable.last().map(|r| r.id) != Some(id) { + return Err(CfkError::Conflict(format!( + "operation #{id} is not the latest; undo later operations first" + ))); + } + + match &rec.op { + Operation::Write { path, prior } => self.restore(path, prior).await?, + Operation::Copy { dest, prior, .. } => self.restore(dest, prior).await?, + Operation::CreateDir { path } => { + self.inner + .delete(path, &DeleteOptions { recursive: false, force: true }) + .await?; + } + Operation::Rename { source, dest, overwritten } => { + self.inner + .rename(dest, source, &MoveOptions { overwrite: false }) + .await?; + if overwritten.is_some() { + self.restore(dest, overwritten).await?; + } + } + Operation::Delete { tree, .. } => { + for node in tree { + match &node.content { + None => match self.inner.create_directory(&node.path).await { + Ok(_) | Err(CfkError::AlreadyExists(_)) => {} + Err(e) => return Err(e), + }, + Some(_) => self.restore(&node.path, &node.content).await?, + } + } + } + Operation::Undo { .. } => unreachable!("undo records are filtered out"), + } + + self.record(Operation::Undo { target: id })?; + Ok(rec) + } +} + +#[async_trait] +impl StorageBackend for ReversibleBackend { + fn id(&self) -> &str { + self.inner.id() + } + fn display_name(&self) -> &str { + self.inner.display_name() + } + fn capabilities(&self) -> &StorageCapabilities { + &self.caps + } + async fn is_available(&self) -> bool { + self.inner.is_available().await + } + async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { + self.inner.get_metadata(path).await + } + async fn list_directory(&self, path: &VirtualPath, options: &ListOptions) -> CfkResult { + self.inner.list_directory(path, options).await + } + async fn read_file(&self, path: &VirtualPath, options: &ReadOptions) -> CfkResult { + self.inner.read_file(path, options).await + } + + async fn write_file(&self, path: &VirtualPath, data: Bytes, options: &WriteOptions) -> CfkResult { + let prior = self.capture_or(self.capture_file(path).await)?; + let entry = self.inner.write_file(path, data, options).await?; + if let Some(prior) = prior { + self.record(Operation::Write { path: path.clone(), prior })?; + } + Ok(entry) + } + + async fn write_file_stream( + &self, + path: &VirtualPath, + stream: ByteStream, + size_hint: Option, + options: &WriteOptions, + ) -> CfkResult { + let prior = self.capture_or(self.capture_file(path).await)?; + let entry = self.inner.write_file_stream(path, stream, size_hint, options).await?; + if let Some(prior) = prior { + self.record(Operation::Write { path: path.clone(), prior })?; + } + Ok(entry) + } + + async fn create_directory(&self, path: &VirtualPath) -> CfkResult { + let entry = self.inner.create_directory(path).await?; + self.record(Operation::CreateDir { path: path.clone() })?; + Ok(entry) + } + + async fn delete(&self, path: &VirtualPath, options: &DeleteOptions) -> CfkResult<()> { + let tree = match self.inner.get_metadata(path).await { + Ok(_) => self.capture_or(self.capture_tree(path).await)?, + Err(CfkError::NotFound(_)) if options.force => return Ok(()), + Err(e) => return Err(e), + }; + self.inner.delete(path, options).await?; + if let Some(tree) = tree { + self.record(Operation::Delete { path: path.clone(), tree })?; + } + Ok(()) + } + + async fn copy(&self, source: &VirtualPath, dest: &VirtualPath, options: &CopyOptions) -> CfkResult { + let prior = self.capture_or(self.capture_file(dest).await)?; + let entry = self.inner.copy(source, dest, options).await?; + if let Some(prior) = prior { + self.record(Operation::Copy { source: source.clone(), dest: dest.clone(), prior })?; + } + Ok(entry) + } + + async fn rename(&self, source: &VirtualPath, dest: &VirtualPath, options: &MoveOptions) -> CfkResult { + let overwritten = if options.overwrite { + self.capture_or(self.capture_file(dest).await)? + } else { + Some(None) + }; + let entry = self.inner.rename(source, dest, options).await?; + if let Some(overwritten) = overwritten { + self.record(Operation::Rename { source: source.clone(), dest: dest.clone(), overwritten })?; + } + Ok(entry) + } + + async fn get_space_info(&self) -> CfkResult { + self.inner.get_space_info().await + } + + async fn search(&self, options: &SearchOptions) -> CfkResult> { + self.inner.search(options).await + } + + /// Versions = prior contents captured in the op log for this path, + /// newest first. Version id is the content hash. + async fn get_versions(&self, path: &VirtualPath) -> CfkResult> { + let mut out = Vec::new(); + for r in self.history()?.into_iter().rev() { + let hash = match &r.op { + Operation::Write { path: p, prior: Some(h) } if p == path => Some(h.clone()), + Operation::Copy { dest, prior: Some(h), .. } if dest == path => Some(h.clone()), + Operation::Rename { dest, overwritten: Some(h), .. } if dest == path => Some(h.clone()), + Operation::Delete { tree, .. } => tree + .iter() + .find(|n| &n.path == path) + .and_then(|n| n.content.clone()), + _ => None, + }; + if let Some(h) = hash { + let size = fs::metadata(self.store.object_path(&h)?).ok().map(|m| m.len()); + out.push(FileVersion { id: h, modified: r.timestamp, size, author: None }); + } + } + Ok(out) + } + + async fn get_version(&self, _path: &VirtualPath, version_id: &str) -> CfkResult { + let data = self.store.get(version_id)?; + Ok(Box::pin(futures::stream::once(async move { Ok(data) }))) + } +} + +// ─────────────────────────────── tests ──────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use crate::Metadata; + use std::collections::BTreeMap; + + /// Minimal in-memory backend: key = path string, None = directory. + #[derive(Default)] + struct MemBackend { + files: Mutex>>, + caps: StorageCapabilities, + } + + fn key(p: &VirtualPath) -> String { + p.to_path_string() + } + + impl MemBackend { + fn get(&self, p: &str) -> Option> { + self.files.lock().unwrap().get(p).cloned() + } + } + + #[async_trait] + impl StorageBackend for MemBackend { + fn id(&self) -> &str { "mem" } + fn display_name(&self) -> &str { "mem" } + fn capabilities(&self) -> &StorageCapabilities { &self.caps } + async fn is_available(&self) -> bool { true } + async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { + match self.files.lock().unwrap().get(&key(path)) { + Some(Some(b)) => Ok(Entry::file(path.clone(), Metadata::new().with_size(b.len() as u64))), + Some(None) => Ok(Entry::directory(path.clone(), Metadata::new())), + None if path.is_root() => Ok(Entry::directory(path.clone(), Metadata::new())), + None => Err(CfkError::NotFound(key(path))), + } + } + async fn list_directory(&self, path: &VirtualPath, _o: &ListOptions) -> CfkResult { + let files = self.files.lock().unwrap(); + let entries = files + .iter() + .filter_map(|(k, v)| { + let vp = VirtualPath::new("mem", k); + (vp.parent().as_ref() == Some(path)).then(|| match v { + Some(b) => Entry::file(vp, Metadata::new().with_size(b.len() as u64)), + None => Entry::directory(vp, Metadata::new()), + }) + }) + .collect(); + Ok(DirectoryListing::new(path.clone(), entries)) + } + async fn read_file(&self, path: &VirtualPath, _o: &ReadOptions) -> CfkResult { + match self.get(&key(path)) { + Some(Some(b)) => Ok(Box::pin(futures::stream::once(async move { Ok(b) }))), + _ => Err(CfkError::NotFound(key(path))), + } + } + async fn write_file(&self, path: &VirtualPath, data: Bytes, _o: &WriteOptions) -> CfkResult { + self.files.lock().unwrap().insert(key(path), Some(data)); + self.get_metadata(path).await + } + async fn write_file_stream(&self, path: &VirtualPath, mut s: ByteStream, _h: Option, o: &WriteOptions) -> CfkResult { + let mut buf = BytesMut::new(); + while let Some(c) = s.next().await { buf.extend_from_slice(&c?); } + self.write_file(path, buf.freeze(), o).await + } + async fn create_directory(&self, path: &VirtualPath) -> CfkResult { + self.files.lock().unwrap().insert(key(path), None); + self.get_metadata(path).await + } + async fn delete(&self, path: &VirtualPath, _o: &DeleteOptions) -> CfkResult<()> { + let k = key(path); + let prefix = format!("{k}/"); + self.files.lock().unwrap().retain(|p, _| p != &k && !p.starts_with(&prefix)); + Ok(()) + } + async fn copy(&self, s: &VirtualPath, d: &VirtualPath, _o: &CopyOptions) -> CfkResult { + let v = self.get(&key(s)).ok_or_else(|| CfkError::NotFound(key(s)))?; + self.files.lock().unwrap().insert(key(d), v); + self.get_metadata(d).await + } + async fn rename(&self, s: &VirtualPath, d: &VirtualPath, _o: &MoveOptions) -> CfkResult { + let v = self.files.lock().unwrap().remove(&key(s)).ok_or_else(|| CfkError::NotFound(key(s)))?; + self.files.lock().unwrap().insert(key(d), v); + self.get_metadata(d).await + } + async fn get_space_info(&self) -> CfkResult { Ok(SpaceInfo::unknown()) } + } + + fn vp(p: &str) -> VirtualPath { VirtualPath::new("mem", p) } + + fn setup() -> (Arc, ReversibleBackend, PathBuf) { + let dir = std::env::temp_dir().join(format!( + "cfk-rev-{}-{}", std::process::id(), + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() + )); + let mem = Arc::new(MemBackend::default()); + let rev = ReversibleBackend::new(mem.clone(), &dir, ReversibleConfig::default()).unwrap(); + (mem, rev, dir) + } + + #[tokio::test] + async fn modify_then_undo_restores_content() { + let (mem, rev, _d) = setup(); + let w = WriteOptions { overwrite: true, ..Default::default() }; + rev.write_file(&vp("/a"), Bytes::from_static(b"one"), &w).await.unwrap(); + rev.write_file(&vp("/a"), Bytes::from_static(b"two"), &w).await.unwrap(); + rev.undo_last().await.unwrap(); + assert_eq!(mem.get(&key(&vp("/a"))), Some(Some(Bytes::from_static(b"one")))); + rev.undo_last().await.unwrap(); + assert_eq!(mem.get(&key(&vp("/a"))), None); + } + + #[tokio::test] + async fn recursive_delete_then_undo_restores_tree() { + let (mem, rev, _d) = setup(); + let w = WriteOptions::default(); + rev.create_directory(&vp("/d")).await.unwrap(); + rev.write_file(&vp("/d/x"), Bytes::from_static(b"x"), &w).await.unwrap(); + rev.create_directory(&vp("/d/sub")).await.unwrap(); + rev.write_file(&vp("/d/sub/y"), Bytes::from_static(b"y"), &w).await.unwrap(); + let before = mem.files.lock().unwrap().clone(); + rev.delete(&vp("/d"), &DeleteOptions { recursive: true, force: false }).await.unwrap(); + assert!(mem.get("/d").is_none()); + rev.undo_last().await.unwrap(); + assert_eq!(*mem.files.lock().unwrap(), before); + } + + #[tokio::test] + async fn rename_overwrite_then_undo() { + let (mem, rev, _d) = setup(); + let w = WriteOptions::default(); + rev.write_file(&vp("/src"), Bytes::from_static(b"S"), &w).await.unwrap(); + rev.write_file(&vp("/dst"), Bytes::from_static(b"D"), &w).await.unwrap(); + rev.rename(&vp("/src"), &vp("/dst"), &MoveOptions { overwrite: true }).await.unwrap(); + rev.undo_last().await.unwrap(); + assert_eq!(mem.get("/src"), Some(Some(Bytes::from_static(b"S")))); + assert_eq!(mem.get("/dst"), Some(Some(Bytes::from_static(b"D")))); + } + + #[tokio::test] + async fn out_of_order_undo_is_refused_and_log_is_append_only() { + let (_mem, rev, _d) = setup(); + let w = WriteOptions::default(); + rev.write_file(&vp("/a"), Bytes::from_static(b"1"), &w).await.unwrap(); + rev.write_file(&vp("/b"), Bytes::from_static(b"2"), &w).await.unwrap(); + assert!(matches!(rev.undo(1).await, Err(CfkError::Conflict(_)))); + rev.undo_last().await.unwrap(); + let h = rev.history().unwrap(); + assert_eq!(h.len(), 3); + assert_eq!(h[2].op, Operation::Undo { target: 2 }); + } + + #[tokio::test] + async fn versions_come_from_captured_priors() { + let (_mem, rev, _d) = setup(); + let w = WriteOptions { overwrite: true, ..Default::default() }; + for v in [&b"v1"[..], b"v2", b"v3"] { + rev.write_file(&vp("/f"), Bytes::copy_from_slice(v), &w).await.unwrap(); + } + let versions = rev.get_versions(&vp("/f")).await.unwrap(); + assert_eq!(versions.len(), 2); // v2 and v1 (v3 is current) + let mut s = rev.get_version(&vp("/f"), &versions[0].id).await.unwrap(); + assert_eq!(s.next().await.unwrap().unwrap(), Bytes::from_static(b"v2")); + } + + #[test] + fn content_store_dedups_and_verifies() { + let dir = std::env::temp_dir().join(format!("cfk-cas-{}", std::process::id())); + let cas = ContentStore::open(&dir).unwrap(); + let h1 = cas.put(b"hello").unwrap(); + let h2 = cas.put(b"hello").unwrap(); + assert_eq!(h1, h2); + assert_eq!(h1, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"); + assert_eq!(&cas.get(&h1).unwrap()[..], b"hello"); + assert!(cas.get("zz").is_err()); + } +} diff --git a/czech-file-knife/docs/HYBRID-OPERATIONS.adoc b/czech-file-knife/docs/HYBRID-OPERATIONS.adoc new file mode 100644 index 000000000..33aaaf3d0 --- /dev/null +++ b/czech-file-knife/docs/HYBRID-OPERATIONS.adoc @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MPL-2.0 += Hybrid-machine operations: design notes +:toc: + +CFK's goal is to be the "Swiss File Knife" of the hybrid machine: one tool +that works on local disks and cloud storage alike and picks the cheapest +correct way to do each job. This page covers two example jobs and how they +rely on the reversible journal (`cfk-core::reversible`). + +== 1. Reversible journal (implemented) + +`ReversibleBackend` wraps any backend. Before each write, delete, move +or copy it saves the prior content in a SHA-256 content store and appends +the inverse operation to an append-only `oplog.jsonl` (the JanusKey model). + +* `cfk history` shows the log, `cfk undo [id]` rolls back the latest operation. +* State lives in `$CFK_JOURNAL_DIR`, or `$XDG_STATE_HOME/cfk/journal`, or `~/.local/state/cfk/journal`. +* The capture limit defaults to 1 GiB (`CFK_JOURNAL_MAX_BYTES`). Bigger + operations are *refused*, not silently made irreversible. + `CFK_NO_JOURNAL=1` opts out. +* Captured priors also back `get_versions`, so every backend gets version + history. + +== 2. Cloud-side operations without a local round trip + +Rule: *never route bytes through the local machine when the provider can +do the job itself.* When the provider can't, stream the bytes through +memory and never stage them on disk. + +[cols="1,2"] +|=== +| Situation | Strategy + +| Same provider (Drive→Drive, S3→S3 in the same region) +| Server-side calls: Drive `files.copy` / `files.update(addParents)`, S3 + `CopyObject` / `UploadPartCopy`, Dropbox `copy_v2`, OneDrive `copy`. + No bytes cross the local link. + +| Converting formats inside a provider +| Drive `files.export` / `files.copy` with a target mimeType (e.g. Doc→PDF) + keeps the work on Google's side. + +| Different providers (Drive→S3) +| Pipe a streamed download into a resumable or multipart upload through a + bounded memory buffer. Local disk use is zero. Bandwidth still flows + through this machine unless a relay (such as a cloud VM running `cfk`) + is configured. +|=== + +Proposed trait extension: `async fn server_side_copy(&self, src, dest) -> +CfkResult>`, where `None` means "can't do it, fall back to +streaming". The planner tries it first. + +== 3. Compressing a file in place when disk space is short + +Example: a 25 GB file, 8 GB free, 4 GB target output, with no extra local +or cloud storage allowed. + +The naive approach (write the compressed file, then delete the original) +needs roughly 25 GB + 4 GB. You can't delete first either, so a normal +compressor can never produce the output. + +=== Algorithm: forward compress plus hole punching + +. Read chunk *i* (for example 256 MiB) of the input. +. Compress it into its own zstd frame and append the frame to `out.zst`. + Concatenated zstd frames form a valid zstd stream, so the result is a + normal `.zst` file. +. `fsync` the output, then record `{chunk: i, in_off, out_off}` in the + journal and `fsync` the journal. +. Only then call `fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)` on + the input range just consumed. The filesystem frees those blocks. +. Repeat. At the end, unlink the (now sparse, empty) input. + +Peak extra space is about one compressed chunk plus the journal, not the +full output. Each chunk frees about 256 MiB and consumes about 40 MiB, so +free space *grows* as the job runs. + +=== Crash safety + +A range is punched only after the frame that replaces it is durable and +journaled. On restart, the job truncates `out.zst` to the last journaled +`out_off` and resumes from `in_off`. No data exists in zero copies at any +point. + +The job is also reversible: decompressing frames `0..k` and writing them +back into the punched ranges restores the original, using the same +hole-filling logic in reverse. + +=== Portability and fallbacks + +* Hole punching works on Linux ext4, XFS, btrfs and tmpfs, and on macOS APFS + via `fcntl(F_PUNCHHOLE)`. Windows NTFS supports it with + `FSCTL_SET_ZERO_DATA` on sparse files. +* Without hole punching, process the file *from the end*: compress the last + chunk into a frame, then `ftruncate` the input to free it. The frames come + out in reverse order, so the output is a small indexed container (or a + seekable zstd file with a frame table) rather than a plain stream. + Reordering it in place is a later step. +* A CoW filesystem with active snapshots won't free blocks. Detect this with + a `statvfs` check after the first chunk and abort cleanly. +* Proof target: frame-then-punch ordering keeps "every input byte is either + still present or in a durable frame" as an invariant. This is a natural + obligation for the absolute-zero and januskey proofs. + +Proposed CLI: `cfk squeeze [--codec zstd] [--level 19] [--chunk 256M] [--resume]`. From ca7f321c463645c39a31c8d64da105a1fe1ec6ef Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:38:15 +0000 Subject: [PATCH 2/4] refactor(czech-file-knife): restructure as standalone RSR repo (rsr-template-repo mint + standards profile) - minted from rsr-template-repo@9335075 via just repo-init; CFK content overlaid - Cargo workspace crates moved to src/cfk-*; fuzz -> tests/fuzz - licence unified to MPL-2.0 (was AGPL in Cargo.toml/packaging) - rsr-profile: preset rust-cli + container, reproducible-build, benchmarks, formal-proofs, published-package, governance-tier (check-rsr-profile OK) - real Justfile recipes, e2e test of the reversible journal, CFK STATE/ECOSYSTEM/ROADMAP/PROOF-NEEDS - this commit is the subtree-split point for hyperpolymath/czech-file-knife --- czech-file-knife/.cicd-hygiene-allow | 12 + czech-file-knife/.claude/CLAUDE.md | 83 -- czech-file-knife/.clinerules | 51 + czech-file-knife/.clusterfuzzlite/build.sh | 4 +- czech-file-knife/.cursorrules | 53 + czech-file-knife/.devcontainer/Containerfile | 34 + czech-file-knife/.devcontainer/README.adoc | 28 + .../.devcontainer/devcontainer.json | 69 ++ czech-file-knife/.editorconfig | 94 +- czech-file-knife/.envrc | 22 + czech-file-knife/.gitattributes | 101 +- czech-file-knife/.github/CODEOWNERS | 14 + czech-file-knife/.github/CODE_OF_CONDUCT.md | 318 ++++++ czech-file-knife/.github/CONTRIBUTING.md | 103 ++ czech-file-knife/.github/FUNDING.yml | 16 +- czech-file-knife/.github/GOVERNANCE.md | 160 ++++ .../.github/ISSUE_TEMPLATE/bug_report.md | 38 - .../.github/ISSUE_TEMPLATE/bug_report.yml | 58 ++ .../.github/ISSUE_TEMPLATE/config.yml | 13 + .../.github/ISSUE_TEMPLATE/custom.md | 10 - .../.github/ISSUE_TEMPLATE/documentation.md | 66 -- .../.github/ISSUE_TEMPLATE/feature_request.md | 20 - .../ISSUE_TEMPLATE/feature_request.yml | 37 + .../.github/ISSUE_TEMPLATE/question.md | 55 -- czech-file-knife/.github/SECURITY.md | 389 ++++++++ czech-file-knife/.github/SUPPORT.md | 7 + .../.github/copilot-instructions.md | 85 ++ .../.github/copilot/coding-agent.yml | 6 + czech-file-knife/.github/dependabot.yml | 43 +- czech-file-knife/.github/hooks/install.sh | 10 + czech-file-knife/.github/hooks/pre-push | 81 ++ .../.github/hooks/scan-secrets.sh | 204 ++++ .../.github/hooks/validate-deed.sh | 393 ++++++++ czech-file-knife/.github/hooks/validate-k9.sh | 389 ++++++++ .../.github/label-classifier.json | 739 ++++++++++++++ czech-file-knife/.github/labels.json | 260 +++++ .../.github/pull_request_template.md | 47 + .../.github/rulesets/Immutable-Tags.json | 28 + czech-file-knife/.github/rulesets/README.adoc | 123 +++ czech-file-knife/.github/rulesets/base.json | 102 ++ .../.github/rulesets/branch-floor.json | 16 + .../.github/scripts/classify-issue.jq | 164 ++++ czech-file-knife/.github/settings.yml | 160 ++++ .../.github/workflows/README.adoc | 63 ++ .../.github/workflows/actions.lock | 296 ++++++ .../.github/workflows/build-notification.yml | 50 + .../.github/workflows/cflite_batch.yml | 1 + .../.github/workflows/cflite_pr.yml | 1 + czech-file-knife/.github/workflows/codeql.yml | 23 +- .../.github/workflows/container.yml | 47 - .../.github/workflows/deed-validate.yml | 59 ++ .../workflows/dependabot-automerge.yml | 137 +++ .../.github/workflows/docker-build.yml | 58 ++ .../.github/workflows/dogfood-gate.yml | 632 ++++++++++++ .../.github/workflows/dogfood-summary.yml | 99 ++ .../workflows/dot-wellknown-enforcement.yml | 157 +++ .../.github/workflows/e2e.yml.template | 224 +++++ .../workflows/eclexiaiser-validate.yml | 64 ++ .../.github/workflows/empty-linter.yml | 89 ++ .../.github/workflows/estate-rules.yml | 97 ++ .../.github/workflows/ghcr-publish.yml | 55 -- .../.github/workflows/governance.yml | 22 +- .../.github/workflows/groove-check.yml | 91 ++ .../.github/workflows/guix-policy.yml | 49 + .../.github/workflows/hypatia-scan.yml | 133 +-- .../.github/workflows/instant-sync.yml | 29 - .../.github/workflows/k9-validate.yml | 64 ++ .../.github/workflows/label-triage.yml | 118 +++ czech-file-knife/.github/workflows/labels.yml | 107 +++ .../.github/workflows/lock-sync-gate.yml | 64 ++ .../.github/workflows/main-estate-audit.yml | 20 + czech-file-knife/.github/workflows/mirror.yml | 134 +-- .../.github/workflows/ossf-best-practices.yml | 97 ++ czech-file-knife/.github/workflows/pages.yml | 91 ++ .../.github/workflows/publish.yml | 134 --- .../.github/workflows/push-email-notify.yml | 58 ++ .../.github/workflows/quality.yml | 79 ++ .../.github/workflows/release.yml | 266 +++--- .../workflows/rsr-compliance-canary.yml | 95 ++ .../.github/workflows/runtime-policy.yml | 72 ++ .../.github/workflows/rust-ci.yml | 19 + .../.github/workflows/scorecard-enforcer.yml | 49 - .../.github/workflows/scorecard.yml | 34 +- .../.github/workflows/secret-scanner.yml | 64 +- .../.github/workflows/security-policy.yml | 54 ++ .../.github/workflows/sonarqube.yml | 69 ++ .../workflows/static-analysis-gate.yml | 454 +++++++++ .../.github/workflows/stress-test.yml | 47 - .../.github/workflows/workflow-linter.yml | 178 ++++ czech-file-knife/.gitignore | 76 +- czech-file-knife/.gitlab-ci.yml | 163 ---- czech-file-knife/.gitleaksignore | 4 + czech-file-knife/.gitmessage | 18 + czech-file-knife/.hypatia-ignore | 55 ++ .../.machine_readable/AGENTIC.scm | 16 - .../.machine_readable/ECOSYSTEM.scm | 20 - .../.machine_readable/ENSAID_CONFIG.a2ml | 96 ++ czech-file-knife/.machine_readable/META.scm | 17 - .../.machine_readable/NEUROSYM.scm | 13 - .../.machine_readable/PLAYBOOK.scm | 13 - .../.machine_readable/PROVENANCE.a2ml | 41 + .../.machine_readable/README.adoc | 40 + czech-file-knife/.machine_readable/STATE.scm | 39 - czech-file-knife/.machine_readable/ai/AI.a2ml | 38 + .../.machine_readable/ai/README.adoc | 24 + .../arrival-pack/README.adoc | 55 ++ .../arrival-pack/arrival-pack.ncl | 90 ++ .../arrival-pack/claude-md.k9.ncl | 59 ++ .../.machine_readable/arrival-pack/extract.sh | 89 ++ .../arrival-pack/generate.sh | 45 + .../.machine_readable/arrival-pack/verify.sh | 27 + .../bot_directives/README.adoc | 41 + .../bot_directives/coverage.a2ml | 61 ++ .../bot_directives/debt.a2ml | 49 + .../bot_directives/methodology.a2ml | 115 +++ .../.machine_readable/bot_directives/rra.a2ml | 55 ++ .../.machine_readable/coaptation/README.adoc | 147 +++ .../.machine_readable/coaptation/coapt.k9.ncl | 70 ++ .../.machine_readable/coaptation/coapt.ncl | 196 ++++ .../.machine_readable/coaptation/coapt.sh | 87 ++ .../coaptation/core/Coaptation.idr | 112 +++ .../coaptation/extract-clauses.sh | 104 ++ .../coaptation/extract-facts.sh | 122 +++ .../.machine_readable/coaptation/grades.ncl | 45 + .../coaptation/receipts/latest.a2ml | 100 ++ .../.machine_readable/coaptation/verify.sh | 34 + .../coaptation/witness-map.ncl | 88 ++ .../.machine_readable/compliance/reuse/dep5 | 62 ++ .../compliance/rust/deny.toml | 64 ++ .../.machine_readable/configs/README.adoc | 3 + .../configs/eclexiaiser.toml | 26 + .../configs/git-cliff/cliff.toml | 119 +++ .../.machine_readable/configs/stapeln.toml | 87 ++ .../.machine_readable/contractiles/INDEX.a2ml | 133 +++ .../.machine_readable/contractiles/Justfile | 720 ++++++++++++++ .../contractiles/README.adoc | 169 ++++ .../.machine_readable/contractiles/_base.ncl | 140 +++ .../contractiles/adjust/Adjustfile.a2ml | 72 ++ .../contractiles/adjust/adjust.k9.ncl | 167 ++++ .../contractiles/adjust/adjust.manifest.a2ml | 47 + .../contractiles/adjust/adjust.ncl | 65 ++ .../contractiles/bust/Bustfile.a2ml | 52 + .../contractiles/bust/bust.k9.ncl | 162 ++++ .../contractiles/bust/bust.manifest.a2ml | 48 + .../contractiles/bust/bust.ncl | 69 ++ .../contractiles/dust/Dustfile.a2ml | 75 ++ .../contractiles/dust/dust.k9.ncl | 172 ++++ .../contractiles/dust/dust.manifest.a2ml | 51 + .../contractiles/dust/dust.ncl | 69 ++ .../contractiles/intend/Intentfile.a2ml | 99 ++ .../contractiles/intend/intend.k9.ncl | 252 +++++ .../contractiles/intend/intend.manifest.a2ml | 73 ++ .../contractiles/intend/intend.ncl | 84 ++ .../contractiles/must/Mustfile.a2ml | 125 +++ .../contractiles/must/must.k9.ncl | 238 +++++ .../contractiles/must/must.manifest.a2ml | 59 ++ .../contractiles/must/must.ncl | 67 ++ .../contractiles/trust/Trustfile.a2ml | 105 ++ .../contractiles/trust/trust.k9.ncl | 278 ++++++ .../contractiles/trust/trust.manifest.a2ml | 72 ++ .../contractiles/trust/trust.ncl | 94 ++ .../descriptiles/AGENTIC.a2ml | 56 ++ .../.machine_readable/descriptiles/CLADE.a2ml | 127 +++ .../descriptiles/ECOSYSTEM.a2ml | 30 + .../.machine_readable/descriptiles/META.a2ml | 53 + .../descriptiles/NEUROSYM.a2ml | 23 + .../descriptiles/PLAYBOOK.a2ml | 137 +++ .../descriptiles/README.adoc | 66 ++ .../.machine_readable/descriptiles/STATE.a2ml | 62 ++ .../descriptiles/VARIANT.a2ml | 40 + .../descriptiles/anchors/ANCHOR.a2ml | 62 ++ .../descriptiles/anchors/README.adoc | 25 + .../integrations/feedback-o-tron.a2ml | 14 + .../integrations/groove.a2ml | 38 + .../integrations/proven.a2ml | 20 + .../integrations/verisimdb.a2ml | 17 + .../integrations/vexometer.a2ml | 19 + .../policies/.maintenance-perms-ignore | 5 + .../policies/MAINTENANCE-AXES.a2ml | 54 ++ .../policies/MAINTENANCE-CHECKLIST.a2ml | 159 +++ .../.machine_readable/policies/README.adoc | 3 + .../SOFTWARE-DEVELOPMENT-APPROACH.a2ml | 53 + .../.machine_readable/root-allow.txt | 85 ++ .../.machine_readable/rsr-profile.a2ml | 30 + .../scripts/forge/README.adoc | 3 + .../scripts/forge/forge-sync.sh | 25 + .../scripts/forge/git-cleanup.sh | 10 + .../scripts/lifecycle/README.adoc | 3 + .../scripts/lifecycle/install-tools.sh | 27 + .../scripts/maintenance/maint-assault.sh | 44 + .../scripts/verification/README.adoc | 3 + .../self-validating/README.adoc | 178 ++++ .../self-validating/examples/ci-config.k9.ncl | 126 +++ .../examples/project-metadata.k9.ncl | 57 ++ .../examples/setup-repo.k9.ncl | 167 ++++ .../self-validating/methodology-guard.k9.ncl | 79 ++ .../self-validating/template-hunt.k9.ncl | 136 +++ .../self-validating/template-kennel.k9.ncl | 54 ++ .../self-validating/template-yard.k9.ncl | 84 ++ czech-file-knife/.mailmap | 1 + czech-file-knife/.tool-versions | 15 +- czech-file-knife/.windsurfrules | 51 + czech-file-knife/AI.a2ml | 16 - czech-file-knife/AI.djot | 100 -- czech-file-knife/CHANGELOG.adoc | 85 ++ czech-file-knife/CITATION.cff | 17 + czech-file-knife/CLAUDE.md | 71 ++ czech-file-knife/CODE_OF_CONDUCT.adoc | 39 - czech-file-knife/CONTRIBUTING.adoc | 162 ++-- czech-file-knife/Cargo.toml | 19 +- czech-file-knife/Containerfile | 38 - czech-file-knife/GEMINI.md | 8 + czech-file-knife/Justfile | 629 +++++++++++- czech-file-knife/LICENSE | 165 ++-- czech-file-knife/LICENSES/CC-BY-SA-4.0.txt | 428 +++++++++ czech-file-knife/LICENSES/MPL-2.0.txt | 373 ++++++++ czech-file-knife/MAINTAINERS.adoc | 47 - czech-file-knife/Mustfile | 13 - czech-file-knife/README.adoc | 261 ++--- czech-file-knife/ROADMAP.adoc | 171 ---- czech-file-knife/RSR_OUTLINE.adoc | 218 ----- czech-file-knife/SECURITY.adoc | 24 - czech-file-knife/TESTING-REPORT.adoc | 84 -- czech-file-knife/TESTING-REPORT.scm | 65 -- czech-file-knife/benches/template_bench.sh | 227 +++++ .../build/container/.containerignore | 59 ++ .../build/container/Containerfile | 41 + czech-file-knife/build/container/README.adoc | 245 +++++ .../build/container/compose.example.yaml | 108 +++ czech-file-knife/build/container/compose.yaml | 99 ++ .../build/container/entrypoint.sh | 63 ++ .../build/container/stapeln/.gatekeeper.yaml | 122 +++ .../container/stapeln/compose.example.toml | 135 +++ .../build/container/stapeln/compose.toml | 94 ++ .../build/container/stapeln/ct-build.sh | 168 ++++ .../build/container/stapeln/deploy.k9.ncl | 170 ++++ .../build/container/stapeln/manifest.toml | 62 ++ .../build/container/stapeln/rokur.toml | 81 ++ .../build/container/stapeln/vordr.toml | 117 +++ czech-file-knife/{ => build}/guix.scm | 0 czech-file-knife/build/just/assess.just | 270 ++++++ czech-file-knife/build/just/container.just | 284 ++++++ czech-file-knife/build/just/groove.just | 98 ++ czech-file-knife/build/just/proofs.just | 61 ++ czech-file-knife/build/just/repo-init.just | 902 ++++++++++++++++++ czech-file-knife/build/just/validate.just | 135 +++ .../{ => build}/packaging/arch/PKGBUILD | 2 +- .../chocolatey/czech-file-knife.nuspec | 0 .../{ => build}/packaging/debian/control | 2 +- .../{ => build}/packaging/debian/rules | 0 .../dev.hyperpolymath.CzechFileKnife.yml | 0 .../{ => build}/packaging/macports/Portfile | 2 +- .../packaging/rpm/czech-file-knife.spec | 2 +- .../packaging/scoop/czech-file-knife.json | 2 +- .../packaging/winget/czech-file-knife.yaml | 2 +- czech-file-knife/ci/.gitlab-ci.yml | 154 +++ czech-file-knife/ci/.pre-commit-config.yaml | 73 ++ czech-file-knife/ci/README.adoc | 43 + czech-file-knife/contractiles/README.adoc | 19 - czech-file-knife/contractiles/dust/Dustfile | 29 - czech-file-knife/contractiles/must/Mustfile | 35 - czech-file-knife/coordination.k9.ncl | 49 + czech-file-knife/czech-file-knife_chora.deed | 152 +++ czech-file-knife/deny.toml | 60 -- czech-file-knife/docs/AFFIRMATION.adoc | 235 +++++ .../docs/AI-INSTALL-README-SECTION.adoc | 20 + .../docs/AI_INSTALLATION_GUIDE.adoc | 133 +++ czech-file-knife/docs/ARCHITECTURE.adoc | 48 + czech-file-knife/docs/AUDIT.adoc | 48 + czech-file-knife/docs/EXPLAINME.adoc | 118 +++ czech-file-knife/docs/GOVERNANCE.adoc | 65 ++ czech-file-knife/docs/MAINTAINERS.adoc | 63 ++ czech-file-knife/docs/QUICKSTART.adoc | 26 + czech-file-knife/docs/README.adoc | 53 + czech-file-knife/docs/RSR-PHILOSOPHY.adoc | 118 +++ czech-file-knife/docs/RSR_OUTLINE.adoc | 258 +++++ czech-file-knife/docs/STATE-VISUALIZER.adoc | 131 +++ czech-file-knife/docs/architecture.adoc | 79 ++ .../DISTRIBUTED_FILESYSTEMS.adoc | 0 .../{ => architecture}/HYBRID-OPERATIONS.adoc | 0 .../docs/architecture/REPOSITORY-MAP.adoc | 267 ++++++ .../docs/architecture/THREAT-MODEL.adoc | 197 ++++ .../docs/architecture/TOPOLOGY.adoc | 36 + .../CFK-CITATIONS.adoc} | 2 +- .../docs/attribution/CITATIONS.adoc | 37 + .../docs/attribution/CODEOWNERS.adoc | 21 + czech-file-knife/docs/attribution/README.adoc | 3 + czech-file-knife/docs/contributing.adoc | 91 ++ .../docs/decisions/0000-template.adoc | 37 + .../decisions/0001-adopt-rsr-standard.adoc | 89 ++ .../docs/decisions/0001-template.adoc | 54 ++ .../docs/decisions/0002-variant-contract.adoc | 75 ++ .../0003-forge-and-sustain-lifecycle.adoc | 118 +++ czech-file-knife/docs/decisions/README.adoc | 3 + .../{ => docs/developer}/ABI-FFI-README.adoc | 162 ++-- .../docs/{ => developer}/IOS_INTEGRATION.adoc | 0 czech-file-knife/docs/developer/README.adoc | 3 + .../docs/developer/invariant-path.adoc | 20 + .../docs/governance/CRG-AUDIT-TEMPLATE.adoc | 288 ++++++ .../docs/governance/CRG-CRITERIA.a2ml | 108 +++ .../docs/governance/CRG-CRITERIA.adoc | 41 + .../governance/MAINTENANCE-CHECKLIST.adoc | 571 +++++++++++ czech-file-knife/docs/governance/README.adoc | 3 + .../SOFTWARE-DEVELOPMENT-APPROACH.adoc | 65 ++ .../governance/TEMPLATE-LINEAGE-AUDIT.adoc | 230 +++++ .../governance/TEMPLATE-STANDARDS-AUDIT.adoc | 178 ++++ czech-file-knife/docs/governance/TSDM.a2ml | 22 + czech-file-knife/docs/governance/TSDM.adoc | 28 + .../docs/governance/audit/README.adoc | 3 + .../governance/audit/compliance/README.adoc | 3 + .../docs/governance/audit/effects/README.adoc | 3 + .../docs/governance/audit/systems/README.adoc | 3 + .../docs/governance/maintenance/README.adoc | 3 + .../maintenance/adaptive/README.adoc | 3 + .../maintenance/corrective/README.adoc | 3 + .../maintenance/perfective/README.adoc | 3 + .../docs/governance/planning/README.adoc | 3 + .../governance/planning/could/README.adoc | 3 + .../docs/governance/planning/must/README.adoc | 3 + .../governance/planning/should/README.adoc | 3 + .../docs/legal/EXHIBIT-A-ETHICAL-USE.txt | 20 + .../docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt | 104 ++ .../{ => docs/legal}/PALIMPSEST.adoc | 0 .../docs/onboarding/QUICKSTART-DEV.adoc | 104 ++ .../onboarding/QUICKSTART-MAINTAINER.adoc | 122 +++ .../docs/onboarding/QUICKSTART-USER.adoc | 125 +++ .../docs/onboarding/llm-warmup-dev.adoc | 21 + .../docs/onboarding/llm-warmup-user.adoc | 21 + .../docs/practice/AI-CONVENTIONS.adoc | 102 ++ czech-file-knife/docs/practice/README.adoc | 3 + .../docs/practice/STATE-VISUALIZER-GUIDE.adoc | 156 +++ .../docs/practice/ci-cost-reduction.adoc | 278 ++++++ .../docs/proposals/root-cleanup.adoc | 231 +++++ czech-file-knife/docs/standards/README.adoc | 3 + czech-file-knife/docs/status/PROOF-NEEDS.adoc | 123 +++ .../docs/status/PROOF-STATUS.adoc | 101 ++ czech-file-knife/docs/status/READINESS.adoc | 186 ++++ czech-file-knife/docs/status/ROADMAP.adoc | 34 + czech-file-knife/docs/status/TEST-NEEDS.adoc | 126 +++ czech-file-knife/docs/theory/README.adoc | 3 + .../docs/theory/computing/README.adoc | 3 + .../docs/theory/formalisms/README.adoc | 3 + .../docs/theory/mathematics/README.adoc | 3 + .../docs/theory/ontologies/README.adoc | 3 + .../docs/theory/other/README.adoc | 3 + .../docs/theory/socio-technical/README.adoc | 3 + czech-file-knife/docs/troubleshooting.adoc | 58 ++ czech-file-knife/docs/usage.adoc | 82 ++ czech-file-knife/docs/whitepapers/README.adoc | 3 + .../docs/whitepapers/academic/README.adoc | 3 + .../docs/whitepapers/industry/README.adoc | 3 + .../docs/whitepapers/outreach/README.adoc | 19 + czech-file-knife/docs/wikis/README.md | 17 + czech-file-knife/examples/README.adoc | 3 + .../sample-manifest.ncl} | 0 czech-file-knife/features/README.adoc | 3 + .../features/boj-server/README.adoc | 16 + .../features/panic-attacker/README.adoc | 27 + czech-file-knife/features/ssg/README.adoc | 3 + .../features/ssg/ssg-bootstrap.sh | 90 ++ czech-file-knife/hooks/validate-codeql.sh | 34 - .../hooks/validate-permissions.sh | 14 - czech-file-knife/hooks/validate-sha-pins.sh | 33 - czech-file-knife/hooks/validate-spdx.sh | 25 - czech-file-knife/license/PMPL-1.0.txt | 162 ---- czech-file-knife/mise.toml | 67 ++ czech-file-knife/ops/podman-compose.yml | 28 - czech-file-knife/ops/scripts/ensure_deps.sh | 47 - czech-file-knife/ops/scripts/ensure_kafka.sh | 37 - .../scripts/campaigns/www-bundle.campaign | 58 ++ .../scripts/check-action-pinning.js | 105 ++ .../scripts/check-adoc-renders.sh | 89 ++ .../scripts/check-invisible-characters.sh | 72 ++ czech-file-knife/scripts/check-lock-sync.sh | 297 ++++++ .../scripts/check-no-md-in-docs.sh | 65 ++ .../scripts/check-no-placeholders.sh | 178 ++++ czech-file-knife/scripts/check-no-vlang.sh | 87 ++ czech-file-knife/scripts/check-proofs.sh | 183 ++++ czech-file-knife/scripts/check-root-shape.sh | 156 +++ .../scripts/check-template-conformance.sh | 263 +++++ .../scripts/check-variant-drift.sh | 122 +++ czech-file-knife/scripts/gen-repo-map.sh | 130 +++ czech-file-knife/scripts/invariant-path.sh | 33 + .../scripts/migrate-wellknown-to-www.sh | 246 +++++ .../scripts/prune-dependabot-ecosystems.rs | 133 +++ czech-file-knife/scripts/rsr-campaign.sh | 415 ++++++++ czech-file-knife/scripts/rust-tool.sh | 51 + czech-file-knife/scripts/scan-dangerous.sh | 74 ++ .../scripts/strip-instruction-blocks.rs | 171 ++++ czech-file-knife/scripts/sweep-wellknown.sh | 421 ++++++++ .../scripts/validate-session-contracts.sh | 34 + czech-file-knife/scripts/validate-template.sh | 491 ++++++++++ czech-file-knife/session/README.adoc | 50 + czech-file-knife/session/custom-checks.k9.ncl | 51 + czech-file-knife/session/dispatch.sh | 139 +++ czech-file-knife/session/local-hooks.sh | 23 + czech-file-knife/sonar-project.properties | 26 + czech-file-knife/src/README.adoc | 3 + czech-file-knife/src/aspects/README.adoc | 56 ++ .../src/aspects/integrity/README.adoc | 3 + .../src/aspects/observability/README.adoc | 3 + .../src/aspects/security/README.adoc | 3 + .../{.nojekyll => src/bridges/.gitkeep} | 0 .../{ => src}/cfk-cache/Cargo.toml | 0 .../{ => src}/cfk-cache/src/blob_store.rs | 1 + .../{ => src}/cfk-cache/src/lib.rs | 1 + .../{ => src}/cfk-cache/src/metadata_cache.rs | 0 .../{ => src}/cfk-cache/src/policy.rs | 0 .../{ => src}/cfk-cache/src/sled_backend.rs | 0 czech-file-knife/{ => src}/cfk-cli/Cargo.toml | 6 +- .../{ => src}/cfk-cli/src/commands.rs | 0 .../{ => src}/cfk-cli/src/main.rs | 0 .../{ => src}/cfk-core/Cargo.toml | 0 .../{ => src}/cfk-core/src/backend.rs | 1 + .../{ => src}/cfk-core/src/entry.rs | 1 + .../{ => src}/cfk-core/src/error.rs | 1 + .../{ => src}/cfk-core/src/lib.rs | 1 + .../{ => src}/cfk-core/src/metadata.rs | 1 + .../{ => src}/cfk-core/src/operations.rs | 1 + .../{ => src}/cfk-core/src/path.rs | 1 + .../{ => src}/cfk-core/src/platform.rs | 1 + .../{ => src}/cfk-core/src/reversible.rs | 0 .../{ => src}/cfk-integrations/Cargo.toml | 0 .../{ => src}/cfk-integrations/src/agrep.rs | 1 + .../{ => src}/cfk-integrations/src/aria2.rs | 1 + .../{ => src}/cfk-integrations/src/lib.rs | 1 + .../{ => src}/cfk-integrations/src/pandoc.rs | 1 + czech-file-knife/{ => src}/cfk-ios/Cargo.toml | 0 .../{ => src}/cfk-ios/src/domain.rs | 1 + .../{ => src}/cfk-ios/src/error.rs | 0 czech-file-knife/{ => src}/cfk-ios/src/ffi.rs | 1 + .../{ => src}/cfk-ios/src/item.rs | 0 czech-file-knife/{ => src}/cfk-ios/src/lib.rs | 1 + .../{ => src}/cfk-ios/src/provider.rs | 0 .../{ => src}/cfk-ios/swift/CfkBridge.h | 0 .../swift/CfkFileProviderExtension.swift | 0 .../cfk-ios/swift/CfkFileProviderItem.swift | 0 .../{ => src}/cfk-providers/Cargo.toml | 0 .../{ => src}/cfk-providers/src/afs.rs | 1 + .../{ => src}/cfk-providers/src/box_com.rs | 1 + .../{ => src}/cfk-providers/src/ceph.rs | 1 + .../{ => src}/cfk-providers/src/dropbox.rs | 1 + .../{ => src}/cfk-providers/src/gdrive.rs | 1 + .../{ => src}/cfk-providers/src/ipfs.rs | 1 + .../{ => src}/cfk-providers/src/lib.rs | 1 + .../{ => src}/cfk-providers/src/local.rs | 1 + .../{ => src}/cfk-providers/src/nfs.rs | 1 + .../{ => src}/cfk-providers/src/ninep.rs | 1 + .../{ => src}/cfk-providers/src/onedrive.rs | 1 + .../{ => src}/cfk-providers/src/protocols.rs | 1 + .../{ => src}/cfk-providers/src/s3.rs | 1 + .../{ => src}/cfk-providers/src/sftp.rs | 1 + .../{ => src}/cfk-providers/src/smb.rs | 1 + .../{ => src}/cfk-providers/src/syncthing.rs | 1 + .../{ => src}/cfk-providers/src/transport.rs | 1 + .../{ => src}/cfk-providers/src/webdav.rs | 1 + .../{ => src}/cfk-search/Cargo.toml | 0 .../{ => src}/cfk-search/src/lib.rs | 0 .../{ => src}/cfk-tui/cfk_tui.gpr | 0 .../cfk-tui/src/cfk-tui-application.ads | 0 .../{ => src}/cfk-tui/src/cfk_tui_main.adb | 0 czech-file-knife/{ => src}/cfk-vfs/Cargo.toml | 0 czech-file-knife/{ => src}/cfk-vfs/src/lib.rs | 0 czech-file-knife/src/contracts/README.adoc | 3 + czech-file-knife/src/core/.gitkeep | 0 czech-file-knife/src/definitions/README.adoc | 3 + czech-file-knife/src/errors/README.adoc | 3 + czech-file-knife/tests/aspect_tests.sh | 134 +++ czech-file-knife/tests/e2e.sh | 64 ++ czech-file-knife/tests/e2e/julia_mint_test.sh | 183 ++++ .../tests/e2e/template_instantiation_test.sh | 422 ++++++++ czech-file-knife/{ => tests}/fuzz/Cargo.toml | 2 +- czech-file-knife/tests/fuzz/README.adoc | 112 +++ .../fuzz/fuzz_targets/fuzz_path.rs | 0 .../tests/invisible-characters-test.sh | 101 ++ .../tests/shape/check_root_shape_test.sh | 121 +++ .../tests/shape/repo_map_determinism_test.sh | 46 + .../workflows/check_adoc_renders_test.sh | 126 +++ .../tests/workflows/check_no_vlang_test.sh | 61 ++ .../tests/workflows/k9_typecheck_test.sh | 17 + .../tests/workflows/mint_cleanup_test.sh | 39 + .../tests/workflows/session_contracts_test.sh | 25 + .../workflows/template_conformance_test.sh | 147 +++ .../workflows/validate_workflows_test.sh | 144 +++ czech-file-knife/verification/README.adoc | 3 + .../verification/benchmarks/README.adoc | 3 + .../verification/coverage/README.adoc | 3 + .../verification/fuzzing/README.adoc | 3 + .../verification/proofs/README.adoc | 60 ++ .../verification/proofs/agda/MANIFEST | 4 + .../verification/proofs/agda/Properties.agda | 37 + .../verification/proofs/coq/MANIFEST | 4 + .../verification/proofs/coq/TypeSafety.v | 73 ++ .../proofs/idris2/ABI/Compliance.idr | 41 + .../proofs/idris2/ABI/Foreign.idr | 53 + .../verification/proofs/idris2/ABI/Layout.idr | 63 ++ .../proofs/idris2/ABI/Platform.idr | 63 ++ .../proofs/idris2/ABI/Pointers.idr | 52 + .../verification/proofs/idris2/MANIFEST | 10 + .../verification/proofs/idris2/Types.idr | 40 + .../verification/proofs/lean4/ApiTypes.lean | 45 + .../verification/proofs/lean4/MANIFEST | 4 + .../verification/proofs/lean4/lean-toolchain | 1 + .../proofs/tlaplus/StateMachine.tla | 91 ++ .../verification/safety_case/README.adoc | 3 + .../verification/simulations/README.adoc | 3 + .../verification/tests/README.adoc | 3 + .../verification/traceability/README.adoc | 3 + czech-file-knife/www/.well-known/ai.txt | 17 + czech-file-knife/www/.well-known/aibdp.json | 79 ++ .../www/.well-known/aibdp.json.license | 2 + czech-file-knife/www/.well-known/humans.txt | 14 + czech-file-knife/www/.well-known/security.txt | 14 + czech-file-knife/www/README.adoc | 117 +++ czech-file-knife/www/dns/bind9/README.adoc | 56 ++ .../www/dns/bind9/named.conf.example | 72 ++ .../www/dns/privacy/ENCRYPTED-DNS.adoc | 97 ++ .../www/dns/records/2.0.192.in-addr.arpa.zone | 14 + czech-file-knife/www/dns/records/README.adoc | 43 + .../www/dns/records/example.invalid.zone | 38 + czech-file-knife/www/errors/403.html | 15 + czech-file-knife/www/errors/404.html | 15 + czech-file-knife/www/errors/429.html | 15 + czech-file-knife/www/errors/500.html | 15 + czech-file-knife/www/errors/502.html | 15 + czech-file-knife/www/errors/503.html | 15 + .../www/policies/acceptable-use.adoc | 30 + czech-file-knife/www/policies/ai-use.adoc | 36 + czech-file-knife/www/policies/privacy.adoc | 33 + czech-file-knife/www/profiles/README.adoc | 69 ++ .../www/profiles/authoritative-dns.toml | 14 + .../www/profiles/baseline-site.toml | 10 + .../www/profiles/consent-aware-web.toml | 18 + .../www/profiles/full-expert.toml | 15 + .../www/profiles/privacy-enhanced.toml | 14 + czech-file-knife/www/public/index.html | 37 + czech-file-knife/www/public/styles/site.css | 10 + .../www/runbooks/cert-rotation.adoc | 45 + czech-file-knife/www/runbooks/deploy.adoc | 60 ++ czech-file-knife/www/runbooks/dns-change.adoc | 65 ++ czech-file-knife/www/runbooks/rollback.adoc | 43 + .../www/runbooks/stage5-wellknown-sweep.adoc | 157 +++ .../www/schemas/aibdp-schema-v0.2.json | 377 ++++++++ .../schemas/aibdp-schema-v0.2.json.license | 2 + .../www/schemas/publishable-paths.txt | 20 + .../www/security_headers/README.adoc | 32 + .../www/security_headers/csp.conf | 23 + czech-file-knife/www/tests/check-aibdp.sh | 106 ++ .../www/tests/check-bind-safety.sh | 128 +++ czech-file-knife/www/tests/check-migration.sh | 214 +++++ czech-file-knife/www/tests/check-profiles.sh | 94 ++ .../www/tests/check-publication-boundary.sh | 150 +++ czech-file-knife/www/tests/check-wellknown.sh | 57 ++ .../controls/aibdp-bad-status.control.json | 7 + .../aibdp-bad-status.control.json.license | 2 + .../controls/aibdp-enforce-430.control.json | 8 + .../aibdp-enforce-430.control.json.license | 2 + .../tests/controls/bad-deploy/dns/named.conf | 1 + .../www/tests/controls/bad-deploy/index.html | 1 + .../controls/bad-deploy/tests/run-all.sh | 2 + .../controls/caddy-serve-everything.control | 7 + .../good-deploy/.well-known/security.txt | 2 + .../controls/good-deploy/errors/404.html | 1 + .../www/tests/controls/good-deploy/index.html | 1 + .../good-deploy/policies/privacy.html | 1 + .../named.conf.open-recursion.control | 20 + .../controls/profile-enforce-430.control.toml | 8 + .../profile-hidden-start.control.toml | 8 + czech-file-knife/www/tests/run-all.sh | 52 + czech-file-knife/www/tls/POLICY.adoc | 60 ++ czech-file-knife/www/webservers/README.adoc | 42 + .../www/webservers/apache/vhost.conf.example | 61 ++ .../www/webservers/caddy/Caddyfile.example | 40 + .../www/webservers/nginx/site.conf.example | 74 ++ 574 files changed, 38792 insertions(+), 3216 deletions(-) create mode 100644 czech-file-knife/.cicd-hygiene-allow delete mode 100644 czech-file-knife/.claude/CLAUDE.md create mode 100644 czech-file-knife/.clinerules create mode 100644 czech-file-knife/.cursorrules create mode 100644 czech-file-knife/.devcontainer/Containerfile create mode 100644 czech-file-knife/.devcontainer/README.adoc create mode 100644 czech-file-knife/.devcontainer/devcontainer.json create mode 100644 czech-file-knife/.envrc create mode 100644 czech-file-knife/.github/CODEOWNERS create mode 100644 czech-file-knife/.github/CODE_OF_CONDUCT.md create mode 100644 czech-file-knife/.github/CONTRIBUTING.md create mode 100644 czech-file-knife/.github/GOVERNANCE.md delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/config.yml delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/custom.md delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/documentation.md delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/question.md create mode 100644 czech-file-knife/.github/SECURITY.md create mode 100644 czech-file-knife/.github/SUPPORT.md create mode 100644 czech-file-knife/.github/copilot-instructions.md create mode 100644 czech-file-knife/.github/copilot/coding-agent.yml create mode 100755 czech-file-knife/.github/hooks/install.sh create mode 100755 czech-file-knife/.github/hooks/pre-push create mode 100755 czech-file-knife/.github/hooks/scan-secrets.sh create mode 100755 czech-file-knife/.github/hooks/validate-deed.sh create mode 100755 czech-file-knife/.github/hooks/validate-k9.sh create mode 100644 czech-file-knife/.github/label-classifier.json create mode 100644 czech-file-knife/.github/labels.json create mode 100644 czech-file-knife/.github/pull_request_template.md create mode 100644 czech-file-knife/.github/rulesets/Immutable-Tags.json create mode 100644 czech-file-knife/.github/rulesets/README.adoc create mode 100644 czech-file-knife/.github/rulesets/base.json create mode 100644 czech-file-knife/.github/rulesets/branch-floor.json create mode 100644 czech-file-knife/.github/scripts/classify-issue.jq create mode 100644 czech-file-knife/.github/settings.yml create mode 100644 czech-file-knife/.github/workflows/README.adoc create mode 100644 czech-file-knife/.github/workflows/actions.lock create mode 100644 czech-file-knife/.github/workflows/build-notification.yml delete mode 100644 czech-file-knife/.github/workflows/container.yml create mode 100644 czech-file-knife/.github/workflows/deed-validate.yml create mode 100644 czech-file-knife/.github/workflows/dependabot-automerge.yml create mode 100644 czech-file-knife/.github/workflows/docker-build.yml create mode 100644 czech-file-knife/.github/workflows/dogfood-gate.yml create mode 100644 czech-file-knife/.github/workflows/dogfood-summary.yml create mode 100644 czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml create mode 100644 czech-file-knife/.github/workflows/e2e.yml.template create mode 100644 czech-file-knife/.github/workflows/eclexiaiser-validate.yml create mode 100644 czech-file-knife/.github/workflows/empty-linter.yml create mode 100644 czech-file-knife/.github/workflows/estate-rules.yml delete mode 100644 czech-file-knife/.github/workflows/ghcr-publish.yml create mode 100644 czech-file-knife/.github/workflows/groove-check.yml create mode 100644 czech-file-knife/.github/workflows/guix-policy.yml delete mode 100644 czech-file-knife/.github/workflows/instant-sync.yml create mode 100644 czech-file-knife/.github/workflows/k9-validate.yml create mode 100644 czech-file-knife/.github/workflows/label-triage.yml create mode 100644 czech-file-knife/.github/workflows/labels.yml create mode 100644 czech-file-knife/.github/workflows/lock-sync-gate.yml create mode 100644 czech-file-knife/.github/workflows/main-estate-audit.yml create mode 100644 czech-file-knife/.github/workflows/ossf-best-practices.yml create mode 100644 czech-file-knife/.github/workflows/pages.yml delete mode 100644 czech-file-knife/.github/workflows/publish.yml create mode 100644 czech-file-knife/.github/workflows/push-email-notify.yml create mode 100644 czech-file-knife/.github/workflows/quality.yml create mode 100644 czech-file-knife/.github/workflows/rsr-compliance-canary.yml create mode 100644 czech-file-knife/.github/workflows/runtime-policy.yml create mode 100644 czech-file-knife/.github/workflows/rust-ci.yml delete mode 100644 czech-file-knife/.github/workflows/scorecard-enforcer.yml create mode 100644 czech-file-knife/.github/workflows/security-policy.yml create mode 100644 czech-file-knife/.github/workflows/sonarqube.yml create mode 100644 czech-file-knife/.github/workflows/static-analysis-gate.yml delete mode 100644 czech-file-knife/.github/workflows/stress-test.yml create mode 100644 czech-file-knife/.github/workflows/workflow-linter.yml delete mode 100644 czech-file-knife/.gitlab-ci.yml create mode 100644 czech-file-knife/.gitleaksignore create mode 100644 czech-file-knife/.gitmessage create mode 100644 czech-file-knife/.hypatia-ignore delete mode 100644 czech-file-knife/.machine_readable/AGENTIC.scm delete mode 100644 czech-file-knife/.machine_readable/ECOSYSTEM.scm create mode 100644 czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml delete mode 100644 czech-file-knife/.machine_readable/META.scm delete mode 100644 czech-file-knife/.machine_readable/NEUROSYM.scm delete mode 100644 czech-file-knife/.machine_readable/PLAYBOOK.scm create mode 100644 czech-file-knife/.machine_readable/PROVENANCE.a2ml create mode 100644 czech-file-knife/.machine_readable/README.adoc delete mode 100644 czech-file-knife/.machine_readable/STATE.scm create mode 100644 czech-file-knife/.machine_readable/ai/AI.a2ml create mode 100644 czech-file-knife/.machine_readable/ai/README.adoc create mode 100644 czech-file-knife/.machine_readable/arrival-pack/README.adoc create mode 100644 czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl create mode 100644 czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl create mode 100755 czech-file-knife/.machine_readable/arrival-pack/extract.sh create mode 100755 czech-file-knife/.machine_readable/arrival-pack/generate.sh create mode 100755 czech-file-knife/.machine_readable/arrival-pack/verify.sh create mode 100644 czech-file-knife/.machine_readable/bot_directives/README.adoc create mode 100644 czech-file-knife/.machine_readable/bot_directives/coverage.a2ml create mode 100644 czech-file-knife/.machine_readable/bot_directives/debt.a2ml create mode 100644 czech-file-knife/.machine_readable/bot_directives/methodology.a2ml create mode 100644 czech-file-knife/.machine_readable/bot_directives/rra.a2ml create mode 100644 czech-file-knife/.machine_readable/coaptation/README.adoc create mode 100644 czech-file-knife/.machine_readable/coaptation/coapt.k9.ncl create mode 100644 czech-file-knife/.machine_readable/coaptation/coapt.ncl create mode 100755 czech-file-knife/.machine_readable/coaptation/coapt.sh create mode 100644 czech-file-knife/.machine_readable/coaptation/core/Coaptation.idr create mode 100755 czech-file-knife/.machine_readable/coaptation/extract-clauses.sh create mode 100755 czech-file-knife/.machine_readable/coaptation/extract-facts.sh create mode 100644 czech-file-knife/.machine_readable/coaptation/grades.ncl create mode 100644 czech-file-knife/.machine_readable/coaptation/receipts/latest.a2ml create mode 100755 czech-file-knife/.machine_readable/coaptation/verify.sh create mode 100644 czech-file-knife/.machine_readable/coaptation/witness-map.ncl create mode 100644 czech-file-knife/.machine_readable/compliance/reuse/dep5 create mode 100644 czech-file-knife/.machine_readable/compliance/rust/deny.toml create mode 100644 czech-file-knife/.machine_readable/configs/README.adoc create mode 100644 czech-file-knife/.machine_readable/configs/eclexiaiser.toml create mode 100644 czech-file-knife/.machine_readable/configs/git-cliff/cliff.toml create mode 100644 czech-file-knife/.machine_readable/configs/stapeln.toml create mode 100644 czech-file-knife/.machine_readable/contractiles/INDEX.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/Justfile create mode 100644 czech-file-knife/.machine_readable/contractiles/README.adoc create mode 100644 czech-file-knife/.machine_readable/contractiles/_base.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl create mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml create mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.ncl create mode 100644 czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/META.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/README.adoc create mode 100644 czech-file-knife/.machine_readable/descriptiles/STATE.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml create mode 100644 czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc create mode 100644 czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml create mode 100644 czech-file-knife/.machine_readable/integrations/groove.a2ml create mode 100644 czech-file-knife/.machine_readable/integrations/proven.a2ml create mode 100644 czech-file-knife/.machine_readable/integrations/verisimdb.a2ml create mode 100644 czech-file-knife/.machine_readable/integrations/vexometer.a2ml create mode 100644 czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore create mode 100644 czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml create mode 100644 czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml create mode 100644 czech-file-knife/.machine_readable/policies/README.adoc create mode 100644 czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml create mode 100644 czech-file-knife/.machine_readable/root-allow.txt create mode 100644 czech-file-knife/.machine_readable/rsr-profile.a2ml create mode 100644 czech-file-knife/.machine_readable/scripts/forge/README.adoc create mode 100755 czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh create mode 100755 czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh create mode 100644 czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc create mode 100755 czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh create mode 100644 czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh create mode 100644 czech-file-knife/.machine_readable/scripts/verification/README.adoc create mode 100644 czech-file-knife/.machine_readable/self-validating/README.adoc create mode 100644 czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl create mode 100644 czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl create mode 100644 czech-file-knife/.mailmap create mode 100644 czech-file-knife/.windsurfrules delete mode 100644 czech-file-knife/AI.a2ml delete mode 100644 czech-file-knife/AI.djot create mode 100644 czech-file-knife/CHANGELOG.adoc create mode 100644 czech-file-knife/CITATION.cff create mode 100644 czech-file-knife/CLAUDE.md delete mode 100644 czech-file-knife/CODE_OF_CONDUCT.adoc delete mode 100644 czech-file-knife/Containerfile create mode 100644 czech-file-knife/GEMINI.md create mode 100644 czech-file-knife/LICENSES/CC-BY-SA-4.0.txt create mode 100644 czech-file-knife/LICENSES/MPL-2.0.txt delete mode 100644 czech-file-knife/MAINTAINERS.adoc delete mode 100644 czech-file-knife/Mustfile delete mode 100644 czech-file-knife/ROADMAP.adoc delete mode 100644 czech-file-knife/RSR_OUTLINE.adoc delete mode 100644 czech-file-knife/SECURITY.adoc delete mode 100644 czech-file-knife/TESTING-REPORT.adoc delete mode 100644 czech-file-knife/TESTING-REPORT.scm create mode 100755 czech-file-knife/benches/template_bench.sh create mode 100644 czech-file-knife/build/container/.containerignore create mode 100644 czech-file-knife/build/container/Containerfile create mode 100644 czech-file-knife/build/container/README.adoc create mode 100644 czech-file-knife/build/container/compose.example.yaml create mode 100644 czech-file-knife/build/container/compose.yaml create mode 100755 czech-file-knife/build/container/entrypoint.sh create mode 100644 czech-file-knife/build/container/stapeln/.gatekeeper.yaml create mode 100644 czech-file-knife/build/container/stapeln/compose.example.toml create mode 100644 czech-file-knife/build/container/stapeln/compose.toml create mode 100755 czech-file-knife/build/container/stapeln/ct-build.sh create mode 100644 czech-file-knife/build/container/stapeln/deploy.k9.ncl create mode 100644 czech-file-knife/build/container/stapeln/manifest.toml create mode 100644 czech-file-knife/build/container/stapeln/rokur.toml create mode 100644 czech-file-knife/build/container/stapeln/vordr.toml rename czech-file-knife/{ => build}/guix.scm (100%) mode change 100644 => 100755 create mode 100644 czech-file-knife/build/just/assess.just create mode 100644 czech-file-knife/build/just/container.just create mode 100644 czech-file-knife/build/just/groove.just create mode 100644 czech-file-knife/build/just/proofs.just create mode 100644 czech-file-knife/build/just/repo-init.just create mode 100644 czech-file-knife/build/just/validate.just rename czech-file-knife/{ => build}/packaging/arch/PKGBUILD (97%) rename czech-file-knife/{ => build}/packaging/chocolatey/czech-file-knife.nuspec (100%) rename czech-file-knife/{ => build}/packaging/debian/control (97%) rename czech-file-knife/{ => build}/packaging/debian/rules (100%) rename czech-file-knife/{ => build}/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml (100%) rename czech-file-knife/{ => build}/packaging/macports/Portfile (97%) rename czech-file-knife/{ => build}/packaging/rpm/czech-file-knife.spec (97%) rename czech-file-knife/{ => build}/packaging/scoop/czech-file-knife.json (95%) rename czech-file-knife/{ => build}/packaging/winget/czech-file-knife.yaml (98%) create mode 100644 czech-file-knife/ci/.gitlab-ci.yml create mode 100644 czech-file-knife/ci/.pre-commit-config.yaml create mode 100644 czech-file-knife/ci/README.adoc delete mode 100644 czech-file-knife/contractiles/README.adoc delete mode 100644 czech-file-knife/contractiles/dust/Dustfile delete mode 100644 czech-file-knife/contractiles/must/Mustfile create mode 100644 czech-file-knife/coordination.k9.ncl create mode 100644 czech-file-knife/czech-file-knife_chora.deed delete mode 100644 czech-file-knife/deny.toml create mode 100644 czech-file-knife/docs/AFFIRMATION.adoc create mode 100644 czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc create mode 100644 czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc create mode 100644 czech-file-knife/docs/ARCHITECTURE.adoc create mode 100644 czech-file-knife/docs/AUDIT.adoc create mode 100644 czech-file-knife/docs/EXPLAINME.adoc create mode 100644 czech-file-knife/docs/GOVERNANCE.adoc create mode 100644 czech-file-knife/docs/MAINTAINERS.adoc create mode 100644 czech-file-knife/docs/QUICKSTART.adoc create mode 100644 czech-file-knife/docs/README.adoc create mode 100644 czech-file-knife/docs/RSR-PHILOSOPHY.adoc create mode 100644 czech-file-knife/docs/RSR_OUTLINE.adoc create mode 100644 czech-file-knife/docs/STATE-VISUALIZER.adoc create mode 100644 czech-file-knife/docs/architecture.adoc rename czech-file-knife/docs/{ => architecture}/DISTRIBUTED_FILESYSTEMS.adoc (100%) rename czech-file-knife/docs/{ => architecture}/HYBRID-OPERATIONS.adoc (100%) create mode 100644 czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc create mode 100644 czech-file-knife/docs/architecture/THREAT-MODEL.adoc create mode 100644 czech-file-knife/docs/architecture/TOPOLOGY.adoc rename czech-file-knife/docs/{CITATIONS.adoc => attribution/CFK-CITATIONS.adoc} (96%) create mode 100644 czech-file-knife/docs/attribution/CITATIONS.adoc create mode 100644 czech-file-knife/docs/attribution/CODEOWNERS.adoc create mode 100644 czech-file-knife/docs/attribution/README.adoc create mode 100644 czech-file-knife/docs/contributing.adoc create mode 100644 czech-file-knife/docs/decisions/0000-template.adoc create mode 100644 czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc create mode 100644 czech-file-knife/docs/decisions/0001-template.adoc create mode 100644 czech-file-knife/docs/decisions/0002-variant-contract.adoc create mode 100644 czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc create mode 100644 czech-file-knife/docs/decisions/README.adoc rename czech-file-knife/{ => docs/developer}/ABI-FFI-README.adoc (67%) rename czech-file-knife/docs/{ => developer}/IOS_INTEGRATION.adoc (100%) create mode 100644 czech-file-knife/docs/developer/README.adoc create mode 100644 czech-file-knife/docs/developer/invariant-path.adoc create mode 100644 czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc create mode 100644 czech-file-knife/docs/governance/CRG-CRITERIA.a2ml create mode 100644 czech-file-knife/docs/governance/CRG-CRITERIA.adoc create mode 100644 czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc create mode 100644 czech-file-knife/docs/governance/README.adoc create mode 100644 czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc create mode 100644 czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc create mode 100644 czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc create mode 100644 czech-file-knife/docs/governance/TSDM.a2ml create mode 100644 czech-file-knife/docs/governance/TSDM.adoc create mode 100644 czech-file-knife/docs/governance/audit/README.adoc create mode 100644 czech-file-knife/docs/governance/audit/compliance/README.adoc create mode 100644 czech-file-knife/docs/governance/audit/effects/README.adoc create mode 100644 czech-file-knife/docs/governance/audit/systems/README.adoc create mode 100644 czech-file-knife/docs/governance/maintenance/README.adoc create mode 100644 czech-file-knife/docs/governance/maintenance/adaptive/README.adoc create mode 100644 czech-file-knife/docs/governance/maintenance/corrective/README.adoc create mode 100644 czech-file-knife/docs/governance/maintenance/perfective/README.adoc create mode 100644 czech-file-knife/docs/governance/planning/README.adoc create mode 100644 czech-file-knife/docs/governance/planning/could/README.adoc create mode 100644 czech-file-knife/docs/governance/planning/must/README.adoc create mode 100644 czech-file-knife/docs/governance/planning/should/README.adoc create mode 100644 czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt create mode 100644 czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt rename czech-file-knife/{ => docs/legal}/PALIMPSEST.adoc (100%) create mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc create mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc create mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc create mode 100644 czech-file-knife/docs/onboarding/llm-warmup-dev.adoc create mode 100644 czech-file-knife/docs/onboarding/llm-warmup-user.adoc create mode 100644 czech-file-knife/docs/practice/AI-CONVENTIONS.adoc create mode 100644 czech-file-knife/docs/practice/README.adoc create mode 100644 czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc create mode 100644 czech-file-knife/docs/practice/ci-cost-reduction.adoc create mode 100644 czech-file-knife/docs/proposals/root-cleanup.adoc create mode 100644 czech-file-knife/docs/standards/README.adoc create mode 100644 czech-file-knife/docs/status/PROOF-NEEDS.adoc create mode 100644 czech-file-knife/docs/status/PROOF-STATUS.adoc create mode 100644 czech-file-knife/docs/status/READINESS.adoc create mode 100644 czech-file-knife/docs/status/ROADMAP.adoc create mode 100644 czech-file-knife/docs/status/TEST-NEEDS.adoc create mode 100644 czech-file-knife/docs/theory/README.adoc create mode 100644 czech-file-knife/docs/theory/computing/README.adoc create mode 100644 czech-file-knife/docs/theory/formalisms/README.adoc create mode 100644 czech-file-knife/docs/theory/mathematics/README.adoc create mode 100644 czech-file-knife/docs/theory/ontologies/README.adoc create mode 100644 czech-file-knife/docs/theory/other/README.adoc create mode 100644 czech-file-knife/docs/theory/socio-technical/README.adoc create mode 100644 czech-file-knife/docs/troubleshooting.adoc create mode 100644 czech-file-knife/docs/usage.adoc create mode 100644 czech-file-knife/docs/whitepapers/README.adoc create mode 100644 czech-file-knife/docs/whitepapers/academic/README.adoc create mode 100644 czech-file-knife/docs/whitepapers/industry/README.adoc create mode 100644 czech-file-knife/docs/whitepapers/outreach/README.adoc create mode 100644 czech-file-knife/docs/wikis/README.md create mode 100644 czech-file-knife/examples/README.adoc rename czech-file-knife/{manifest/sample.ncl => examples/sample-manifest.ncl} (100%) create mode 100644 czech-file-knife/features/README.adoc create mode 100644 czech-file-knife/features/boj-server/README.adoc create mode 100644 czech-file-knife/features/panic-attacker/README.adoc create mode 100644 czech-file-knife/features/ssg/README.adoc create mode 100755 czech-file-knife/features/ssg/ssg-bootstrap.sh delete mode 100755 czech-file-knife/hooks/validate-codeql.sh delete mode 100755 czech-file-knife/hooks/validate-permissions.sh delete mode 100755 czech-file-knife/hooks/validate-sha-pins.sh delete mode 100755 czech-file-knife/hooks/validate-spdx.sh delete mode 100644 czech-file-knife/license/PMPL-1.0.txt create mode 100644 czech-file-knife/mise.toml delete mode 100644 czech-file-knife/ops/podman-compose.yml delete mode 100755 czech-file-knife/ops/scripts/ensure_deps.sh delete mode 100755 czech-file-knife/ops/scripts/ensure_kafka.sh create mode 100644 czech-file-knife/scripts/campaigns/www-bundle.campaign create mode 100644 czech-file-knife/scripts/check-action-pinning.js create mode 100755 czech-file-knife/scripts/check-adoc-renders.sh create mode 100755 czech-file-knife/scripts/check-invisible-characters.sh create mode 100755 czech-file-knife/scripts/check-lock-sync.sh create mode 100644 czech-file-knife/scripts/check-no-md-in-docs.sh create mode 100755 czech-file-knife/scripts/check-no-placeholders.sh create mode 100755 czech-file-knife/scripts/check-no-vlang.sh create mode 100755 czech-file-knife/scripts/check-proofs.sh create mode 100755 czech-file-knife/scripts/check-root-shape.sh create mode 100755 czech-file-knife/scripts/check-template-conformance.sh create mode 100755 czech-file-knife/scripts/check-variant-drift.sh create mode 100755 czech-file-knife/scripts/gen-repo-map.sh create mode 100755 czech-file-knife/scripts/invariant-path.sh create mode 100755 czech-file-knife/scripts/migrate-wellknown-to-www.sh create mode 100644 czech-file-knife/scripts/prune-dependabot-ecosystems.rs create mode 100644 czech-file-knife/scripts/rsr-campaign.sh create mode 100644 czech-file-knife/scripts/rust-tool.sh create mode 100755 czech-file-knife/scripts/scan-dangerous.sh create mode 100644 czech-file-knife/scripts/strip-instruction-blocks.rs create mode 100755 czech-file-knife/scripts/sweep-wellknown.sh create mode 100644 czech-file-knife/scripts/validate-session-contracts.sh create mode 100755 czech-file-knife/scripts/validate-template.sh create mode 100644 czech-file-knife/session/README.adoc create mode 100644 czech-file-knife/session/custom-checks.k9.ncl create mode 100755 czech-file-knife/session/dispatch.sh create mode 100755 czech-file-knife/session/local-hooks.sh create mode 100644 czech-file-knife/sonar-project.properties create mode 100644 czech-file-knife/src/README.adoc create mode 100644 czech-file-knife/src/aspects/README.adoc create mode 100644 czech-file-knife/src/aspects/integrity/README.adoc create mode 100644 czech-file-knife/src/aspects/observability/README.adoc create mode 100644 czech-file-knife/src/aspects/security/README.adoc rename czech-file-knife/{.nojekyll => src/bridges/.gitkeep} (100%) rename czech-file-knife/{ => src}/cfk-cache/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-cache/src/blob_store.rs (99%) rename czech-file-knife/{ => src}/cfk-cache/src/lib.rs (99%) rename czech-file-knife/{ => src}/cfk-cache/src/metadata_cache.rs (100%) rename czech-file-knife/{ => src}/cfk-cache/src/policy.rs (100%) rename czech-file-knife/{ => src}/cfk-cache/src/sled_backend.rs (100%) rename czech-file-knife/{ => src}/cfk-cli/Cargo.toml (85%) rename czech-file-knife/{ => src}/cfk-cli/src/commands.rs (100%) rename czech-file-knife/{ => src}/cfk-cli/src/main.rs (100%) rename czech-file-knife/{ => src}/cfk-core/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-core/src/backend.rs (99%) rename czech-file-knife/{ => src}/cfk-core/src/entry.rs (98%) rename czech-file-knife/{ => src}/cfk-core/src/error.rs (99%) rename czech-file-knife/{ => src}/cfk-core/src/lib.rs (93%) rename czech-file-knife/{ => src}/cfk-core/src/metadata.rs (97%) rename czech-file-knife/{ => src}/cfk-core/src/operations.rs (96%) rename czech-file-knife/{ => src}/cfk-core/src/path.rs (99%) rename czech-file-knife/{ => src}/cfk-core/src/platform.rs (99%) rename czech-file-knife/{ => src}/cfk-core/src/reversible.rs (100%) rename czech-file-knife/{ => src}/cfk-integrations/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-integrations/src/agrep.rs (98%) rename czech-file-knife/{ => src}/cfk-integrations/src/aria2.rs (98%) rename czech-file-knife/{ => src}/cfk-integrations/src/lib.rs (97%) rename czech-file-knife/{ => src}/cfk-integrations/src/pandoc.rs (98%) rename czech-file-knife/{ => src}/cfk-ios/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-ios/src/domain.rs (99%) rename czech-file-knife/{ => src}/cfk-ios/src/error.rs (100%) rename czech-file-knife/{ => src}/cfk-ios/src/ffi.rs (99%) rename czech-file-knife/{ => src}/cfk-ios/src/item.rs (100%) rename czech-file-knife/{ => src}/cfk-ios/src/lib.rs (98%) rename czech-file-knife/{ => src}/cfk-ios/src/provider.rs (100%) rename czech-file-knife/{ => src}/cfk-ios/swift/CfkBridge.h (100%) rename czech-file-knife/{ => src}/cfk-ios/swift/CfkFileProviderExtension.swift (100%) rename czech-file-knife/{ => src}/cfk-ios/swift/CfkFileProviderItem.swift (100%) rename czech-file-knife/{ => src}/cfk-providers/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-providers/src/afs.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/box_com.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/ceph.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/dropbox.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/gdrive.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/ipfs.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/lib.rs (98%) rename czech-file-knife/{ => src}/cfk-providers/src/local.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/nfs.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/ninep.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/onedrive.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/protocols.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/s3.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/sftp.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/smb.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/syncthing.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/transport.rs (99%) rename czech-file-knife/{ => src}/cfk-providers/src/webdav.rs (99%) rename czech-file-knife/{ => src}/cfk-search/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-search/src/lib.rs (100%) rename czech-file-knife/{ => src}/cfk-tui/cfk_tui.gpr (100%) rename czech-file-knife/{ => src}/cfk-tui/src/cfk-tui-application.ads (100%) rename czech-file-knife/{ => src}/cfk-tui/src/cfk_tui_main.adb (100%) rename czech-file-knife/{ => src}/cfk-vfs/Cargo.toml (100%) rename czech-file-knife/{ => src}/cfk-vfs/src/lib.rs (100%) create mode 100644 czech-file-knife/src/contracts/README.adoc create mode 100644 czech-file-knife/src/core/.gitkeep create mode 100644 czech-file-knife/src/definitions/README.adoc create mode 100644 czech-file-knife/src/errors/README.adoc create mode 100755 czech-file-knife/tests/aspect_tests.sh create mode 100755 czech-file-knife/tests/e2e.sh create mode 100644 czech-file-knife/tests/e2e/julia_mint_test.sh create mode 100755 czech-file-knife/tests/e2e/template_instantiation_test.sh rename czech-file-knife/{ => tests}/fuzz/Cargo.toml (93%) create mode 100644 czech-file-knife/tests/fuzz/README.adoc rename czech-file-knife/{ => tests}/fuzz/fuzz_targets/fuzz_path.rs (100%) create mode 100755 czech-file-knife/tests/invisible-characters-test.sh create mode 100755 czech-file-knife/tests/shape/check_root_shape_test.sh create mode 100755 czech-file-knife/tests/shape/repo_map_determinism_test.sh create mode 100755 czech-file-knife/tests/workflows/check_adoc_renders_test.sh create mode 100755 czech-file-knife/tests/workflows/check_no_vlang_test.sh create mode 100644 czech-file-knife/tests/workflows/k9_typecheck_test.sh create mode 100644 czech-file-knife/tests/workflows/mint_cleanup_test.sh create mode 100644 czech-file-knife/tests/workflows/session_contracts_test.sh create mode 100755 czech-file-knife/tests/workflows/template_conformance_test.sh create mode 100755 czech-file-knife/tests/workflows/validate_workflows_test.sh create mode 100644 czech-file-knife/verification/README.adoc create mode 100644 czech-file-knife/verification/benchmarks/README.adoc create mode 100644 czech-file-knife/verification/coverage/README.adoc create mode 100644 czech-file-knife/verification/fuzzing/README.adoc create mode 100644 czech-file-knife/verification/proofs/README.adoc create mode 100644 czech-file-knife/verification/proofs/agda/MANIFEST create mode 100644 czech-file-knife/verification/proofs/agda/Properties.agda create mode 100644 czech-file-knife/verification/proofs/coq/MANIFEST create mode 100644 czech-file-knife/verification/proofs/coq/TypeSafety.v create mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr create mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr create mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Layout.idr create mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Platform.idr create mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr create mode 100644 czech-file-knife/verification/proofs/idris2/MANIFEST create mode 100644 czech-file-knife/verification/proofs/idris2/Types.idr create mode 100644 czech-file-knife/verification/proofs/lean4/ApiTypes.lean create mode 100644 czech-file-knife/verification/proofs/lean4/MANIFEST create mode 100644 czech-file-knife/verification/proofs/lean4/lean-toolchain create mode 100644 czech-file-knife/verification/proofs/tlaplus/StateMachine.tla create mode 100644 czech-file-knife/verification/safety_case/README.adoc create mode 100644 czech-file-knife/verification/simulations/README.adoc create mode 100644 czech-file-knife/verification/tests/README.adoc create mode 100644 czech-file-knife/verification/traceability/README.adoc create mode 100644 czech-file-knife/www/.well-known/ai.txt create mode 100644 czech-file-knife/www/.well-known/aibdp.json create mode 100644 czech-file-knife/www/.well-known/aibdp.json.license create mode 100644 czech-file-knife/www/.well-known/humans.txt create mode 100644 czech-file-knife/www/.well-known/security.txt create mode 100644 czech-file-knife/www/README.adoc create mode 100644 czech-file-knife/www/dns/bind9/README.adoc create mode 100644 czech-file-knife/www/dns/bind9/named.conf.example create mode 100644 czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc create mode 100644 czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone create mode 100644 czech-file-knife/www/dns/records/README.adoc create mode 100644 czech-file-knife/www/dns/records/example.invalid.zone create mode 100644 czech-file-knife/www/errors/403.html create mode 100644 czech-file-knife/www/errors/404.html create mode 100644 czech-file-knife/www/errors/429.html create mode 100644 czech-file-knife/www/errors/500.html create mode 100644 czech-file-knife/www/errors/502.html create mode 100644 czech-file-knife/www/errors/503.html create mode 100644 czech-file-knife/www/policies/acceptable-use.adoc create mode 100644 czech-file-knife/www/policies/ai-use.adoc create mode 100644 czech-file-knife/www/policies/privacy.adoc create mode 100644 czech-file-knife/www/profiles/README.adoc create mode 100644 czech-file-knife/www/profiles/authoritative-dns.toml create mode 100644 czech-file-knife/www/profiles/baseline-site.toml create mode 100644 czech-file-knife/www/profiles/consent-aware-web.toml create mode 100644 czech-file-knife/www/profiles/full-expert.toml create mode 100644 czech-file-knife/www/profiles/privacy-enhanced.toml create mode 100644 czech-file-knife/www/public/index.html create mode 100644 czech-file-knife/www/public/styles/site.css create mode 100644 czech-file-knife/www/runbooks/cert-rotation.adoc create mode 100644 czech-file-knife/www/runbooks/deploy.adoc create mode 100644 czech-file-knife/www/runbooks/dns-change.adoc create mode 100644 czech-file-knife/www/runbooks/rollback.adoc create mode 100644 czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc create mode 100644 czech-file-knife/www/schemas/aibdp-schema-v0.2.json create mode 100644 czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license create mode 100644 czech-file-knife/www/schemas/publishable-paths.txt create mode 100644 czech-file-knife/www/security_headers/README.adoc create mode 100644 czech-file-knife/www/security_headers/csp.conf create mode 100755 czech-file-knife/www/tests/check-aibdp.sh create mode 100755 czech-file-knife/www/tests/check-bind-safety.sh create mode 100755 czech-file-knife/www/tests/check-migration.sh create mode 100755 czech-file-knife/www/tests/check-profiles.sh create mode 100755 czech-file-knife/www/tests/check-publication-boundary.sh create mode 100755 czech-file-knife/www/tests/check-wellknown.sh create mode 100644 czech-file-knife/www/tests/controls/aibdp-bad-status.control.json create mode 100644 czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license create mode 100644 czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json create mode 100644 czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license create mode 100644 czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf create mode 100644 czech-file-knife/www/tests/controls/bad-deploy/index.html create mode 100644 czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh create mode 100644 czech-file-knife/www/tests/controls/caddy-serve-everything.control create mode 100644 czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt create mode 100644 czech-file-knife/www/tests/controls/good-deploy/errors/404.html create mode 100644 czech-file-knife/www/tests/controls/good-deploy/index.html create mode 100644 czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html create mode 100644 czech-file-knife/www/tests/controls/named.conf.open-recursion.control create mode 100644 czech-file-knife/www/tests/controls/profile-enforce-430.control.toml create mode 100644 czech-file-knife/www/tests/controls/profile-hidden-start.control.toml create mode 100755 czech-file-knife/www/tests/run-all.sh create mode 100644 czech-file-knife/www/tls/POLICY.adoc create mode 100644 czech-file-knife/www/webservers/README.adoc create mode 100644 czech-file-knife/www/webservers/apache/vhost.conf.example create mode 100644 czech-file-knife/www/webservers/caddy/Caddyfile.example create mode 100644 czech-file-knife/www/webservers/nginx/site.conf.example diff --git a/czech-file-knife/.cicd-hygiene-allow b/czech-file-knife/.cicd-hygiene-allow new file mode 100644 index 000000000..1513822b2 --- /dev/null +++ b/czech-file-knife/.cicd-hygiene-allow @@ -0,0 +1,12 @@ +# Code-hygiene gate allowlist (consumed by cicd-suite actions/code-hygiene-check). +# Patterns are git pathspec excludes, applied to both the debt-marker scan and +# the proof-circumvention scan. +# +# The two entries below are TEMPLATE SCAFFOLDS, not implementation debt: +# their TODOs are the instantiation seams every minted repo is meant to fill +# in (entrypoint command, e2e sections). Excluding them keeps the gate's +# debt-tracking signal clean for real source in instantiated repos, which +# inherit this file. If an instantiated repo later owns genuine debt in +# these paths, resolve or issue-link it there — do not widen this list. +build/container/entrypoint.sh +tests/e2e.sh diff --git a/czech-file-knife/.claude/CLAUDE.md b/czech-file-knife/.claude/CLAUDE.md deleted file mode 100644 index 9c8e5fd97..000000000 --- a/czech-file-knife/.claude/CLAUDE.md +++ /dev/null @@ -1,83 +0,0 @@ -## Machine-Readable Artefacts - -The following files in `.machine_readable/` contain structured project metadata: - -- `STATE.scm` - Current project state and progress -- `META.scm` - Architecture decisions and development practices -- `ECOSYSTEM.scm` - Position in the ecosystem and related projects -- `AGENTIC.scm` - AI agent interaction patterns -- `NEUROSYM.scm` - Neurosymbolic integration config -- `PLAYBOOK.scm` - Operational runbook - ---- - -# CLAUDE.md - AI Assistant Instructions - -## Language Policy (Hyperpolymath Standard) - -### ALLOWED Languages & Tools - -| Language/Tool | Use Case | Notes | -|---------------|----------|-------| -| **AffineScript** | Primary application code | Affine-typed, compiles to typed-wasm or ESM | -| **Bun** | JS runtime & package management (tier 1) | Default for all new work. Runs compiled ESM/JS directly — no bundler step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. | -| **Rust** | Performance-critical, systems, WASM | Preferred for CLI tools | -| **Tauri 2.0+** | Mobile apps (iOS/Android) | Rust backend + web UI | -| **Dioxus** | Mobile apps (native UI) | Pure Rust, React-like | -| **Gleam** | Backend services | Runs on BEAM or compiles to JS | -| **Bash/POSIX Shell** | Scripts, automation | Keep minimal | -| **JavaScript** | Only where AffineScript cannot | MCP protocol glue, Bun APIs | -| **Nickel** | Configuration language | For complex configs | -| **Guile Scheme** | State/meta files | STATE.scm, META.scm, ECOSYSTEM.scm | -| **Julia** | Batch scripts, data processing | Per RSR | -| **OCaml** | AffineScript compiler | Language-specific | -| **Ada** | Safety-critical systems | Where required | - -### BANNED - Do Not Use - -| Banned | Replacement | -|--------|-------------| -| TypeScript | AffineScript | -| Deno | Bun | -| Node.js | Bun | -| npm | Bun | -| pnpm/yarn | Bun | -| Go | Rust | -| Python | Julia/Rust/AffineScript | -| Java/Kotlin | Rust/Tauri/Dioxus | -| Swift | Tauri/Dioxus | -| React Native | Tauri/Dioxus | -| Flutter/Dart | Tauri/Dioxus | - -### Mobile Development - -**No exceptions for Kotlin/Swift** - use Rust-first approach: - -1. **Tauri 2.0+** - Web UI (AffineScript) + Rust backend, MIT/Apache-2.0 -2. **Dioxus** - Pure Rust native UI, MIT/Apache-2.0 - -Both are FOSS with independent governance (no Big Tech). - -### Enforcement Rules - -1. **No new TypeScript files** - Convert existing TS to AffineScript -2. **Use `package.json` + `bun.lock` for JS runtime deps** - Bun is npm-compatible; a manifest is REQUIRED -3. **`bun install --production` for production deps** - resolved from `package.json`, pinned via `bun.lock` -4. **No Go code** - Use Rust instead -5. **No Python anywhere** - Use Julia for data/batch, Rust for systems, AffineScript for apps -6. **No Kotlin/Swift for mobile** - Use Tauri 2.0+ or Dioxus - -### Package Management - -- **Primary**: Guix (guix.scm) -- **Fallback**: Nix (flake.nix) -- **JS deps**: Bun (`package.json` + `bun.lock`). Declare tooling as a devDependency and run `bunx --no-install --bun ` — a bare `bunx ` can fetch an unpinned package and may start Node via its shebang. - -### Security Requirements - -- No MD5/SHA1 for security (use SHA256+) -- HTTPS only (no HTTP URLs) -- No hardcoded secrets -- SHA-pinned dependencies -- SPDX license headers on all files - diff --git a/czech-file-knife/.clinerules b/czech-file-knife/.clinerules new file mode 100644 index 000000000..fb38d21d8 --- /dev/null +++ b/czech-file-knife/.clinerules @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# Authoritative source: docs/practice/AI-CONVENTIONS.adoc + +# STARTUP: Read the repo deed (*_chora.deed at the repo root) first, then .machine_readable/descriptiles/STATE.a2ml. + +# LICENSE +# All original code: MPL-2.0. +# Never AGPL-3.0. MPL-2.0 only as platform-required fallback. +# SPDX header required on every source file. +# Copyright: Jonathan D.A. Jewell (hyperpolymath) + +# STATE FILES (.machine_readable/ ONLY) +# Never create in repo root: STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, +# AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml. +# The .machine_readable/ directory is the single source of truth. + +# BANNED PATTERNS +# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO +# Haskell: unsafeCoerce, unsafePerformIO, undefined, error +# OCaml: Obj.magic, Obj.repr, Obj.obj +# Coq: Admitted +# Lean: sorry +# Rust: transmute (unless FFI with // SAFETY: comment) + +# JS/TS RUNTIMES — ordered preference, reach for the first that can do the job +# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) +# 1. Bun default for all new work; runs .ts directly, no build step +# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm +# 3. pnpm only where an upstream toolchain needs a node_modules layout +# 4. npm last resort; permitted, never preferred — a noted decision +# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is +# RETIRED: ReScript is no longer used in this estate, so that rule named a dead +# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. + +# BANNED LANGUAGES +# Go -> Rust +# Python -> Julia or Rust + +# CONTAINERS +# Runtime: Podman first. +# File: Containerfile (never Dockerfile). +# Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. + +# ABI/FFI +# This repo does not carry the estate Idris2/Zig ABI seam (not declared in +# .machine_readable/rsr-profile.a2ml). The only FFI is the C ABI in +# src/cfk-ios (Swift File Provider bridge). + +# BUILD: Use just (Justfile) for all tasks. +# STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/czech-file-knife/.clusterfuzzlite/build.sh b/czech-file-knife/.clusterfuzzlite/build.sh index f0c8f3e89..31b15f12e 100644 --- a/czech-file-knife/.clusterfuzzlite/build.sh +++ b/czech-file-knife/.clusterfuzzlite/build.sh @@ -5,9 +5,9 @@ cd $SRC/czech-file-knife # Build fuzz targets using cargo-fuzz -cargo +nightly fuzz build +cargo +nightly fuzz build --fuzz-dir tests/fuzz # Copy fuzz targets to $OUT -for target in $(cargo +nightly fuzz list); do +for target in $(cargo +nightly fuzz list --fuzz-dir tests/fuzz); do cp ./target/x86_64-unknown-linux-gnu/release/$target $OUT/ done diff --git a/czech-file-knife/.cursorrules b/czech-file-knife/.cursorrules new file mode 100644 index 000000000..82200daf6 --- /dev/null +++ b/czech-file-knife/.cursorrules @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# Authoritative source: docs/practice/AI-CONVENTIONS.adoc + +# Read the repo deed (*_chora.deed in the repo root) FIRST: canonical file locations +# live in its (ply ...) canonical-locations clauses. + +# LICENSE +# All original code: MPL-2.0 (SPDX header required on every file). +# Copyright: Jonathan D.A. Jewell (hyperpolymath) + +# STATE FILES +# .deed metadata files go in .machine_readable/ ONLY. +# Never create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, or PLAYBOOK.a2ml. + +# BANNED PATTERNS +# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO +# Haskell: unsafeCoerce, unsafePerformIO, undefined, error +# OCaml: Obj.magic, Obj.repr, Obj.obj +# Coq: Admitted +# Lean: sorry +# Rust: transmute (unless FFI with // SAFETY: comment) + +# JS RUNTIMES — ordered preference, reach for the first that can do the job +# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) +# 1. Bun default for all new work; runs .ts directly, no build step +# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm +# 3. pnpm only where an upstream toolchain needs a node_modules layout +# 4. npm last resort; permitted, never preferred — a noted decision +# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is +# RETIRED: ReScript is no longer used in this estate, so that rule named a dead +# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. + +# BANNED LANGUAGES +# Go -> use Rust +# Python -> use Julia or Rust + +# CONTAINERS +# Runtime: Podman (never Docker) +# File: Containerfile (never Dockerfile) +# Base: cgr.dev/chainguard/wolfi-base:latest + +# ABI/FFI STANDARD +# No estate Idris2/Zig ABI seam in this repo (see rsr-profile.a2ml); +# the only FFI is the C ABI in src/cfk-ios (Swift bridge). + +# BUILD SYSTEM +# Use just (Justfile) for all build, test, lint, and format tasks. + +# CODE STYLE +# Use descriptive variable names. +# Annotate and document all files. +# Add SPDX-License-Identifier headers to every source file. diff --git a/czech-file-knife/.devcontainer/Containerfile b/czech-file-knife/.devcontainer/Containerfile new file mode 100644 index 000000000..2d7c5c7ff --- /dev/null +++ b/czech-file-knife/.devcontainer/Containerfile @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Dev Container image for Czech File Knife +# Base: Chainguard Wolfi (minimal, supply-chain-secure) +# Build: podman build -t Czech File Knife-dev -f .devcontainer/Containerfile . +# `:latest` suits a dev container (rebuilt daily); pin by digest +# (...@sha256:) if you need a reproducible dev environment. + +FROM cgr.dev/chainguard/wolfi-base:latest + +# Install common development tools +RUN apk update && apk add --no-cache \ + bash \ + curl \ + git \ + openssh-client \ + ca-certificates \ + build-base \ + posix-libc-utils \ + shadow \ + && rm -rf /var/cache/apk/* + +# Create non-root dev user (matches devcontainer.json remoteUser) +RUN groupadd -g 1000 nonroot || true \ + && useradd -m -u 1000 -g 1000 -s /bin/bash nonroot || true + +# Set workspace directory +WORKDIR /workspaces/Czech File Knife + +# Default shell +ENV SHELL=/bin/bash + +USER nonroot diff --git a/czech-file-knife/.devcontainer/README.adoc b/czech-file-knife/.devcontainer/README.adoc new file mode 100644 index 000000000..e23a08cfb --- /dev/null +++ b/czech-file-knife/.devcontainer/README.adoc @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Dev Container Usage +:author: Jonathan D.A. Jewell + +== Overview + +This dev container uses `cgr.dev/chainguard/wolfi-base` with git, curl, bash, and just pre-installed. Dev container features add git, just, and nickel automatically. + +== VS Code (Local) + +. Install the *Dev Containers* extension (`ms-vscode-remote.remote-containers`). +. Set `dev.containers.dockerPath` to `podman` in VS Code settings. +. Open the repo folder, then choose **Reopen in Container** from the command palette. + +== GitHub Codespaces + +. From the repository on GitHub, click **Code > Codespaces > New codespace**. +. The container builds automatically from this configuration. + +== Gitpod + +. Prefix the repo URL with `https://gitpod.io/#` to launch a workspace. +. Gitpod reads `devcontainer.json` and builds the environment. + +== Customization + +Replace `Czech File Knife` placeholders in both `devcontainer.json` and `Containerfile` with your actual project name. Run `just deps` to verify the environment after first launch. diff --git a/czech-file-knife/.devcontainer/devcontainer.json b/czech-file-knife/.devcontainer/devcontainer.json new file mode 100644 index 000000000..a50659a48 --- /dev/null +++ b/czech-file-knife/.devcontainer/devcontainer.json @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +// +// Dev Container configuration for Czech File Knife +// Works with: VS Code Dev Containers, GitHub Codespaces, Gitpod +// Container runtime: Podman (recommended) or any OCI-compliant runtime +{ + "name": "Czech File Knife", + + "build": { + "dockerfile": "Containerfile", + "context": ".." + }, + + "features": { + "ghcr.io/devcontainers/features/git:1": { + "ppa": false, + "version": "latest" + }, + "ghcr.io/jdx/devcontainer-features/just:1": {}, + "ghcr.io/nickel-lang/devcontainer-feature:0": {} + }, + + "postCreateCommand": "just deps", + + "remoteUser": "nonroot", + + "containerEnv": { + "EDITOR": "code --wait", + "LANG": "C.UTF-8" + }, + + "customizations": { + "vscode": { + "extensions": [ + "EditorConfig.EditorConfig", + "eamodio.gitlens", + "streetsidesoftware.code-spell-checker", + "timonwong.shellcheck", + "tamasfe.even-better-toml", + "skellock.just", + "redhat.vscode-yaml", + "DavidAnson.vscode-markdownlint", + "asciidoctor.asciidoctor-vscode", + "usernamehw.errorlens" + ], + "settings": { + "editor.formatOnSave": true, + "editor.insertSpaces": true, + "editor.tabSize": 2, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "[makefile]": { + "editor.insertSpaces": false + } + } + }, + "codespaces": { + "openFiles": [ + "README.adoc" + ] + } + }, + + "forwardPorts": [], + + "shutdownAction": "stopContainer" +} diff --git a/czech-file-knife/.editorconfig b/czech-file-knife/.editorconfig index f67573749..a6faed49a 100644 --- a/czech-file-knife/.editorconfig +++ b/czech-file-knife/.editorconfig @@ -1,34 +1,46 @@ -# czech-file-knife - Editor Configuration +# SPDX-License-Identifier: MPL-2.0 +# Hyperpolymath estate canonical .editorconfig (standards#343 phase 1) +# Canon lives in czech-file-knife; do not add a per-repo name to this header. # https://editorconfig.org root = true +# --- Estate baseline ------------------------------------------------------- [*] charset = utf-8 end_of_line = lf -indent_size = 2 indent_style = space +indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true +# --- Prose: trailing whitespace is significant ----------------------------- [*.md] trim_trailing_whitespace = false [*.adoc] trim_trailing_whitespace = false +# --- 4-space languages ----------------------------------------------------- [*.rs] indent_size = 4 -[*.ex] -indent_size = 2 +[*.zig] +indent_size = 4 -[*.exs] -indent_size = 2 +[*.jl] +indent_size = 4 -[*.zig] +[*.c] +indent_size = 4 + +[*.h] +indent_size = 4 + +[*.php] indent_size = 4 +# --- 3-space languages (Ada / GNAT house style) ---------------------------- [*.ada] indent_size = 3 @@ -38,13 +50,17 @@ indent_size = 3 [*.ads] indent_size = 3 +[*.gpr] +indent_size = 3 + +# --- 2-space languages (explicit; matches the [*] default) ----------------- [*.hs] indent_size = 2 -[*.res] +[*.ex] indent_size = 2 -[*.resi] +[*.exs] indent_size = 2 [*.ncl] @@ -56,13 +72,69 @@ indent_size = 2 [*.scm] indent_size = 2 -[*.nix] +[*.idr] +indent_size = 2 + +[*.ipkg] +indent_size = 2 + +[*.k9] +indent_size = 2 + +[*.agda] +indent_size = 2 + +[*.lean] indent_size = 2 +[*.ebnf] +indent_size = 2 + +# --- Tab-mandatory formats ------------------------------------------------- + +# --- Task runners ---------------------------------------------------------- [Justfile] indent_style = space indent_size = 4 -[justfile] +[*.just] indent_style = space indent_size = 4 + +[Mustfile] +indent_style = space +indent_size = 4 + +# --- Platform-mandated line endings ---------------------------------------- +# Windows batch/PowerShell hosts require CRLF; keep in step with .gitattributes. +[*.bat] +end_of_line = crlf + +[*.cmd] +end_of_line = crlf + +[*.ps1] +end_of_line = crlf + +# --- Legacy / retired toolchains (retained for byte hygiene only) ---------- +# ReScript (LANGUAGE-POLICY 1.2) and Nix (retired 2026-06-01) are no longer +# adopted for new work. These sections are inert where the files are absent and +# keep surviving files from drifting. Removal is a per-repo judgement. + +[*.a2ml] +indent_size = 2 + +[*.go] +indent_style = tab + +[*.nix] +indent_size = 2 + +[*.res] +indent_size = 2 + +[*.resi] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/czech-file-knife/.envrc b/czech-file-knife/.envrc new file mode 100644 index 000000000..6fe01c3d8 --- /dev/null +++ b/czech-file-knife/.envrc @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +# Activate development environment +# Install direnv: https://direnv.net/ + +# Load .tool-versions if asdf is available +if has mise; then + use mise +fi + +# Load Guix shell if build/guix.scm exists +if has guix && [ -f build/guix.scm ]; then + use guix +fi + +# Project environment variables +export PROJECT_NAME="Czech File Knife" +export RSR_TIER="infrastructure" +# Add non-secret project env vars above (e.g. DATABASE_URL, service endpoints). +# Real secrets belong in .env (gitignored) — never commit them to .envrc. + +# Source .env if it exists (gitignored) +dotenv_if_exists diff --git a/czech-file-knife/.gitattributes b/czech-file-knife/.gitattributes index e860a85c1..8578ef91b 100644 --- a/czech-file-knife/.gitattributes +++ b/czech-file-knife/.gitattributes @@ -1,54 +1,117 @@ # SPDX-License-Identifier: MPL-2.0 -# RSR-compliant .gitattributes +# Hyperpolymath estate canonical .gitattributes (standards#343 phase 1) +# Canon lives in czech-file-knife; do not add a per-repo name to this header. +# +# Only diff drivers that git actually ships are used here. MEASURED on git +# 2.47.3: diff=go and diff=zig are NOT drivers (git silently falls back to the +# default heuristic); the Go driver is named `golang`. * text=auto eol=lf -# Source +# --- Source ---------------------------------------------------------------- *.rs text eol=lf diff=rust *.ex text eol=lf diff=elixir *.exs text eol=lf diff=elixir -*.jl text eol=lf -*.res text eol=lf -*.resi text eol=lf *.ada text eol=lf diff=ada *.adb text eol=lf diff=ada *.ads text eol=lf diff=ada +*.gpr text eol=lf diff=ada +*.go text eol=lf diff=golang +*.scm text eol=lf diff=scheme linguist-language=Scheme +*.rkt text eol=lf +*.jl text eol=lf *.hs text eol=lf +*.zig text eol=lf *.chpl text eol=lf -*.scm text eol=lf +*.idr text eol=lf linguist-language=Idris +*.ipkg text eol=lf linguist-language=Idris +*.agda text eol=lf linguist-language=Agda +*.lagda text eol=lf linguist-language=Agda +*.lean text eol=lf +# `.v` is ambiguous (Coq / Verilog / V). This estate's `.v` files are Coq +# proofs; 49 repos currently mislabel them `linguist-language=V`. +*.v text eol=lf linguist-language=Coq *.ncl text eol=lf -*.nix text eol=lf +*.k9 text eol=lf linguist-language=Nickel +*.deed text eol=lf linguist-language=SCM +*.ebnf text eol=lf +*.js text eol=lf +*.sh text eol=lf diff=bash +*.bash text eol=lf diff=bash + +# --- Windows hosts require CRLF -------------------------------------------- +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf -# Docs +# --- Docs ------------------------------------------------------------------ *.md text eol=lf diff=markdown *.adoc text eol=lf *.txt text eol=lf +*.tex text eol=lf diff=tex +*.bib text eol=lf diff=bibtex -# Data +# --- Data / config --------------------------------------------------------- *.json text eol=lf +*.jsonl text eol=lf *.yaml text eol=lf *.yml text eol=lf *.toml text eol=lf +*.svg text eol=lf +*.csv text eol=lf +*.html text eol=lf diff=html +*.css text eol=lf diff=css -# Config +# --- Repo control files ---------------------------------------------------- .gitignore text eol=lf .gitattributes text eol=lf -justfile text eol=lf -Makefile text eol=lf +.editorconfig text eol=lf +.tool-versions text eol=lf +Justfile text eol=lf +*.just text eol=lf +Mustfile text eol=lf Containerfile text eol=lf -# Scripts -*.sh text eol=lf - -# Binary +# --- Binary ---------------------------------------------------------------- *.png binary *.jpg binary +*.jpeg binary *.gif binary +*.webp binary +*.ico binary *.pdf binary +*.woff binary *.woff2 binary +*.ttf binary +*.otf binary +*.eot binary *.zip binary +*.tar binary *.gz binary +*.xz binary +*.bz2 binary +*.so binary +*.dylib binary +*.dll binary +*.exe binary +*.wasm binary +*.rlib binary +*.beam binary + +# --- Generated lockfiles: no diff noise, not counted as source ------------- +Cargo.lock text eol=lf -diff linguist-generated=true +mix.lock text eol=lf -diff linguist-generated=true +bun.lock text eol=lf -diff linguist-generated=true +bun.lockb binary -diff linguist-generated=true +pnpm-lock.yaml text eol=lf -diff linguist-generated=true +package-lock.json text eol=lf -diff linguist-generated=true -# Lock files -Cargo.lock text eol=lf -diff -flake.lock text eol=lf -diff +# --- Legacy / retired toolchains (byte hygiene only) ----------------------- +# ReScript is retired (LANGUAGE-POLICY 1.2); Nix was retired 2026-06-01. These +# lines keep surviving files normalised and keep retired tech out of the +# GitHub primary-language badge. Removal is a per-repo judgement, never a sweep. +*.res text eol=lf +*.resi text eol=lf +**/*.res linguist-detectable=false +*.nix text eol=lf +flake.lock text eol=lf -diff linguist-generated=true diff --git a/czech-file-knife/.github/CODEOWNERS b/czech-file-knife/.github/CODEOWNERS new file mode 100644 index 000000000..8d339b776 --- /dev/null +++ b/czech-file-knife/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# CODEOWNERS - Define code review assignments +# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +# +# Replace hyperpolymath with your GitHub username or team + +# Default owners for everything +* @hyperpolymath + +# Security-sensitive files require explicit review +SECURITY.md @hyperpolymath +.github/workflows/ @hyperpolymath +Trustfile.a2ml @hyperpolymath +.machine_readable/ @hyperpolymath diff --git a/czech-file-knife/.github/CODE_OF_CONDUCT.md b/czech-file-knife/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..8082c512e --- /dev/null +++ b/czech-file-knife/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,318 @@ + +# Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in czech-file-knife a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +We recognise that a thriving open source community requires **psychological safety** — an environment where people can contribute, ask questions, make mistakes, and learn without fear of ridicule or retaliation. + +--- + +## Our Standards + +### Expected Behaviour + +The following behaviours contribute to a positive environment: + +**Communication** +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Giving and gracefully accepting constructive feedback +- Assuming good intent while addressing impact +- Communicating clearly and patiently, especially with newcomers + +**Collaboration** +- Focusing on what is best for the community +- Showing empathy and kindness toward other community members +- Being collaborative rather than competitive +- Mentoring and supporting less experienced contributors +- Celebrating others' contributions and successes + +**Professionalism** +- Accepting responsibility and apologising to those affected by our mistakes +- Learning from the experience and avoiding repetition +- Respecting others' time and attention +- Staying on topic in project spaces +- Following project guidelines and conventions + +**Accessibility** +- Using plain language and avoiding unnecessary jargon +- Providing alt text for images and transcripts for audio/video +- Being patient with those using assistive technologies +- Accommodating different communication styles and needs +- Recognising that not everyone communicates the same way + +### Unacceptable Behaviour + +The following behaviours are considered harassment and are unacceptable: + +**Harassment** +- The use of sexualised language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Deliberate intimidation, stalking, or following (online or in-person) +- Unwelcome physical contact or simulated physical contact (e.g., emoji) +- Sustained disruption of talks, events, or online discussions + +**Discrimination** +- Discriminatory jokes and language +- Posting or threatening to post others' personally identifying information ("doxing") +- Advocating for, or encouraging, any of the above behaviour +- Microaggressions — subtle, often unintentional, discriminatory comments or actions + +**Professional Misconduct** +- Publishing others' private information without explicit permission +- Misrepresenting affiliation or contributions +- Plagiarism or claiming credit for others' work +- Retaliating against anyone who reports a Code of Conduct violation +- Other conduct which could reasonably be considered inappropriate in a professional setting + +### Grey Areas + +Some situations require judgement. When uncertain: + +- **Intent vs Impact**: Good intentions do not excuse harmful impact. Focus on making things right. +- **Power Dynamics**: Those with more power (maintainers, employers, experienced contributors) must be especially mindful of their impact. +- **Cultural Differences**: What's acceptable varies by culture. When in doubt, err on the side of caution and ask. +- **Humour**: Jokes at others' expense are rarely funny to everyone. Punch up, not down. + +--- + +## Scope + +This Code of Conduct applies within all community spaces, including: + +**Online Spaces** +- Repository discussions, issues, and pull/merge requests +- Project chat channels (Matrix, Discord, Slack, IRC) +- Mailing lists and forums +- Social media when representing the project +- Video calls and virtual meetings + +**In-Person Spaces** +- Conferences, meetups, and events +- Workshops and training sessions +- Any gathering where you represent the project + +**Representation** +This Code of Conduct also applies when an individual is officially representing the community in public spaces. Examples include: + +- Using an official project email address +- Posting via an official social media account +- Acting as an appointed representative at an event +- Speaking on behalf of the project + +--- + +## Enforcement + +### Reporting + +If you experience or witness unacceptable behaviour, or have any other concerns, please report it as soon as possible. + +**How to Report** + +| Method | Details | Best For | +|--------|---------|----------| +| **Email** | j.d.a.jewell@open.ac.uk | Detailed reports, sensitive matters | +| **Private Message** | Contact any maintainer directly | Quick questions, minor issues | + +**What to Include** + +- Your contact information (unless anonymous) +- Names/usernames of those involved +- Description of what happened +- When and where it occurred +- Any witnesses +- Any supporting evidence (screenshots, links) +- How you would like us to respond (if you have a preference) + +**What Happens Next** + +1. You will receive acknowledgment within **48 hours** +2. The Code of Conduct Committee will review the report +3. We may ask for additional information +4. We will determine appropriate action +5. We will inform you of the outcome (respecting others' privacy) + +### Confidentiality + +All reports will be handled with discretion: + +- Reporter identity is protected by default +- Details are shared only with those who need to know +- We will ask before naming you in any communication +- Anonymous reports are accepted and investigated + +### Conflicts of Interest + +If the Code of Conduct Committee are themselves involved in an incident: + +- They will recuse themselves from the process +- Another maintainer or external party will handle the report +- We will disclose any potential conflicts + +--- + +## Enforcement Guidelines + +The Code of Conduct Committee will follow these guidelines in determining consequences: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behaviour deemed unprofessional or unwelcome. + +**Consequence**: A private, written warning providing clarity around the nature of the violation and an explanation of why the behaviour was inappropriate. A public apology may be requested. + +**Duration**: Immediate + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behaviour. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +**Duration**: 1-4 weeks + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behaviour. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +**Duration**: 1-6 months + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +**Duration**: Permanent (with appeal rights after 12 months) + +### Enforcement Across Perimeters + +This project uses the estate's **Tri-Perimeter Contribution Framework (TPCF)**, a +graduated trust model defined in `hyperpolymath/standards` +(`rhodium-standard-repositories/README.adoc`): + +- 🔒 **Perimeter 1 (Core)** — maintainers only; shell runtime, build systems +- 🧠 **Perimeter 2 (Expert)** — trusted contributors; protocol extensions, validators +- 🌱 **Perimeter 3 (Community)** — open to all; docs, tests, proposals + +For contributors with elevated access (Perimeter 2 or 1): + +| Level | Additional Consequence | +|-------|----------------------| +| Correction | Noted in contributor record | +| Warning | Access privileges may be temporarily reduced | +| Temporary Ban | Access reduced to Perimeter 3 for ban duration | +| Permanent Ban | All access revoked | + +--- + +## Appeals + +If you believe an enforcement decision was made in error: + +1. **Wait 7 days** after the decision (cooling-off period) +2. **Email** j.d.a.jewell@open.ac.uk with subject line "Appeal: [Original Report ID]" +3. **Explain** why you believe the decision should be reconsidered +4. **Provide** any new information not previously available + +**Appeals Process** + +- Appeals are reviewed by the Code of Conduct Committee, excluding anyone involved in the original +- You will receive a response within 14 days +- The appeals decision is final +- You may only appeal once per incident + +**Grounds for Appeal** + +- Procedural errors in the original investigation +- New evidence not previously available +- Disproportionate response to the violation +- Misunderstanding of facts + +--- + +## Supporting Those Who Report + +We are committed to supporting those who report violations: + +**We Will** +- Believe and take all reports seriously +- Respect your privacy and confidentiality preferences +- Keep you informed of progress (if you wish) +- Take steps to protect you from retaliation +- Provide resources if you need support + +**We Will Not** +- Require you to confront the person directly +- Dismiss reports without investigation +- Reveal your identity without consent +- Tolerate retaliation against reporters +- Rush you to make decisions + +--- + +## Prevention + +Beyond enforcement, we actively work to prevent issues: + +**Onboarding** +- All contributors are expected to read this Code of Conduct +- Perimeter 2 applicants must confirm they've read and understood it +- Maintainers receive additional training on enforcement + +**Culture** +- We model the behaviour we expect +- We intervene early when we see potential issues +- We thank people for positive contributions +- We create opportunities for diverse voices + +**Review** +- This Code of Conduct is reviewed annually +- Community feedback is welcomed +- Changes are communicated clearly + +--- + +## Acknowledgments + +This Code of Conduct is adapted from: + +- [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1 +- [Django Code of Conduct](https://www.djangoproject.com/conduct/) +- [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) +- [Python Community Code of Conduct](https://www.python.org/psf/conduct/) + +We thank these communities for their leadership in creating welcoming spaces. + +--- + +## Questions? + +If you have questions about this Code of Conduct: + +- Open a [Discussion](https://github.com/hyperpolymath/czech-file-knife/discussions) (for general questions) +- Email j.d.a.jewell@open.ac.uk (for private questions) +- Contact any maintainer directly + +--- + +## Summary + +**Be kind. Be respectful. Be collaborative.** + +We're all here because we care about this project. Let's make it a place where everyone can do their best work. + +--- + +Last updated: 2026 · Based on Contributor Covenant 2.1 diff --git a/czech-file-knife/.github/CONTRIBUTING.md b/czech-file-knife/.github/CONTRIBUTING.md new file mode 100644 index 000000000..b1c101ba8 --- /dev/null +++ b/czech-file-knife/.github/CONTRIBUTING.md @@ -0,0 +1,103 @@ + +``` +# Clone the repository +git clone https://github.com/hyperpolymath/czech-file-knife.git +cd czech-file-knife + +# Using Guix (recommended for reproducibility) +guix shell -D -f build/guix.scm + +# Or using toolbox/distrobox +toolbox create czech-file-knife-dev +toolbox enter czech-file-knife-dev +# Install dependencies manually + +# Verify setup +just check # or: cargo check / mix compile / etc. +just test # Run test suite +``` + +### Repository Structure + +The authoritative map is **generated** from the tree and checked in CI, so it +cannot drift: +[`docs/architecture/REPOSITORY-MAP.adoc`](../docs/architecture/REPOSITORY-MAP.adoc). +Regenerate it with `just repo-map`. + +A hand-written tree used to live here. It described `lib/`, `extensions/`, +`plugins/` and `spec/` directories that this repository has never contained, +which is precisely why the map is now generated rather than typed. + +--- + +## How to Contribute + +### Reporting Bugs + +**Before reporting**: +1. Search existing issues +2. Check if it's already fixed in `main` +3. Determine which perimeter the bug affects + +**When reporting**: + +Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: + +- Clear, descriptive title +- Environment details (OS, versions, toolchain) +- Steps to reproduce +- Expected vs actual behaviour +- Logs, screenshots, or minimal reproduction + +### Suggesting Features + +**Before suggesting**: +1. Check the [roadmap](../docs/status/ROADMAP.adoc) if available +2. Search existing issues and discussions +3. Consider which perimeter the feature belongs to + +**When suggesting**: + +Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) and include: + +- Problem statement (what pain point does this solve?) +- Proposed solution +- Alternatives considered +- Which perimeter this affects + +### Your First Contribution + +Look for issues labelled: + +- [`good first issue`](https://github.com/hyperpolymath/czech-file-knife/labels/good%20first%20issue) — Simple Perimeter 3 tasks +- [`help wanted`](https://github.com/hyperpolymath/czech-file-knife/labels/help%20wanted) — Community help needed +- [`documentation`](https://github.com/hyperpolymath/czech-file-knife/labels/documentation) — Docs improvements +- [`perimeter-3`](https://github.com/hyperpolymath/czech-file-knife/labels/perimeter-3) — Community sandbox scope + +--- + +## Development Workflow + +### Branch Naming +``` +docs/short-description # Documentation (P3) +test/what-added # Test additions (P3) +feat/short-description # New features (P2) +fix/issue-number-description # Bug fixes (P2) +refactor/what-changed # Code improvements (P2) +security/what-fixed # Security fixes (P1-2) +``` + +### Commit Messages + +We follow [Conventional Commits](https://www.conventionalcommits.org/): +``` +(): + +[optional body] + +[optional footer] +``` diff --git a/czech-file-knife/.github/FUNDING.yml b/czech-file-knife/.github/FUNDING.yml index 688a442ca..557149be4 100644 --- a/czech-file-knife/.github/FUNDING.yml +++ b/czech-file-knife/.github/FUNDING.yml @@ -1,7 +1,17 @@ -# SPDX-License-Identifier: MPL-2.0 -# Funding platforms for hyperpolymath projects -# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository +# SPDX-License-Identifier: MPL-2.0 for code +# SPDX-License-Identifier: CC-BY-SA-4.0 for documentation +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# These are supported funding model platforms + +buy_me_a_coffee: jonathan.jewell +community_bridge: jonathan-jewell github: hyperpolymath +issuehunt: hyperpolymath ko_fi: hyperpolymath +lfx_crowdfunding: hyperpolymath liberapay: hyperpolymath +open_collective: jonathan-jewell +patreon: cc_studio +polar: hyperpolymath +thanks_dev: hyperpolymath diff --git a/czech-file-knife/.github/GOVERNANCE.md b/czech-file-knife/.github/GOVERNANCE.md new file mode 100644 index 000000000..ec74fec6a --- /dev/null +++ b/czech-file-knife/.github/GOVERNANCE.md @@ -0,0 +1,160 @@ + +# Project Governance + +This document describes the governance model for **Czech File Knife**. + +--- + +## Project Governance Model + +Czech File Knife follows a **Benevolent Dictator For Life (BDFL)** governance model. +This model is well-suited for solo maintainers and small project teams where rapid, +consistent decision-making is more valuable than formal consensus processes. + +The BDFL has final authority on all project decisions, including technical direction, +release schedules, contributor access, and community standards. + +> **Transition clause:** When the core team exceeds three active maintainers, this +> project should transition to a **consensus-based governance model** with documented +> voting procedures. That transition should itself be recorded as an Architecture +> Decision Record (ADR) in `docs/decisions/`. + +--- + +## Decision Making + +### Day-to-day decisions + +- The BDFL makes final decisions on all matters. +- Routine decisions (bug fixes, dependency updates, minor improvements) may be made + by any maintainer with commit access. +- Maintainers are expected to use good judgement and seek input on non-trivial changes. + +### Proposing changes + +- Contributors can propose changes by opening issues or pull requests. +- Significant changes (new features, breaking changes, architectural shifts) should + be discussed in an issue before implementation begins. +- The BDFL will provide a clear accept/reject decision with reasoning. + +### Architecture Decision Records (ADRs) + +- Significant technical decisions are documented as ADRs in `docs/decisions/`. +- ADR statuses: `proposed`, `accepted`, `deprecated`, `superseded`, `rejected`. +- ADRs provide a historical record of why decisions were made and what alternatives + were considered. +- See `.machine_readable/descriptiles/META.a2ml` for the machine-readable ADR index. + +--- + +## Roles + +### BDFL (Benevolent Dictator For Life) + +- The project creator and ultimate decision-maker. +- Sets the project's technical direction and long-term vision. +- Has final say on all matters, including maintainer appointments and removals. +- Responsible for ensuring the project adheres to RSR standards. + +### Maintainer + +- Has commit access to the repository. +- Reviews and merges pull requests. +- Triages issues and manages releases. +- Upholds code quality, security standards, and the Code of Conduct. +- Listed in [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc). + +### Contributor + +- Anyone who submits pull requests, opens issues, or participates in discussions. +- Does not have direct commit access. +- Contributions are reviewed by maintainers before merging. +- All contributors must follow the [Code of Conduct](CODE_OF_CONDUCT.md). + +### Bot + +- Automated agents managed via your bot orchestration system. +- Perform automated code review, security scanning, dependency updates, and + standards enforcement. +- Bot actions are subject to the same quality and review standards as human + contributions. +- Configure your bots in `.machine_readable/bot_directives/`. + +--- + +## Becoming a Maintainer + +A contributor may be nominated to become a maintainer when they demonstrate: + +1. **Sustained quality contributions** -- a track record of well-crafted pull requests + that follow project conventions and require minimal revision. +2. **Understanding of RSR standards** -- familiarity with the Repository Structure + Requirements, security policies, and CI/CD workflows used across the project. +3. **Constructive participation** -- helpful issue triage, thoughtful code review + comments, and mentoring of other contributors. +4. **Reliability** -- consistent engagement over a meaningful period (typically 3+ + months of active contribution). + +### Process + +1. An existing maintainer nominates the candidate by opening a private discussion + with the BDFL. +2. The BDFL reviews the candidate's contribution history and community interactions. +3. The BDFL approves or declines the nomination, with reasoning provided to the + nominator. +4. If approved, the new maintainer is added to [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) and + granted appropriate repository access. + +--- + +## Removing a Maintainer + +A maintainer may be removed under the following circumstances: + +- **Inactivity**: No meaningful contributions or reviews for 12 or more consecutive + months. The maintainer will be contacted before removal and offered the option to + move to emeritus status voluntarily. +- **Code of Conduct violation**: Behaviour that violates the + [Code of Conduct](CODE_OF_CONDUCT.md), as determined through the enforcement + process described therein. +- **BDFL discretion**: The BDFL may remove a maintainer for other reasons (e.g., + repeated disregard for project standards, loss of trust). Reasoning will be + documented privately. + +Removed maintainers are moved to the Emeritus section of +[MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) unless removal was due to a serious Code of Conduct +violation. + +--- + +## Code of Conduct + +All participants in this project are expected to follow the +[Code of Conduct](CODE_OF_CONDUCT.md). The Code of Conduct applies to all project +spaces, including issues, pull requests, discussions, and any forum where the project +is represented. + +Enforcement of the Code of Conduct is described in that document. The BDFL serves as +the final arbiter in conduct disputes. + +--- + +## Amendments + +This governance document may be amended by the BDFL at any time. All amendments will +be: + +1. Documented as an ADR in `docs/decisions/` explaining the rationale for the change. +2. Committed to the repository with a clear commit message. +3. Communicated to existing maintainers and contributors via the project's usual + channels. + +Substantive changes (e.g., changing the governance model itself) should be discussed +with the community before adoption, even though the BDFL retains final authority. + +--- + +Copyright (c) 2026 hyperpolymath. Licensed under MPL-2.0. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.md b/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 987aab6bc..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[Bug]: " -labels: 'bug, priority: unset, triage' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..41699b61b --- /dev/null +++ b/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Bug report +description: Something behaves differently from what it claims to do. +labels: ["bug", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Please report what you **observed**, not what you inferred. A command and + its actual output is worth more than a description of the problem. + - type: textarea + id: what-happened + attributes: + label: What happened + description: The observed behaviour, with the exact command and its output. + placeholder: | + $ just verify + error: ... + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: What you expected instead + validations: + required: true + - type: textarea + id: repro + attributes: + label: Minimal reproduction + description: The shortest sequence that reproduces it from a clean checkout. + validations: + required: true + - type: input + id: version + attributes: + label: Version / commit + description: Output of `git rev-parse --short HEAD`. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, and the output of `just --version` and `mise current` if relevant. + validations: + required: false + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I have reported observed output rather than a summary of it. + required: true + - label: This is not a security vulnerability (those go via a private advisory). + required: true diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..29c723403 --- /dev/null +++ b/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# Issue chooser configuration. Blank issues stay enabled so that reports which +# fit neither form are not silently discouraged. +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/hyperpolymath/czech-file-knife/security/advisories/new + about: Report privately via a security advisory. Do NOT open a public issue. + - name: Question or support request + url: https://github.com/hyperpolymath/czech-file-knife/discussions + about: Ask a question. See .github/SUPPORT.md for what to expect. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/custom.md b/czech-file-knife/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81fa..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/documentation.md b/czech-file-knife/.github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index 4fcb9f9fa..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -name: Documentation -about: Report unclear, missing, or incorrect documentation -title: "[DOCS]: " -labels: 'documentation, priority: unset, triage' -assignees: '' - ---- - -name: Documentation -description: Report unclear, missing, or incorrect documentation -title: "[Docs]: " -labels: ["documentation", "triage"] -body: - - type: markdown - attributes: - value: | - Help us improve our documentation by reporting issues or gaps. - - - type: dropdown - id: type - attributes: - label: Documentation issue type - options: - - Missing (documentation doesn't exist) - - Incorrect (information is wrong) - - Unclear (confusing or hard to follow) - - Outdated (no longer accurate) - - Typo or grammar - validations: - required: true - - - type: input - id: location - attributes: - label: Location - description: Where is this documentation? (URL, file path, or section name) - placeholder: README.adoc, section "Installation" - validations: - required: true - - - type: textarea - id: description - attributes: - label: Description - description: What's the problem with the current documentation? - placeholder: Describe what's wrong or missing - validations: - required: true - - - type: textarea - id: suggestion - attributes: - label: Suggested improvement - description: How should it be fixed or improved? - placeholder: The documentation should say... - validations: - required: false - - - type: checkboxes - id: contribution - attributes: - label: Contribution - options: - - label: I would be willing to submit a PR to fix this - required: false diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.md b/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3e8fa7e7e..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: 'enhancement, priority: unset, triage' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..068c5c2f6 --- /dev/null +++ b/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Feature request +description: Propose a capability this project does not yet have. +labels: ["enhancement", "needs-triage"] +body: + - type: textarea + id: problem + attributes: + label: The problem + description: What are you unable to do today? Describe the situation, not the solution. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed change + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Including doing nothing — say why that is insufficient. + validations: + required: false + - type: dropdown + id: scope + attributes: + label: Scope + description: Would this change the template's shape, and therefore every repo minted from it? + options: + - Repo-local — affects only this project + - Template-wide — would propagate to minted repos + - Not sure + validations: + required: true diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/question.md b/czech-file-knife/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index fd0e2a5cc..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Question -about: Ask a question about usage or behaviour -title: "[QUESTION]: " -labels: question, triage -assignees: '' - ---- - -name: Question -description: Ask a question about usage or behaviour -title: "[Question]: " -labels: ["question", "triage"] -body: - - type: markdown - attributes: - value: | - Have a question? You can also ask in [Discussions](../discussions) for broader conversations. - - - type: textarea - id: question - attributes: - label: Your question - description: What would you like to know? - placeholder: How do I...? - validations: - required: true - - - type: textarea - id: context - attributes: - label: Context - description: Any relevant context that helps us answer your question - placeholder: I'm trying to achieve X and I've tried Y... - validations: - required: false - - - type: textarea - id: research - attributes: - label: What I've already tried - description: What have you already looked at or attempted? - placeholder: I've read the README and searched issues but... - validations: - required: false - - - type: checkboxes - id: checked - attributes: - label: Pre-submission checklist - options: - - label: I have searched existing issues and discussions - required: true - - label: I have read the documentation - required: true diff --git a/czech-file-knife/.github/SECURITY.md b/czech-file-knife/.github/SECURITY.md new file mode 100644 index 000000000..34cbd3cb2 --- /dev/null +++ b/czech-file-knife/.github/SECURITY.md @@ -0,0 +1,389 @@ + +# Security Policy + + + +We take security seriously. We appreciate your efforts to responsibly disclose vulnerabilities and will make every effort to acknowledge your contributions. + +## Table of Contents + +- [Reporting a Vulnerability](#reporting-a-vulnerability) +- [What to Include](#what-to-include) +- [Response Timeline](#response-timeline) +- [Disclosure Policy](#disclosure-policy) +- [Scope](#scope) +- [Safe Harbour](#safe-harbour) +- [Recognition](#recognition) +- [Security Updates](#security-updates) +- [Security Best Practices](#security-best-practices) + +--- + +## Reporting a Vulnerability + +### Preferred Method: GitHub Security Advisories + +The preferred method for reporting security vulnerabilities is through GitHub's Security Advisory feature: + +1. Navigate to [Report a Vulnerability](https://github.com/hyperpolymath/czech-file-knife/security/advisories/new) +2. Click **"Report a vulnerability"** +3. Complete the form with as much detail as possible +4. Submit — we'll receive a private notification + +This method ensures: + +- End-to-end encryption of your report +- Private discussion space for collaboration +- Coordinated disclosure tooling +- Automatic credit when the advisory is published + +### Alternative: Email + +If you cannot use GitHub Security Advisories, you may email us directly: + +| | | +|---|---| +| **Email** | j.d.a.jewell@open.ac.uk | + +This mailbox is not encrypted. For anything sensitive enough to need +encryption, prefer GitHub Security Advisories above — the report stays private +to the maintainers until an advisory is published. + +> **⚠️ Important:** Do not report security vulnerabilities through public GitHub issues, pull requests, discussions, or social media. + +--- + +## What to Include + +A good vulnerability report helps us understand and reproduce the issue quickly. + +### Required Information + +- **Description**: Clear explanation of the vulnerability +- **Impact**: What an attacker could achieve (confidentiality, integrity, availability) +- **Affected versions**: Which versions/commits are affected +- **Reproduction steps**: Detailed steps to reproduce the issue + +### Helpful Additional Information + +- **Proof of concept**: Code, scripts, or screenshots demonstrating the vulnerability +- **Attack scenario**: Realistic attack scenario showing exploitability +- **CVSS score**: Your assessment of severity (use [CVSS 3.1 Calculator](https://www.first.org/cvss/calculator/3.1)) +- **CWE ID**: Common Weakness Enumeration identifier if known +- **Suggested fix**: If you have ideas for remediation +- **References**: Links to related vulnerabilities, research, or advisories + +### Example Report Structure + +```markdown +## Summary +[One-sentence description of the vulnerability] + +## Vulnerability Type +[e.g., SQL Injection, XSS, SSRF, Path Traversal, etc.] + +## Affected Component +[File path, function name, API endpoint, etc.] + +## Affected Versions +[Version range or specific commits] + +## Severity Assessment +- CVSS 3.1 Score: [X.X] +- CVSS Vector: [CVSS:3.1/AV:X/AC:X/PR:X/UI:X/S:X/C:X/I:X/A:X] + +## Description +[Detailed technical description] + +## Steps to Reproduce +1. [First step] +2. [Second step] +3. [...] + +## Proof of Concept +[Code, curl commands, screenshots, etc.] + +## Impact +[What can an attacker achieve?] + +## Suggested Remediation +[Optional: your ideas for fixing] + +## References +[Links to related issues, CVEs, research] +``` + +--- + +## Response Timeline + +We commit to the following response times: + +| Stage | Timeframe | Description | +|-------|-----------|-------------| +| **Initial Response** | 48 hours | We acknowledge receipt and confirm we're investigating | +| **Triage** | 7 days | We assess severity, confirm the vulnerability, and estimate timeline | +| **Status Update** | Every 7 days | Regular updates on remediation progress | +| **Resolution** | 90 days | Target for fix development and release (complex issues may take longer) | +| **Disclosure** | 90 days | Public disclosure after fix is available (coordinated with you) | + +> **Note:** These are targets, not guarantees. Complex vulnerabilities may require more time. We'll communicate openly about any delays. + +--- + +## Disclosure Policy + +We follow **coordinated disclosure** (also known as responsible disclosure): + +1. **You report** the vulnerability privately +2. **We acknowledge** and begin investigation +3. **We develop** a fix and prepare a release +4. **We coordinate** disclosure timing with you +5. **We publish** security advisory and fix simultaneously +6. **You may publish** your research after disclosure + +### Our Commitments + +- We will not take legal action against researchers who follow this policy +- We will work with you to understand and resolve the issue +- We will credit you in the security advisory (unless you prefer anonymity) +- We will notify you before public disclosure +- We will publish advisories with sufficient detail for users to assess risk + +### Your Commitments + +- Report vulnerabilities promptly after discovery +- Give us reasonable time to address the issue before disclosure +- Do not access, modify, or delete data beyond what's necessary to demonstrate the vulnerability +- Do not degrade service availability (no DoS testing on production) +- Do not share vulnerability details with others until coordinated disclosure + +### Disclosure Timeline + +``` +Day 0 You report vulnerability +Day 1-2 We acknowledge receipt +Day 7 We confirm vulnerability and share initial assessment +Day 7-90 We develop and test fix +Day 90 Coordinated public disclosure + (earlier if fix is ready; later by mutual agreement) +``` + +If we cannot reach agreement on disclosure timing, we default to 90 days from your initial report. + +--- + +## Scope + +### In Scope ✅ + +The following are within scope for security research: + +- This repository (`hyperpolymath/czech-file-knife`) and all its code +- Official releases and packages published from this repository +- Documentation that could lead to security issues +- Build and deployment configurations in this repository +- Dependencies (report here, we'll coordinate with upstream) + +### Out of Scope ❌ + +The following are **not** in scope: + +- Third-party services we integrate with (report directly to them) +- Social engineering attacks against maintainers +- Physical security +- Denial of service attacks against production infrastructure +- Spam, phishing, or other non-technical attacks +- Issues already reported or publicly known +- Theoretical vulnerabilities without proof of concept + +### Qualifying Vulnerabilities + +We're particularly interested in: + +- Remote code execution +- SQL injection, command injection, code injection +- Authentication/authorisation bypass +- Cross-site scripting (XSS) and cross-site request forgery (CSRF) +- Server-side request forgery (SSRF) +- Path traversal / local file inclusion +- Information disclosure (credentials, PII, secrets) +- Cryptographic weaknesses +- Deserialisation vulnerabilities +- Memory safety issues (buffer overflows, use-after-free, etc.) +- Supply chain vulnerabilities (dependency confusion, etc.) +- Significant logic flaws + +### Non-Qualifying Issues + +The following generally do not qualify as security vulnerabilities: + +- Missing security headers on non-sensitive pages +- Clickjacking on pages without sensitive actions +- Self-XSS (requires victim to paste code) +- Missing rate limiting (unless it enables a specific attack) +- Username/email enumeration (unless high-risk context) +- Missing cookie flags on non-sensitive cookies +- Software version disclosure +- Verbose error messages (unless exposing secrets) +- Best practice deviations without demonstrable impact + +--- + +## Safe Harbour + +We support security research conducted in good faith. + +### Our Promise + +If you conduct security research in accordance with this policy: + +- ✅ We will not initiate legal action against you +- ✅ We will not report your activity to law enforcement +- ✅ We will work with you in good faith to resolve issues +- ✅ We consider your research authorised under the Computer Fraud and Abuse Act (CFAA), UK Computer Misuse Act, and similar laws +- ✅ We waive any potential claim against you for circumvention of security controls + +### Good Faith Requirements + +To qualify for safe harbour, you must: + +- Comply with this security policy +- Report vulnerabilities promptly +- Avoid privacy violations (do not access others' data) +- Avoid service degradation (no destructive testing) +- Not exploit vulnerabilities beyond proof-of-concept +- Not use vulnerabilities for profit (beyond bug bounties where offered) + +> **⚠️ Important:** This safe harbour does not extend to third-party systems. Always check their policies before testing. + +--- + +## Recognition + +We believe in recognising security researchers who help us improve. + +### Hall of Fame + +Researchers who report valid vulnerabilities will be acknowledged in our [Security Acknowledgments](SECURITY-ACKNOWLEDGMENTS.md) (unless they prefer anonymity). + +Recognition includes: + +- Your name (or chosen alias) +- Link to your website/profile (optional) +- Brief description of the vulnerability class +- Date of report + +### What We Offer + +- ✅ Public credit in security advisories +- ✅ Acknowledgment in release notes +- ✅ Entry in our Hall of Fame +- ✅ Reference/recommendation letter upon request (for significant findings) + +### What We Don't Currently Offer + +- ❌ Monetary bug bounties +- ❌ Hardware or swag +- ❌ Paid security research contracts + +> **Note:** We're a community project with limited resources. Your contributions help everyone who uses this software. + +--- + +## Security Updates + +### Receiving Updates + +To stay informed about security updates: + +- **Watch this repository**: Click "Watch" → "Custom" → Select "Security alerts" +- **GitHub Security Advisories**: Published at [Security Advisories](https://github.com/hyperpolymath/czech-file-knife/security/advisories) +- **Release notes**: Security fixes noted in [CHANGELOG](../CHANGELOG.md) + +### Update Policy + +| Severity | Response | +|----------|----------| +| **Critical/High** | Patch release as soon as fix is ready | +| **Medium** | Included in next scheduled release (or earlier) | +| **Low** | Included in next scheduled release | + +### Supported Versions + + + +| Version | Supported | Notes | +|---------|-----------|-------| +| `main` branch | ✅ Yes | Latest development | +| Latest release | ✅ Yes | Current stable | +| Previous minor release | ✅ Yes | Security fixes backported | +| Older versions | ❌ No | Please upgrade | + +--- + +## Security Best Practices + +When using czech-file-knife, we recommend: + +### General + +- Keep dependencies up to date +- Use the latest stable release +- Subscribe to security notifications +- Review configuration against security documentation +- Follow principle of least privilege + +### For Contributors + +- Never commit secrets, credentials, or API keys +- Use signed commits (`git config commit.gpgsign true`) +- Review dependencies before adding them +- Run security linters locally before pushing +- Report any concerns about existing code + +--- + +## Additional Resources + +- [Security Advisories](https://github.com/hyperpolymath/czech-file-knife/security/advisories) +- [Changelog](../CHANGELOG.md) +- [Contributing Guidelines](CONTRIBUTING.md) +- [CVE Database](https://cve.mitre.org/) +- [CVSS Calculator](https://www.first.org/cvss/calculator/3.1) + +--- + +## Contact + +| Purpose | Contact | +|---------|---------| +| **Security issues** | [Report via GitHub](https://github.com/hyperpolymath/czech-file-knife/security/advisories/new) or j.d.a.jewell@open.ac.uk | +| **General questions** | [GitHub Discussions](https://github.com/hyperpolymath/czech-file-knife/discussions) | +| **Other enquiries** | See [README](../README.adoc) for contact information | + +--- + +## Policy Changes + +This security policy may be updated from time to time. Significant changes will be: + +- Committed to this repository with a clear commit message +- Noted in the changelog +- Announced via GitHub Discussions (for major changes) + +--- + +*Thank you for helping keep czech-file-knife and its users safe.* 🛡️ + +--- + +Last updated: 2026 · Policy version: 1.0.0 diff --git a/czech-file-knife/.github/SUPPORT.md b/czech-file-knife/.github/SUPPORT.md new file mode 100644 index 000000000..1bf7f22e2 --- /dev/null +++ b/czech-file-knife/.github/SUPPORT.md @@ -0,0 +1,7 @@ +# Support + +For questions, help, and community discussion: + +- GitHub Discussions: https://github.com/hyperpolymath/czech-file-knife/discussions +- GitHub Issues: https://github.com/hyperpolymath/czech-file-knife/issues +- Documentation: See README.adoc in the root directory. diff --git a/czech-file-knife/.github/copilot-instructions.md b/czech-file-knife/.github/copilot-instructions.md new file mode 100644 index 000000000..4564efa76 --- /dev/null +++ b/czech-file-knife/.github/copilot-instructions.md @@ -0,0 +1,85 @@ + + + + +# Copilot Instructions + +## Before Writing Code + +- Read the repo deed (`*_chora.deed` in the repo root) for canonical file locations + (its `(ply ...)` clauses carry the canonical-locations tables). +- State files (.a2ml) live in `.machine_readable/` ONLY, never the root. + +## License + +- SPDX: `MPL-2.0` on all new files. +- Never use AGPL-3.0. +- Copyright: `Jonathan D.A. Jewell (hyperpolymath) ` + +## Code Style + +- Use descriptive variable names. +- Annotate and document all files. +- Add SPDX header to every source file. +- Use `just` for build/test/lint commands. + +## Banned Patterns + +- Idris2: no `believe_me`, no `assert_total` +- Haskell: no `unsafeCoerce`, no `unsafePerformIO` +- OCaml: no `Obj.magic` +- Coq: no `Admitted` +- Lean: no `sorry` +- Rust: no `transmute` unless FFI with `// SAFETY:` comment + +## JavaScript / TypeScript runtimes + +Ordered preference (`standards/3-practice/LANGUAGE-POLICY.adoc` §1) — reach for the first +that can do the job: + +1. **Bun** — default for all new work. Runs compiled ESM/JS directly, no bundler + step. Uses an npm-compatible `package.json` plus `bun.lock`; both are + expected, not anti-patterns. +2. **pnpm** — only where an upstream toolchain requires `node_modules`. +3. **npm** — last resort. Permitted, never preferred; a deliberate, noted choice. + +**Deno is being removed**, not grandfathered. Owner ruling 2026-08-26: *"deno is +to go and bun is the way we are going, put it first everywhere unless not +possible and explain why if not."* Existing Deno projects migrate to Bun; where +Bun genuinely cannot be used, document the reason in the repo. + +**TypeScript is not the language for new application code — AffineScript is.** +`LANGUAGE-POLICY.adoc` §1.2, ruled 2026-08-25, separates two questions the older +text ran together: *runtime* is Bun (where `.ts` runs at all, Bun runs it), while +the *language* target is AffineScript. TypeScript is permitted only where +AffineScript cannot reach — the same narrow, transitional carve-out JavaScript +holds for MCP protocol glue and runtime APIs. ReScript remains banned; its +migration destination is AffineScript. + +## Banned Languages + +- No Go (use Rust) +- No Python (use Julia or Rust) +- No Nix (use Guix) +- No Deno for new work — being removed estate-wide; use Bun (owner ruling 2026-08-26) +- No ReScript (`LANGUAGE-POLICY.adoc` §3) — migrate to AffineScript + +## Containers + +- Use Podman, never Docker. +- Name the file `Containerfile`, never `Dockerfile`. +- Base image: `cgr.dev/chainguard/wolfi-base:latest`. + +## ABI/FFI + +- This repo does not carry the Idris2/Zig ABI seam; the only FFI is the C ABI in `src/cfk-ios`. +- Cargo workspace crates live in `src/cfk-*`; every destructive operation must go through `ReversibleBackend`. + +## State Files + +Never create these in the repo root: +STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml. +They belong in `.machine_readable/` only. diff --git a/czech-file-knife/.github/copilot/coding-agent.yml b/czech-file-knife/.github/copilot/coding-agent.yml new file mode 100644 index 000000000..a719a773b --- /dev/null +++ b/czech-file-knife/.github/copilot/coding-agent.yml @@ -0,0 +1,6 @@ +mcp_servers: + boj-server: + command: npx + args: ["-y", "@hyperpolymath/boj-server@latest"] + env: + BOJ_URL: http://localhost:7700 diff --git a/czech-file-knife/.github/dependabot.yml b/czech-file-knife/.github/dependabot.yml index 72c209756..39fb47669 100644 --- a/czech-file-knife/.github/dependabot.yml +++ b/czech-file-knife/.github/dependabot.yml @@ -1,5 +1,10 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dependabot configuration for RSR-compliant repositories +# Covers common ecosystems - remove unused ones for your project + version: 2 updates: + # GitHub Actions - always include - package-ecosystem: "github-actions" directory: "/" schedule: @@ -8,21 +13,31 @@ updates: actions: patterns: - "*" - - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" + open-pull-requests-limit: 2 + + # Rust/Cargo + # + # `open-pull-requests-limit: 0` suppresses routine version-update PRs + # (no weekly patch-bump noise) while leaving Dependabot SECURITY PRs + # flowing. Under GitHub's current Dependabot behaviour (2024+), using + # an `ignore:` rule with `version-update:semver-patch` would ALSO + # silence security PRs that happen to be patch-level — historically + # the cause of estate-wide vulns sitting un-PR'd for weeks. + # `open-pull-requests-limit: 0` is the GitHub-endorsed way to say + # "security only, not routine bumps". Pair with the + # dependabot-automerge.yml workflow for low-touch security + # maintenance. ignore: - - dependency-name: "*" - update-types: ["version-update:semver-patch"] - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "pip" + # HOLD: github/codeql-action at v4.38.0 (SHA-pinned). v4.38.1 fails + # GitHub workflow-startup validation estate-wide (nexia-list#100; + # SHA-form re-bump bypassed versions-scoped ignores - see + # nexia-list#101/#104). Hold until upstream clears 4.38.1 or a + # newer release verifies green; revisit deliberately. + - dependency-name: "github/codeql-action" + + + - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" + open-pull-requests-limit: 0 diff --git a/czech-file-knife/.github/hooks/install.sh b/czech-file-knife/.github/hooks/install.sh new file mode 100755 index 000000000..b853bb423 --- /dev/null +++ b/czech-file-knife/.github/hooks/install.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Point this clone's git hooks at .github/hooks/ so the local Dogfood Gate runs +# on push. Idempotent; safe to re-run. +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" +git config core.hooksPath .github/hooks +git config commit.template .gitmessage +chmod +x .github/hooks/pre-push .github/hooks/validate-deed.sh .github/hooks/validate-k9.sh 2>/dev/null || true +echo "Installed: core.hooksPath -> .github/hooks (pre-push DEED+K9 gate active), commit.template -> .gitmessage." diff --git a/czech-file-knife/.github/hooks/pre-push b/czech-file-knife/.github/hooks/pre-push new file mode 100755 index 000000000..7e006867b --- /dev/null +++ b/czech-file-knife/.github/hooks/pre-push @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# pre-push — local Dogfood Gate. +# +# Runs the SAME DEED + K9 validators the CI Dogfood Gate runs, but here on +# your machine before the push leaves, so format drift is caught in seconds +# instead of surfacing as red CI minutes later. The vendored validators in +# this directory are byte-identical to the pinned CI action scripts and are +# refreshed by the estate `refresh-githooks` sweep. +# +# Enable once per clone: +# git config core.hooksPath .github/hooks +# (or run: .github/hooks/install.sh) +# +# Override for an emergency push: git push --no-verify +# +# The DEED/K9 validators are skipped gracefully if their tooling is absent. +# The SECRET gate is not: see .github/hooks/scan-secrets.sh for why it fails closed. + +set -euo pipefail + +HOOK_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" +status=0 + +# git feeds pre-push one " " +# line per ref on stdin. Capture it HERE, before any validator runs, and hand it +# to the scanners in K9_PUSH_RANGES so they can scan exactly the commits being +# pushed rather than the whole history. Guarded on `! -t 0`: when the hook is +# run by hand from a terminal there are no ref lines, and an unguarded `cat` +# would block forever on the tty. +K9_PUSH_RANGES="" +if [ ! -t 0 ]; then K9_PUSH_RANGES="$(cat)"; fi +export K9_PUSH_RANGES + +run() { + local label="$1" script="$2" + if [ ! -f "$HOOK_DIR/$script" ]; then + echo "[pre-push] ($label) validator missing: $script — skipping" >&2 + return 0 + fi + echo "[pre-push] $label…" + if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then + status=1 + fi +} + +# Same as run(), but a MISSING script is a failure rather than a skip. Used for +# the secret gate only: "validator absent" and "no secrets found" produce the +# same silence, so a skip here is a gate that reports success having examined +# nothing. The DEED/K9 validators keep the lenient behaviour — a missing +# formatter costs a red CI run, a missing secret scanner costs a leaked key. +run_required() { + local label="$1" script="$2" + if [ ! -f "$HOOK_DIR/$script" ]; then + echo "[pre-push] ($label) BLOCKED: required validator missing: $script" >&2 + echo "[pre-push] Restore it from the template, or override: git push --no-verify" >&2 + status=1 + return 0 + fi + echo "[pre-push] $label…" + if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then + status=1 + fi +} + +run "DEED manifests" "validate-deed.sh" +run "K9 contracts" "validate-k9.sh" +run_required "Secrets" "scan-secrets.sh" + +if [ "$status" -ne 0 ]; then + echo "" >&2 + echo "[pre-push] BLOCKED: validation failed (see errors above)." >&2 + echo "[pre-push] Fix the files, or override with: git push --no-verify" >&2 + exit 1 +fi + +echo "[pre-push] Dogfood Gate passed." +exit 0 diff --git a/czech-file-knife/.github/hooks/scan-secrets.sh b/czech-file-knife/.github/hooks/scan-secrets.sh new file mode 100755 index 000000000..b26dd40c1 --- /dev/null +++ b/czech-file-knife/.github/hooks/scan-secrets.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# scan-secrets.sh — local pre-push secret gate (TruffleHog). +# +# ── Why this is NOT duplicated work ───────────────────────────────────────── +# The estate runs a TWO-TIER secret defence: +# +# CI gitleaks, via the standards `secret-scanner-reusable.yml` +# (gitleaks + rust-secrets + shell-secrets). Catches what reaches the +# remote, on every pull_request and push to main. +# LOCAL TruffleHog — this script — before the push leaves the machine. +# +# Different detection engines at different checkpoints. The reusable's header +# retires TruffleHog "as redundant with gitleaks"; that judgement is scoped to +# CI, where running both cost twice over one population. It is not a reason to +# leave the local tier unguarded. DO NOT delete this script as duplication — +# see .github/workflows/README.adoc, "Secret scanning is single-sourced". +# +# ── Fail closed ───────────────────────────────────────────────────────────── +# A missing or non-functional scanner BLOCKS the push and prints an install +# hint. The sibling validators skip gracefully when their tooling is absent; a +# SECRET gate must not, because "skipped" and "clean" are indistinguishable in +# the output, and the machines least likely to carry the toolchain are exactly +# the ones whose pushes nobody has vetted. +# The escape hatch is unchanged and documented: git push --no-verify +# +# ── Two measured instrument traps this script is written around ───────────── +# 1. TRUFFLEHOG EXITS 0 WHEN IT FINDS SECRETS. `--fail` is mandatory, and with +# it the found-secrets status is 183 — NOT 1. Every test here is for +# non-zero; never compare against 1. +# 2. PIPING A SCANNER DESTROYS ITS EXIT CODE — a pipeline reports the LAST +# command's status. Measured: `gitleaks … | tail` returns rc=0 while +# printing "leaks found: 1". Nothing here pipes the scanner; output is +# captured to a file and printed after the status is read. +# +# Env overrides: +# TRUFFLEHOG explicit path to the binary (used by the tests) +# K9_SECRETS_MAX_DEPTH commits to scan for a brand-new branch (default 500) +# K9_SECRETS_VERIFY=1 enable live credential verification (see below) + +set -euo pipefail + +REPO_ROOT="${INPUT_PATH:-$(git rev-parse --show-toplevel)}" +MAX_DEPTH="${K9_SECRETS_MAX_DEPTH:-500}" +ZERO="0000000000000000000000000000000000000000" + +# Verification OFF by default. `--no-verification` keeps the hook offline and +# fast, and — the real reason — verification transmits candidate credentials to +# the issuing provider's API on every push. A detected-but-unverified secret +# must still block, so verification buys only false-positive reduction at the +# cost of egress. Opt in with K9_SECRETS_VERIFY=1. +VERIFY_ARGS=(--no-verification) +if [ "${K9_SECRETS_VERIFY:-0}" = "1" ]; then VERIFY_ARGS=(); fi + +# ── The SonarCloud detector must be excluded, and why ─────────────────────── +# TruffleHog's SonarCloud detector matches ANY bare 40-hex string. This estate +# SHA-pins every GitHub Action by doctrine, so every pin is a 40-hex string and +# every pin reads as a secret. MEASURED on a clean clone of this template: +# 24 findings, 0 verified, 100% SonarCloud, 12 distinct values, ALL exactly +# 40 hex, all in .github/workflows/ — and .github/workflows/actions.lock +# carries one of them verbatim as `commit: 'sha1-ede1191ef…'`. +# Left in, the gate blocks every push on this repo and every inheritor of the +# template: a gate that always fires is uninstallable, and an uninstallable gate +# gets deleted. With the detector excluded the same history scans rc=0 (5,103 +# chunks, 0 findings), and a planted AWS/Slack credential still returns 183. +# +# What this gives up, and what covers it: a genuine SonarCloud token is also +# 40-hex, so it is indistinguishable from a pin BY SHAPE — no path or context +# filter recovers it. CI's gitleaks tier catches it instead; measured, gitleaks +# under the estate baseline (`useDefault = true`) flags a SonarCloud token as +# `generic-api-key`, rc=1. The two tiers are complementary here by design. +DETECTOR_ARGS=(--exclude-detectors SonarCloud) + +# ── Resolve the binary by INVOKING it ─────────────────────────────────────── +# `command -v` is not enough: a mise shim with no pinned global version sits on +# PATH, answers `command -v` happily, and fails every actual invocation with +# "No version is set for shim". Installed is not invokable. Each candidate is +# therefore probed with `--version` and only accepted on exit 0. +probe() { [ -n "${1:-}" ] && [ -x "$1" ] && "$1" --version >/dev/null 2>&1; } + +TH="" +if probe "${TRUFFLEHOG:-}"; then + TH="$TRUFFLEHOG" +elif c="$(command -v trufflehog 2>/dev/null)" && probe "$c"; then + TH="$c" +else + # mise installs tree, newest version first. MISE_DATA_DIR when exported, + # else mise's documented default. No machine-specific path is hardcoded. + _md="${MISE_DATA_DIR:-$HOME/.local/share/mise}" + while IFS= read -r c; do + if probe "$c"; then TH="$c"; break; fi + done < <(find "$_md/installs" -mindepth 3 -maxdepth 3 -type f -name trufflehog \ + -path '*trufflehog*' 2>/dev/null | sort -rV) +fi + +if [ -z "$TH" ]; then + cat >&2 <<'HINT' +[scan-secrets] BLOCKED: TruffleHog is not installed, or is a dead shim. + + This gate fails closed on purpose — a secret scanner that skips silently is + indistinguishable from one that found nothing. + + Install (mise, as used by this estate): + mise use -g aqua:trufflesecurity/trufflehog@3.96.0 + + Or point the hook at an existing binary: + TRUFFLEHOG=/path/to/trufflehog git push + + Emergency override (you are asserting the push carries no secrets): + git push --no-verify +HINT + exit 1 +fi + +# ── Build the scan ranges from the pre-push stdin lines ───────────────────── +# pre-push receives " " per ref +# and forwards them in K9_PUSH_RANGES. Scanning only the commits actually being +# pushed is both the correct population for this gate and far faster than the +# whole history (which CI already covers). +# ── Findings and scanner errors BOTH block, but are NOT the same thing ─────── +# TruffleHog returns 183 for "secrets found" and other non-zero codes for "I +# could not scan" (a broken ref, an unreadable repo, a bad flag). Both must fail +# closed — an unscanned push is exactly as unvetted as an unchecked one — but +# they must be REPORTED apart. Measured here: a stale remote ref pointing at +# 0000…0 made every scan exit 1 with "upload-pack: not our ref", and a handler +# that said "Secrets detected" for any non-zero told the developer to go hunting +# for a credential that did not exist. A gate that misnames its own failure +# sends people looking in the wrong place, which is how gates get switched off. +status=0 +scanned=0 +found=0 +errored=0 +out="$(mktemp)" +trap 'rm -f "$out"' EXIT + +# Run TruffleHog against a git ref/range with the configured verify/detector args. +# Takes a human-readable label ($1) and any extra trufflehog arguments, captures +# output to a file (not piped, to preserve the exit code), and sets the shared +# found/errored/status variables based on the result: 0 = clean, 183 = secrets +# found, anything else = scanner error. +scan() { + local label="$1"; shift + local rc=0 + # NOT piped — see trap 2 in the header. + "$TH" git "file://$REPO_ROOT" "$@" "${VERIFY_ARGS[@]}" "${DETECTOR_ARGS[@]}" \ + --fail --no-update >"$out" 2>&1 || rc=$? + scanned=$((scanned + 1)) + if [ "$rc" -eq 183 ]; then + echo "[scan-secrets] FINDINGS while scanning $label:" >&2 + cat "$out" >&2 + found=1 + status=1 + elif [ "$rc" -ne 0 ]; then + echo "[scan-secrets] SCANNER ERROR (exit $rc) while scanning $label —" >&2 + echo "[scan-secrets] this is a failure to scan, NOT a detected secret:" >&2 + cat "$out" >&2 + errored=1 + status=1 + fi +} + +if [ -n "${K9_PUSH_RANGES:-}" ]; then + while read -r local_ref local_sha remote_ref remote_sha; do + [ -z "${local_sha:-}" ] && continue + [ "$local_sha" = "$ZERO" ] && continue # branch deletion + if [ "${remote_sha:-$ZERO}" = "$ZERO" ]; then + scan "new branch ${local_ref:-HEAD} (last $MAX_DEPTH commits)" \ + --branch "$local_sha" --max-depth "$MAX_DEPTH" + else + scan "${local_ref:-HEAD} since ${remote_sha:0:12}" \ + --branch "$local_sha" --since-commit "$remote_sha" + fi + done <<< "$K9_PUSH_RANGES" +fi + +# No ranges (hook invoked directly, or an empty push): scan the bounded tail of +# HEAD rather than reporting a pass over nothing. A gate that reports success +# having examined zero commits is the vacuity this estate keeps re-learning. +if [ "$scanned" -eq 0 ]; then + scan "HEAD (no push ranges; last $MAX_DEPTH commits)" --max-depth "$MAX_DEPTH" +fi + +if [ "$found" -ne 0 ]; then + echo "[scan-secrets] Secrets detected in the commits being pushed." >&2 + echo "[scan-secrets] Remove them and rewrite the offending commits." >&2 + echo "[scan-secrets] Override (only if these are false positives): git push --no-verify" >&2 +fi + +if [ "$errored" -ne 0 ]; then + echo "[scan-secrets] TruffleHog could not complete the scan (see above)." >&2 + echo "[scan-secrets] BLOCKED because an unscanned push is an unvetted push." >&2 + echo "[scan-secrets] Common cause: a broken local ref. Check with: git fsck" >&2 + echo "[scan-secrets] Override (you are asserting this push carries no secrets):" >&2 + echo "[scan-secrets] git push --no-verify" >&2 +fi + +if [ "$status" -ne 0 ]; then + exit 1 +fi + +echo "[scan-secrets] TruffleHog clean ($scanned range(s) scanned, $("$TH" --version 2>&1 | head -1))." +exit 0 diff --git a/czech-file-knife/.github/hooks/validate-deed.sh b/czech-file-knife/.github/hooks/validate-deed.sh new file mode 100755 index 000000000..3973abe03 --- /dev/null +++ b/czech-file-knife/.github/hooks/validate-deed.sh @@ -0,0 +1,393 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# validate-deed.sh — DEED manifest validation script +# +# Scans for .a2ml and .deed files (dual-accept per owner ruling R-H2) and validates: +# 1. Required fields: agent-id or pedigree name, version +# 2. SPDX-License-Identifier header presence +# 3. Attestation block structure (if present) +# 4. Section heading syntax ([section] or ## section) +# +# Environment variables: +# INPUT_PATH — Directory to scan (default: .) +# INPUT_STRICT — Promote warnings to errors (default: false) +# +# Exit codes: +# 0 — All files valid (or only warnings in non-strict mode) +# 1 — Validation errors found + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +SCAN_PATH="${INPUT_PATH:-.}" +STRICT="${INPUT_STRICT:-false}" +PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" +GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" + +# Parse paths-ignore: newline-separated fragments, blank lines and # comments +# stripped. Each fragment is a substring match against the file path. Pattern +# adopted from hyperpolymath/hypatia#243 — content-pattern validators must +# distinguish a target from a vendored / fixture file that legitimately +# contains the very pattern being checked. +PATHS_IGNORE=() +while IFS= read -r _frag; do + # Strip leading and trailing whitespace (canonical bash idiom). + _frag="${_frag#"${_frag%%[![:space:]]*}"}" + _frag="${_frag%"${_frag##*[![:space:]]}"}" + [[ -z "$_frag" || "$_frag" == \#* ]] && continue + PATHS_IGNORE+=("$_frag") +done <<< "$PATHS_IGNORE_RAW" + +# Returns 0 if path should be skipped (matches any ignore fragment) +path_ignored() { + local p="$1" frag + for frag in "${PATHS_IGNORE[@]}"; do + [[ "$p" == *"$frag"* ]] && return 0 + done + return 1 +} + +# Counters +FILES_SCANNED=0 +ERRORS=0 +WARNINGS=0 + +# --------------------------------------------------------------------------- +# Helper: emit GitHub annotation +# --------------------------------------------------------------------------- +# Usage: annotate +# level: error | warning | notice +annotate() { + local level="$1" file="$2" line="$3" message="$4" + echo "::${level} file=${file},line=${line}::${message}" +} + +# --------------------------------------------------------------------------- +# Helper: report issue (respects strict mode) +# --------------------------------------------------------------------------- +# Usage: report_issue +# severity: error | warning +report_issue() { + local severity="$1" file="$2" line="$3" message="$4" + + if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then + severity="error" + fi + + annotate "$severity" "$file" "$line" "$message" + + if [[ "$severity" == "error" ]]; then + ERRORS=$((ERRORS + 1)) + else + WARNINGS=$((WARNINGS + 1)) + fi +} + +# --------------------------------------------------------------------------- +# Validator: check a single .a2ml file +# --------------------------------------------------------------------------- +validate_deed() { + local file="$1" + FILES_SCANNED=$((FILES_SCANNED + 1)) + + # --- Check 1: SPDX header --- + # The SPDX-License-Identifier should appear in the first 10 lines + local has_spdx=false + local line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ $line_num -gt 10 ]]; then + break + fi + if [[ "$line" == *"SPDX-License-Identifier"* ]]; then + has_spdx=true + break + fi + done < "$file" + + if [[ "$has_spdx" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing SPDX-License-Identifier in first 10 lines" + fi + + # --- Check 2: Required identity fields --- + # DEED files must contain either: + # - agent-id = "..." or agent_id = "..." + # - pedigree block with name field + # - name = "..." at top level (for AI manifests) + # - project = "..." (for STATE.a2ml) + local has_identity=false + local has_version=false + local first_form_seen=false + line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Check for identity fields (various DEED patterns) + # TOML/kv form: `name = "..."`, `project = "..."`, `agent-id = "..."`. + # + # `archetype` is the identity key of the ARCHETYPE.a2ml shape — the + # `just repo-init` scaffolding descriptors under archetypes/. It names the + # archetype exactly as `name` names a manifest, and is the fourth + # dialect this recogniser accommodates alongside TOML, s-expression and + # brace-block. Without it, archetypes/julia-library/ARCHETYPE.a2ml + # failed with "Missing required identity field" — on main, in this repo + # and in every repo instantiated from it. + # + # Recognising the shape is the right fix rather than adding a redundant + # `name = "julia-library"` beside `archetype = "julia-library"`: that + # file's own header states an archetype "must not write fiction", and + # duplicating its identity to satisfy a grep is exactly that. + if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|archetype)[[:space:]]*= ]]; then + has_identity=true + fi + # S-expression form: `(name "...")`, `(project "...")`, + # `(agent-id "...")`. Some DEED dialects (audit registries, + # classification stores) use Lisp-style s-expressions for the + # metadata block instead of TOML. Identity carries the same + # semantics; only the syntax differs. Match at any indent so it + # also picks up entries nested under `(metadata ...)`. + if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(agent[-_]id|name|project)[[:space:]]+\" ]]; then + has_identity=true + fi + # Colon / brace-block form: `name: "..."`, `id: "..."`, `project: "..."`. + # YAML-ish and brace-block DEED dialects (e.g. `Trust { name: "..." }`, + # `id: "tsdm-standard"`) carry the same identity semantics; only the + # delimiter (`:` vs `=`) differs. `id` is the brace-block spelling of an + # identity key. + if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|id)[[:space:]]*: ]]; then + has_identity=true + fi + # DEED s-expression head form: `(estate-deed`, `(repo-deed`, + # `(estate-atlas-deed`, `(praxis-deed`. Per DEED-GRAMMAR-SPEC + # <>, a file whose first form is one of the four declared + # heads is a deed of that kind, and the head satisfies the structural + # half of identity. Only the FIRST form is eligible — checking every + # line would let a malformed file open with some other form and append + # a deed head lower down to buy identity. Ported from + # hyperpolymath/deed-ecosystem validate-action/validate-a2ml.sh so the + # local hook and the CI action agree on what a deed is. + if [[ "$first_form_seen" == "false" && "$line" =~ ^[[:space:]]*\( ]]; then + first_form_seen=true + if [[ "$line" =~ ^[[:space:]]*\((estate-deed|repo-deed|estate-atlas-deed|praxis-deed)([[:space:]]|$) ]]; then + has_identity=true + fi + fi + # DEED keyword identity form: `:canonical-name "..."` and the two other + # identity keywords the spec names. Leading colon: none of the forms + # above match it, because they test the bare words. + if [[ "$line" =~ ^[[:space:]]*:(canonical-name|estate-authority|agent-id)[[:space:]] ]]; then + has_identity=true + fi + # Check for version field — TOML form + if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*= ]]; then + has_version=true + fi + # Version field — s-expression form + if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(version|schema_version)[[:space:]]+\" ]]; then + has_version=true + fi + # Version field — colon / brace-block form + if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*: ]]; then + has_version=true + fi + # DEED keyword version form: `:schema-version "1.0.0"` — leading colon, + # hyphenated, REQUIRED on all four deed heads. The three patterns above + # spell it `schema_version` with no leading colon, so a conforming deed + # matched none of them. `:registry-version` is a distinct, optional + # atlas field and never satisfies the version requirement. + if [[ "$line" =~ ^[[:space:]]*:schema-version[[:space:]] ]]; then + has_version=true + fi + done < "$file" + + # AI manifest files (0-AI-MANIFEST.a2ml, 0.1-AI-MANIFEST.a2ml, etc.) + # use markdown-style headers and free text, so identity check is relaxed + local basename + basename="$(basename "$file")" + local is_manifest=false + if [[ "$basename" == *"AI-MANIFEST"* ]]; then + is_manifest=true + fi + # Canonical typed manifests under .machine_readable/descriptiles/ — identity comes + # from the enclosing directory + filename, not an in-file field. Sibling + # files in the same directory (ECOSYSTEM.a2ml, STATE.a2ml) DO carry their + # own $name/project and continue to be validated normally. + case "$basename" in + AGENTIC.a2ml|META.a2ml|NEUROSYM.a2ml|PLAYBOOK.a2ml|AI.a2ml) + # AI.a2ml = free-text "AI Assistant Instructions" manifest, the same + # doc type as 0-AI-MANIFEST.a2ml but with the bare name; identity is + # carried by the enclosing repo/plugin dir, not an in-file field. + is_manifest=true + ;; + # Dockerfile-style top-level typed manifests (Intentfile, Trustfile, …) + # use markdown-flavoured DEED; identity is carried by the parent repo. + *file.a2ml) + is_manifest=true + ;; + esac + + # Contractile-shape DEED files use `@directive:` syntax instead of + # TOML `key = value`. Trustfile.a2ml, Intentfile.a2ml, Mustfile.a2ml, + # Adjustfile.a2ml etc. are policy / trust / intent / abstract files + # whose identity is implicit in their @-prefixed directives + # (`@trust-level`, `@intent`, ...) rather than a TOML name/version + # pair. Treating them as manifest-shape produces 100% false positives — + # they're a different DEED doc type. Detected by the presence of any + # contractile directive in the file body. + local is_contractile_shape=false + if grep -qE '^@(abstract|trust-level|trust-boundary|trust-actions|trust-deny|intent|must|adjust|end)([[:space:]]*:|$)' "$file"; then + is_contractile_shape=true + fi + + # Canonical structured DEED tree. Everything under a `.machine_readable/` + # directory is a typed agent-readable doc (CLADE, ANCHOR, STATE, + # ECOSYSTEM, bot_directives/{debt,coverage,methodology}, ai/AI, + # policies/*, integrations/*, …). Per the RSR convention these carry + # identity structurally — owning repo + path + filename — not via an + # in-file `name`/`agent-id`. This generalises the `.machine_readable/descriptiles/` + # rationale above to the whole tree: czech-file-knife itself ships these + # files without an in-file identity key, so requiring one produces + # estate-wide false positives on every repo built from the canonical + # template. Files outside `.machine_readable/` are still validated. + local is_structural_identity=false + if [[ "$file" == *"/.machine_readable/"* || "$file" == "./.machine_readable/"* || "$file" == ".machine_readable/"* ]]; then + is_structural_identity=true + fi + + if [[ "$has_identity" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Missing required identity field (agent-id, name, or project)" + fi + + if [[ "$has_version" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing version or schema_version field" + fi + + # --- Check 3: Attestation block structure --- + # If file contains [attestation] or ## ATTESTATION, validate it has + # required sub-fields: proof or signature + local in_attestation=false + local attestation_line=0 + local attestation_has_content=false + line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Detect attestation section start + if [[ "$line" =~ ^\[attestation\] ]] || [[ "$line" =~ ^##[[:space:]]+[Aa]ttestation ]] || [[ "$line" =~ ^##[[:space:]]+ATTESTATION ]]; then + in_attestation=true + attestation_line=$line_num + continue + fi + + # Detect next section (ends attestation block) + if [[ "$in_attestation" == "true" ]]; then + if [[ "$line" =~ ^\[.+\] ]] || [[ "$line" =~ ^##[[:space:]] ]]; then + in_attestation=false + continue + fi + # Check for content in attestation block + if [[ "$line" =~ (proof|signature|verified|hash)[[:space:]]*= ]]; then + attestation_has_content=true + fi + fi + done < "$file" + + if [[ $attestation_line -gt 0 && "$attestation_has_content" == "false" ]]; then + report_issue "warning" "$file" "$attestation_line" \ + "Attestation block found but missing proof/signature/hash fields" + fi + + # --- Check 4: Section heading syntax --- + # Validate that [section] headings are well-formed (no unclosed brackets) + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + # Lines starting with [ should have a matching ] + if [[ "$line" =~ ^\[ && ! "$line" =~ ^\[.+\] ]]; then + # Exclude markdown-style links and multi-line values + if [[ ! "$line" =~ ^\[.*\]\( && ! "$line" =~ ^\[TODO && ! "$line" =~ ^\[YOUR ]]; then + report_issue "warning" "$file" "$line_num" \ + "Possibly malformed section heading: unclosed bracket" + fi + fi + done < "$file" +} + +# --------------------------------------------------------------------------- +# Main: discover and validate .a2ml files +# --------------------------------------------------------------------------- + +echo "::group::DEED Manifest Validation" +echo "Scanning ${SCAN_PATH} for .a2ml files..." +echo "" + +# Find all manifest files (.a2ml legacy + .deed — dual-accept; extension migration = standards #837, the DEED conversion campaign), excluding .git +mapfile -t deed_candidates < <(find "$SCAN_PATH" \( -name '*.a2ml' -o -name '*.deed' \) -not -path '*/.git/*' -type f | sort) + +# Apply paths-ignore filter +deed_files=() +SKIPPED=0 +for _f in "${deed_candidates[@]}"; do + if path_ignored "$_f"; then + SKIPPED=$((SKIPPED + 1)) + continue + fi + deed_files+=("$_f") +done + +if [[ $SKIPPED -gt 0 ]]; then + echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" +fi + +if [[ ${#deed_files[@]} -eq 0 ]]; then + echo "::notice::No .a2ml files found in ${SCAN_PATH}" + echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "::endgroup::" + exit 0 +fi + +echo "Found ${#deed_files[@]} .a2ml file(s)" +echo "" + +for file in "${deed_files[@]}"; do + echo " Validating: ${file}" + validate_deed "$file" +done + +echo "" +echo "────────────────────────────────────────" +echo "Files scanned: ${FILES_SCANNED}" +echo "Errors: ${ERRORS}" +echo "Warnings: ${WARNINGS}" +echo "Strict mode: ${STRICT}" +echo "────────────────────────────────────────" + +# Write outputs for GitHub Actions +{ + echo "files_scanned=${FILES_SCANNED}" + echo "errors=${ERRORS}" + echo "warnings=${WARNINGS}" +} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + +echo "::endgroup::" + +# Exit with failure if errors were found +if [[ $ERRORS -gt 0 ]]; then + echo "::error::DEED validation failed with ${ERRORS} error(s)" + exit 1 +fi + +echo "DEED validation passed." +exit 0 diff --git a/czech-file-knife/.github/hooks/validate-k9.sh b/czech-file-knife/.github/hooks/validate-k9.sh new file mode 100755 index 000000000..02845a4c2 --- /dev/null +++ b/czech-file-knife/.github/hooks/validate-k9.sh @@ -0,0 +1,389 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# validate-k9.sh — K9 configuration file validation script +# +# Scans for .k9 and .k9.ncl files and validates: +# 1. K9! magic number on line 1 +# 2. Pedigree block presence with required fields (name, version) +# 3. Security level is one of: kennel, yard, hunt (case-insensitive) +# 4. Hunt-level files must have a signature or signature_required field +# 5. SPDX-License-Identifier header presence +# +# Environment variables: +# INPUT_PATH — Directory to scan (default: .) +# INPUT_STRICT — Promote warnings to errors (default: false) +# +# Exit codes: +# 0 — All files valid (or only warnings in non-strict mode) +# 1 — Validation errors found + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +SCAN_PATH="${INPUT_PATH:-.}" +STRICT="${INPUT_STRICT:-false}" +PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" +GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" + +# Parse paths-ignore: newline-separated fragments, blank lines and # comments +# stripped. Each fragment is a substring match against the file path. Pattern +# adopted from hyperpolymath/hypatia#243 — content-pattern validators must +# distinguish a target from a vendored / fixture file that legitimately +# contains the very pattern being checked. +PATHS_IGNORE=() +while IFS= read -r _frag; do + # Strip leading and trailing whitespace (canonical bash idiom). + _frag="${_frag#"${_frag%%[![:space:]]*}"}" + _frag="${_frag%"${_frag##*[![:space:]]}"}" + [[ -z "$_frag" || "$_frag" == \#* ]] && continue + PATHS_IGNORE+=("$_frag") +done <<< "$PATHS_IGNORE_RAW" + +# Returns 0 if path should be skipped (matches any ignore fragment) +path_ignored() { + local p="$1" frag + for frag in "${PATHS_IGNORE[@]}"; do + [[ "$p" == *"$frag"* ]] && return 0 + done + return 1 +} + +# Counters +FILES_SCANNED=0 +ERRORS=0 +WARNINGS=0 + +# Valid security levels (the leash metaphor) +VALID_LEVELS="kennel yard hunt" + +# --------------------------------------------------------------------------- +# Helper: emit GitHub annotation +# --------------------------------------------------------------------------- +annotate() { + local level="$1" file="$2" line="$3" message="$4" + echo "::${level} file=${file},line=${line}::${message}" +} + +# --------------------------------------------------------------------------- +# Helper: report issue (respects strict mode) +# --------------------------------------------------------------------------- +report_issue() { + local severity="$1" file="$2" line="$3" message="$4" + + if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then + severity="error" + fi + + annotate "$severity" "$file" "$line" "$message" + + if [[ "$severity" == "error" ]]; then + ERRORS=$((ERRORS + 1)) + else + WARNINGS=$((WARNINGS + 1)) + fi +} + +# --------------------------------------------------------------------------- +# Helper: normalise a security level string +# --------------------------------------------------------------------------- +# Strips quotes, leading/trailing whitespace, Nickel enum tick prefix +normalise_level() { + local raw="$1" + # Remove surrounding quotes, tick prefix ('Kennel -> Kennel), whitespace + raw="${raw#*=}" # Remove everything before = + raw="${raw//\"/}" # Remove double quotes + raw="${raw//\'/}" # Remove single quotes (Nickel tick) + raw="${raw//,/}" # Remove trailing commas + raw="${raw## }" # Trim leading space + raw="${raw%% }" # Trim trailing space + raw="${raw%%#*}" # Remove inline comments + raw="${raw## }" # Trim again + raw="${raw%% }" + echo "${raw,,}" # Lowercase +} + +# --------------------------------------------------------------------------- +# Validator: check a single K9 file +# --------------------------------------------------------------------------- +validate_k9() { + local file="$1" + FILES_SCANNED=$((FILES_SCANNED + 1)) + + # --- Check 1: K9! magic number on first non-empty line --- + local first_content_line="" + local first_content_line_num=0 + local line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + # Skip empty lines + if [[ -z "${line// /}" ]]; then + continue + fi + first_content_line="$line" + first_content_line_num=$line_num + break + done < "$file" + + if [[ "$first_content_line" != "K9!" ]]; then + report_issue "error" "$file" "$first_content_line_num" \ + "Missing K9! magic number. First non-empty line must be exactly 'K9!'" + fi + + # --- Check 2: SPDX header --- + local has_spdx=false + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ $line_num -gt 10 ]]; then + break + fi + if [[ "$line" == *"SPDX-License-Identifier"* ]]; then + has_spdx=true + break + fi + done < "$file" + + if [[ "$has_spdx" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing SPDX-License-Identifier in first 10 lines" + fi + + # --- Check 3: Pedigree block with required fields --- + local has_pedigree=false + local has_pedigree_name=false + local has_pedigree_version=false + local has_security_level=false + local security_level_value="" + local security_level_line=0 + local has_signature_field=false + local in_pedigree=false + local pedigree_depth=0 + + # Resolve a one-hop `let` indirection before scanning. + # + # Nickel lets you build the pedigree separately and attach it by name: + # + # let component_pedigree = { + # metadata = { name = "verisimdb-test-infra", ... }, + # } in + # { pedigree = component_pedigree, ... } + # + # The brace-depth scanner below only sees `pedigree = component_pedigree,` + # — a line with no braces — so depth never rises, the block appears empty, + # and `name` is invisible. The file is valid; the grep could not follow the + # reference. Observed on verisimdb/connectors/test-infra/deploy.k9.ncl, + # which does declare metadata.name at its line 23. + # + # If `pedigree = ` names a binding, treat `let = {` + # as the block opener too. One hop only: chased aliases would need a real + # Nickel evaluator, and `nickel typecheck` cannot be used here because the + # mandatory `K9!` magic line on line 1 is not valid Nickel. + local pedigree_alias="" + pedigree_alias=$(sed -nE 's/^[[:space:]]*pedigree[[:space:]]*=[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*,?[[:space:]]*$/\1/p' "$file" | head -1) + + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Detect pedigree block start. Note: do NOT `continue` here — the + # `pedigree = {` line itself contains the opening brace that + # establishes the block. Falling through to the brace counter + # below makes depth start at 1, so a subsequent `security = {…},` + # closing brace correctly takes depth to 1 (not 0), keeping us + # inside the pedigree block when later fields (name/version/leash) + # are checked. Previously the `continue` skipped this opening + # brace, depth started at 0, and the first nested block's close + # prematurely terminated the validator's view of the pedigree — + # making `pedigree.metadata.name` invisible. + if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then + has_pedigree=true + in_pedigree=true + pedigree_depth=0 + # fall through + fi + + # `let = {` where is what `pedigree =` points at. + # See the pedigree_alias note above. + if [[ -n "$pedigree_alias" ]] && \ + [[ "$line" =~ ^[[:space:]]*let[[:space:]]+${pedigree_alias}[[:space:]]*= ]]; then + has_pedigree=true + in_pedigree=true + pedigree_depth=0 + # fall through — this line carries the opening brace + fi + + if [[ "$in_pedigree" == "true" ]]; then + # Track brace depth to know when pedigree block ends + local opens closes + opens="${line//[^\{]/}" + closes="${line//[^\}]/}" + pedigree_depth=$(( pedigree_depth + ${#opens} - ${#closes} )) + + if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" ]]; then + # Check this final line too before leaving + : + fi + + # Check for name field within pedigree.metadata or pedigree directly. + # Two patterns cover both multi-line and single-line pedigrees: + # 1. ^[[:space:]]+name[[:space:]]*= — the normal multi-line case where + # `name = "..."` appears on its own indented line. + # 2. [[:space:]]name[[:space:]]*= — inline within a single-line + # pedigree assignment such as: + # pedigree = component_pedigree & { name = "foo" } + # (root cause: developer-ecosystem@baab1534 — single-line form + # was missed entirely because the pedigree block opened and + # closed in one line, never reaching the ^[[:space:]]+ check on + # a subsequent iteration.) + if [[ "$line" =~ ^[[:space:]]+name[[:space:]]*= ]] || \ + [[ "$line" =~ [[:space:]]name[[:space:]]*= ]]; then + has_pedigree_name=true + fi + + # Check for version field + if [[ "$line" =~ ^[[:space:]]+(version|schema_version)[[:space:]]*= ]] || \ + [[ "$line" =~ [[:space:]](version|schema_version)[[:space:]]*= ]]; then + has_pedigree_version=true + fi + + # Check for security level (leash field) + if [[ "$line" =~ ^[[:space:]]+(leash|security_level)[[:space:]]*= ]]; then + has_security_level=true + security_level_value="$(normalise_level "$line")" + security_level_line=$line_num + fi + + # Check for signature fields + if [[ "$line" =~ ^[[:space:]]+(signature|signature_required)[[:space:]]*= ]]; then + has_signature_field=true + fi + + # End of pedigree block + if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" && "$line" == *"}"* ]]; then + in_pedigree=false + fi + fi + + # Also check for signature fields outside pedigree (top-level) + if [[ "$line" =~ ^[[:space:]]*(signature)[[:space:]]*= ]]; then + has_signature_field=true + fi + done < "$file" + + if [[ "$has_pedigree" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Missing pedigree block. K9 files must contain a 'pedigree = { ... }' section" + else + if [[ "$has_pedigree_name" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Pedigree block missing 'name' field (in pedigree.metadata.name or pedigree.name)" + fi + + if [[ "$has_pedigree_version" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Pedigree block missing 'version' or 'schema_version' field" + fi + fi + + # --- Check 4: Security level validation --- + if [[ "$has_security_level" == "true" ]]; then + local level_valid=false + for valid in $VALID_LEVELS; do + if [[ "$security_level_value" == "$valid" ]]; then + level_valid=true + break + fi + done + + if [[ "$level_valid" == "false" ]]; then + report_issue "error" "$file" "$security_level_line" \ + "Invalid security level '${security_level_value}'. Must be one of: kennel, yard, hunt" + fi + else + if [[ "$has_pedigree" == "true" ]]; then + report_issue "warning" "$file" 1 \ + "No security level (leash/security_level) found in pedigree block" + fi + fi + + # --- Check 5: Hunt-level signature requirement --- + if [[ "$security_level_value" == "hunt" && "$has_signature_field" == "false" ]]; then + report_issue "error" "$file" "$security_level_line" \ + "Hunt-level K9 file must include a 'signature' or 'signature_required' field" + fi +} + +# --------------------------------------------------------------------------- +# Main: discover and validate K9 files +# --------------------------------------------------------------------------- + +echo "::group::K9 Configuration Validation" +echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." +echo "" + +# Find all K9 files, excluding .git directory +mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) + +# Apply paths-ignore filter +k9_files=() +SKIPPED=0 +for _f in "${k9_candidates[@]}"; do + if path_ignored "$_f"; then + SKIPPED=$((SKIPPED + 1)) + continue + fi + k9_files+=("$_f") +done + +if [[ $SKIPPED -gt 0 ]]; then + echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" +fi + +if [[ ${#k9_files[@]} -eq 0 ]]; then + echo "::notice::No K9 files found in ${SCAN_PATH}" + echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "::endgroup::" + exit 0 +fi + +echo "Found ${#k9_files[@]} K9 file(s)" +echo "" + +for file in "${k9_files[@]}"; do + echo " Validating: ${file}" + validate_k9 "$file" +done + +echo "" +echo "────────────────────────────────────────" +echo "Files scanned: ${FILES_SCANNED}" +echo "Errors: ${ERRORS}" +echo "Warnings: ${WARNINGS}" +echo "Strict mode: ${STRICT}" +echo "────────────────────────────────────────" + +# Write outputs for GitHub Actions +{ + echo "files_scanned=${FILES_SCANNED}" + echo "errors=${ERRORS}" + echo "warnings=${WARNINGS}" +} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + +echo "::endgroup::" + +# Exit with failure if errors were found +if [[ $ERRORS -gt 0 ]]; then + echo "::error::K9 validation failed with ${ERRORS} error(s)" + exit 1 +fi + +echo "K9 validation passed." +exit 0 diff --git a/czech-file-knife/.github/label-classifier.json b/czech-file-knife/.github/label-classifier.json new file mode 100644 index 000000000..d349eaad4 --- /dev/null +++ b/czech-file-knife/.github/label-classifier.json @@ -0,0 +1,739 @@ +{ + "_generated_from": ".github/label-classifier.yml + .github/labels.yml in hyperpolymath/.git-private-farm", + "_do_not_edit": "regenerate with scripts/gen-classifier-json.py", + "version": 1, + "prefix_split_on": "/", + "title_prefix": { + "docs": { + "type": "documentation" + }, + "ci": { + "type": "chore", + "areas": [ + "cicd" + ] + }, + "governance": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "roadmap": { + "type": "enhancement", + "meta": "meta:roadmap" + }, + "chore": { + "type": "chore" + }, + "build": { + "type": "chore", + "areas": [ + "cicd" + ] + }, + "security": { + "type": "chore", + "areas": [ + "security" + ] + }, + "proof": { + "type": "chore", + "areas": [ + "proofs" + ] + }, + "proofs": { + "type": "chore", + "areas": [ + "proofs" + ] + }, + "proof-debt": { + "type": "tech-debt", + "areas": [ + "proofs" + ] + }, + "epic": { + "type": "enhancement", + "meta": "meta:umbrella" + }, + "umbrella": { + "type": "enhancement", + "meta": "meta:umbrella" + }, + "tracking": { + "type": "chore", + "meta": "meta:umbrella" + }, + "campaign": { + "type": "enhancement", + "meta": "meta:campaign" + }, + "hygiene": { + "type": "tech-debt" + }, + "audit": { + "type": "research" + }, + "estate": { + "type": "chore", + "scope": "scope:estate" + }, + "automation": { + "type": "enhancement", + "areas": [ + "automation" + ] + }, + "research": { + "type": "research" + }, + "refactor": { + "type": "refactor" + }, + "test": { + "type": "testing" + }, + "tests": { + "type": "testing" + }, + "feat": { + "type": "enhancement" + }, + "fix": { + "type": "bug" + }, + "bug": { + "type": "bug" + }, + "perf": { + "type": "enhancement", + "areas": [ + "performance" + ] + }, + "codegen": { + "type": "enhancement", + "areas": [ + "architecture" + ] + }, + "packaging": { + "type": "chore", + "areas": [ + "packaging" + ] + }, + "policy": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "ops": { + "type": "chore", + "areas": [ + "automation" + ] + }, + "standard": { + "type": "chore", + "areas": [ + "governance" + ] + }, + "migration": { + "type": "refactor", + "areas": [ + "migration" + ] + }, + "drift": { + "type": "tech-debt" + }, + "corrective": { + "type": "bug" + }, + "adaptive": { + "type": "enhancement" + }, + "perfective": { + "type": "enhancement" + }, + "preventive": { + "type": "tech-debt" + }, + "machine-readable": { + "type": "tech-debt" + }, + "parser": { + "type": "bug" + }, + "lang": { + "type": "bug" + }, + "clippy": { + "type": "tech-debt" + }, + "release": { + "type": "chore" + }, + "upstream": { + "type": "chore" + }, + "hardening": { + "type": "chore", + "areas": [ + "security" + ] + }, + "deps": { + "type": "chore" + }, + "rustsec": { + "type": "chore", + "areas": [ + "security" + ] + }, + "track": { + "type": "chore", + "meta": "meta:umbrella" + }, + "tracker": { + "type": "chore", + "meta": "meta:umbrella" + }, + "wiki": { + "type": "documentation" + }, + "reclassify": { + "type": "refactor" + }, + "backlog": { + "type": "chore" + }, + "core": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "evidence": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "manifest": { + "type": "enhancement", + "areas": [ + "design" + ] + }, + "backends": { + "type": "enhancement", + "areas": [ + "design" + ] + } + }, + "bracket_tag": { + "campaign": { + "meta": "meta:campaign" + }, + "umbrella": { + "meta": "meta:umbrella" + }, + "gov": { + "areas": [ + "governance" + ] + }, + "proofs/a": { + "areas": [ + "proofs" + ] + }, + "proofs/b": { + "areas": [ + "proofs" + ] + }, + "proofs/c": { + "areas": [ + "proofs" + ] + }, + "estate": { + "scope": "scope:estate" + }, + "repo": { + "scope": "scope:repo" + }, + "feature": { + "type": "enhancement" + }, + "integration": { + "areas": [ + "conformance" + ] + }, + "reference": { + "type": "documentation" + }, + "register": { + "type": "documentation" + }, + "p0": { + "priority": "priority:p0" + }, + "p1": { + "priority": "priority:p1" + }, + "p2": { + "priority": "priority:p2" + }, + "et-l2": { + "areas": [ + "conformance" + ] + }, + "et-l4": { + "areas": [ + "conformance" + ] + } + }, + "keyword_area": { + "proofs": [ + "agda", + "coq", + "rocq", + "idris", + "lean", + "isabelle", + "hol", + "mizar", + "why3", + "tla", + "alloy", + "dafny", + "acl2", + "pvs", + "metamath", + "z3", + "smt", + "prover", + "provers", + "theorem", + "theorems", + "axiom", + "axioms", + "postulate", + "postulates", + "believe_me", + "sorry", + "proof obligation", + "proof obligations", + "proof hole", + "proof holes", + "proof suite", + "proof-pipeline", + "proof debt", + "proof-debt", + "metatheory", + "mechanize", + "qed" + ], + "cicd": [ + "workflow", + "github action", + "actions.lock", + "lockfile", + "runner", + "startup_failure", + "dependabot", + "check run", + "required context", + "scorecard", + "codeql", + "ci/cd" + ], + "licensing": [ + "spdx", + "licence", + "license", + "reuse", + "copyright", + "attribution", + "agpl", + "mpl" + ], + "security": [ + "gitleaks", + "secret", + "vulnerabilit", + "advisory", + "supply chain", + "cve" + ], + "bindings": [ + "abi", + "ffi", + "wasm", + "jni", + "c api", + "interop", + "extern \"c\"", + "nif", + "snif" + ], + "packaging": [ + "guix", + "nix", + "container", + "containerfile", + "docker", + "flatpak", + "oci image" + ], + "scaffolding": [ + "rsr", + "scaffold", + "template", + "repo-init", + "instantiat", + "placeholder" + ], + "governance": [ + "ruleset", + "policy", + "compliance", + "governance", + "branch protection", + "codeowners", + "code of conduct" + ], + "migration": [ + "rescript", + "to-affinescript", + "\u2192 affinescript", + "port", + "deno", + "bun" + ], + "automation": [ + "bot", + "gitbot", + "hypatia", + "sustainabot", + "oikosbot", + "fan-out", + "fanout", + "dispatch", + "self-heal" + ], + "performance": [ + "latency", + "throughput", + "binary size", + "memory", + "hot path", + "regression" + ] + }, + "keyword_type": { + "tech-debt": [ + "debt", + "drift", + "hygiene", + "stale", + "cleanup", + "follow-up", + "clean up", + "left over", + "leftover", + "anti-pattern", + "inconsistency", + "inconsistent", + "placeholder", + "placeholders", + "tbd", + "todo", + "todos", + "unfilled" + ], + "documentation": [ + "document", + "docs", + "readme", + "adoc", + "prose", + "docs/", + "changelog", + "explainme", + "quickstart", + "wiki", + "docstring", + "doc tree" + ], + "testing": [ + "test", + "tests", + "fuzz", + "bench", + "coverage", + "crash-consistency", + "linearizability", + "equivalence", + "property-correspondence", + "property-based", + "test suite", + "proptest" + ], + "bug": [ + "broken", + "fails", + "failing", + "crash", + "oom", + "regression", + "incorrect", + "does not", + "panic", + "panics", + "unreachable", + "mangled", + "never run", + "never ran", + "never succeeded", + "never fires", + "cannot fail", + "deadlock", + "hangs" + ], + "refactor": [ + "refactor", + "restructure", + "consolidate", + "consolidation", + "reconcile", + "reconciliation", + "unify", + "dedupe", + "re-point", + "repoint", + "extract", + "retire", + "retire duplicate", + "deduplicate", + "reclassify", + "migrate" + ], + "research": [ + "investigat", + "explore", + "spike", + "work out", + "triage", + "assess", + "survey", + "gap analysis", + "self-audit", + "inventory", + "weakness list", + "theory", + "synthesis", + "prioritised weakness", + "feasibility" + ], + "decision": [ + "ruling", + "decide", + "decision", + "adjudicat", + "which of" + ], + "enhancement": [ + "add", + "implement", + "support", + "introduce", + "enable", + "expand", + "expansion", + "extend", + "wire", + "complete", + "build", + "create", + "port" + ] + }, + "meta_signal": { + "meta:umbrella": [ + "umbrella", + "epic", + "master issue", + "parent issue", + "sub-issues", + "child issues" + ], + "meta:campaign": [ + "campaign" + ], + "meta:roadmap": [ + "roadmap", + "capability-expansion", + "future work" + ], + "meta:recurring": [ + "recurring", + "recurrence", + "standing", + "every run", + "each week" + ] + }, + "status_signal": { + "status:blocked": [ + "blocked on", + "blocked:", + "(blocked", + "is blocked", + "gated on", + "waiting on upstream", + "needs upstream" + ], + "status:needs-owner": [ + "unassigned", + "needs an owner", + "no owner" + ], + "status:needs-ruling": [ + "needs a ruling", + "awaiting ruling", + "owner decision needed" + ] + }, + "scope_signal": { + "scope:estate": [ + "estate-wide", + "estate wide", + "across the estate", + "all repos", + "fleet-wide" + ] + }, + "tier_of": { + "bug": "type", + "enhancement": "type", + "documentation": "type", + "refactor": "type", + "tech-debt": "type", + "testing": "type", + "chore": "type", + "research": "type", + "decision": "type", + "question": "type", + "cicd": "area", + "security": "area", + "proofs": "area", + "governance": "area", + "design": "area", + "architecture": "area", + "performance": "area", + "bindings": "area", + "migration": "area", + "packaging": "area", + "licensing": "area", + "automation": "area", + "scaffolding": "area", + "conformance": "area", + "priority:p0": "priority", + "priority:p1": "priority", + "priority:p2": "priority", + "priority:p3": "priority", + "status:blocked": "status", + "status:ready": "status", + "status:needs-owner": "status", + "status:needs-ruling": "status", + "status:do-not-automate": "status", + "meta:umbrella": "meta", + "meta:campaign": "meta", + "meta:roadmap": "meta", + "meta:recurring": "meta", + "scope:estate": "scope", + "scope:repo": "scope" + }, + "tier_max": { + "type": 1, + "area": null, + "priority": 1, + "status": 1, + "meta": 1, + "scope": 1 + }, + "types": [ + "bug", + "enhancement", + "documentation", + "refactor", + "tech-debt", + "testing", + "chore", + "research", + "decision", + "question" + ], + "frozen": [ + "dependencies", + "duplicate", + "elixir", + "gitar-approved", + "github_actions", + "good first issue", + "help wanted", + "invalid", + "javascript", + "never-stale", + "nix", + "pinned", + "python", + "rust", + "security", + "stale", + "wontfix" + ], + "precedence": { + "meta:campaign": 0, + "meta:umbrella": 1, + "meta:recurring": 2, + "meta:roadmap": 3, + "priority:p0": 0, + "priority:p1": 1, + "priority:p2": 2, + "priority:p3": 3, + "status:blocked": 0, + "status:needs-ruling": 1, + "status:needs-owner": 2, + "status:do-not-automate": 3, + "status:ready": 4, + "scope:estate": 0, + "scope:repo": 1, + "bug": 0, + "decision": 1, + "tech-debt": 2, + "testing": 3, + "documentation": 4, + "refactor": 5, + "research": 6, + "enhancement": 7, + "chore": 8, + "question": 9 + } +} diff --git a/czech-file-knife/.github/labels.json b/czech-file-knife/.github/labels.json new file mode 100644 index 000000000..78786d4e1 --- /dev/null +++ b/czech-file-knife/.github/labels.json @@ -0,0 +1,260 @@ +{ + "_generated_from": ".github/labels.yml in hyperpolymath/.git-private-farm", + "_do_not_edit": "regenerate with scripts/gen-labels-json.py", + "version": 1, + "labels": [ + { + "name": "bug", + "color": "d73a4a", + "description": "Something is broken or behaves incorrectly", + "tier": "type" + }, + { + "name": "enhancement", + "color": "a2eeef", + "description": "New capability or improvement to existing behaviour", + "tier": "type" + }, + { + "name": "documentation", + "color": "0075ca", + "description": "Docs, prose, diagrams, READMEs, ADRs", + "tier": "type" + }, + { + "name": "refactor", + "color": "c5def5", + "description": "Restructuring that preserves observable behaviour", + "tier": "type" + }, + { + "name": "tech-debt", + "color": "fbca04", + "description": "Known shortcut, drift, or hygiene owed - includes cleanup", + "tier": "type" + }, + { + "name": "testing", + "color": "bfd4f2", + "description": "Tests, benchmarks, fuzzing, property checks, coverage", + "tier": "type" + }, + { + "name": "chore", + "color": "ededed", + "description": "Routine maintenance with no behaviour change", + "tier": "type" + }, + { + "name": "research", + "color": "d4c5f9", + "description": "Open investigation; the outcome is knowledge, not code", + "tier": "type" + }, + { + "name": "decision", + "color": "8b5cf6", + "description": "A ruling is required before work can proceed", + "tier": "type" + }, + { + "name": "question", + "color": "d876e3", + "description": "Further information is requested", + "tier": "type" + }, + { + "name": "cicd", + "color": "006b75", + "description": "CI/CD: workflows, actions, lockfiles, pins, runners, release gates", + "tier": "area" + }, + { + "name": "security", + "color": "006b75", + "description": "Security posture, secrets, scanning, advisories, supply chain", + "tier": "area" + }, + { + "name": "proofs", + "color": "006b75", + "description": "Formal verification: Agda, Coq, Idris, Lean, Z3/SMT, axiom debt", + "tier": "area" + }, + { + "name": "governance", + "color": "006b75", + "description": "Policy, rulesets, standards, compliance, and their enforcement", + "tier": "area" + }, + { + "name": "design", + "color": "006b75", + "description": "Design of an interface, protocol, grammar, or type theory", + "tier": "area" + }, + { + "name": "architecture", + "color": "006b75", + "description": "Structural/system-level shape and runtime behaviour", + "tier": "area" + }, + { + "name": "performance", + "color": "006b75", + "description": "Throughput, latency, memory, binary size", + "tier": "area" + }, + { + "name": "bindings", + "color": "006b75", + "description": "ABI, FFI, WASM, and cross-language interop surfaces", + "tier": "area" + }, + { + "name": "migration", + "color": "006b75", + "description": "Porting between languages or toolchains (e.g. -> AffineScript)", + "tier": "area" + }, + { + "name": "packaging", + "color": "006b75", + "description": "Guix, Nix, containers, distribution artefacts", + "tier": "area" + }, + { + "name": "licensing", + "color": "006b75", + "description": "Licences, SPDX headers, REUSE compliance, attribution", + "tier": "area" + }, + { + "name": "automation", + "color": "006b75", + "description": "Bots, schedulers, dispatch, self-healing, fan-out", + "tier": "area" + }, + { + "name": "scaffolding", + "color": "006b75", + "description": "RSR templates, repo init, instantiation, project skeletons", + "tier": "area" + }, + { + "name": "conformance", + "color": "006b75", + "description": "Conformance to an external or internal specification", + "tier": "area" + }, + { + "name": "priority:p0", + "color": "b60205", + "description": "Critical - drop other work", + "tier": "priority" + }, + { + "name": "priority:p1", + "color": "d93f0b", + "description": "High - schedule next", + "tier": "priority" + }, + { + "name": "priority:p2", + "color": "e99695", + "description": "Normal - queue it", + "tier": "priority" + }, + { + "name": "priority:p3", + "color": "f9d0c4", + "description": "Low - nice to have", + "tier": "priority" + }, + { + "name": "status:blocked", + "color": "fbca04", + "description": "Cannot proceed until a dependency clears", + "tier": "status" + }, + { + "name": "status:ready", + "color": "fbca04", + "description": "Fully specified and ready to be picked up", + "tier": "status" + }, + { + "name": "status:needs-owner", + "color": "fbca04", + "description": "Unassigned and needs someone to take it", + "tier": "status" + }, + { + "name": "status:needs-ruling", + "color": "fbca04", + "description": "Awaiting an owner decision", + "tier": "status" + }, + { + "name": "status:do-not-automate", + "color": "fbca04", + "description": "Bots and sweeps must not touch this issue", + "tier": "status" + }, + { + "name": "meta:umbrella", + "color": "5319e7", + "description": "Parent issue aggregating child issues", + "tier": "meta" + }, + { + "name": "meta:campaign", + "color": "5319e7", + "description": "Coordinated multi-repo push with a defined end state", + "tier": "meta" + }, + { + "name": "meta:roadmap", + "color": "5319e7", + "description": "Forward planning; not yet actionable work", + "tier": "meta" + }, + { + "name": "meta:recurring", + "color": "5319e7", + "description": "Recurs on a schedule or by trigger; never finally closed", + "tier": "meta" + }, + { + "name": "scope:estate", + "color": "bfdadc", + "description": "Affects many or all repos across the estate", + "tier": "scope" + }, + { + "name": "scope:repo", + "color": "bfdadc", + "description": "Confined to this repository", + "tier": "scope" + } + ], + "frozen": [ + "dependencies", + "duplicate", + "elixir", + "gitar-approved", + "github_actions", + "good first issue", + "help wanted", + "invalid", + "javascript", + "never-stale", + "nix", + "pinned", + "python", + "rust", + "security", + "stale", + "wontfix" + ] +} diff --git a/czech-file-knife/.github/pull_request_template.md b/czech-file-knife/.github/pull_request_template.md new file mode 100644 index 000000000..2cca1e1f5 --- /dev/null +++ b/czech-file-knife/.github/pull_request_template.md @@ -0,0 +1,47 @@ + +## Summary + + + +## Changes + + + +- + +## RSR Quality Checklist + + + +### Required + +- [ ] Tests pass (`just test` or equivalent) +- [ ] Code is formatted (`just fmt` or equivalent) +- [ ] Linter is clean (no new warnings or errors) +- [ ] No banned language patterns (no , no npm/bun, no Go/Python) +- [ ] No `unsafe` blocks without `// SAFETY:` comments +- [ ] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) +- [ ] SPDX license headers present on all new/modified source files +- [ ] No secrets, credentials, or `.env` files included + +### As Applicable + +- [ ] `.machine_readable/descriptiles/STATE.a2ml` updated (if project state changed) +- [ ] `.machine_readable/descriptiles/ECOSYSTEM.a2ml` updated (if integrations changed) +- [ ] `.machine_readable/descriptiles/META.a2ml` updated (if architectural decisions changed) +- [ ] Documentation updated for user-facing changes +- [ ] `TOPOLOGY.md` updated (if architecture changed) +- [ ] `CHANGELOG` or release notes updated +- [ ] New dependencies reviewed for license compatibility (MPL-2.0 / MPL-2.0) +- [ ] ABI/FFI changes validated (`src/interface/abi/` and `src/interface/ffi/` consistent) + +## Testing + + + +## Screenshots + + diff --git a/czech-file-knife/.github/rulesets/Immutable-Tags.json b/czech-file-knife/.github/rulesets/Immutable-Tags.json new file mode 100644 index 000000000..75fe73a05 --- /dev/null +++ b/czech-file-knife/.github/rulesets/Immutable-Tags.json @@ -0,0 +1,28 @@ +{ + "name": "Immutable-Tags", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "~ALL" + ], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "update" + }, + { + "type": "required_signatures" + } + ] +} diff --git a/czech-file-knife/.github/rulesets/README.adoc b/czech-file-knife/.github/rulesets/README.adoc new file mode 100644 index 000000000..17d45efd6 --- /dev/null +++ b/czech-file-knife/.github/rulesets/README.adoc @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Repository rulesets + +[IMPORTANT] +==== +**GitHub does not read this directory.** Unlike `.github/workflows/`, +`.github/dependabot.yml` or `.github/settings.yml`, ruleset JSON in a repository +path is *not* auto-applied by any GitHub feature. These files are +**documentation of intent plus a ready-to-POST payload** — nothing more. + +A ruleset only takes effect once someone applies it via the REST API or imports +it in the web UI. Until then the protection described here **is not in force**. +==== + +== Applying a ruleset + +Per ADR-0003 the *Configure* stage is an operator stage: identity, visibility and +branch/tag protection are set out of band. Automation is future work and must not +be described as existing. + +[source,bash] +---- +# Apply (create) a ruleset on the current repo: +gh api --method POST \ + -H "Accept: application/vnd.github+json" \ + "/repos/{owner}/{repo}/rulesets" \ + --input .github/rulesets/base.json + +# List what is actually in force — the only authoritative answer: +gh api "/repos/{owner}/{repo}/rulesets" --jq '.[] | "\(.id)\t\(.name)\t\(.enforcement)"' + +# Update an existing ruleset in place (needs its numeric id from the list above): +gh api --method PUT \ + "/repos/{owner}/{repo}/rulesets/" \ + --input .github/rulesets/base.json +---- + +== Files + +`base.json`:: Canonical branch protection ruleset for the default branch (`~DEFAULT_BRANCH`). +Enforces deleted-branch protection, linear history, squash merge, signatures and required +status checks, without unsatisfiable coverage or deadlocking approval requirements. +Sourced verbatim from `hyperpolymath/standards` `config/rulesets/base.json` (§7.3). + +`branch-floor.json`:: The irreducible branch floor for the default branch: deletion and +non-fast-forward protection only, with no bypass actors. This is the ruleset that keeps a +branch recoverable when the richer `Base` ruleset is unavailable or has been removed. +Sourced verbatim from `hyperpolymath/standards` `config/rulesets/branch-floor.json`. + +`Immutable-Tags.json`:: Makes release tags immutable — blocks tag deletion, +non-fast-forward updates, and overwrites, and requires signed tags. + +[NOTE] +==== +Files here are named to match the *live ruleset name*, which is not always the +upstream filename. Upstream ships `branch-floor.json` and `immutable-tags.json` +(lowercase, with a matching lowercase `name` inside the JSON); this repository's +`Immutable-Tags.json` declares `"name": "Immutable-Tags"`. Renaming a file here +changes nothing on GitHub, but it does change which upstream file it is diffed +against — keep that in mind when syncing. +==== + +== What is actually enforced on this repository + +Files are intent; this table is fact. Regenerate it with: + +[source,bash] +---- +gh api "/repos/{owner}/{repo}/rulesets" --jq '.[] | "\(.id)\t\(.name)\t\(.enforcement)\t\(.target)"' +---- + +|=== +| Live ruleset | Enforcement | Target | Covers + +| `Base` | active | branch | deletion, required signatures, code scanning (CodeQL/Hypatia/Scorecard), Copilot review, pull request +| `Branch-Floor` | active | branch | deletion, non-fast-forward +| `Immutable-Tags` | active | tag | creation, deletion, non-fast-forward, update, required signatures +|=== + +=== Known drift between these files and what is live + +The files below are *not* a transcript of the live rulesets. This is the +divergence as of 2026-09-28, recorded rather than silently reconciled because +`base.json` is sourced verbatim from upstream and must not be forked here. + +*Live `Base` rule that no file describes* :: + +`code_scanning` (with CodeQL, Hypatia and Scorecard tools) and +`copilot_code_review` are enforced live but appear in neither `base.json` nor any +upstream file synced into this directory. Upstream's nearest equivalent, +`config/rulesets/Optimus-Extras.json`, declares `code_scanning` with an *empty* +`code_scanning_tools` list, so it does not describe what is live here either. + +*`base.json` rules that are not in force* :: + +`required_linear_history` is in `base.json` but is not enforced by any live +ruleset. `required_status_checks` (an empty list, so a no-op) and +`non_fast_forward` are likewise absent from live `Base` — though +`non_fast_forward` *is* covered, by `Branch-Floor`. + +*`Immutable-Tags.json` lags the live ruleset* :: + +The live tag ruleset includes a `creation` rule; this file does not. Upstream +`immutable-tags.json` also populates `bypass_actors` (repository-role 5 and an +integration) with `bypass_mode: always`, and this file declares none. + +[WARNING] +==== +`bypass_actors` cannot be diffed from a low-privilege token: the REST API returns +`null` for that field unless the caller has admin on the repository. Absence of +`bypass_actors` in the output above is *not* evidence that none are configured. +==== + +== Verifying + +Do not infer that a ruleset is active because the JSON is present. Check with the +`gh api ... /rulesets` list above. A ruleset that exists as a file but was never +POSTed is the protection equivalent of a check that cannot fail. + +Conversely, do not infer that the estate is protected because a ruleset exists. +A ruleset with `enforcement: disabled` is inert, and a `pull_request` rule with +`allowed_merge_methods: []` can never be satisfied at all. diff --git a/czech-file-knife/.github/rulesets/base.json b/czech-file-knife/.github/rulesets/base.json new file mode 100644 index 000000000..136c0a0d9 --- /dev/null +++ b/czech-file-knife/.github/rulesets/base.json @@ -0,0 +1,102 @@ +{ + "name": "Base", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "~DEFAULT_BRANCH" + ], + "exclude": [] + } + }, + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1236702, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 29110, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 15368, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 347564, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 46505, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1143301, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1144995, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 12526, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 2538504, + "actor_type": "Integration", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_signatures" + }, + { + "type": "required_linear_history" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": false, + "required_reviewers": [], + "allowed_merge_methods": [ + "squash" + ] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [] + } + } + ] +} diff --git a/czech-file-knife/.github/rulesets/branch-floor.json b/czech-file-knife/.github/rulesets/branch-floor.json new file mode 100644 index 000000000..465908ac8 --- /dev/null +++ b/czech-file-knife/.github/rulesets/branch-floor.json @@ -0,0 +1,16 @@ +{ + "name": "Branch-Floor", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" } + ] +} diff --git a/czech-file-knife/.github/scripts/classify-issue.jq b/czech-file-knife/.github/scripts/classify-issue.jq new file mode 100644 index 000000000..6467c74ec --- /dev/null +++ b/czech-file-knife/.github/scripts/classify-issue.jq @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Classify one issue title against the estate label taxonomy. +# +# jq -r --arg title "docs: fix the README" \ +# --argjson have '[]' \ +# -f .github/scripts/classify-issue.jq .github/label-classifier.json +# +# Prints one label per line, or NOTHING when it cannot place the issue +# confidently. Nothing printed means "leave it for a human" -- a correct +# outcome, not a failure. +# +# WHY jq AND NOT PYTHON +# +# Python is fully banned estate-wide: the `governance / Language / package +# anti-pattern policy` gate runs `git ls-files '*.py'` and fails the PR +# ("Python is fully banned -- use AffineScript/Rust/SPARK/Julia"). This file +# is dispatched into every repo in the estate, so shipping it as .py would +# mean shipping an exemption into every repo too -- normalising the policy +# away by sweep. jq is preinstalled on every GitHub runner, is not on the +# banned list, needs no action (so no actions.lock entry can drift), and the +# rules are already JSON. +# +# The canonical implementation remains scripts/label-classify.py in the hub, +# which never runs in CI. tests/test-classifier-parity.py asserts this file +# agrees with it on every title in the corpus. +# +# `$have` lists labels the issue already carries. Anything already present is +# never re-suggested, and the classifier stays out of any max-1 tier the issue +# already has a label in, so a human's classification is never overridden. + +# Escape every non-alphanumeric so a keyword is matched literally. Escaping +# punctuation that needs no escape is harmless in Oniguruma. +def reesc: gsub("(?[^A-Za-z0-9 _])"; "\\\(.c)"); + +def norm: (. // "") | ascii_downcase + | sub("^[[:space:]]+"; "") | sub("[[:space:]]+$"; ""); + +# Asymmetric boundary: STRICT on the left, inflection-tolerant on the right. +# +# Measured over the issue corpus, the two error directions are not symmetric: +# * every false positive is a LEFT-side prefix -- `lean` in "clean up", +# `abi` in "capability", `mpl` in "Implement", `ffi` in "AffineScript", +# `smt` in "wasmtime". The left boundary must stay strict. +# * every real miss is a RIGHT-side inflection -- `test` vs "tests", +# `theorem` vs "theorems", `todo` vs "TODOs", `scaffold` vs "scaffolding". +# +# The right side therefore admits a CLOSED set of inflections. Closed, not open +# (`.*`), because an open right side re-admits the prefix false positives. +# +# `ion`/`ation` are excluded from the base set: they mint unrelated words +# (`port` + `ion` = "portion", and `port` is a live keyword). They are enabled +# only for shapes that are unambiguously truncated stems -- `-at` +# (instantiat, investigat, adjudicat) and `-ment` (document, implement). +def kwrx($kw): + ( "s|es|ed|d|ing|er|ers|y|ies" + + (if ($kw | endswith("at")) then "|ion|ions|e" + elif ($kw | endswith("ment")) then "|ation|ations" + else "" end) + ) as $suf + # Boundaries are conditional: a keyword not starting alphanumeric has no left + # boundary to enforce, and one not ending alphanumeric takes no suffix. + | (if ($kw | test("^[A-Za-z0-9]")) then "(?[^\\]]{1,25})\\]")) // null) as $m + | if $m == null then {rule: null, rest: $t} + else (($m.tag | norm | split("#")[0]) | norm) as $tag + | { rule: ($R.bracket_tag[$tag] // null), + rest: ($t | sub("^[[:space:]]*\\[[^\\]]{1,25}\\]"; "")) } + end; + +# Leading `word:` / `word(scope):` conventional-commit prefix. +def prefixrule($R; $t): + (($t | capture("^[[:space:]]*(?[A-Za-z][A-Za-z0-9_./-]{1,24})(?:[[:space:]]*\\([^)]*\\))?[[:space:]]*:")) // null) as $m + | if $m == null then null + else ($m.w | norm) as $k + # Compound prefixes such as "adaptive/must:" carry their meaning in the + # ISO 14764 category only; the modality does not label. + | (if ($R.prefix_split_on // "") != "" and ($k | contains($R.prefix_split_on)) + then ($k | split($R.prefix_split_on) | .[0]) else $k end) as $key + | ($R.title_prefix[$key] // null) + end; + +def signals($R; $tl; $sec): + [ ($R[$sec] // {}) | to_entries[] + | select(.value | any(. as $k | kwhit($k; $tl))) + | .key ]; + +# The HIGHEST-PRECEDENCE matching type, not merely the first in key order. +def kwtype($R; $tl): + [ $R.keyword_type | to_entries[] + | select(.value | any(. as $k | kwhit($k; $tl))) + | .key ] + | if length == 0 then null + else min_by([($R.precedence[.] // 99), .]) end; + +# Drop violations of each tier's `max`, keeping the highest-precedence member. +def enforce($R; $labels): + ($labels | unique) + | group_by($R.tier_of[.] // "?") + | map( ($R.tier_of[.[0]] // "?") as $tier + | ($R.tier_max[$tier] // null) as $mx + | if $mx == null or (length <= $mx) then . + else (sort_by([($R.precedence[.] // 99), .]))[0:$mx] end ) + | flatten; + +def classify($R; $title; $have0): + ($title // "") as $t0 + | ($t0 | norm) as $tl + | ($have0 | map(select(. != null and . != "")) + | unique) as $have + | ($R.tier_of | keys) as $canon + | $R.types as $types + | bracket($R; $t0) as $b + | (if $b.rule != null then ($b.rule | rulelabels) else [] end) as $l1 + | prefixrule($R; $b.rest) as $pr + | (if $pr != null then ($pr | rulelabels) else [] end) as $l2 + | (($b.rule != null) or ($pr != null)) as $matched0 + # 3. keyword areas are additive and never contribute a type + | ($l1 + $l2 + signals($R; $tl; "keyword_area")) as $acc + # 4. a type only if neither the rules nor the issue already supplied one + | (if (($acc + $have) | any(. as $x | $types | index($x))) + then null else kwtype($R; $tl) end) as $ty + | ($acc + (if $ty != null then [$ty] else [] end)) as $acc + | ($matched0 or ($ty != null)) as $matched + | ( $acc + + signals($R; $tl; "status_signal") + + signals($R; $tl; "meta_signal") + + signals($R; $tl; "scope_signal") ) as $acc + # NOTE: `frozen` is deliberately NOT subtracted. Frozen means "never rename or + # delete this label" -- `security` is frozen because triage.yml pins it in + # exempt-issue-labels. APPLYING it to an issue is correct; only the + # definition is protected. + | ($acc | map(select(. as $x | $canon | index($x))) | unique) as $acc + | enforce($R; $acc + ($have | map(select(. as $x | $canon | index($x))))) as $acc + | ($acc - $have) as $out + # Stay out of any max-1 tier the issue ALREADY has a label in -- a human's, + # or one an ISSUE_TEMPLATE applied. A prefix rule fires unconditionally, so + # "fix: ..." on an issue already labelled `enhancement` would otherwise add + # `bug` beside it. This covers every max-1 tier (type, priority, status, + # meta, scope), not just type. + | ( [ $R.tier_max | to_entries[] | select(.value == 1) | .key ] + | map(. as $t | select($have | any(($R.tier_of[.] // "?") == $t))) + ) as $lockedtiers + | ($out | map(select(($R.tier_of[.] // "?") as $t | ($lockedtiers | index($t)) | not))) as $out + # A rule must actually have FIRED: keyword-area hits alone are not enough. + | if ($matched | not) then [] + # a type is mandatory + elif ((($out + $have) | any(. as $x | $types | index($x))) | not) then [] + else ($out | sort) end; + +classify(.; $title; $have) | .[] diff --git a/czech-file-knife/.github/settings.yml b/czech-file-knife/.github/settings.yml new file mode 100644 index 000000000..b9baefd76 --- /dev/null +++ b/czech-file-knife/.github/settings.yml @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Repository settings for probot/settings GitHub App. +# https://github.com/probot/settings +# +# This file defines repository-level configuration that is automatically +# applied by the probot/settings app when changes are pushed to the default +# branch. Install the app at: https://github.com/apps/settings +# +# ─── THIS FILE MUST NEVER DECLARE REPOSITORY IDENTITY ───────────────────────── +# +# It carries NO `name`, `description`, `homepage` or `private` key, and it must +# never gain one. The reason is a real incident, not a hypothetical: +# +# This file previously read `name: "czech-file-knife"`. probot/settings applies it on +# every push to the default branch, so it submitted the literal string +# `czech-file-knife` as the repository name. GitHub sanitises an invalid name by +# collapsing each run of illegal characters to a dash — `czech-file-knife` became +# `-REPO-`. The template renamed itself on every push, its old URL 404'd, and +# it was mistaken for a deleted repository. `description` was likewise left +# reading the literal `Canonical RSR (Rhodium Standard Repository) template — governance, CI/CD, machine-readable metadata, ABI/FFI seam and formal-verification scaffolding that hyperpolymath projects are instantiated from.` on the live repo. +# +# Two properties make identity keys unsafe here specifically: +# +# 1. This is a TEMPLATE. `just repo-init` fills placeholders in repos minted by the +# scaffolder — but GitHub's "Use this template" button copies the default +# branch verbatim and never runs `just repo-init`. Any placeholder left in a +# probot-managed file therefore reaches children unrendered. +# 2. Identity is not shareable. The template must be public while children +# default private; a child cannot inherit either `name` or `private` from +# its parent without being wrong. +# +# Repository identity and visibility are therefore set OUT OF BAND: once per +# repo, at creation time, by the operator (the Configure stage of ADR-0003). +# `just repo-init` deliberately runs NO `gh` commands — it prints the exact +# `gh repo edit` commands as next steps instead. Fail-closed default: repos +# stay private unless the owner flips visibility deliberately; the template's +# own name and visibility are set deliberately by the owner. +# +# Everything below is safe to inherit: it is true of every RSR repo regardless +# of that repo's name, purpose or visibility. +# +# Enforced by `scripts/check-no-placeholders.sh`, which fails if this file +# contains a `{{` token or declares any of the four identity keys. + +# ─── Repository Settings ─────────────────────────────────────────────────────── + +repository: + has_issues: true + has_projects: true + has_wiki: false + has_downloads: true + default_branch: main + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + delete_branch_on_merge: true + enable_automated_security_fixes: true + enable_vulnerability_alerts: true + +# ─── Labels ──────────────────────────────────────────────────────────────────── + +labels: + - name: "bug" + color: "d73a4a" + description: "Something isn't working" + + - name: "enhancement" + color: "a2eeef" + description: "New feature or request" + + - name: "documentation" + color: "0075ca" + description: "Improvements or additions to documentation" + + - name: "security" + color: "e4e669" + description: "Security-related issue or vulnerability" + + - name: "good first issue" + color: "7057ff" + description: "Good for newcomers" + + - name: "help wanted" + color: "008672" + description: "Extra attention is needed" + + - name: "question" + color: "d876e3" + description: "Further information is requested" + + - name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists" + + - name: "invalid" + color: "e4e669" + description: "This doesn't seem right" + + - name: "wontfix" + color: "ffffff" + description: "This will not be worked on" + + - name: "dependencies" + color: "0366d6" + description: "Pull requests that update a dependency file" + + - name: "ci/cd" + color: "fbca04" + description: "Continuous integration and deployment" + + - name: "rsr" + color: "006b75" + description: "Rhodium Standard Repository compliance" + + - name: "hypatia" + color: "5319e7" + description: "Hypatia neurosymbolic scanner finding" + + - name: "bot" + color: "b4a8d1" + description: "Automated action by gitbot-fleet" + + - name: "breaking-change" + color: "b60205" + description: "Introduces a breaking change" + + - name: "performance" + color: "f9d0c4" + description: "Performance improvement" + + - name: "refactor" + color: "c5def5" + description: "Code refactoring with no functional change" + +# ─── Branch Protection ───────────────────────────────────────────────────────── + +# A required context must name a check that is actually EMITTED, or the branch +# deadlocks: the check never reports, so it stays permanently pending, and with +# enforce_admins even the owner cannot merge or push. This block previously +# required three contexts, two of which no repo has ever emitted — +# +# "codeql" is emitted as `analyze (actions, none)` (job id + matrix) +# "hypatia-scan" is emitted as `scan / Hypatia Neurosymbolic Analysis` +# (a reusable-workflow call always reports `caller / called`) +# +# — while "openssf-compliance" resolved only because its job *id* is literally +# `openssf-compliance` and it declares no `name:` and no matrix. That is the +# rule: pin the job **id** to the context string. Note this file is applied by +# probot on every push to the default branch, so a wrong context here does not +# merely describe protection, it re-imposes the deadlock on every push. +# +# `contexts` is deliberately EMPTY rather than aspirational. Requiring a check +# that cannot pass is the same defect as requiring one that cannot report, and +# on this repo no check can currently run at all: GitHub Actions is billing- +# blocked for PRIVATE repositories on this account ("The job was not started +# because recent account payments have failed or your spending limit needs to be +# increased"), which is why every workflow here fails with zero steps while the +# estate's public repos run normally. Repopulate this list — one context at a +# time, each pinned to a job id, each verified green — once Actions can run. diff --git a/czech-file-knife/.github/workflows/README.adoc b/czech-file-knife/.github/workflows/README.adoc new file mode 100644 index 000000000..c7a232815 --- /dev/null +++ b/czech-file-knife/.github/workflows/README.adoc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += GitHub Workflows — what runs, and why some look duplicated + +This directory holds the repo's CI/CD. A few workflows *look* like they overlap +but are deliberately distinct — documented here so the apparent duplication is +not "tidied away" into a real coverage gap. + +== Hypatia runs in two places (on purpose) + +* `hypatia-scan.yml` — the **standalone security scan** (push / PR / weekly). + Independent; this is the repo's own Hypatia coverage. +* `static-analysis-gate.yml` -> `hypatia-scan` job — runs Hypatia as part of the + **gate**, then the `deposit-findings` job hands the results to the + **gitbot-fleet learning** pipeline. This job is also a *required status check*. + +Same tool, two different consumers (security scan vs fleet learning). Removing +either loses real function — keep both. + +== Secret scanning is single-sourced *in CI* — and two-tier overall + +`secret-scanner.yml` calls the standards reusable (gitleaks + a Rust-secrets +check). An inline TruffleHog job was removed: the reusable deliberately retired +TruffleHog as redundant with gitleaks, so re-adding it was duplicated work, not +extra coverage. + +**That retirement is scoped to CI.** The estate runs a deliberate *two-tier* +secret defence, and the second tier is not in this directory: + +[cols="1,2,3"] +|=== +| Tier | Engine | Where + +| CI +| gitleaks (+ rust-secrets, shell-secrets) +| `secret-scanner.yml` -> standards reusable. Runs on pull_request and on push + to main — i.e. on what has *already reached* the remote. + +| Local +| TruffleHog +| `.github/hooks/scan-secrets.sh`, called by `.github/hooks/pre-push`. Runs *before* the + push leaves the machine, on exactly the commits being pushed. +|=== + +Different engines at different checkpoints — the local tier is the only one that +can stop a key before it exists on a server, where revocation, not deletion, is +the only real remedy. Ratified by the owner, 2026-09-14: *"we [run] gitleaks on +github regularly but [run] trufflehog prior to pushes using the hooks system."* + +*Do not delete `.github/hooks/scan-secrets.sh` as duplication of `secret-scanner.yml`.* +It is the other half of this design, not a second copy of this half. The two +tiers also cover for each other's blind spots: the hook excludes TruffleHog's +SonarCloud detector (it matches any 40-hex string, so every SHA-pinned action +reads as a secret — measured, 24 findings on a clean clone, all of them action +pins), and gitleaks in CI catches a genuine SonarCloud token as `generic-api-key`. + +== SAST tools are complementary, not redundant + +* `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows). +* `sonarqube.yml` — SonarCloud (shell / JS surface; see `sonar-project.properties`). +* `static-analysis-gate.yml` — panic-attack + Hypatia gate (see above). + +Each targets a different language/surface; together they cover what no single +analyser does. diff --git a/czech-file-knife/.github/workflows/actions.lock b/czech-file-knife/.github/workflows/actions.lock new file mode 100644 index 000000000..929a39d7e --- /dev/null +++ b/czech-file-knife/.github/workflows/actions.lock @@ -0,0 +1,296 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.2' +workflows: + '.github/workflows/build-notification.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/cflite_batch.yml': + - 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1' + '.github/workflows/cflite_pr.yml': + - 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1' + '.github/workflows/codeql.yml': + - 'actions/checkout@v7.0.1' + - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63' + '.github/workflows/deed-validate.yml': + - 'actions/checkout@v7.0.1' + - 'hyperpolymath/deed-ecosystem@main' + '.github/workflows/dependabot-automerge.yml': + - 'dependabot/fetch-metadata@v3.1.0' + '.github/workflows/docker-build.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/dogfood-gate.yml': + - 'actions/checkout@v7.0.1' + - 'erlef/setup-beam@v1.24.1' + - 'hyperpolymath/deed-ecosystem@main' + - 'hyperpolymath/k9-ecosystem@main' + '.github/workflows/dogfood-summary.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/dot-wellknown-enforcement.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/eclexiaiser-validate.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/empty-linter.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/estate-rules.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/governance.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/groove-check.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/guix-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/hypatia-scan.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/k9-validate.yml': + - 'actions/checkout@v7.0.1' + - 'hyperpolymath/k9-ecosystem@main' + '.github/workflows/label-triage.yml': [] + '.github/workflows/labels.yml': [] + '.github/workflows/lock-sync-gate.yml': [] + '.github/workflows/main-estate-audit.yml': + - 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d' + '.github/workflows/mirror.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/ossf-best-practices.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/pages.yml': + - 'actions/cache@v6.1.0' + - 'actions/checkout@v7.0.1' + - 'actions/configure-pages@v6.0.0' + - 'actions/deploy-pages@v5.0.1' + - 'actions/upload-pages-artifact@v5.0.0' + - 'haskell-actions/setup@v2.12.0' + '.github/workflows/push-email-notify.yml': + - 'hyperpolymath/smtp-notify-action@v0.3.0' + '.github/workflows/quality.yml': + - 'actions/checkout@v7.0.1' + - 'editorconfig-checker/action-editorconfig-checker@v3.0.0' + '.github/workflows/release.yml': + - 'actions/attest-build-provenance@v4.2.2' + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'softprops/action-gh-release@v3.0.3' + '.github/workflows/rsr-compliance-canary.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/runtime-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/rust-ci.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/scorecard.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/secret-scanner.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/security-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/sonarqube.yml': + - 'actions/checkout@v7.0.1' + - 'sonarsource/sonarqube-scan-action@v8.2.2' + '.github/workflows/static-analysis-gate.yml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'erlef/setup-beam@v1.24.1' + '.github/workflows/workflow-linter.yml': + - 'actions/checkout@v7.0.1' + - 'oven-sh/setup-bun@v2.2.0' +dependencies: + 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': + ref: '6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + owner_id: 580492 + repo_id: 298565987 + 'actions/attest-build-provenance@v4.2.2': + ref: 'v4.2.2' + commit: 'sha1-4d101475d8b20a2381f78447822ac1eab6504dd8' + owner_id: 44036562 + repo_id: 760702757 + uses: + - 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d' + 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d': + ref: 'v4.2.1' + commit: 'sha1-508db95dd578ae2727ebd6217d5ba78e4fbda05d' + owner_id: 44036562 + repo_id: 760701061 + 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': + ref: '55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + owner_id: 44036562 + repo_id: 215566462 + 'actions/cache@v6.1.0': + ref: 'v6.1.0' + commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + owner_id: 44036562 + repo_id: 215566462 + 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1': + ref: '3d3c42e5aac5ba805825da76410c181273ba90b1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/configure-pages@v6.0.0': + ref: 'v6.0.0' + commit: 'sha1-45bfe0192ca1faeb007ade9deae92b16b8254a0d' + owner_id: 44036562 + repo_id: 513659658 + 'actions/deploy-pages@v5.0.1': + ref: 'v5.0.1' + commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' + owner_id: 44036562 + repo_id: 438112499 + 'actions/download-artifact@v8.0.1': + ref: 'v8.0.1' + commit: 'sha1-3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' + owner_id: 44036562 + repo_id: 192626254 + 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a': + ref: '043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': + ref: 'v7.0.0' + commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-pages-artifact@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-fc324d3547104276b827a68afc52ff2a11cc49c9' + owner_id: 44036562 + repo_id: 496012378 + uses: + - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + 'dependabot/fetch-metadata@v3.1.0': + ref: 'v3.1.0' + commit: 'sha1-25dd0e34f4fe68f24cc83900b1fe3fe149efef98' + owner_id: 27347476 + repo_id: 371068214 + 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': + ref: '6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + owner_id: 1940490 + repo_id: 260749683 + 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393': + ref: '51f63319f592f97930c73d9c46184d20bd206393' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'editorconfig-checker/action-editorconfig-checker@v3.0.0': + ref: 'v3.0.0' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': + ref: '54075bcc5e249e4758d363f27d099f55d843f124' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'erlef/setup-beam@v1.24.1': + ref: 'v1.24.1' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1': + ref: 'v1' + commit: 'sha1-884713a6c30a92e5e8544c39945cd7cb630abcd1' + owner_id: 1342004 + repo_id: 400046858 + 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63': + ref: 'v4.38.0' + commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' + owner_id: 9919 + repo_id: 259445878 + 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938': + ref: 'cdf488f595d80d6e07e03d4674febd5ab45fa938' + commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' + owner_id: 9919 + repo_id: 259445878 + 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406': + ref: 'abea47f85e598557f500fa1fd2ab7464fcb39406' + commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' + owner_id: 1006268 + repo_id: 212984112 + 'haskell-actions/setup@v2.12.0': + ref: 'v2.12.0' + commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' + owner_id: 75048950 + repo_id: 623796603 + 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5': + ref: '0da816c05ae7486671d863e07935f93981c0c2d5' + commit: 'sha1-0da816c05ae7486671d863e07935f93981c0c2d5' + owner_id: 6759885 + repo_id: 1326697643 + 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d': + ref: '55556cf5ba71ba744695861503c13148d3915a5d' + commit: 'sha1-55556cf5ba71ba744695861503c13148d3915a5d' + owner_id: 6759885 + repo_id: 1326697643 + uses: + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5' + 'hyperpolymath/deed-ecosystem@main': + ref: 'main' + commit: 'sha1-44f9c9fc42901a55e0f489a40eabf4173e550d1d' + owner_id: 6759885 + repo_id: 1275649586 + 'hyperpolymath/k9-ecosystem@main': + ref: 'main' + commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' + owner_id: 6759885 + repo_id: 1275650185 + 'hyperpolymath/smtp-notify-action@v0.3.0': + ref: 'v0.3.0' + commit: 'sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be' + owner_id: 6759885 + repo_id: 1352485172 + 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540': + ref: 'da2c748aad55c1a1dcba00b60fe4a35017bc6540' + commit: 'sha1-da2c748aad55c1a1dcba00b60fe4a35017bc6540' + owner_id: 6759885 + repo_id: 1116521501 + uses: + - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + - 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + - 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393' + - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' + - 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938' + - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406' + - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' + - 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' + 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': + ref: '2d1146689b8cda280b9bc96326124645441f03bc' + commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' + owner_id: 67707773 + repo_id: 421101922 + 'oven-sh/setup-bun@v2.2.0': + ref: 'v2.2.0' + commit: 'sha1-0c5077e51419868618aeaa5fe8019c62421857d6' + owner_id: 108928776 + repo_id: 512644635 + 'softprops/action-gh-release@v3.0.3': + ref: 'v3.0.3' + commit: 'sha1-efb35369e0ad2afab669f228072c1b0d510eae64' + owner_id: 2242 + repo_id: 204253808 + 'sonarsource/sonarqube-scan-action@v8.2.2': + ref: 'v8.2.2' + commit: 'sha1-ba9859eae8dd6bd29e412f25ddbbef3d032000f4' + owner_id: 545988 + repo_id: 366408409 + 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555': + ref: 'e83874834305fe9a4a2997156cb26c5de65a8555' + commit: 'sha1-e83874834305fe9a4a2997156cb26c5de65a8555' + owner_id: 135788 + repo_id: 208510314 diff --git a/czech-file-knife/.github/workflows/build-notification.yml b/czech-file-knife/.github/workflows/build-notification.yml new file mode 100644 index 000000000..e43da5b7f --- /dev/null +++ b/czech-file-knife/.github/workflows/build-notification.yml @@ -0,0 +1,50 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# +# OPTIONAL: BoJ Server Build Trigger +# This workflow notifies a BoJ Server instance when code is pushed. +# It is a no-op if BOJ_SERVER_URL is not set or the server is unreachable. +# To enable: set BOJ_SERVER_URL as a repository variable (secrets cannot gate +# a job-level if:). A BOJ_SERVER_URL secret is still honoured at run time. +# To disable: delete this file or leave BOJ_SERVER_URL unset. +name: BoJ Server Build Trigger +on: + push: + branches: [main, master] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + trigger-boj: + runs-on: ubuntu-latest + timeout-minutes: 15 + if: ${{ vars.BOJ_SERVER_URL != '' }} + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + - name: Trigger BoJ Server (Casket/ssg-mcp) + env: + BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }} + REPO_NAME: ${{ github.repository }} + BRANCH_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ -z "$BOJ_URL" ]; then + echo "BOJ_SERVER_URL not configured - skipping" + exit 0 + fi + + payload="$(jq -cn \ + --arg repo "$REPO_NAME" \ + --arg branch "$BRANCH_NAME" \ + --arg engine "casket" \ + '{repo:$repo, branch:$branch, engine:$engine}')" + + curl -sf -X POST "${BOJ_URL}/cartridges/ssg-mcp/invoke" \ + -H "Content-Type: application/json" \ + --data "$payload" \ + || echo "BoJ server unreachable - skipping (non-fatal)" diff --git a/czech-file-knife/.github/workflows/cflite_batch.yml b/czech-file-knife/.github/workflows/cflite_batch.yml index fa25c0e5d..e11e36e5e 100644 --- a/czech-file-knife/.github/workflows/cflite_batch.yml +++ b/czech-file-knife/.github/workflows/cflite_batch.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: ClusterFuzzLite batch fuzzing on: diff --git a/czech-file-knife/.github/workflows/cflite_pr.yml b/czech-file-knife/.github/workflows/cflite_pr.yml index 2bed247ad..edb9391a7 100644 --- a/czech-file-knife/.github/workflows/cflite_pr.yml +++ b/czech-file-knife/.github/workflows/cflite_pr.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: ClusterFuzzLite PR fuzzing on: diff --git a/czech-file-knife/.github/workflows/codeql.yml b/czech-file-knife/.github/workflows/codeql.yml index 5eb5987f1..517077e75 100644 --- a/czech-file-knife/.github/workflows/codeql.yml +++ b/czech-file-knife/.github/workflows/codeql.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: CodeQL Security Analysis on: @@ -6,11 +7,16 @@ on: pull_request: branches: [main, master] schedule: - - cron: '0 6 1 * *' -permissions: read-all + - cron: '0 6 1 * *' # monthly 1st 06:00 UTC (Actions burn cut, standards#288) +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read jobs: analyze: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read security-events: write @@ -18,17 +24,24 @@ jobs: fail-fast: false matrix: include: + # Default to `actions` — scaffolded repos rarely have JS/TS + # failures on every CodeQL run. The `actions` extractor scans + # workflow files which every repo has. Override per-repo if + # the scaffolded project actually contains JS/TS code. + # Per hypatia rule `codeql_language_matrix_mismatch`. - language: actions build-mode: none steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.28.1 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.28.1 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) with: category: "/language:${{ matrix.language }}" diff --git a/czech-file-knife/.github/workflows/container.yml b/czech-file-knife/.github/workflows/container.yml deleted file mode 100644 index 0b5709e98..000000000 --- a/czech-file-knife/.github/workflows/container.yml +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Container Build -on: - push: - tags: - - 'v*' - workflow_dispatch: -permissions: read-all -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} -jobs: - build-and-push: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - name: Log in to GitHub Container Registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - - name: Build and push - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v5 - with: - context: . - file: ./Containerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/czech-file-knife/.github/workflows/deed-validate.yml b/czech-file-knife/.github/workflows/deed-validate.yml new file mode 100644 index 000000000..f05a5ddc5 --- /dev/null +++ b/czech-file-knife/.github/workflows/deed-validate.yml @@ -0,0 +1,59 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: DEED Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + deed-validate: + name: Validate DEED manifests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for A2ML files + id: detect + run: | + COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ]; then + echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" + fi + + - name: "🟡 CHECK: Validate DEED manifests" + if: steps.detect.outputs.count > 0 + uses: hyperpolymath/deed-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + MANIFEST_COUNT="${{ steps.detect.outputs.count }}" + if [ "$MANIFEST_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## DEED Manifest Validation + + :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + + Copy it from [czech-file-knife](https://github.com/hyperpolymath/czech-file-knife). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. + EOF + else + echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/czech-file-knife/.github/workflows/dependabot-automerge.yml b/czech-file-knife/.github/workflows/dependabot-automerge.yml new file mode 100644 index 000000000..31a7b6aa7 --- /dev/null +++ b/czech-file-knife/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,137 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# +# dependabot-automerge.yml — enable GitHub's native auto-merge on +# Dependabot pull requests that match a declared severity / ecosystem +# policy. Pairs with `.github/dependabot.yml`'s +# `open-pull-requests-limit: 0` + security-only pattern (see the +# cargo block there). +# +# What this does: +# - Triggers on every Dependabot PR. +# - Reads the PR's update-type metadata via the dependabot/fetch-metadata +# action (no free-text parsing). +# - Requires CI to be green before merge (GitHub's auto-merge enforces +# required status checks). +# - Gates merge behind a severity+ecosystem policy table. Default is +# low+medium security updates only. +# +# Why auto-merge on GitHub (not via a bot like rhodibot) is the right +# layer: GitHub enforces branch protection + required checks natively, +# and the PR author is already `dependabot[bot]`. Rhodibot doesn't need +# to know anything about ecosystems — GitHub handles the merge mechanics +# once we approve. +# +# Threat model: +# - A compromised upstream package with a bogus security advisory +# could propose a malicious version bump. Mitigation: require at +# least one non-automated reviewer for HIGH+CRITICAL severity +# (done below — we explicitly refuse to auto-approve those). +# - A compromised Dependabot itself is an Akerlof claim-grounder +# problem. Not in scope here; track under +# `project_claim_grounders_dual_use_akerlof.md`. +# +# Dogfooding: this workflow template is itself subject to the same +# Dependabot config via the github-actions ecosystem block, so SHA +# bumps for dependabot/fetch-metadata flow through the same path. + +name: Dependabot Auto-Merge +on: + pull_request: + types: [opened, reopened, synchronize] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false +permissions: + contents: read + pull-requests: write # needed to approve + # NB: keep narrow — do NOT add secrets: read or id-token: write here. + # The write scope auto-merge needs is elevated PER JOB below, not here: a + # job-level permissions: block REPLACES this one, so the job restates both. +jobs: + automerge: + # Only run for PRs actually authored by Dependabot. + if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + permissions: + contents: write # needed to enable auto-merge (gh pr merge --auto) + pull-requests: write # needed to approve + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + # --- Policy gate ------------------------------------------------------- + # Outputs from fetch-metadata we care about: + # update-type → version-update:semver-{patch,minor,major} + # dependency-type → direct:{development,production} | indirect + # alert-state → AUTO_DISMISSED | DISMISSED | FIXED | OPEN + # ghsa-id → GHSA-... if this is a security PR + # --- Policy ------------------------------------------------------------- + # AUTO-APPROVE + AUTO-MERGE when: + # 1. This is a SECURITY update (ghsa-id present), AND + # 2. Update is patch or minor, AND + # 3. Severity ≤ moderate (Dependabot doesn't expose severity + # directly in fetch-metadata; infer from the absence of + # HIGH/CRITICAL labels added by Dependabot). + # Otherwise: do nothing. Human reviews HIGH+CRITICAL security + # updates and all non-security bumps. + - name: Decide policy outcome + id: policy + env: + GHSA_ID: ${{ steps.meta.outputs.ghsa-id }} + UPDATE_TYPE: ${{ steps.meta.outputs.update-type }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} + run: | + set -euo pipefail + + is_security=false + [ -n "$GHSA_ID" ] && is_security=true + + # Owner policy (deliberate: velocity over caution). Auto-merge EVERY + # Dependabot update — patch, minor AND major, security or routine. + # Safe because GitHub's auto-merge only COMPLETES once the required + # checks (secret-scanner, codeql, hypatia-scan, openssf-compliance, + # build/test) are green: a bump that breaks fails CI and the PR stays + # OPEN with an email ("oi, it broke") instead of landing on a red + # main; a bump that passes lands within minutes with no chasing. + # This is preferred over making Dependabot a ruleset BYPASS actor, + # which would let bumps skip those very checks (no gate, no signal). + echo "action=automerge" >> "$GITHUB_OUTPUT" + echo "security=$is_security" >> "$GITHUB_OUTPUT" + echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" + echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT" + - name: Approve PR (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR_URL" \ + --body "Auto-approving Dependabot security update (${{ steps.policy.outputs.ghsa }}, ${{ steps.policy.outputs.update_type }}). Policy: low/moderate security patches/minors only." + - name: Enable auto-merge (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr merge --auto --squash "$PR_URL" + - name: Write decision to step summary + env: + ACTION: ${{ steps.policy.outputs.action }} + IS_SECURITY: ${{ steps.policy.outputs.security }} + UPDATE_TYPE: ${{ steps.policy.outputs.update_type }} + GHSA: ${{ steps.policy.outputs.ghsa }} + run: | + { + echo "## Dependabot Auto-Merge Decision" + echo "" + echo "| Field | Value |" + echo "|-------|-------|" + echo "| Policy action | \`$ACTION\` |" + echo "| Security update | \`$IS_SECURITY\` |" + echo "| Update type | \`$UPDATE_TYPE\` |" + echo "| GHSA ID | \`${GHSA:-n/a}\` |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/czech-file-knife/.github/workflows/docker-build.yml b/czech-file-knife/.github/workflows/docker-build.yml new file mode 100644 index 000000000..f795d2eee --- /dev/null +++ b/czech-file-knife/.github/workflows/docker-build.yml @@ -0,0 +1,58 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: container build +on: + pull_request: + paths: + - 'build/container/**' + - 'build/just/container.just' + - '.github/workflows/container-build.yml' + push: + tags: ['v*'] + workflow_dispatch: + +# Scope + cancel superseded runs (estate guardrail). +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + container: + # OFF by default — costs nothing in derived repos. A repo opts in by setting + # the repository/organisation variable CONTAINER_CI=true. When enabled it + # runs on OWNED self-hosted runners (no metered GitHub Actions minutes); + # override the labels with the CONTAINER_RUNNER variable (a JSON array). + if: vars.CONTAINER_CI == 'true' + runs-on: ${{ fromJSON(vars.CONTAINER_RUNNER || '["self-hosted","owned","container"]') }} + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + + - name: Tooling check + run: | + command -v just >/dev/null 2>&1 || { echo "::error::just not found on this runner"; exit 1; } + # The container recipes auto-detect the engine (podman | nerdctl | docker). + for e in podman nerdctl docker; do command -v "$e" >/dev/null 2>&1 && { echo "engine: $e"; break; }; done + + - name: Build image + run: just container-build + + - name: Verify compose configuration + run: just container-verify + + - name: Scan image (trivy, best-effort) + run: | + if command -v trivy >/dev/null 2>&1; then + trivy image --severity HIGH,CRITICAL --exit-code 0 "${{ github.event.repository.name }}:latest" || true + else + echo "trivy not installed on this runner — skipping image scan" + fi + + - name: Sign & verify .ctp bundle (tags only) + if: startsWith(github.ref, 'refs/tags/v') + run: just container-sign # cerro-torre: build + pack + Ed25519 sign + verify diff --git a/czech-file-knife/.github/workflows/dogfood-gate.yml b/czech-file-knife/.github/workflows/dogfood-gate.yml new file mode 100644 index 000000000..6274422af --- /dev/null +++ b/czech-file-knife/.github/workflows/dogfood-gate.yml @@ -0,0 +1,632 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate +# Validates that the repo uses hyperpolymath's own formats and tools. +# Companion to static-analysis-gate.yml (security) — this is for format compliance. +name: Dogfood Gate + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read + +jobs: + # --------------------------------------------------------------------------- + # Job 1: DEED manifest validation + # --------------------------------------------------------------------------- + deed-validate: + name: Validate DEED manifests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for manifest files (.a2ml/.deed) + id: detect + run: | + COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ]; then + echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" + fi + + - name: Validate DEED manifests + if: steps.detect.outputs.count > 0 + uses: hyperpolymath/deed-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + MANIFEST_COUNT="${{ steps.detect.outputs.count }}" + if [ "$MANIFEST_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## DEED Manifest Validation + + :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + + Copy it from [czech-file-knife](https://github.com/hyperpolymath/czech-file-knife). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. + EOF + else + echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 2: K9 contract validation + # --------------------------------------------------------------------------- + k9-validate: + name: Validate K9 contracts + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for K9 files + id: detect + run: | + COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ + -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ + -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) + echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" + echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then + echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." + fi + + - name: Validate K9 contracts + if: steps.detect.outputs.k9_count > 0 + uses: hyperpolymath/k9-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + K9_COUNT="${{ steps.detect.outputs.k9_count }}" + CFG_COUNT="${{ steps.detect.outputs.config_count }}" + if [ "$K9_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## K9 Contract Validation + + :warning: **No K9 contract files found.** Run `k9iser` to generate contracts. + + Generate contracts with: `k9iser generate .` + EOF + else + echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 3: Empty-linter — invisible character detection + # --------------------------------------------------------------------------- + empty-lint: + name: "🔴 GATE: Empty-linter (invisible characters)" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Scan for invisible characters + id: lint + run: | + RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" + BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" + if ! scripts/check-invisible-characters.sh \ + "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then + echo "::error::Invisible-character scanner failed; refusing a partial pass" + exit 2 + fi + + FINDINGS=0 + while IFS= read -r -d '' filepath; do + FINDINGS=$((FINDINGS + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" + done < "$RESULTS_FILE" + + BLOCKING=0 + while IFS= read -r -d '' filepath; do + BLOCKING=$((BLOCKING + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" + done < "$BLOCKING_FILE" + + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" + echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + + if [ "$BLOCKING" -gt 0 ]; then + echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + - name: Write summary + run: | + if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then + FINDINGS="${{ steps.lint.outputs.findings }}" + if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 4: Groove manifest check (for repos that should expose services) + # --------------------------------------------------------------------------- + groove-check: + name: "🟡 CHECK: Groove manifest check" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for Groove manifest + id: groove + run: | + # Check for static or dynamic Groove endpoints + HAS_MANIFEST="false" + HAS_GROOVE_CODE="false" + + # Canonical manifest location is www/.well-known/groove/ (issue #53); + # the repository-root path is accepted, with a warning, during the + # migration window. + MANIFEST="" + if [ -f "www/.well-known/groove/manifest.json" ]; then + MANIFEST="www/.well-known/groove/manifest.json" + elif [ -f ".well-known/groove/manifest.json" ]; then + MANIFEST=".well-known/groove/manifest.json" + echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -n "$MANIFEST" ]; then + HAS_MANIFEST="true" + # Validate the manifest JSON + if ! jq empty "$MANIFEST" 2>/dev/null; then + # Gate, don't annotate: an unparseable manifest is a real error, + # not a warning — same behaviour as the standalone + # groove-check.yml (this job had drifted to annotation-only, + # the class of "check that cannot fail" from nexia-list#49). + echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" + exit 1 + else + SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") + echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" + fi + fi + + # Check for Groove endpoint code (Rust, Elixir, Zig, V) + if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then + HAS_GROOVE_CODE="true" + fi + + # Check if this repo likely serves HTTP in production. Key on + # production HTTP-server framework markers only. A bare `TcpListener` + # is deliberately NOT a signal: it is dominated by test/utility use + # (e.g. a #[cfg(test)] loopback fake), so matching it produced a + # spurious groove nudge on client-only apps. A real hand-rolled Rust + # server still pairs the listener with `axum::serve`/`hyper::Server`, + # which are matched here. + HAS_SERVER="false" + if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + HAS_SERVER="true" + fi + + echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" + echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" + echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" + + if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then + echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." + fi + + - name: Write summary + run: | + echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" + + # --------------------------------------------------------------------------- + # Job 5: eclexiaiser manifest validation + # --------------------------------------------------------------------------- + eclexiaiser-validate: + name: Validate eclexiaiser manifest + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check and validate eclexiaiser manifest + id: eclex + run: | + if [ ! -f "eclexiaiser.toml" ]; then + # Check if repo has a Containerfile — if so, recommend eclexiaiser + if [ -f "Containerfile" ]; then + echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." + fi + echo "has_manifest=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_manifest=true" >> "$GITHUB_OUTPUT" + + # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). + # Structural presence checks only — deep schema validation is eclexiaiser's own job. + err=0 + grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } + grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } + grep -qE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } + if [ "$err" -ne 0 ]; then + exit 1 + fi + fns=$(grep -cE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml) + echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" + + - name: Write summary + run: | + if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" + fi + + # --------------------------------------------------------------------------- + # Job 6: Canon lockstep — does this template still implement the canon? + # + # The canon<->spine binding. hyperpolymath/standards publishes canon.lock: the + # released identity of the law, with a sha256 per law artefact. This repo + # declares the SAME hashes in .machine_readable/rsr-profile.a2ml [canon]. + # + # Before this job, the declaration was two free-text strings + # (`spec = "rsr-criteria-v2"`, `declares-against = "2.0.0-draft"`) and nothing + # failed when the canon changed. This makes it a comparison. + # + # Deliberately a bare `run:` step with NO `uses:`. Adding an action reference + # would have required an actions.lock row, and this repo's lock records + # FLOATING refs ('actions/checkout@v7.0.1') where the canon's records SHAs - + # so a pinned `uses:` here would mismatch the lock and fail at LOAD time with + # `jobs=0` and no annotation. Read-only over the public internet needs no auth. + # --------------------------------------------------------------------------- + canon-lockstep: + name: Canon lockstep + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Fetch the canon's released identity + id: canon + run: | + set -euo pipefail + LOCK="$RUNNER_TEMP/canon.lock" + URL="https://raw.githubusercontent.com/hyperpolymath/standards/main/canon.lock" + if ! curl -fsSL --retry 3 --max-time 30 "$URL" -o "$LOCK"; then + echo "::error::could not fetch canon.lock from $URL" + echo "The canon does not publish a released identity, so this repo" + echo "cannot be shown to implement it. Fix at source: add canon.lock." + exit 1 + fi + # Read (not compute) the criteria hash canon.lock asserts. + # + # awk only JOINS the record — the artefact entries are inline tables + # that span lines — then grep -oE extracts the hash. Deliberately the + # same two-step the canon's own check-canon-lockstep.sh uses, so + # there is one technique across both repos. + # + # Two traps this avoids, both found by running it rather than reading + # it. (1) An earlier version used `gsub(/.*"|"/,"",s)` to strip the + # quotes; the `.*"` is GREEDY, so it ate the entire string and the + # hash came out EMPTY. (2) awk interval expressions are not portable + # across mawk/gawk builds; grep -oE is. + rec="$(awk ' + /^[[:space:]]*#/ { next } + /^criteria[[:space:]]*=/ { on=1 } + on { + line=$0; sub(/#.*/,"",line); rec = rec " " line + if (line ~ /}/) { on=0 } + } + END { print rec }' "$LOCK")" + want="$(printf '%s\n' "$rec" \ + | grep -oE 'sha256[[:space:]]*=[[:space:]]*"[0-9a-f]{64}"' \ + | grep -oE '[0-9a-f]{64}' | head -1)" + if [ -z "$want" ]; then + echo "::error::canon.lock carries no criteria sha256 — malformed, or" + echo "the criteria record no longer starts a line with 'criteria ='." + exit 1 + fi + echo "want=$want" >> "$GITHUB_OUTPUT" + + - name: Compare this repo's declared pin + env: + WANT: ${{ steps.canon.outputs.want }} + # This job has NO `uses:` steps, deliberately: any added action would + # have to be pinned in actions.lock, and `uses ⊆ actions.lock` is already + # failing on main, so this job must not make it worse. That means no + # actions/checkout — and therefore NO WORKING TREE. Which is what + # broke this job: it read "machine-readable/rsr-profile.a2ml" from a + # directory that was never populated, so it failed 100% of the time + # with "no rsr-profile.a2ml", and would have whatever the pin said. + # + # Fetch the profile by SHA instead, exactly as canon.lock is fetched + # above. Deliberately NOT github.sha: on pull_request that is the + # ephemeral MERGE commit, which is not on the remote and would 404. + # The event's head sha is the commit that actually exists there. + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + set -euo pipefail + PROFILE="$RUNNER_TEMP/rsr-profile.a2ml" + fetched="" + for p in ".machine_readable/rsr-profile.a2ml" "machine-readable/rsr-profile.a2ml"; do + URL="https://raw.githubusercontent.com/hyperpolymath/czech-file-knife/${HEAD_SHA}/${p}" + if curl -fsSL --retry 3 --max-time 30 "$URL" -o "$PROFILE" 2>/dev/null; then + fetched="$p"; break + fi + done + if [ -z "$fetched" ]; then + echo "::error::no rsr-profile.a2ml at ${HEAD_SHA:0:7} — this repo cannot declare a canon pin" + echo "Looked for .machine_readable/rsr-profile.a2ml and machine-readable/rsr-profile.a2ml." + exit 1 + fi + echo "profile: $fetched @ ${HEAD_SHA:0:7}" + + # ALL THREE PINS, BOTH SIDES. + # + # This step used to compare only criteria_sha256, while this repo's own + # rsr-profile.a2ml stated that "these values ... equal canon.lock + # [canon.artifacts]" — plural. The claim was wider than the check, so + # `version` and `gates_sha256` could sit at stale values and nothing + # reported it. They did: canon 2.0.1 changed gates_sha256, and this repo + # kept asserting 2.0.0 and the superseded hash. A pin nothing verifies + # is not a binding. See hyperpolymath/standards docs/AUDIT.adoc F8. + # + # Both readers below are the technique the canon's own + # check-canon-lockstep.sh uses, so there is one idiom across both repos. + canon_key() { + awk -v key="$1" ' + /^[[:space:]]*#/ { next } + /^\[/ { f = ($0 == "[canon]") ? 1 : 0; next } + f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { + sub(/^[^=]*=[[:space:]]*/, ""); gsub(/^["[:space:]]+|["[:space:]]+$/, ""); print; exit + }' "$2" + } + # The artefact records are inline tables spanning lines, so awk JOINS the + # record and grep extracts the hash. Deliberately not anchored to the + # sha256 being on any particular line of the record. + artefact_hash() { + awk -v key="$1" ' + $0 ~ "^" key "[[:space:]]*=" { f = 1 } + f { buf = buf " " $0 } + f && /sha256[[:space:]]*=/ { print buf; exit } + ' "$2" | grep -oE '[0-9a-f]{64}' | head -1 + } + + want_crit="$WANT" + want_gates="$(artefact_hash gates "$RUNNER_TEMP/canon.lock")" + want_ver="$(canon_key version "$RUNNER_TEMP/canon.lock")" + got_crit="$(canon_key criteria_sha256 "$PROFILE")" + got_gates="$(canon_key gates_sha256 "$PROFILE")" + got_ver="$(canon_key version "$PROFILE")" + + # An unreadable pin must not pass. Hashing or parsing nothing yields an + # empty string, and `"" = ""` is a green build with nothing behind it — + # the same class of bug as this job reading a file that was never there. + for pair in "want_crit:$want_crit" "want_gates:$want_gates" "want_ver:$want_ver" \ + "got_crit:$got_crit" "got_gates:$got_gates" "got_ver:$got_ver"; do + if [ -z "${pair#*:}" ]; then + echo "::error::could not read ${pair%%:*} — refusing to compare an empty value" + exit 1 + fi + done + + ok=1 + row() { + mark=":x:" + if [ "$2" = "$3" ]; then mark=":white_check_mark:"; else ok=0; fi + printf '| `%s` | `%s` | `%s` | %s |\n' "$1" "${2:0:16}" "${3:0:16}" "$mark" + } + { + echo "## Canon lockstep" + echo "" + echo "| pin | canon.lock asserts | this repo declares | |" + echo "|---|---|---|---|" + row version "$want_ver" "$got_ver" + row criteria_sha256 "$want_crit" "$got_crit" + row gates_sha256 "$want_gates" "$got_gates" + } >> "$GITHUB_STEP_SUMMARY" + if [ "$ok" = "1" ]; then + echo ":white_check_mark: This template implements the canon at HEAD." + exit 0 + fi + echo "::error::canon lockstep broken — the template does not implement the canon at HEAD" + { + echo "" + echo ":x: **Lockstep broken.**" + echo "" + echo "The canon's law changed and this template has not adopted it." + echo "Order matters (\`canon.lock [canon.lockstep].order\` is" + echo "\`spine-adopts-then-canon-releases\`): re-pin \`[canon]\` in" + echo "\`.machine_readable/rsr-profile.a2ml\` to the new hashes and fix any" + echo "criteria this repo now fails, BEFORE the canon release lands." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + # --------------------------------------------------------------------------- + # Job 7: Dogfooding summary + # --------------------------------------------------------------------------- + dogfood-summary: + name: "ℹ️ ADVISORY: Dogfooding compliance summary (non-gating)" + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [deed-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate, canon-lockstep] + if: always() + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Generate dogfooding scorecard + run: | + SCORE=0 + MAX=6 + + # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) + if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + DEED_STATUS=":white_check_mark:" + else + DEED_STATUS=":x:" + fi + + # K9 contracts present? + if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + K9_STATUS=":white_check_mark:" + else + K9_STATUS=":x:" + fi + + # .editorconfig present? + if [ -f ".editorconfig" ]; then + SCORE=$((SCORE + 1)) + EC_STATUS=":white_check_mark:" + else + EC_STATUS=":x:" + fi + + # Groove manifest or code? + if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + GROOVE_STATUS=":white_check_mark:" + else + GROOVE_STATUS=":ballot_box_with_check:" + fi + + # VeriSimDB integration? + if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + VSDB_STATUS=":white_check_mark:" + else + VSDB_STATUS=":ballot_box_with_check:" + fi + + # eclexiaiser energy tracking? + if [ -f "eclexiaiser.toml" ]; then + SCORE=$((SCORE + 1)) + ECLEX_STATUS=":white_check_mark:" + else + ECLEX_STATUS=":ballot_box_with_check:" + fi + + cat <> "$GITHUB_STEP_SUMMARY" + ## Dogfooding Scorecard + + **Score: ${SCORE}/${MAX}** + + | Tool/Format | Status | Notes | + |-------------|--------|-------| + | DEED repo deed (`_chora.deed`) | ${DEED_STATUS} | Required for all RSR repos | + | K9 contracts | ${K9_STATUS} | Required for repos with config files | + | .editorconfig | ${EC_STATUS} | Required for all repos | + | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | + | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | + | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | + + --- + *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/czech-file-knife) workflow.* + *Dogfooding is guinea pig fooding — we test our tools on ourselves.* + EOF + + rsr-score-self: + name: RSR oracle — dogfood this repo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/standards + path: _standards + - uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/hypatia + path: _hypatia + - name: Setup Elixir for the oracle + uses: erlef/setup-beam@v1.24.1 + with: + elixir-version: '1.19.4' + otp-version: '28.3' + - name: Compile the oracle + working-directory: _hypatia + run: mix deps.get && mix compile + - name: Score this repo against the canon + working-directory: _hypatia + run: | + # --ssot is explicit until the hypatia M-3 fix lands (the + # --standards default still points at the pre-reorg path). + mix hypatia.rsr_score .. \ + --ssot ../_standards/0-canon/rsr/rsr-criteria-v2.a2ml \ + --write + - name: Show the scorecard + if: always() + run: | + if [ -f .machine_readable/descriptiles/SCORECARD.a2ml ]; then + cat .machine_readable/descriptiles/SCORECARD.a2ml + else + echo "::warning::no scorecard written — the oracle failed; see the step log" + fi + # Deliberately NO --fail-under: the point is to publish the honest + # scorecard (provisional=true until the coverage worklist is triaged), + # not to gate the spine on itself. Tightens in the PR that lands the + # first coverage tranche. diff --git a/czech-file-knife/.github/workflows/dogfood-summary.yml b/czech-file-knife/.github/workflows/dogfood-summary.yml new file mode 100644 index 000000000..2df5c3dd3 --- /dev/null +++ b/czech-file-knife/.github/workflows/dogfood-summary.yml @@ -0,0 +1,99 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "ℹ️ ADVISORY: Dogfooding Compliance Scorecard" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dogfood-summary: + name: "Dogfooding compliance summary" + runs-on: ubuntu-latest + timeout-minutes: 15 + if: always() + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Generate dogfooding scorecard + run: | + SCORE=0 + MAX=6 + + # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) + if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + DEED_STATUS=":white_check_mark:" + else + DEED_STATUS=":x:" + fi + + # K9 contracts present? + if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + K9_STATUS=":white_check_mark:" + else + K9_STATUS=":x:" + fi + + # .editorconfig present? + if [ -f ".editorconfig" ]; then + SCORE=$((SCORE + 1)) + EC_STATUS=":white_check_mark:" + else + EC_STATUS=":x:" + fi + + # Groove manifest or code? + if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + GROOVE_STATUS=":white_check_mark:" + else + GROOVE_STATUS=":ballot_box_with_check:" + fi + + # VeriSimDB integration? + if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + VSDB_STATUS=":white_check_mark:" + else + VSDB_STATUS=":ballot_box_with_check:" + fi + + # eclexiaiser energy tracking? + if [ -f "eclexiaiser.toml" ]; then + SCORE=$((SCORE + 1)) + ECLEX_STATUS=":white_check_mark:" + else + ECLEX_STATUS=":ballot_box_with_check:" + fi + + cat <> "$GITHUB_STEP_SUMMARY" + ## Dogfooding Scorecard + + **Score: ${SCORE}/${MAX}** + + | Tool/Format | Status | Notes | + |-------------|--------|-------| + | DEED manifest (repo deed `*_chora.deed`) | ${DEED_STATUS} | Required for all RSR repos | + | K9 contracts | ${K9_STATUS} | Required for repos with config files | + | .editorconfig | ${EC_STATUS} | Required for all repos | + | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | + | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | + | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | + + --- + *Generated by the Dogfooding Compliance workflow.* + *Dogfooding is guinea pig fooding — we test our tools on ourselves.* + EOF diff --git a/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml b/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml new file mode 100644 index 000000000..7ce166edf --- /dev/null +++ b/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml @@ -0,0 +1,157 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Well-Known Standards (RFC 9116 + RSR) +on: + push: + branches: [main, master] + paths: + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) + - 'security.txt' + pull_request: + paths: + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) + schedule: + # Weekly expiry check (Mondays 09:00 UTC) + - cron: '0 9 * * 1' + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: RFC 9116 security.txt validation + run: | + SECTXT="" + if [ -f "www/.well-known/security.txt" ]; then + SECTXT="www/.well-known/security.txt" + elif [ -f ".well-known/security.txt" ]; then + SECTXT=".well-known/security.txt" + echo "::warning::security.txt at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + elif [ -f "security.txt" ]; then + SECTXT="security.txt" + echo "::warning::security.txt at repository root — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -z "$SECTXT" ]; then + echo "::error::No security.txt found — required for OpenSSF Best Practices. See https://github.com/hyperpolymath/well-known-ecosystem" + exit 1 + fi + + # Required: Contact + grep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; } + + # Required: Expires + if ! grep -q "^Expires:" "$SECTXT"; then + echo "::error::Missing Expires field" + exit 1 + fi + + # Check expiry + EXPIRES=$(grep "^Expires:" "$SECTXT" | cut -d: -f2- | tr -d ' ' | head -1) + if date -d "$EXPIRES" > /dev/null 2>&1; then + DAYS=$(( ($(date -d "$EXPIRES" +%s) - $(date +%s)) / 86400 )) + if [ $DAYS -lt 0 ]; then + echo "::error::security.txt EXPIRED" + exit 1 + elif [ $DAYS -lt 30 ]; then + echo "::warning::security.txt expires in $DAYS days" + else + echo "✅ security.txt valid ($DAYS days)" + fi + fi + - name: RSR well-known compliance + run: | + MISSING="" + LEGACY="" + for f in security.txt ai.txt humans.txt; do + if [ -f "www/.well-known/$f" ]; then + : + elif [ -f ".well-known/$f" ] || { [ "$f" = security.txt ] && [ -f security.txt ]; }; then + LEGACY="$LEGACY $f" + else + MISSING="$MISSING $f" + fi + done + if [ -n "$LEGACY" ]; then + echo "::warning::legacy .well-known location (canonical is www/.well-known/):$LEGACY — run scripts/migrate-wellknown-to-www.sh" + fi + + if [ -n "$MISSING" ]; then + echo "::warning::Missing RSR recommended files:$MISSING" + echo "Reference: https://github.com/hyperpolymath/well-known-ecosystem/.well-known/" + else + echo "✅ RSR well-known compliant" + fi + # Stage 5 (#119) sweeps ~270 repositories. #106 claimed CI "drives the + # migrator"; until now it only printed advice, so nothing verified that a + # repo would actually survive the migration. This step runs the real + # migrator and turns every conflict into an annotation. + # + # --dry-run because this job holds contents: read and cannot push: the + # point is to surface divergence early in the migration window, not to + # mutate a checkout that will be thrown away. The sweep driver + # (scripts/sweep-wellknown.sh) is what performs the real migration. + - name: Legacy root .well-known/ migration report + run: | + if [ ! -d .well-known ]; then + echo "No root .well-known/ — nothing to migrate." + exit 0 + fi + if [ ! -f scripts/migrate-wellknown-to-www.sh ]; then + echo "::warning::root .well-known/ present but no migrator — template is behind #53 stage 5." + exit 0 + fi + bash scripts/migrate-wellknown-to-www.sh --dry-run --report wellknown-migration.tsv + echo "--- planned migration ---" + cat wellknown-migration.tsv + # Conflicts are warnings, not errors, for the duration of the + # migration window (#106): a repo must not go red for a condition + # the sweep exists to fix. It must not be silent either. + while IFS=$'\t' read -r action path detail; do + case "$action" in + quarantined|conflict|left) + echo "::warning title=well-known migration::$action $path — $detail" + ;; + esac + done < <(tail -n +2 wellknown-migration.tsv) + - name: www bundle self-test + run: | + if [ -d www/tests ]; then + bash www/tests/run-all.sh + else + echo "www/tests absent — site-operations bundle not present; skipping." + fi + - name: Mixed content check + run: | + MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com' | head -5 || true) + if [ -n "$MIXED" ]; then + echo "::error::Mixed content (HTTP in HTML)" + echo "$MIXED" + exit 1 + fi + echo "✅ No mixed content" + - name: DNS security records check + if: hashFiles('CNAME') != '' + run: | + DOMAIN=$(cat CNAME 2>/dev/null | tr -d '\n') + if [ -n "$DOMAIN" ]; then + echo "Checking DNS for $DOMAIN..." + # CAA record + dig +short CAA "$DOMAIN" | grep -q "issue" && echo "✅ CAA record" || echo "::warning::No CAA record" + # DNSSEC + dig +dnssec +short "$DOMAIN" | grep -q "RRSIG" && echo "✅ DNSSEC" || echo "::warning::No DNSSEC" + fi diff --git a/czech-file-knife/.github/workflows/e2e.yml.template b/czech-file-knife/.github/workflows/e2e.yml.template new file mode 100644 index 000000000..984245107 --- /dev/null +++ b/czech-file-knife/.github/workflows/e2e.yml.template @@ -0,0 +1,224 @@ +# ⚠ THIS FILE IS A SCAFFOLD, NOT A WORKFLOW. +# +# It is named .yml.template deliberately: GitHub Actions reads only *.yml and +# *.yaml under .github/workflows/, so this file is ignored until someone +# instantiates it. +# +# It used to be called e2e.yml, and because every job block below is commented +# out, `jobs:` parsed as null — an invalid workflow. It therefore FAILED on +# every run, in every repository minted from this template, without ever +# executing a single check. 29 repos inherited that. +# +# A scaffold that is invalid until edited WILL be merged unedited. Naming it +# so the platform ignores it removes the failure without removing the guidance. +# +# TO USE: copy to .github/workflows/e2e.yml, uncomment the block matching your +# stack, delete the rest, and confirm it can FAIL before treating it as a gate +# (standards docs/language-testing-standards.md R10). +# +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard E2E + Aspect + Benchmark Workflow Template +# +# Covers ALL merge requirement test categories: +# - E2E (end-to-end pipeline tests) +# - Aspect (cross-cutting concern validation) +# - Benchmarks (performance regression detection) +# - Readiness (Component Readiness Grade: D/C/B) +# +# INSTRUCTIONS: Uncomment and customise the section matching your stack. +# Delete sections that don't apply. See examples in each job. + +name: E2E + Aspect + Bench +on: + push: + branches: [main, master, develop] + paths: + - 'src/**' + - 'src/interface/ffi/**' + - 'tests/**' + - '.github/workflows/e2e.yml' + pull_request: + branches: [main, master] + paths: + - 'src/**' + - 'src/interface/ffi/**' + - 'tests/**' + workflow_dispatch: +permissions: read-all +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true +jobs: + # ─── Default: template smoke (always runs — not a silent no-op) ───── + # Runs the template's own E2E harness (tests/e2e.sh). On the bare template + # this is a clean pass (no stack-specific checks enabled yet); downstream + # repos either extend tests/e2e.sh or uncomment a stack block below. + e2e: + name: E2E — template smoke + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + - name: Run E2E harness + run: | + if [ -f tests/e2e.sh ]; then + bash tests/e2e.sh + else + echo "::notice::no tests/e2e.sh present — nothing to run" + fi + +# ─── End-to-End Tests (downstream stack-specific examples) ───────── +# Uncomment ONE of the following e2e job blocks matching your stack. + +## === RUST E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo build --release +# - run: bash tests/e2e.sh +# # OR: cargo test --test end_to_end -- --nocapture + +## === ZIG FFI E2E === +# e2e: +# name: E2E — FFI Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 +# with: +# version: 0.15.1 +# - run: cd ffi/zig && zig build test +# - run: bash tests/e2e.sh + +## === ELIXIR E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 +# with: +# otp-version: '27.0' +# elixir-version: '1.17' +# - run: mix deps.get && mix compile --warnings-as-errors +# - run: mix test test/integration/e2e_test.exs --trace + +## === DENO/ E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 +# with: +# deno-version: v2.x +# - run: deno install --node-modules-dir=auto +# - run: deno task res:build # ReScript compile +# - run: deno test tests/e2e/ + +## === PLAYWRIGHT (Browser E2E) === +# e2e-playwright: +# name: Playwright — ${{ matrix.project }} +# runs-on: ubuntu-latest +# timeout-minutes: 20 +# strategy: +# fail-fast: false +# matrix: +# project: [chromium-1080p, firefox-1080p, webkit-1080p] +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 +# with: +# deno-version: v2.x +# - run: deno install --node-modules-dir=auto +# - run: npx playwright install --with-deps +# - run: npx playwright test --project=${{ matrix.project }} +# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# if: failure() +# with: +# name: playwright-traces-${{ matrix.project }} +# path: test-results/**/trace.zip +# retention-days: 7 + +## === HASKELL E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0 +# with: +# ghc-version: '9.6' +# cabal-version: '3.10' +# - run: cabal build all +# - run: bash tests/integration-test.sh + +# ─── Aspect Tests ────────────────────────────────────────────────── +# Cross-cutting concerns: thread safety, ABI contracts, SPDX, dangerous patterns +# Uncomment and customise: + +# aspect-tests: +# name: Aspect — Architectural Invariants +# runs-on: ubuntu-latest +# timeout-minutes: 10 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - run: bash tests/aspect_tests.sh + +# ─── Benchmarks ──────────────────────────────────────────────────── +# Performance regression detection. Uncomment matching stack: + +## === RUST BENCH === +# benchmarks: +# name: Bench — Performance Regression +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo bench 2>&1 | tee /tmp/bench-results.txt +# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# if: always() +# with: +# name: benchmark-results +# path: /tmp/bench-results.txt +# retention-days: 30 + +## === ZIG BENCH === +# benchmarks: +# name: Bench — Performance Regression +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 +# with: +# version: 0.15.1 +# - run: cd ffi/zig && zig build bench + +# ─── Readiness (CRG) ────────────────────────────────────────────── +# Component Readiness Grade: D (runs) → C (correct) → B (edge cases) + +# readiness: +# name: Readiness — Grade D/C/B +# runs-on: ubuntu-latest +# timeout-minutes: 10 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable +# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 +# - run: cargo test --test readiness -- --nocapture diff --git a/czech-file-knife/.github/workflows/eclexiaiser-validate.yml b/czech-file-knife/.github/workflows/eclexiaiser-validate.yml new file mode 100644 index 000000000..aae748161 --- /dev/null +++ b/czech-file-knife/.github/workflows/eclexiaiser-validate.yml @@ -0,0 +1,64 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: Eclexiaiser Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + eclexiaiser-validate: + name: "🟡 CHECK: Validate eclexiaiser manifest" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check and validate eclexiaiser manifest + id: eclex + run: | + if [ ! -f "eclexiaiser.toml" ]; then + # Check if repo has a Containerfile — if so, recommend eclexiaiser + if [ -f "Containerfile" ]; then + echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." + fi + echo "has_manifest=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_manifest=true" >> "$GITHUB_OUTPUT" + + # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). + # Structural presence checks only — deep schema validation is eclexiaiser's own job. + err=0 + grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } + grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } + grep -qE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } + if [ "$err" -ne 0 ]; then + exit 1 + fi + fns=$(grep -cE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml) + echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" + + - name: "Write summary" + run: | + if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/czech-file-knife/.github/workflows/empty-linter.yml b/czech-file-knife/.github/workflows/empty-linter.yml new file mode 100644 index 000000000..7bdf23715 --- /dev/null +++ b/czech-file-knife/.github/workflows/empty-linter.yml @@ -0,0 +1,89 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🔴 GATE: Invisible Character Detection" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + empty-lint: + name: "🔴 GATE: Empty-linter (invisible characters)" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Scan for invisible characters + id: lint + run: | + RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" + BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" + if ! scripts/check-invisible-characters.sh \ + "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then + echo "::error::Invisible-character scanner failed; refusing a partial pass" + exit 2 + fi + + FINDINGS=0 + while IFS= read -r -d '' filepath; do + FINDINGS=$((FINDINGS + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" + done < "$RESULTS_FILE" + + BLOCKING=0 + while IFS= read -r -d '' filepath; do + BLOCKING=$((BLOCKING + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" + done < "$BLOCKING_FILE" + + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" + echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + + if [ "$BLOCKING" -gt 0 ]; then + echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + - name: "Write summary" + run: | + if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then + FINDINGS="${{ steps.lint.outputs.findings }}" + if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/czech-file-knife/.github/workflows/estate-rules.yml b/czech-file-knife/.github/workflows/estate-rules.yml new file mode 100644 index 000000000..2cd65682c --- /dev/null +++ b/czech-file-knife/.github/workflows/estate-rules.yml @@ -0,0 +1,97 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Estate Rules — enforces hyperpolymath estate-wide conventions: +# * root shape (allowlist of permitted root entries) +# * AsciiDoc-by-default (no .md files under docs/) +# * AsciiDoc that renders (every tracked .adoc parses with no diagnostics) +# * V-lang is banned (no V-lang scaffolding or references; Zig is the FFI default) +# +# Each rule is enforced by an executable check script under scripts/. Failures +# are surfaced as workflow errors so the rule can't silently regress. + +name: Estate Rules +on: + push: + branches: [main] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + estate-rules: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + # Prove the instrument works before trusting its reading. This gate was + # one-directional for months, which is indistinguishable from a working + # gate until you try to make it fail. + - name: Root-shape gate self-test (must fail on drift) + run: bash tests/shape/check_root_shape_test.sh + - name: Root shape allowlist + run: bash scripts/check-root-shape.sh . + # The map is generated. Five hand-written predecessors all rotted because + # nothing diffed them. + # The freshness check below is only meaningful if the generator is + # environment-independent. It wasn't: sort is locale-dependent, so the + # map generated differently in CI than locally. Vary the locale first. + - name: Repository map generates identically across locales + run: bash tests/shape/repo_map_determinism_test.sh + - name: Repository map is not stale + run: | + cp docs/architecture/REPOSITORY-MAP.adoc /tmp/map.before + bash scripts/gen-repo-map.sh . + diff -u /tmp/map.before docs/architecture/REPOSITORY-MAP.adoc \ + || { echo "::error::REPOSITORY-MAP.adoc is stale - run 'just repo-map'"; exit 1; } + - name: AsciiDoc by default (no .md under docs/) + run: bash scripts/check-no-md-in-docs.sh . + # The rule above checks the file EXTENSION only, so a Markdown body + # renamed to .adoc passes it. This one checks the file actually parses. + # asciidoctor's --failure-level defaults to FATAL, so a document emitting + # WARNING or ERROR still exits 0; the version is pinned because the + # verdict rests on a stderr comparison. + - name: Install asciidoctor + run: sudo gem install asciidoctor -v 2.0.26 --no-document + - name: AsciiDoc render gate self-test (must catch what bare asciidoctor misses) + run: bash tests/workflows/check_adoc_renders_test.sh + - name: Every tracked .adoc renders clean + run: bash scripts/check-adoc-renders.sh . + - name: No V-language references + run: bash scripts/check-no-vlang.sh . + # Was written but wired to nothing, so it had never run in CI. + - name: V-language gate self-test + run: bash tests/workflows/check_no_vlang_test.sh + - name: Install verified Nickel 1.17.0 + run: | + mkdir -p "$RUNNER_TEMP/nickel-bin" + curl --proto '=https' --proto-redir '=https' --fail --silent --show-error --location \ + https://github.com/nickel-lang/nickel/releases/download/1.17.0/nickel-x86_64-linux \ + --output "$RUNNER_TEMP/nickel-bin/nickel" + echo "afcdfa6e0fff31760cf229e85997456c02c00b8b3b84ff38f897ac7b3f39ae34 $RUNNER_TEMP/nickel-bin/nickel" | sha256sum --check --strict + chmod +x "$RUNNER_TEMP/nickel-bin/nickel" + echo "$RUNNER_TEMP/nickel-bin" >> "$GITHUB_PATH" + - name: Evaluate session contracts + run: bash scripts/validate-session-contracts.sh + - name: Check Nickel typecheck controls + run: bash tests/workflows/k9_typecheck_test.sh + - name: Verify session envelope controls + run: bash tests/workflows/session_contracts_test.sh + - name: Verify mint cleanup controls + run: bash tests/workflows/mint_cleanup_test.sh + # The CHECK half of the template contract. repo-init has always written an + # answer-file (.machine_readable/PROVENANCE.a2ml); nothing ever read it. + # This is the missing reader, and it covers the #200/#201/#203 class: + # self-parenting identity, unresolved pins, and leaked template branches. + # + # The template itself self-skips (archetypes/ present), so the real check + # runs in minted children. What THIS proves is that the instrument can + # fail — the only property that matters for a gate, and the one that was + # missing in #49, #64 and the conformance gate that "reported them + # conforming" upstream. + - name: Template conformance gate self-test (must fail on drift) + run: bash tests/workflows/template_conformance_test.sh diff --git a/czech-file-knife/.github/workflows/ghcr-publish.yml b/czech-file-knife/.github/workflows/ghcr-publish.yml deleted file mode 100644 index 3cdd024ac..000000000 --- a/czech-file-knife/.github/workflows/ghcr-publish.yml +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Publish to GHCR -permissions: read-all -on: - release: - types: [published] - workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} -jobs: - build-and-push: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - id-token: write - attestations: write - steps: - - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - - name: Install nerdctl and containerd - run: | - # Install containerd - sudo apt-get update - sudo apt-get install -y containerd - sudo systemctl start containerd - - # Install nerdctl full (includes containerd, CNI plugins, buildkit) - NERDCTL_VERSION=2.2.1 - curl -fsSL "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION}-linux-amd64.tar.gz" -o /tmp/nerdctl.tar.gz - sudo tar -xzf /tmp/nerdctl.tar.gz -C /usr/local - sudo mkdir -p /opt/cni/bin - sudo cp /usr/local/libexec/cni/* /opt/cni/bin/ 2>/dev/null || true - - # Start buildkitd daemon - sudo /usr/local/bin/buildkitd & - sleep 3 - - name: Log in to GHCR - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | sudo nerdctl login ghcr.io -u ${{ github.actor }} --password-stdin - - name: Build image - run: | - sudo nerdctl build -f Containerfile -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} . - sudo nerdctl tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - name: Push image - run: | - sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} - sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - name: Tag release version - if: github.event_name == 'release' - run: | - VERSION=${{ github.event.release.tag_name }} - sudo nerdctl tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION} - sudo nerdctl push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION} diff --git a/czech-file-knife/.github/workflows/governance.yml b/czech-file-knife/.github/workflows/governance.yml index 2d0fcb2df..73366eae5 100644 --- a/czech-file-knife/.github/workflows/governance.yml +++ b/czech-file-knife/.github/workflows/governance.yml @@ -1,23 +1,21 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# governance.yml — single wrapper calling the shared estate governance bundle -# in hyperpolymath/standards instead of carrying per-repo copies. -# -# Replaces the per-repo governance scaffolding removed in the same commit: -# quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml, -# security-policy.yml, rsr-antipattern.yml, wellknown-enforcement.yml, -# workflow-linter.yml -# -# Load-bearing build/security workflows stay standalone in the repo -# (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing). - name: Governance + on: push: branches: [main, master] pull_request: + branches: [main, master] workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: + actions: read # required by the reusable workflow (staleness check reads workflow runs) contents: read + jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@210f14e753c80064ec1bcae72f1d654dd9b0e687 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/groove-check.yml b/czech-file-knife/.github/workflows/groove-check.yml new file mode 100644 index 000000000..f0e8785bb --- /dev/null +++ b/czech-file-knife/.github/workflows/groove-check.yml @@ -0,0 +1,91 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: Groove Protocol Compliance" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + groove-check: + name: "🟡 CHECK: Groove manifest check" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for Groove manifest + id: groove + run: | + # Check for static or dynamic Groove endpoints + HAS_MANIFEST="false" + HAS_GROOVE_CODE="false" + + # Canonical manifest location is www/.well-known/groove/ (issue #53); + # the repository-root path is accepted, with a warning, during the + # migration window. + MANIFEST="" + if [ -f "www/.well-known/groove/manifest.json" ]; then + MANIFEST="www/.well-known/groove/manifest.json" + elif [ -f ".well-known/groove/manifest.json" ]; then + MANIFEST=".well-known/groove/manifest.json" + echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -n "$MANIFEST" ]; then + HAS_MANIFEST="true" + # Validate the manifest JSON + if ! jq empty "$MANIFEST" 2>/dev/null; then + echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" + exit 1 + else + SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") + echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" + fi + fi + + # Check for Groove endpoint code (Rust, Elixir, Zig, V) + if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then + HAS_GROOVE_CODE="true" + fi + + # Check if this repo likely serves HTTP in production. Key on + # production HTTP-server framework markers only. A bare `TcpListener` + # is deliberately NOT a signal: it is dominated by test/utility use + # (e.g. a #[cfg(test)] loopback fake), so matching it produced a + # spurious groove nudge on client-only apps. A real hand-rolled Rust + # server still pairs the listener with `axum::serve`/`hyper::Server`, + # which are matched here. + HAS_SERVER="false" + if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + HAS_SERVER="true" + fi + + echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" + echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" + echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" + + if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then + echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." + fi + + - name: "Write summary" + run: | + echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" diff --git a/czech-file-knife/.github/workflows/guix-policy.yml b/czech-file-knife/.github/workflows/guix-policy.yml new file mode 100644 index 000000000..6363018b0 --- /dev/null +++ b/czech-file-knife/.github/workflows/guix-policy.yml @@ -0,0 +1,49 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Guix Package Policy +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Enforce Guix-only package policy + run: | + # Guix is the sole package manager estate-wide. Nix is BANNED. + HAS_GUIX=$(find . -path ./.git -prune -o \( -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" \) -print 2>/dev/null | head -1) + HAS_NIX=$(find . -path ./.git -prune -o -name "*.nix" -print 2>/dev/null | head -1) + + # Hard-fail on any Nix file — Nix is banned, migrate to Guix. + if [ -n "$HAS_NIX" ]; then + echo "::error::Nix is banned estate-wide (Guix only). Remove flake.nix/*.nix and use guix.scm: $HAS_NIX" + exit 1 + fi + + # Warn on non-reproducible lock files; prefer Guix manifests. + NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) + if [ -n "$NEW_LOCKS" ]; then + echo "⚠️ Lock files detected. Prefer Guix manifests for reproducibility." + fi + + if [ -n "$HAS_GUIX" ]; then + echo "✅ Guix package management detected" + else + echo "ℹ️ Consider adding guix.scm / .guix-channel for reproducible builds" + fi + + echo "✅ Guix package policy check passed" diff --git a/czech-file-knife/.github/workflows/hypatia-scan.yml b/czech-file-knife/.github/workflows/hypatia-scan.yml index 5db7732bc..997e008b8 100644 --- a/czech-file-knife/.github/workflows/hypatia-scan.yml +++ b/czech-file-knife/.github/workflows/hypatia-scan.yml @@ -1,125 +1,30 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# Hypatia Neurosymbolic CI/CD Security Scan +# +# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate +# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to +# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this +# workflow is the repo's independent security scan. Same tool, two consumers. +# See .github/workflows/README.adoc. name: Hypatia Security Scan + on: push: branches: [main, master, develop] pull_request: branches: [main, master] schedule: - - cron: '0 0 * * 0' # Weekly on Sunday + - cron: '0 0 * * 0' workflow_dispatch: -permissions: read-all -jobs: - scan: - name: Hypatia Neurosymbolic Analysis - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - with: - fetch-depth: 0 # Full history for better pattern analysis - - name: Setup Elixir for Hypatia scanner - uses: erlef/setup-beam@2f0cc07b4b9bea248ae098aba9e1a8a1de5ec24c # v1.18.2 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - name: Clone Hypatia - run: | - if [ ! -d "$HOME/hypatia" ]; then - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" - fi - - name: Build Hypatia scanner (if needed) - working-directory: ${{ env.HOME }}/hypatia - run: | - if [ ! -f hypatia-v2 ]; then - echo "Building hypatia-v2 scanner..." - cd scanner - mix deps.get - mix escript.build - mv hypatia ../hypatia-v2 - fi - - name: Run Hypatia scan - id: scan - run: | - echo "Scanning repository: ${{ github.repository }}" - - # Run scanner - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json - - # Count findings - FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) - echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT - - # Extract severity counts - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json) - - echo "critical=$CRITICAL" >> $GITHUB_OUTPUT - echo "high=$HIGH" >> $GITHUB_OUTPUT - echo "medium=$MEDIUM" >> $GITHUB_OUTPUT - - echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY - echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY - echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY - echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY - - name: Upload findings artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: hypatia-findings - path: hypatia-findings.json - retention-days: 90 - - name: Submit findings to gitbot-fleet (Phase 2) - if: steps.scan.outputs.findings_count > 0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SHA: ${{ github.sha }} - run: "echo \"\U0001F4E4 Submitting ${{ steps.scan.outputs.findings_count }} findings to gitbot-fleet...\"\n\n# Clone gitbot-fleet to temp directory\nFLEET_DIR=\"/tmp/gitbot-fleet-$$\"\ngit clone https://github.com/hyperpolymath/gitbot-fleet.git \"$FLEET_DIR\"\n\n# Run submission script\nbash \"$FLEET_DIR/scripts/submit-finding.sh\" hypatia-findings.json\n\n# Cleanup\nrm -rf \"$FLEET_DIR\"\n\necho \"✅ Finding submission complete\"\n" - - name: Check for critical issues - if: steps.scan.outputs.critical > 0 - run: | - echo "⚠️ Critical security issues found!" - echo "Review hypatia-findings.json for details" - # Don't fail the build yet - just warn - # exit 1 - - name: Generate scan report - run: | - cat << EOF > hypatia-report.md - # Hypatia Security Scan Report - - **Repository:** ${{ github.repository }} - **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - **Commit:** ${{ github.sha }} - ## Summary +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + actions: read # required by the reusable workflow (staleness check reads workflow runs) + contents: read + security-events: write - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.scan.outputs.critical }} | - | High | ${{ steps.scan.outputs.high }} | - | Medium | ${{ steps.scan.outputs.medium }} | - | **Total**| ${{ steps.scan.outputs.findings_count }} | - - ## Next Steps - - 1. Review findings in the artifact: hypatia-findings.json - 2. Auto-fixable issues will be addressed by robot-repo-automaton (Phase 3) - 3. Manual review required for complex issues - - ## Learning - - These findings feed Hypatia's learning engine to improve future rules. - - --- - *Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence* - EOF - - cat hypatia-report.md >> $GITHUB_STEP_SUMMARY - - name: Comment on PR with findings - if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - script: "const fs = require('fs');\nconst findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));\n\nconst critical = findings.filter(f => f.severity === 'critical').length;\nconst high = findings.filter(f => f.severity === 'high').length;\n\nlet comment = `## \U0001F50D Hypatia Security Scan\\n\\n`;\ncomment += `**Findings:** ${findings.length} issues detected\\n\\n`;\ncomment += `| Severity | Count |\\n|----------|-------|\\n`;\ncomment += `| \U0001F534 Critical | ${critical} |\\n`;\ncomment += `| \U0001F7E0 High | ${high} |\\n`;\ncomment += `| \U0001F7E1 Medium | ${findings.length - critical - high} |\\n\\n`;\n\nif (critical > 0) {\n comment += `⚠️ **Action Required:** Critical security issues found!\\n\\n`;\n}\n\ncomment += `
View findings\\n\\n`;\ncomment += `\\`\\`\\`json\\n${JSON.stringify(findings.slice(0, 10), null, 2)}\\n\\`\\`\\`\\n`;\ncomment += `
\\n\\n`;\ncomment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;\n\ngithub.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: comment\n});\n" +jobs: + hypatia: + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/instant-sync.yml b/czech-file-knife/.github/workflows/instant-sync.yml deleted file mode 100644 index f9db7f45c..000000000 --- a/czech-file-knife/.github/workflows/instant-sync.yml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Instant Forge Sync - Triggers propagation to all forges on push/release -name: Instant Sync -on: - push: - branches: [main, master] - release: - types: [published] -permissions: - contents: read -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Trigger Propagation - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 - with: - token: ${{ secrets.FARM_DISPATCH_TOKEN }} - repository: hyperpolymath/.git-private-farm - event-type: propagate - client-payload: |- - { - "repo": "${{ github.event.repository.name }}", - "ref": "${{ github.ref }}", - "sha": "${{ github.sha }}", - "forges": "" - } - - name: Confirm - run: echo "::notice::Propagation triggered for ${{ github.event.repository.name }}" diff --git a/czech-file-knife/.github/workflows/k9-validate.yml b/czech-file-knife/.github/workflows/k9-validate.yml new file mode 100644 index 000000000..548c3bbfb --- /dev/null +++ b/czech-file-knife/.github/workflows/k9-validate.yml @@ -0,0 +1,64 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: K9 Contract Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + k9-validate: + name: "🟡 CHECK: Validate K9 contracts" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for K9 files + id: detect + run: | + COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ + -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ + -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) + echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" + echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then + echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." + fi + + - name: "🟡 CHECK: Validate K9 contracts" + if: steps.detect.outputs.k9_count > 0 + uses: hyperpolymath/k9-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + K9_COUNT="${{ steps.detect.outputs.k9_count }}" + CFG_COUNT="${{ steps.detect.outputs.config_count }}" + if [ "$K9_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## K9 Contract Validation + + :warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts. + + Generate contracts with: `k9iser generate .` + EOF + else + echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/czech-file-knife/.github/workflows/label-triage.yml b/czech-file-knife/.github/workflows/label-triage.yml new file mode 100644 index 000000000..b7522268c --- /dev/null +++ b/czech-file-knife/.github/workflows/label-triage.yml @@ -0,0 +1,118 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Label Triage + +# Classify newly-filed issues against the estate label taxonomy. +# +# The sweep that established the taxonomy is a one-off; this is what stops it +# decaying. Without it every new issue arrives unlabelled and the 55%-unlabelled +# state rebuilds itself. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces +# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow +# the lock does not list is rejected before any step runs (startup_failure, and +# therefore no check run at all). A dispatched workflow lands in repos whose +# lock has not been regenerated, so it must not depend on any action. +# +# ⚠ THE CLASSIFIER IS jq, NOT PYTHON. Python is fully banned estate-wide -- the +# `governance / Language / package anti-pattern policy` gate runs +# `git ls-files '*.py'` and fails the PR. Shipping a .py into 416 repos would +# mean shipping an exemption into 416 repos. jq is preinstalled on every GitHub +# runner, is not banned, and needs no action. +# +# Deliberately conservative: +# - ADDITIVE ONLY. It never removes a label and never overrides a human's +# classification: anything already on the issue is passed in via `have` and +# is never re-suggested, and the classifier stays out of any max-1 tier the +# issue already carries a label in. +# - SILENT WHEN UNSURE. Nothing is printed unless a prefix, bracket or type +# rule actually fired. Roughly 70% of the historical corpus classified this +# way; the rest is meant to reach a human. +# - NEVER FAILS THE ISSUE. Every step is best-effort; a missing payload or an +# API hiccup exits 0 rather than leaving a red mark on someone's bug report. + +on: + issues: + types: [opened, reopened] + workflow_dispatch: + inputs: + issue: + description: "Issue number to (re)classify" + required: true + +permissions: + issues: write + contents: read + +jobs: + triage: + runs-on: ubuntu-latest + timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #68) + steps: + - name: Classify and label + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NUM: ${{ github.event.issue.number || inputs.issue }} + run: | + set -uo pipefail + work=$(mktemp -d); RULES=$work/rules.json; SCRIPT=$work/classify.jq + + # fetch instead of checking out -- no action means no lock entry to drift + gh api "repos/$GITHUB_REPOSITORY/contents/.github/label-classifier.json?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$RULES" || true + gh api "repos/$GITHUB_REPOSITORY/contents/.github/scripts/classify-issue.jq?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$SCRIPT" || true + if [[ ! -s "$RULES" || ! -s "$SCRIPT" ]]; then + echo "no classifier payload in this repo - nothing to do" + exit 0 + fi + + TITLE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" --json title --jq .title) || exit 0 + echo "issue #$NUM: $TITLE" + + # Labels this repo actually defines. --limit 1000 is GitHub's real + # per-repo ceiling; the default of 30 would silently hide most of the + # taxonomy. Fetched BEFORE the label read below so that read stays as + # close to the write as possible. + mapfile -t DEFINED < <(gh label list -R "$GITHUB_REPOSITORY" --limit 1000 \ + --json name --jq '.[].name' 2>/dev/null) + + # Labels already present; a human's work is never overridden. Read + # HERE rather than earlier: every API call between this read and the + # edit below widens a window in which someone could add a type label + # and get a second one back from us. Only the local jq call is inside it. + HAVE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" \ + --json labels --jq '[.labels[].name]' 2>/dev/null) || HAVE='[]' + [[ -n "$HAVE" ]] || HAVE='[]' + echo "already has: $HAVE" + + mapfile -t ADD < <(jq -r --arg title "$TITLE" --argjson have "$HAVE" \ + -f "$SCRIPT" "$RULES" 2>/dev/null) + if [[ ${#ADD[@]} -eq 0 || -z "${ADD[0]:-}" ]]; then + echo "no confident classification - leaving for a human" + exit 0 + fi + + apply=() + for want in "${ADD[@]}"; do + for def in "${DEFINED[@]}"; do + if [[ "$want" == "$def" ]]; then apply+=("$want"); break; fi + done + done + if [[ ${#apply[@]} -eq 0 ]]; then + echo "classified as ${ADD[*]} but this repo defines none of them - run the label sync" + exit 0 + fi + + printf 'applying: %s\n' "${apply[*]}" + # Build the arguments as an ARRAY. The previous form was an unquoted + # command substitution, so the shell re-split its output on spaces and + # a label name containing whitespace would arrive as several broken + # arguments. No canonical label contains a space today, which is + # exactly why this would have failed quietly the first time one did. + # (Also clears actionlint SC2046.) + edit_args=() + for lab in "${apply[@]}"; do edit_args+=(--add-label "$lab"); done + gh issue edit "$NUM" -R "$GITHUB_REPOSITORY" "${edit_args[@]}" \ + || echo "label apply failed - not failing the run" + exit 0 diff --git a/czech-file-knife/.github/workflows/labels.yml b/czech-file-knife/.github/workflows/labels.yml new file mode 100644 index 000000000..31618e4b7 --- /dev/null +++ b/czech-file-knife/.github/workflows/labels.yml @@ -0,0 +1,107 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Labels + +# Applies the canonical estate label set from .github/labels.json. +# +# Additive and idempotent by design: it CREATES missing labels and UPDATES +# colour/description drift. It never deletes, and it never touches a label in +# the `frozen` list -- those are applied by Dependabot / PR automation, or are +# wired into triage.yml's exempt-issue-labels, and renaming them breaks things. +# +# jq is preinstalled on GitHub runners; PyYAML is not, which is why the payload +# is JSON rather than YAML. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces +# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow +# the lock does not list is rejected before any step runs (startup_failure, and +# therefore no check run at all). A dispatched workflow lands in repos whose +# lock has not been regenerated, so it must not depend on any action. + +on: + workflow_dispatch: + push: + paths: + - '.github/labels.json' + schedule: + - cron: "23 4 1 * *" # monthly drift repair + +permissions: + issues: write + contents: read + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #69) + steps: + - name: Apply canonical labels + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ⚠ LOAD-BEARING. This workflow deliberately does not check the repo + # out (no `uses:`, so no actions.lock entry can drift), which means + # `gh label create` / `gh label edit` have no git remote to infer a + # target from. Without GH_REPO every mutation fails, and because the + # errors used to be discarded the step still exited 0 reporting + # "created=0 updated=0" -- a silent, estate-wide no-op. + GH_REPO: ${{ github.repository }} + run: | + set -uo pipefail + work=$(mktemp -d); PAYLOAD=$work/labels.json + + # fetch instead of checking out -- no action means no lock entry to drift + gh api "repos/$GITHUB_REPOSITORY/contents/.github/labels.json?ref=$GITHUB_SHA" \ + --jq '.content' 2>/dev/null | base64 -d > "$PAYLOAD" || true + [ -s "$PAYLOAD" ] || { echo "no .github/labels.json - nothing to do"; exit 0; } + + mapfile -t FROZEN < <(jq -r '.frozen[]' "$PAYLOAD") + created=0; updated=0; skipped=0; failed=0 + + existing=$(gh api "repos/$GITHUB_REPOSITORY/labels" --paginate \ + --jq '.[] | [.name, .color, (.description // "")] | @tsv') + + while IFS=$'\t' read -r name color desc; do + [ -z "$name" ] && continue + frozen=0 + for f in "${FROZEN[@]}"; do [ "$f" = "$name" ] && frozen=1 && break; done + + cur=$(printf '%s\n' "$existing" | awk -F'\t' -v n="$name" '$1==n{print;exit}') + if [ -z "$cur" ]; then + # A MISSING label is created even when frozen. "Frozen" protects a + # label's DEFINITION from being renamed or recoloured -- it was + # never meant to stop the label existing. Skipping creation broke + # `security`, the one canonical label that is also frozen: it was + # absent from 10 of 12 sampled repos, and label-triage drops any + # label the repo does not define, so every `security` finding was + # silently discarded estate-wide. + if err=$(gh label create "$name" --color "$color" \ + --description "$desc" 2>&1 >/dev/null); then + created=$((created+1)); sleep 0.4 + else + echo " create failed: $name -- ${err:-unknown}"; failed=$((failed+1)) + fi + else + # Present AND frozen: leave it exactly as it is. + if [ "$frozen" -eq 1 ]; then skipped=$((skipped+1)); continue; fi + ccol=$(cut -f2 <<<"$cur"); cdesc=$(cut -f3- <<<"$cur") + if [ "${ccol,,}" != "${color,,}" ] || [ "$cdesc" != "$desc" ]; then + if err=$(gh label edit "$name" --color "$color" \ + --description "$desc" 2>&1 >/dev/null); then + updated=$((updated+1)); sleep 0.4 + else + echo " edit failed: $name -- ${err:-unknown}"; failed=$((failed+1)) + fi + fi + fi + done < <(jq -r '.labels[] | [.name, .color, .description] | @tsv' "$PAYLOAD") + + echo "created=$created updated=$updated frozen-skipped=$skipped failed=$failed" + + # Fail ONLY on the misconfiguration shape: work was attempted, every + # attempt failed. That is the silent-no-op signature. A single flaky + # label must not turn the whole estate's CI red. + if [ "$failed" -gt 0 ] && [ "$((created + updated))" -eq 0 ]; then + echo "every label mutation failed - the sync did nothing. Check GH_REPO and token scope." + exit 1 + fi + exit 0 diff --git a/czech-file-knife/.github/workflows/lock-sync-gate.yml b/czech-file-knife/.github/workflows/lock-sync-gate.yml new file mode 100644 index 000000000..db046cb76 --- /dev/null +++ b/czech-file-knife/.github/workflows/lock-sync-gate.yml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Lock Sync Gate + +# Fails any pull request whose .github/workflows/actions.lock has drifted from +# the workflow YAML. That drift is not cosmetic: GitHub refuses such a run at +# startup, creating ZERO jobs, and reports only "This run likely failed because +# of a workflow file issue." A single grouped Dependabot bump can take out most +# of a repository's CI that way, because Dependabot rewrites `uses:` refs in the +# YAML and cannot touch the lockfile. Measured across 200 repositories on +# 2026-09-22: 39 had silently dead CI from exactly this cause. +# See hyperpolymath/standards#968. +# +# This workflow deliberately carries NO `uses:` of its own. It checks out by +# calling git in a `run:` step instead of using actions/checkout, so it has no +# lockfile entry to go stale and is structurally immune to the very failure it +# detects. Do not add a `uses:` to this file. +# +# There is also no `paths:` filter, on purpose: a filtered workflow never +# reports on pull requests that miss the filter, which deadlocks any branch +# ruleset that requires this check. + +on: + workflow_dispatch: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: lock-sync-gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + lock-sync: + name: actions.lock is in sync with the workflow YAML + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out without actions/checkout + env: + REPO: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + TOKEN: ${{ github.token }} + run: | + set -euo pipefail + # Authenticate the fetch. An anonymous clone works only for public + # repositories; this gate must also run on private ones. The header + # form is used rather than a token in the remote URL so the + # credential is never written into .git/config. + AUTH="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${TOKEN}" | base64 -w0)" + git init -q . + git remote add origin "https://github.com/${REPO}.git" + git -c http.extraheader="${AUTH}" fetch -q --depth 1 origin "${SHA}" + git checkout -q FETCH_HEAD + echo "checked out ${SHA}" + + - name: Verify lockfile synchronisation + run: | + set -euo pipefail + test -x scripts/check-lock-sync.sh \ + || { echo "::error::scripts/check-lock-sync.sh missing or not executable"; exit 1; } + ./scripts/check-lock-sync.sh diff --git a/czech-file-knife/.github/workflows/main-estate-audit.yml b/czech-file-knife/.github/workflows/main-estate-audit.yml new file mode 100644 index 000000000..c856ee3f9 --- /dev/null +++ b/czech-file-knife/.github/workflows/main-estate-audit.yml @@ -0,0 +1,20 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +name: Central Estate CI/CD Audit + +# The called reusable declares `permissions: contents: read`; a caller must grant +# at least what the reusable declares, and this grants exactly that — no more. +permissions: + contents: read + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_call: + +jobs: + call-estate-audit: + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@55556cf5ba71ba744695861503c13148d3915a5d # cicd-suite MAIN (branch-reachable): remote-form callee — 27 full-SHA refs, de-onboarded; witnessed cross-repo in rsr runs 35282081912 + 35283168495 (jobs spawn; gates audit THIS repo). Prior f9e173a pin (via #137) = deleted PR head: pull-ref-only reachability -> 0-job creation rejection; its onboarded @main/tag form was separately proven startup_failure cross-repo (35280642055). Repin on callee updates; never to PR-head commits. diff --git a/czech-file-knife/.github/workflows/mirror.yml b/czech-file-knife/.github/workflows/mirror.yml index 9e41bcbbe..35c568381 100644 --- a/czech-file-knife/.github/workflows/mirror.yml +++ b/czech-file-knife/.github/workflows/mirror.yml @@ -1,120 +1,28 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell name: Mirror to Git Forges on: push: branches: [main] workflow_dispatch: -permissions: read-all +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false +permissions: + actions: read # required by the reusable workflow (staleness check reads workflow runs) + contents: read jobs: - mirror-gitlab: - runs-on: ubuntu-latest - if: vars.GITLAB_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }} - - name: Mirror to GitLab - run: | - ssh-keyscan -t ed25519 gitlab.com >> ~/.ssh/known_hosts - git remote add gitlab git@gitlab.com:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force gitlab main - mirror-bitbucket: - runs-on: ubuntu-latest - if: vars.BITBUCKET_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }} - - name: Mirror to Bitbucket - run: | - ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts - git remote add bitbucket git@bitbucket.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force bitbucket main - mirror-codeberg: - runs-on: ubuntu-latest - if: vars.CODEBERG_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }} - - name: Mirror to Codeberg - run: | - ssh-keyscan -t ed25519 codeberg.org >> ~/.ssh/known_hosts - git remote add codeberg git@codeberg.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force codeberg main - mirror-sourcehut: - runs-on: ubuntu-latest - if: vars.SOURCEHUT_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }} - - name: Mirror to SourceHut - run: | - ssh-keyscan -t ed25519 git.sr.ht >> ~/.ssh/known_hosts - git remote add sourcehut git@git.sr.ht:~hyperpolymath/${{ github.event.repository.name }} || true - git push --force sourcehut main - mirror-disroot: - runs-on: ubuntu-latest - if: vars.DISROOT_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }} - - name: Mirror to Disroot - run: | - ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts - git remote add disroot git@git.disroot.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force disroot main - mirror-gitea: - runs-on: ubuntu-latest - if: vars.GITEA_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 - with: - ssh-private-key: ${{ secrets.GITEA_SSH_KEY }} - - name: Mirror to Gitea - run: | - ssh-keyscan -t ed25519 ${{ vars.GITEA_HOST }} >> ~/.ssh/known_hosts - git remote add gitea git@${{ vars.GITEA_HOST }}:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force gitea main - mirror-radicle: - runs-on: ubuntu-latest - if: vars.RADICLE_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 - - name: Setup Rust - uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable - with: - toolchain: stable - - name: Install Radicle - run: | - # Install via cargo (safer than curl|sh) - cargo install radicle-cli --locked - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - name: Mirror to Radicle - run: | - echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle - chmod 600 ~/.radicle/keys/radicle - rad sync --announce || echo "Radicle sync attempted" + mirror: + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 + # Explicit secrets map — no `secrets: inherit` (Hypatia WH008, alert #131). + # All seven are the callee's complete optional contract (standards + # mirror-reusable.yml@da2c748); behaviour is unchanged, future secrets + # are no longer shared implicitly. + secrets: + GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} + BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }} + CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }} + SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }} + DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }} + GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }} + RADICLE_KEY: ${{ secrets.RADICLE_KEY }} diff --git a/czech-file-knife/.github/workflows/ossf-best-practices.yml b/czech-file-knife/.github/workflows/ossf-best-practices.yml new file mode 100644 index 000000000..b3aa90eeb --- /dev/null +++ b/czech-file-knife/.github/workflows/ossf-best-practices.yml @@ -0,0 +1,97 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# OpenSSF Best Practices compliance gate — blocks PRs and pushes that lack +# required files or still contain unfilled placeholder tokens. +name: OpenSSF Compliance +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + openssf-compliance: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + - name: Check SECURITY.md exists and has substance + run: | + SECFILE="" + [ -f "SECURITY.md" ] && SECFILE="SECURITY.md" + [ -f "SECURITY.adoc" ] && SECFILE="SECURITY.adoc" + [ -f ".github/SECURITY.md" ] && SECFILE=".github/SECURITY.md" + + if [ -z "$SECFILE" ]; then + echo "::error::SECURITY.md (or SECURITY.adoc) is required for OpenSSF Best Practices" + exit 1 + fi + + LINES=$(wc -l < "$SECFILE") + if [ "$LINES" -lt 10 ]; then + echo "::error::$SECFILE has only $LINES lines — must have >10 lines of substantive content" + exit 1 + fi + echo "SECURITY file: OK ($SECFILE, $LINES lines)" + - name: Check LICENSE exists + run: | + if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ]; then + echo "::error::LICENSE file is required for OpenSSF Best Practices" + exit 1 + fi + echo "LICENSE: OK" + - name: Check CONTRIBUTING exists + run: | + if [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] \ + && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ]; then + echo "::error::CONTRIBUTING file is required for OpenSSF Best Practices" + exit 1 + fi + echo "CONTRIBUTING: OK" + - name: Check README exists + run: | + if [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && [ ! -f "README.rst" ] && [ ! -f "README.txt" ] && [ ! -f "README" ]; then + echo "::error::README file is required for OpenSSF Best Practices" + exit 1 + fi + echo "README: OK" + - name: Check .machine_readable directory and STATE.a2ml + run: | + if [ ! -d ".machine_readable" ]; then + echo "::error::.machine_readable/ directory is required" + exit 1 + fi + + if [ ! -f ".machine_readable/descriptiles/STATE.a2ml" ]; then + echo "::error::.machine_readable/descriptiles/STATE.a2ml is required" + exit 1 + fi + echo ".machine_readable/descriptiles/STATE.a2ml: OK" + - name: Check CHANGELOG exists + run: | + if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG.adoc" ] && [ ! -f "CHANGES.md" ]; then + echo "::error::CHANGELOG.md is required for OpenSSF Best Practices" + exit 1 + fi + echo "CHANGELOG: OK" + - name: Check no unfilled placeholder tokens + # Delegates to the same script tests/e2e/template_instantiation_test.sh + # runs, so the gate and the test can never disagree about the rule. + # This step used to check a hand-maintained list of required files that + # omitted .github/settings.yml and ANCHOR.a2ml — the two places the + # leaks actually were. The script scans everything and allow-lists the + # legitimate carriers instead. + run: bash scripts/check-no-placeholders.sh . + - name: Summary + run: | + echo "=== OpenSSF Best Practices Compliance: PASS ===" + echo "All required files present and placeholder-free." diff --git a/czech-file-knife/.github/workflows/pages.yml b/czech-file-knife/.github/workflows/pages.yml new file mode 100644 index 000000000..a176cb208 --- /dev/null +++ b/czech-file-knife/.github/workflows/pages.yml @@ -0,0 +1,91 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# GitHub Pages via casket-ssg (hyperpolymath's pure-Haskell static site generator). +# Replaces the orphan one-off Pages deployment with a reproducible build. +name: GitHub Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Serialise Pages deploys; never cancel an in-flight deploy. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Checkout casket-ssg + uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/casket-ssg + path: .casket-ssg + + - name: Setup GHCup + uses: haskell-actions/setup@v2.12.1 + with: + ghc-version: '9.8.2' + cabal-version: '3.10' + + - name: Cache Cabal + uses: actions/cache@v6.1.0 + with: + path: | + ~/.cabal/packages + ~/.cabal/store + .casket-ssg/dist-newstyle + key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} + + - name: Build casket-ssg + working-directory: .casket-ssg + run: cabal build + + - name: Build site + run: | + mkdir -p site _site + # Seed site/index.md from README if the author hasn't provided one. + if [ ! -f site/index.md ]; then + { + echo "---" + echo "title: $(basename "$PWD")" + echo "date: $(date +%Y-%m-%d)" + echo "---" + if [ -f README.adoc ]; then cat README.adoc + elif [ -f README.md ]; then cat README.md + else printf '\n# %s\n\nDocumentation coming soon.\n' "$(basename "$PWD")" + fi + } > site/index.md + fi + cd .casket-ssg && cabal run casket-ssg -- build ../site ../_site + + - name: Setup Pages + uses: actions/configure-pages@v6.0.0 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5.0.0 + with: + path: '_site' + + deploy: + timeout-minutes: 20 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5.0.1 diff --git a/czech-file-knife/.github/workflows/publish.yml b/czech-file-knife/.github/workflows/publish.yml deleted file mode 100644 index fcea8d134..000000000 --- a/czech-file-knife/.github/workflows/publish.yml +++ /dev/null @@ -1,134 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2025 hyperpolymath -# -# Publish workspace crates to crates.io on release tags -# Publishes in dependency order: core → providers → cache → search → vfs → integrations → cli -# Requires: CARGO_REGISTRY_TOKEN secret - -name: Publish to crates.io -on: - push: - tags: - - 'v[0-9]+.*' - workflow_dispatch: - inputs: - dry_run: - description: 'Dry run (no actual publish)' - required: false - default: 'true' - type: boolean - crate: - description: 'Specific crate to publish (leave empty for all)' - required: false - default: '' - type: string -permissions: read-all -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 -jobs: - publish: - name: Publish crates - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable - with: - toolchain: stable - - name: Cache cargo - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y pkg-config libssl-dev libfuse3-dev libsqlite3-dev - - name: Verify workspace builds - run: cargo build --workspace --release - - name: Run workspace tests - run: cargo test --workspace - # Publish in dependency order with delays for index updates - - name: Publish cfk-core - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-core' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-core --dry-run - else - cargo publish -p cfk-core - sleep 30 # Wait for crates.io index update - fi - - name: Publish cfk-providers - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-providers' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-providers --dry-run - else - cargo publish -p cfk-providers - sleep 30 - fi - - name: Publish cfk-cache - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-cache' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-cache --dry-run - else - cargo publish -p cfk-cache - sleep 30 - fi - - name: Publish cfk-search - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-search' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-search --dry-run - else - cargo publish -p cfk-search - sleep 30 - fi - - name: Publish cfk-vfs - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-vfs' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-vfs --dry-run - else - cargo publish -p cfk-vfs - sleep 30 - fi - - name: Publish cfk-integrations - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-integrations' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-integrations --dry-run - else - cargo publish -p cfk-integrations - sleep 30 - fi - - name: Publish cfk-cli - if: github.event.inputs.crate == '' || github.event.inputs.crate == 'cfk-cli' - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - cargo publish -p cfk-cli --dry-run - else - cargo publish -p cfk-cli - fi - - name: Create GitHub Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 - with: - generate_release_notes: true - draft: false diff --git a/czech-file-knife/.github/workflows/push-email-notify.yml b/czech-file-knife/.github/workflows/push-email-notify.yml new file mode 100644 index 000000000..6414b39e7 --- /dev/null +++ b/czech-file-knife/.github/workflows/push-email-notify.yml @@ -0,0 +1,58 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Dormant push-email notification. ARMED by setting the repo variable +# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; +# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by +# new repos from the template; placed on existing repos by the farm sweep. +# +# Re-landed after the 2026-07-20 notification-storm freeze (removed in +# 09f94c5), now on hyperpolymath/smtp-notify-action: Node-free, the SMTP +# session is Idris2-specified and machine-checked, the binary is Zig-built, +# byte-reproducible, and SHA-256-pinned inside the action itself. +name: Push email notification +on: + push: + # Branch pushes only; the job condition separately excludes branch deletions. + branches: ['**'] +concurrency: + # Deliberately per-RUN, so no run is ever queued behind another and none is + # ever cancelled. Do NOT "tidy" this into a shared group such as + # ${{ github.workflow }}-${{ github.ref }}. GitHub's workflow-syntax docs: + # "By default, any existing pending job or workflow in the same concurrency + # group will be canceled and the new queued job or workflow will take its + # place." That happens regardless of cancel-in-progress, which governs only + # the RUNNING job. On this workflow it silently loses a notification email, + # with no error anywhere. Every run here reports a DISTINCT commit, so there + # is no redundant work for a concurrency limit to remove. + # The docs also offer `queue: max` (up to 100 pending); not used, because 100 + # is still a cap whereas a per-run group needs none. + # Verified with zizmor 1.30.0: deleting this block raises concurrency-limits; + # this form silences it exactly as a shared group would. + group: push-email-${{ github.run_id }}-${{ github.run_attempt }} + cancel-in-progress: false +permissions: {} +jobs: + notify: + name: Email on push + if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' && github.event.deleted != true }} + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Send push notification email + uses: hyperpolymath/smtp-notify-action@v0.3.0 # NOSONAR — pin authority is actions.lock (sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be) + with: + server_address: ${{ secrets.SMTP_HOST }} + server_port: ${{ secrets.SMTP_PORT }} + # Standard submission uses mandatory STARTTLS; other ports retain implicit TLS. + secure: ${{ secrets.SMTP_PORT == '587' && 'starttls' || 'implicit' }} + username: ${{ secrets.SMTP_USER }} + password: ${{ secrets.SMTP_PASS }} + from: "GitHub Push <${{ secrets.SMTP_USER }}>" + to: "jonathan.jewell@gmail.com j.d.a.jewell@open.ac.uk" + subject: "[${{ github.repository }}] push to ${{ github.ref_name }} by ${{ github.actor }}" + body: | + Repository: ${{ github.repository }} + Branch: ${{ github.ref_name }} + Pusher: ${{ github.actor }} + Compare: ${{ github.event.compare }} + Head msg: ${{ github.event.head_commit.message }} diff --git a/czech-file-knife/.github/workflows/quality.yml b/czech-file-knife/.github/workflows/quality.yml new file mode 100644 index 000000000..c9c638c7f --- /dev/null +++ b/czech-file-knife/.github/workflows/quality.yml @@ -0,0 +1,79 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Code Quality +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +permissions: + contents: read +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Check file permissions + run: | + find . -type f -perm /111 -name "*.sh" | head -10 || true + - name: ShellCheck (error severity) + run: | + # Error severity only: parse failures, unterminated find -exec, shebang + # not on line 1. Warnings are deliberately not gated — a gate that fires + # on style gets switched off, and these are the findings that break + # scripts at runtime. + # + # Vendored/upstream trees are excluded: patching a third party's test + # fixtures creates permanent divergence and conflicts on every sync. + fail=0 + while IFS= read -r f; do + case "/$f" in + */node_modules/*|*/vendor/*|*/third_party/*|*/rescript-ecosystem/*) continue ;; + esac + shellcheck -S error "$f" || fail=1 + done < <(git ls-files '*.sh' '*.bash') + if [ "$fail" -ne 0 ]; then + echo "::error::shellcheck reported error-severity findings (see above)" + exit 1 + fi + echo "✅ shellcheck: no error-severity findings" + + - name: Check TODO/FIXME + run: | + echo "=== TODOs ===" + grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.res" --include="*.py" --include="*.ex" . | head -20 || echo "None found" + - name: Check for large files + run: | + find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" + - name: EditorConfig check + uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 + continue-on-error: true + docs: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Check documentation + run: | + MISSING="" + [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && MISSING="$MISSING README" + [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ] && MISSING="$MISSING LICENSE" + [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ] && MISSING="$MISSING CONTRIBUTING" + + if [ -n "$MISSING" ]; then + echo "::warning::Missing docs:$MISSING" + else + echo "✅ Core documentation present" + fi diff --git a/czech-file-knife/.github/workflows/release.yml b/czech-file-knife/.github/workflows/release.yml index 35488bfbf..b85b40874 100644 --- a/czech-file-knife/.github/workflows/release.yml +++ b/czech-file-knife/.github/workflows/release.yml @@ -1,151 +1,159 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2025 hyperpolymath - +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Release workflow — triggered by version tags (v*). +# Builds artifacts, generates changelog via git-cliff, creates a GitHub Release, +# and produces GitHub native build-provenance attestations (OIDC + Sigstore). name: Release on: push: - tags: ['v*'] - workflow_dispatch: - inputs: - tag: - description: 'Release tag (e.g., v0.1.0)' - required: true -permissions: read-all -env: - CARGO_TERM_COLOR: always + tags: + - 'v*' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false +permissions: + contents: read jobs: - build-binaries: - name: Build ${{ matrix.target }} - runs-on: ${{ matrix.os }} + build: + name: Build Artifacts + runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - binary: cfk - - target: x86_64-unknown-linux-musl - os: ubuntu-latest - binary: cfk - - target: aarch64-unknown-linux-gnu - os: ubuntu-latest - binary: cfk - - target: x86_64-apple-darwin - os: macos-latest - binary: cfk - - target: aarch64-apple-darwin - os: macos-latest - binary: cfk - - target: x86_64-pc-windows-msvc - os: windows-latest - binary: cfk.exe steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable - with: - targets: ${{ matrix.target }} - - name: Install cross-compilation tools - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - sudo apt-get update - sudo apt-get install -y gcc-aarch64-linux-gnu - - name: Configure aarch64 linker - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: | - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - - name: Install musl tools - if: matrix.target == 'x86_64-unknown-linux-musl' - run: | - sudo apt-get update - sudo apt-get install -y musl-tools - - name: Install FUSE (Linux) - if: runner.os == 'Linux' - run: sudo apt-get install -y libfuse3-dev - - name: Install macFUSE (macOS) - if: runner.os == 'macOS' - run: brew install macfuse || true - - name: Build - run: cargo build --release --package cfk-cli --target ${{ matrix.target }} - - name: Package (Unix) - if: runner.os != 'Windows' + - uses: actions/checkout@v7.0.1 + - name: Detect project type and build + id: build run: | - cd target/${{ matrix.target }}/release - tar -czvf cfk-${{ github.ref_name }}-${{ matrix.target }}.tar.gz ${{ matrix.binary }} - sha256sum cfk-${{ github.ref_name }}-${{ matrix.target }}.tar.gz > cfk-${{ github.ref_name }}-${{ matrix.target }}.tar.gz.sha256 - - name: Package (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - cd target/${{ matrix.target }}/release - Compress-Archive -Path ${{ matrix.binary }} -DestinationPath cfk-${{ github.ref_name }}-${{ matrix.target }}.zip - Get-FileHash cfk-${{ github.ref_name }}-${{ matrix.target }}.zip -Algorithm SHA256 | ForEach-Object { "$($_.Hash.ToLower()) cfk-${{ github.ref_name }}-${{ matrix.target }}.zip" } | Out-File -FilePath cfk-${{ github.ref_name }}-${{ matrix.target }}.zip.sha256 - - name: Upload artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 - with: - name: cfk-${{ matrix.target }} - path: | - target/${{ matrix.target }}/release/cfk-${{ github.ref_name }}-${{ matrix.target }}.* - build-deb: - name: Build .deb package + # Auto-detect build system from project files. + # Order matters: more specific markers checked first. + if [ -f "mix.exs" ]; then + echo "::notice::Detected Elixir/Gleam project (mix.exs)" + echo "build_type=mix" >> "$GITHUB_OUTPUT" + mix local.hex --force --if-missing + mix local.rebar --force --if-missing + mix deps.get --only prod + MIX_ENV=prod mix release + elif [ -f "Cargo.toml" ]; then + echo "::notice::Detected Rust project (Cargo.toml)" + echo "build_type=cargo" >> "$GITHUB_OUTPUT" + cargo build --release + elif [ -f "build.zig" ]; then + echo "::notice::Detected Zig project (build.zig)" + echo "build_type=zig" >> "$GITHUB_OUTPUT" + zig build -Doptimize=ReleaseSafe + elif [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then + echo "::notice::Detected Deno project (deno.json)" + echo "build_type=deno" >> "$GITHUB_OUTPUT" + deno task build + elif [ -f "gossamer.conf.json" ]; then + echo "::notice::Detected Gossamer project (gossamer.conf.json)" + echo "build_type=gossamer" >> "$GITHUB_OUTPUT" + gossamer build + elif [ -f "gleam.toml" ]; then + echo "::notice::Detected Gleam project (gleam.toml)" + echo "build_type=gleam" >> "$GITHUB_OUTPUT" + gleam build + elif [ -f "rebar.config" ]; then + echo "::notice::Detected Erlang/Rebar project (rebar.config)" + echo "build_type=rebar" >> "$GITHUB_OUTPUT" + rebar3 as prod release + elif [ -f "Justfile" ] || [ -f "justfile" ]; then + echo "::notice::Detected Justfile — running 'just build'" + echo "build_type=just" >> "$GITHUB_OUTPUT" + just build + else + echo "::error::No recognised build system found." + echo "Expected one of: mix.exs, Cargo.toml, build.zig, deno.json, gossamer.conf.json, gleam.toml, rebar.config, Justfile" + exit 1 + fi + # TODO: Upload build artifacts if needed (pin actions/upload-artifact + # to a full commit SHA when enabling, per the SHA-pin policy): + # - uses: actions/upload-artifact@ # vX.Y.Z + # with: + # name: release-artifacts + # path: target/release/ + changelog: + name: Generate Changelog runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read - needs: build-binaries + outputs: + changelog: ${{ steps.cliff.outputs.content }} + version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Install cargo-deb - run: cargo install cargo-deb - - name: Build .deb - run: cargo deb --package cfk-cli - - name: Upload .deb - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + - uses: actions/checkout@v7.0.1 with: - name: deb-package - path: target/debian/*.deb - build-rpm: - name: Build .rpm package - runs-on: ubuntu-latest - permissions: - contents: read - needs: build-binaries - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Install cargo-generate-rpm - run: cargo install cargo-generate-rpm - - name: Build binary - run: cargo build --release --package cfk-cli - - name: Strip binary - run: strip target/release/cfk - - name: Build .rpm - run: cargo generate-rpm --package cfk-cli - - name: Upload .rpm - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + fetch-depth: 0 + - name: Extract version from tag + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + - name: Install git-cliff + run: | + curl -sSfL https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-$(uname -m)-unknown-linux-gnu.tar.gz \ + | tar -xz --strip-components=1 -C /usr/local/bin/ git-cliff-*/git-cliff + - name: Generate changelog for this release + id: cliff + run: | + # Generate changelog for the current tag only + CHANGELOG=$(git cliff --latest --strip header) + # Write to output using delimiter to handle multiline + { + echo "content<> "$GITHUB_OUTPUT" + - name: Update full CHANGELOG.md + run: | + git cliff --output CHANGELOG.md + - name: Upload updated CHANGELOG.md + uses: actions/upload-artifact@v7.0.1 with: - name: rpm-package - path: target/generate-rpm/*.rpm + name: changelog + path: CHANGELOG.md + retention-days: 5 release: - name: Create Release + name: Create GitHub Release + needs: [build, changelog] runs-on: ubuntu-latest - needs: [build-binaries, build-deb, build-rpm] + timeout-minutes: 15 permissions: contents: write + id-token: write # mint the OIDC token attestation provenance is signed with + attestations: write # write the build-provenance attestation (the "claim") steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - name: Download all artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v4 - with: - path: artifacts - - name: Collect release assets - run: | - mkdir -p release - find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.sha256" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release/ \; - ls -la release/ - - name: Create Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 + - uses: actions/checkout@v7.0.1 + # TODO: Download build artifacts if uploading to the release (pin + # actions/download-artifact to a full commit SHA when enabling): + # - uses: actions/download-artifact@ # vX.Y.Z + # with: + # name: release-artifacts + # path: artifacts/ + - name: Create GitHub Release + uses: softprops/action-gh-release@v3.0.3 with: - files: release/* - generate_release_notes: true + body: ${{ needs.changelog.outputs.changelog }} draft: false + prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }} + generate_release_notes: false + # TODO: Add artifact files to the release + # files: | + # artifacts/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # GitHub native artifact attestation (build provenance). Generates a + # signed, verifiable claim binding each released artifact to this build + # (commit, workflow, runner) via OIDC + Sigstore — verify with + # `gh attest verify --repo ${{ github.repository }}`. + # Replaces the older SLSA-generator job; native attestations need no + # separate isolated workflow. + # TODO: point subject-path at the artifacts this release actually ships + # (must match the `files:` uploaded above, e.g. artifacts/*). + - name: Attest build provenance + if: ${{ hashFiles('artifacts/*') != '' }} # skip until real artifacts are wired + uses: actions/attest-build-provenance@v4.2.2 + with: + subject-path: 'artifacts/*' diff --git a/czech-file-knife/.github/workflows/rsr-compliance-canary.yml b/czech-file-knife/.github/workflows/rsr-compliance-canary.yml new file mode 100644 index 000000000..13e0a55a1 --- /dev/null +++ b/czech-file-knife/.github/workflows/rsr-compliance-canary.yml @@ -0,0 +1,95 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# rhodibot.yml — RSR compliance CANARY (report-only) +# +# Rhodibot does NOT mutate this repository. It never deletes, renames, +# rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS +# what an auto-fixer would have changed and reports it. +# +# Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it +# detects something it considers non-compliant — that is itself a MAJOR +# WARNING. Either the repo has drifted, OR rhodibot's own rules have +# diverged from the normative style it is meant to enforce. Both warrant +# a human look, so the canary FAILS the run when it finds would-mutate +# drift. Dangerous-pattern hits are advisory warnings only. +# +# Licence note: SPDX/licence drift is reported for MANUAL, owner-only +# correction. Rhodibot must never edit a licence header (estate directive). + +name: "\U0001F916 Rhodibot — RSR Compliance Canary" +on: + schedule: + - cron: '0 6 * * 1' # Every Monday at 06:00 UTC + workflow_dispatch: # Manual trigger + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + canary: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 1 + - name: Rhodibot — detect drift (no mutations) + run: | + set -uo pipefail + DRIFT=0 + warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); } + note() { echo "::warning title=Rhodibot advisory::$*"; } + + echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY" + + # --- would-DELETE: banned files --- + for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do + [ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)" + done + # would-DELETE: stale snapshots + for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do + [ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)" + done + # would-RENAME: legacy manifest name (a repo deed at root is the + # deed-era equivalent — standards#837) + if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ] && ! ls *_chora.deed >/dev/null 2>&1; then + warn "AI.a2ml present without 0-AI-MANIFEST.a2ml or a repo deed (would be renamed)" + fi + # would-DELETE: duplicate community files + [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)" + if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then + warn "stub README.md alongside README.adoc (would be removed)" + fi + # SPDX drift — MANUAL owner-only fix, never auto-edited + for dotfile in .gitignore .gitattributes .editorconfig; do + if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then + warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)" + fi + done + # would-CREATE: missing required files + [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)" + [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)" + + # --- unfixable compliance gaps (also drift) --- + [ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || ls *_chora.deed >/dev/null 2>&1 || warn "missing AI manifest (0-AI-MANIFEST.a2ml or repo deed *_chora.deed)" + [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file" + [ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README" + + # --- advisory only: dangerous verification-bypass patterns --- + for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do + count=$(grep -rl "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || true) + [ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)" + done + + echo "" >> "$GITHUB_STEP_SUMMARY" + if [ "$DRIFT" -gt 0 ]; then + echo "🔴 **Canary tripped: $DRIFT would-mutate finding(s).** Either the repo drifted or rhodibot's rules diverged from the norm — investigate (no edits were made)." >> "$GITHUB_STEP_SUMMARY" + echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed." + exit 1 + fi + echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY" + echo "✅ Rhodibot canary clean — no drift, no mutations." diff --git a/czech-file-knife/.github/workflows/runtime-policy.yml b/czech-file-knife/.github/workflows/runtime-policy.yml new file mode 100644 index 000000000..51ec04fa6 --- /dev/null +++ b/czech-file-knife/.github/workflows/runtime-policy.yml @@ -0,0 +1,72 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Runtime and package-manager policy check. +# +# Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc §1. +# Ordering: Bun (1st) > Deno (2nd) > pnpm (3rd) > npm (last resort). +# +# REPLACES npm-bun-blocker.yml, which failed any build carrying `bun.lockb` with +# the message "npm/bun artifacts detected. Use Deno instead." That gate blocked +# what is now the FIRST-choice runtime and mandated the second. It was present in +# 55 repositories. +# +# What this fails on, deliberately: +# MIXED TOOLCHAINS -- two different package managers' lockfiles in one repo. +# That is real, actionable drift: two dependency graphs that can disagree. +# What it does NOT fail on: +# Using bun, pnpm or npm. Deno is RETIRED (2026-08-26). npm is LAST but PERMITTED; the check reports +# the tier in use so drift is visible without blocking legitimate work. +name: Runtime Policy +on: + push: + branches: [main, master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + runtime-policy: + name: Runtime Policy + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + + - name: Report runtime tier and reject mixed toolchains + run: | + set -euo pipefail + + bun=0; deno=0; pnpm=0; npm=0 + [ -f bun.lockb ] || [ -f bun.lock ] && bun=1 || true + [ -f deno.lock ] || [ -f deno.json ] || [ -f deno.jsonc ] && deno=1 || true + [ -f pnpm-lock.yaml ] && pnpm=1 || true + [ -f package-lock.json ] && npm=1 || true + + total=$((bun + deno + pnpm + npm)) + + if [ "$total" -eq 0 ]; then + echo "::notice::No JS/TS package manager in use — nothing to check." + exit 0 + fi + + # Report the tier actually in use (LANGUAGE-POLICY.adoc §1). + [ "$bun" -eq 1 ] && echo "Bun — tier 1 (preferred)" + [ "$deno" -eq 1 ] && echo "::warning::Deno lockfile/config present. Deno is BEING REMOVED, not grandfathered (owner ruling 2026-08-26, canon standards#655). Migrate to Bun — package.json + bun.lock. This is a warning rather than an error only while the estate migration is in flight; it escalates to an error when that completes." + [ "$pnpm" -eq 1 ] && echo "pnpm — tier 3" + [ "$npm" -eq 1 ] && echo "::warning::npm lockfile present. npm is tier 4, the last resort — permitted, never preferred. See LANGUAGE-POLICY.adoc §1." + + if [ "$total" -gt 1 ]; then + echo "::error::Mixed toolchains: $total package managers have lockfiles in this repository." + echo "Two dependency graphs that can disagree is real drift. Pick one — preferring the" + echo "highest tier present — and delete the others' lockfiles." + exit 1 + fi + + echo "✅ Single package manager in use." diff --git a/czech-file-knife/.github/workflows/rust-ci.yml b/czech-file-knife/.github/workflows/rust-ci.yml new file mode 100644 index 000000000..05369dd9b --- /dev/null +++ b/czech-file-knife/.github/workflows/rust-ci.yml @@ -0,0 +1,19 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Rust CI — thin wrapper calling the shared estate reusable in +# hyperpolymath/standards. Configure once, propagate everywhere. +# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. +name: Rust CI +on: + push: + branches: [main, master] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + actions: read + contents: read +jobs: + rust-ci: + uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/scorecard-enforcer.yml b/czech-file-knife/.github/workflows/scorecard-enforcer.yml deleted file mode 100644 index 9632349ff..000000000 --- a/czech-file-knife/.github/workflows/scorecard-enforcer.yml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - runs OpenSSF Scorecard and fails on low scores -name: OpenSSF Scorecard Enforcer -on: - push: - branches: [main] - schedule: - - cron: '0 6 * * 1' # Weekly on Monday - workflow_dispatch: -permissions: read-all -jobs: - scorecard: - runs-on: ubuntu-latest - permissions: - security-events: write - id-token: write # For OIDC - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3 - with: - sarif_file: results.sarif - # Check specific high-priority items - check-critical: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - - name: Check SECURITY.md exists - run: | - if [ ! -f "SECURITY.md" ]; then - echo "::error::SECURITY.md is required" - exit 1 - fi - - name: Check for pinned dependencies - run: | - # Check workflows for unpinned actions - unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true) - if [ -n "$unpinned" ]; then - echo "::warning::Found unpinned actions:" - echo "$unpinned" - fi diff --git a/czech-file-knife/.github/workflows/scorecard.yml b/czech-file-knife/.github/workflows/scorecard.yml index 86b2ab8ab..f2ce70367 100644 --- a/czech-file-knife/.github/workflows/scorecard.yml +++ b/czech-file-knife/.github/workflows/scorecard.yml @@ -1,28 +1,26 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: OSSF Scorecard + on: - push: - branches: [main, master] schedule: - cron: '0 4 * * *' workflow_dispatch: -permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + actions: read # required by the reusable workflow (staleness check reads workflow runs) + contents: read + jobs: - analysis: - runs-on: ubuntu-latest + scorecard: + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 + # Reusable called-workflow permissions are CAPPED by the caller's grants; + # without security-events: write here the scorecard SARIF upload fails with + # startup_failure (hypatia WF018). id-token: write enables OIDC publish. permissions: + contents: read security-events: write id-token: write - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - - name: Upload results - uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v3.31.8 - with: - sarif_file: results.sarif diff --git a/czech-file-knife/.github/workflows/secret-scanner.yml b/czech-file-knife/.github/workflows/secret-scanner.yml index efdb0a29a..9a7b13693 100644 --- a/czech-file-knife/.github/workflows/secret-scanner.yml +++ b/czech-file-knife/.github/workflows/secret-scanner.yml @@ -1,49 +1,29 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - scans for hardcoded secrets before they reach main name: Secret Scanner on: pull_request: push: branches: [main] -permissions: read-all +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + actions: read + contents: read +# Single secret scanner. The standards reusable runs gitleaks (+ a Rust-secrets +# check). An inline TruffleHog job previously lived here, but the reusable +# DELIBERATELY retired TruffleHog as redundant (gitleaks gives sufficient +# coverage at lower cost — see the reusable's header). Re-adding it was +# duplicated work, not extra coverage, so it has been removed. See +# .github/workflows/README.adoc. jobs: - trufflehog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - with: - fetch-depth: 0 # Full history for scanning - - name: TruffleHog Secret Scan - uses: trufflesecurity/trufflehog@8a8ef8526528d8a4ff3e2c90be08e25ef8efbd9b # v3 - with: - extra_args: --only-verified --fail - # Rust-specific: Check for hardcoded crypto values - rust-secrets: - runs-on: ubuntu-latest - if: ${{ hashFiles('**/Cargo.toml') != '' }} - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4 - - name: Check for hardcoded secrets in Rust - run: | - # Patterns that suggest hardcoded secrets - PATTERNS=( - 'const.*SECRET.*=.*"' - 'const.*KEY.*=.*"[a-zA-Z0-9]{16,}"' - 'const.*TOKEN.*=.*"' - 'let.*api_key.*=.*"' - 'HMAC.*"[a-fA-F0-9]{32,}"' - 'password.*=.*"[^"]+"' - ) - - found=0 - for pattern in "${PATTERNS[@]}"; do - if grep -rn --include="*.rs" -E "$pattern" src/; then - echo "WARNING: Potential hardcoded secret found matching: $pattern" - found=1 - fi - done - - if [ $found -eq 1 ]; then - echo "::error::Potential hardcoded secrets detected. Use environment variables instead." - exit 1 - fi + scan: + # The reusable installs and executes a pinned gitleaks binary directly. + # Since standards#500 it neither comments on PRs nor reads workflow-run + # metadata, so contents: read is the complete permission contract. Keep + # this job-level block explicit: it replaces the workflow-level grant and + # is therefore the cap GitHub applies to the called workflow. + permissions: + contents: read + uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/security-policy.yml b/czech-file-knife/.github/workflows/security-policy.yml new file mode 100644 index 000000000..3c64369d5 --- /dev/null +++ b/czech-file-knife/.github/workflows/security-policy.yml @@ -0,0 +1,54 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Security Policy +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Security checks + run: | + FAILED=false + + # Block MD5/SHA1 for security (allow for checksums/caching) + WEAK_CRYPTO=$(grep -rE 'md5\(|sha1\(' --include="*.py" --include="*.rb" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" . 2>/dev/null | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) + if [ -n "$WEAK_CRYPTO" ]; then + echo "⚠️ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" + echo "$WEAK_CRYPTO" + fi + + # Block HTTP URLs (except localhost) + HTTP_URLS=$(grep -rE 'http://[^l][^o][^c]' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) + if [ -n "$HTTP_URLS" ]; then + echo "⚠️ HTTP URLs found. Use HTTPS:" + echo "$HTTP_URLS" + fi + + # Block hardcoded secrets patterns + SECRETS=$(grep -rEi '(api_key|apikey|secret_key|password)\s*[=:]\s*["\x27][A-Za-z0-9+/=]{20,}' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.env" . 2>/dev/null | grep -v 'example\|sample\|test\|mock\|placeholder' | head -3 || true) + if [ -n "$SECRETS" ]; then + echo "❌ Potential hardcoded secrets detected!" + FAILED=true + fi + + if [ "$FAILED" = true ]; then + exit 1 + fi + + echo "✅ Security policy check passed" diff --git a/czech-file-knife/.github/workflows/sonarqube.yml b/czech-file-knife/.github/workflows/sonarqube.yml new file mode 100644 index 000000000..0b735fd82 --- /dev/null +++ b/czech-file-knife/.github/workflows/sonarqube.yml @@ -0,0 +1,69 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# SonarQube Cloud (SonarCloud) static analysis. Analysis scope + exclusions live +# in sonar-project.properties. Requires the SONAR_TOKEN repository secret +# (Settings -> Secrets and variables -> Actions) and a SonarCloud project: +# https://sonarcloud.io/project/overview?id=hyperpolymath_czech-file-knife +# Mirrors the boj-server arrangement. +# +# WHY THE TOKEN GUARD (added 2026-07-28) +# -------------------------------------- +# This repo has NO Actions secrets configured at all, so `secrets.SONAR_TOKEN` +# expanded to the empty string and the scanner failed every run with +# "Not authorized or project not found" -- a permanent red that blocked merges +# through the ruleset's code_quality rule. It was never a code-quality signal: +# the scan never ran. +# +# SonarCloud's AUTOMATIC analysis is separately enabled here and does report +# (check name "SonarCloud Code Analysis"), so no coverage is lost by skipping. +# +# The guard makes the state honest rather than red: with no token the job +# reports that it is unconfigured and exits 0; the moment a SONAR_TOKEN secret +# is added it runs for real, with no further edit needed. Deleting the workflow +# would have discarded a correct configuration over a missing secret. +name: SonarQube +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + sonarqube: + name: SonarQube + runs-on: ubuntu-latest + timeout-minutes: 15 + # Secrets cannot be referenced from a job-level `if:`, so the presence test + # is carried through an env var set at job scope and read in a first step. + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - name: Report configuration state + id: cfg + run: | + if [ -z "${SONAR_TOKEN}" ]; then + echo "configured=false" >> "$GITHUB_OUTPUT" + echo "::notice::SONAR_TOKEN is not configured for this repository - skipping the CI-based scan." + echo "SonarCloud automatic analysis (check: 'SonarCloud Code Analysis') is unaffected." + echo "To enable this scan: Settings > Secrets and variables > Actions > new secret SONAR_TOKEN." + else + echo "configured=true" >> "$GITHUB_OUTPUT" + echo "SONAR_TOKEN present - running the CI-based scan." + fi + + - name: Checkout + if: steps.cfg.outputs.configured == 'true' + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 # full history for accurate new-code detection + + - name: SonarQube Scan + if: steps.cfg.outputs.configured == 'true' + uses: SonarSource/sonarqube-scan-action@v8.2.2 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/czech-file-knife/.github/workflows/static-analysis-gate.yml b/czech-file-knife/.github/workflows/static-analysis-gate.yml new file mode 100644 index 000000000..62134f39c --- /dev/null +++ b/czech-file-knife/.github/workflows/static-analysis-gate.yml @@ -0,0 +1,454 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Static Analysis Gate — Required by branch protection rules. +# Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. +name: Static Analysis Gate +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + # --------------------------------------------------------------------------- + # Job 1: panic-attack assail + # --------------------------------------------------------------------------- + panic-attack-assail: + name: panic-attack assail + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Install panic-attack (if available) + id: install + run: | + # Try to fetch the latest release binary from the org + PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" + if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then + curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" + chmod +x /usr/local/bin/panic-attack + echo "installed=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::panic-attack binary not available — skipping assail" + echo "installed=false" >> "$GITHUB_OUTPUT" + fi + - name: Run panic-attack assail + id: assail + if: steps.install.outputs.installed == 'true' + run: | + set +e + panic-attack assail --format json . > panic-attack-findings.json + PA_EXIT=$? + set -e + + # Same defect class as the Hypatia job below: `2>&1` folded the + # scanner's stderr into the JSON payload, so every jq parse failed, + # every count silently became 0 via `|| echo 0`, and "Fail on critical + # findings" could never fire on any input. Keep stderr on the log. + if [ ! -s panic-attack-findings.json ]; then + echo "[]" > panic-attack-findings.json + fi + + # Deliberately a WARNING, not a failure. panic-attack is a downloaded + # release binary whose exit-code and output contract are not verified + # here, and it has no confirmed --exit-zero equivalent, so we surface a + # malformed payload in the log rather than block on an unverified tool. + # Promote to `exit 1` (as the Hypatia job does) once that contract is + # confirmed -- see the follow-up issue linked from this PR. + if ! jq -e 'type == "array"' panic-attack-findings.json >/dev/null 2>&1; then + echo "::warning::panic-attack output is not a JSON array (exit ${PA_EXIT}); counts below are unreliable" + fi + + # Parse finding counts + TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0) + CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + HIGH=$(jq '[.[] | select(.severity == "high")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + LOW=$(jq '[.[] | select(.severity == "low")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + echo "high=$HIGH" >> "$GITHUB_OUTPUT" + echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" + echo "low=$LOW" >> "$GITHUB_OUTPUT" + echo "exit_code=$PA_EXIT" >> "$GITHUB_OUTPUT" + - name: Emit check annotations + if: steps.install.outputs.installed == 'true' + run: | + # Convert JSON findings into GitHub Actions annotations + # Findings carry no `.message` (keys: action,file,line,reason,rule_module, + # severity,type), so every annotation read "null". `.file` is an absolute + # runner path, which GitHub cannot anchor to the diff, so it is made + # workspace-relative here. + jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | + (.file | ltrimstr($ws + "/")) as $f | + (.reason // .message // .type // "finding") as $m | + if .severity == "critical" then + "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + elif .severity == "high" then + "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + else + "::warning file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + end + ' panic-attack-findings.json || true + - name: Write step summary + if: steps.install.outputs.installed == 'true' + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## panic-attack assail Results + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.assail.outputs.critical }} | + | High | ${{ steps.assail.outputs.high }} | + | Medium | ${{ steps.assail.outputs.medium }} | + | Low | ${{ steps.assail.outputs.low }} | + | **Total**| ${{ steps.assail.outputs.total }} | + EOF + - name: Create stub findings (when panic-attack unavailable) + if: steps.install.outputs.installed != 'true' + run: | + echo "[]" > panic-attack-findings.json + echo "## panic-attack assail" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" + - name: Upload panic-attack findings + uses: actions/upload-artifact@v7.0.1 + with: + name: panic-attack-findings + path: panic-attack-findings.json + retention-days: 90 + - name: Fail on critical findings + if: steps.install.outputs.installed == 'true' && steps.assail.outputs.critical > 0 + run: | + echo "::error::panic-attack found ${{ steps.assail.outputs.critical }} critical issue(s) — blocking merge" + exit 1 + # --------------------------------------------------------------------------- + # Job 2: hypatia-scan + # + # NOTE — this is NOT a duplicate of the standalone `hypatia-scan.yml`. + # * THIS job runs Hypatia inside the gate and hands its findings to the + # `deposit-findings` job below, which feeds the gitbot-fleet LEARNING + # pipeline (artifact -> fleet). It is a REQUIRED status check. + # * `hypatia-scan.yml` runs Hypatia standalone as the repo's security scan, + # independent of the fleet-learning deposit. + # Same tool, two different consumers — keep both. See .github/workflows/README.adoc. + # --------------------------------------------------------------------------- + hypatia-scan: + name: Hypatia neurosymbolic scan + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Setup Elixir for Hypatia scanner + id: beam + continue-on-error: true + uses: erlef/setup-beam@v1.24.1 + with: + elixir-version: '1.19.4' + otp-version: '28.3' + - name: Clone and build Hypatia + id: build + continue-on-error: true + run: | + git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" 2>/dev/null || true + if [ -f "$HOME/hypatia/mix.exs" ]; then + cd "$HOME/hypatia" + # Build escript if neither hypatia nor hypatia-v2 exists + if [ ! -f hypatia ] && [ ! -f hypatia-v2 ]; then + mix deps.get + mix escript.build + fi + echo "ready=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::Hypatia scanner not available — skipping scan" + echo "ready=false" >> "$GITHUB_OUTPUT" + fi + - name: Run Hypatia scan + id: scan + if: steps.build.outputs.ready == 'true' + run: | + set +e + HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json + HYP_EXIT=$? + set -e + + # --exit-zero is Hypatia's own documented CI recipe (lib/hypatia/cli.ex), + # for exactly this case: "use in CI when a downstream step gates on + # severity counts". Findings go to stdout, the one-line summary to + # stderr, and the process exits 0 unless the SCANNER itself failed. + # + # Do NOT redirect stderr into the payload with `2>&1`: that folds the + # summary line into the JSON, so every parse fails, the old `[]` + # fallback substituted a clean result, CRITICAL was always 0, and the + # gate below could never fire on any input. Keep stderr on the log. + if [ "$HYP_EXIT" -ne 0 ]; then + echo "::error::Hypatia scanner execution failed with exit ${HYP_EXIT}" + exit "$HYP_EXIT" + fi + # `jq empty` is NOT sufficient -- it succeeds on any valid JSON, + # including a bare string, object or null. Assert the array. + if [ ! -s hypatia-findings.json ] || ! jq -e 'type == "array"' hypatia-findings.json >/dev/null; then + echo "::error::Hypatia did not produce a valid JSON findings array" + exit 1 + fi + + TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) + CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json 2>/dev/null || echo 0) + HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' hypatia-findings.json 2>/dev/null || echo 0) + LOW=$(jq '[.[] | select(.severity == "low")] | length' hypatia-findings.json 2>/dev/null || echo 0) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + echo "high=$HIGH" >> "$GITHUB_OUTPUT" + echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" + echo "low=$LOW" >> "$GITHUB_OUTPUT" + - name: Emit check annotations + if: steps.build.outputs.ready == 'true' + run: | + # Findings carry no `.message` (keys: action,file,line,reason,rule_module, + # severity,type), so every annotation read "null". `.file` is an absolute + # runner path, which GitHub cannot anchor to the diff, so it is made + # workspace-relative here. + jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | + (.file | ltrimstr($ws + "/")) as $f | + (.reason // .message // .type // "finding") as $m | + if .severity == "critical" then + "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" + elif .severity == "high" then + "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" + else + "::warning file=\($f),line=\(.line // 1)::[hypatia] \($m)" + end + ' hypatia-findings.json || true + - name: Write step summary + if: steps.build.outputs.ready == 'true' + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Hypatia Scan Results + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.scan.outputs.critical }} | + | High | ${{ steps.scan.outputs.high }} | + | Medium | ${{ steps.scan.outputs.medium }} | + | Low | ${{ steps.scan.outputs.low }} | + | **Total**| ${{ steps.scan.outputs.total }} | + EOF + - name: Create stub findings (when Hypatia unavailable) + if: steps.build.outputs.ready != 'true' + run: | + echo "[]" > hypatia-findings.json + echo "## Hypatia Scan" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: Hypatia scanner not available in this environment." >> "$GITHUB_STEP_SUMMARY" + - name: Upload hypatia findings + uses: actions/upload-artifact@v7.0.1 + with: + name: hypatia-findings + path: hypatia-findings.json + retention-days: 90 + - name: Fail on critical security findings + if: steps.build.outputs.ready == 'true' && steps.scan.outputs.critical > 0 + run: | + echo "::error::Hypatia found ${{ steps.scan.outputs.critical }} critical security issue(s) — blocking merge" + exit 1 + # --------------------------------------------------------------------------- + # Job 3: patch-bridge triage (CVE contextual assessment) + # --------------------------------------------------------------------------- + patch-bridge-triage: + name: Patch Bridge CVE triage + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Install panic-attack (if available) + id: install + run: | + PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" + if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then + curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" + chmod +x /usr/local/bin/panic-attack + echo "installed=true" >> "$GITHUB_OUTPUT" + else + echo "::notice::panic-attack binary not available — skipping Patch Bridge" + echo "installed=false" >> "$GITHUB_OUTPUT" + fi + - name: Run Patch Bridge triage + id: triage + if: steps.install.outputs.installed == 'true' + run: | + set +e + panic-attack bridge triage --format json . > bridge-report.json 2>&1 + PB_EXIT=$? + set -e + + if [ ! -s bridge-report.json ] || ! jq empty bridge-report.json 2>/dev/null; then + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json + fi + + UNMITIGABLE=$(jq '.unmitigable // 0' bridge-report.json) + MITIGATED=$(jq '.mitigated // 0' bridge-report.json) + CONCATENATIVE=$(jq '.concatenative // 0' bridge-report.json) + INFORMATIONAL=$(jq '.informational // 0' bridge-report.json) + + echo "unmitigable=$UNMITIGABLE" >> "$GITHUB_OUTPUT" + echo "mitigated=$MITIGATED" >> "$GITHUB_OUTPUT" + echo "concatenative=$CONCATENATIVE" >> "$GITHUB_OUTPUT" + echo "informational=$INFORMATIONAL" >> "$GITHUB_OUTPUT" + - name: Write step summary + if: steps.install.outputs.installed == 'true' + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Patch Bridge CVE Triage + + | Classification | Count | + |----------------|-------| + | Unmitigable | ${{ steps.triage.outputs.unmitigable }} | + | Mitigated | ${{ steps.triage.outputs.mitigated }} | + | Concatenative | ${{ steps.triage.outputs.concatenative }} | + | Informational | ${{ steps.triage.outputs.informational }} | + + Unmitigable CVEs require dependency replacement or rearchitecture. + Mitigated CVEs have active controls with soundness proofs. + Concatenative risks are CVE combinations that multiply severity. + EOF + - name: Create stub report (when unavailable) + if: steps.install.outputs.installed != 'true' + run: | + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json + echo "## Patch Bridge CVE Triage" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" + - name: Upload bridge report + uses: actions/upload-artifact@v7.0.1 + with: + name: bridge-report + path: bridge-report.json + retention-days: 90 + - name: Fail on unmitigable CVEs in critical paths + if: steps.install.outputs.installed == 'true' && steps.triage.outputs.unmitigable > 0 + run: | + echo "::warning::Patch Bridge found ${{ steps.triage.outputs.unmitigable }} unmitigable CVE(s) — review required" + # Warning only, not blocking. Unmitigable means the developer needs + # to make an architectural decision, not that the PR is wrong. + # --------------------------------------------------------------------------- + # Job 4: deposit-findings (combines + archives for gitbot-fleet) + # --------------------------------------------------------------------------- + deposit-findings: + name: Deposit findings for gitbot-fleet + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [panic-attack-assail, hypatia-scan, patch-bridge-triage] + if: always() + steps: + - name: Download panic-attack findings + uses: actions/download-artifact@v8.0.1 + with: + name: panic-attack-findings + path: findings/ + - name: Download hypatia findings + uses: actions/download-artifact@v8.0.1 + with: + name: hypatia-findings + path: findings/ + - name: Download bridge report + uses: actions/download-artifact@v8.0.1 + with: + name: bridge-report + path: findings/ + - name: Combine findings into unified report + id: combine + run: | + PA_FILE="findings/panic-attack-findings.json" + HYP_FILE="findings/hypatia-findings.json" + + # Ensure both files exist and are valid JSON arrays + for f in "$PA_FILE" "$HYP_FILE"; do + if [ ! -s "$f" ] || ! jq empty "$f" 2>/dev/null; then + echo "[]" > "$f" + fi + done + + # Tag each finding with its source scanner + jq '[.[] | . + {"scanner": "panic-attack"}]' "$PA_FILE" > /tmp/pa-tagged.json + jq '[.[] | . + {"scanner": "hypatia"}]' "$HYP_FILE" > /tmp/hyp-tagged.json + + # Read bridge report (CVE triage, not findings array) + BRIDGE_FILE="findings/bridge-report.json" + if [ ! -s "$BRIDGE_FILE" ] || ! jq empty "$BRIDGE_FILE" 2>/dev/null; then + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > "$BRIDGE_FILE" + fi + + # Build unified report envelope + jq -n \ + --arg repo "${{ github.repository }}" \ + --arg sha "${{ github.sha }}" \ + --arg ref "${{ github.ref }}" \ + --arg run_id "${{ github.run_id }}" \ + --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --slurpfile pa /tmp/pa-tagged.json \ + --slurpfile hyp /tmp/hyp-tagged.json \ + --slurpfile bridge "$BRIDGE_FILE" \ + '{ + schema_version: "1.1.0", + repository: $repo, + commit_sha: $sha, + ref: $ref, + run_id: $run_id, + timestamp: $ts, + findings: ($pa[0] + $hyp[0]), + patch_bridge: $bridge[0] + }' > findings/unified-findings.json + + TOTAL=$(jq '.findings | length' findings/unified-findings.json) + CRITICAL=$(jq '[.findings[] | select(.severity == "critical")] | length' findings/unified-findings.json) + HIGH=$(jq '[.findings[] | select(.severity == "high")] | length' findings/unified-findings.json) + MEDIUM=$(jq '[.findings[] | select(.severity == "medium" or .severity == "warn")] | length' findings/unified-findings.json) + LOW=$(jq '[.findings[] | select(.severity == "low")] | length' findings/unified-findings.json) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + echo "high=$HIGH" >> "$GITHUB_OUTPUT" + echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" + echo "low=$LOW" >> "$GITHUB_OUTPUT" + - name: Upload unified findings (fleet scanner picks these up) + uses: actions/upload-artifact@v7.0.1 + with: + name: unified-findings + path: findings/unified-findings.json + retention-days: 90 + - name: Write deposit summary + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Unified Findings Deposit + + **Repository:** ${{ github.repository }} + **Commit:** \`${{ github.sha }}\` + **Deposited at:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.combine.outputs.critical }} | + | High | ${{ steps.combine.outputs.high }} | + | Medium | ${{ steps.combine.outputs.medium }} | + | Low | ${{ steps.combine.outputs.low }} | + | **Total**| ${{ steps.combine.outputs.total }} | + + Findings saved as \`unified-findings\` artifact. + The gitbot-fleet scanner will ingest these on its next pass. + EOF diff --git a/czech-file-knife/.github/workflows/stress-test.yml b/czech-file-knife/.github/workflows/stress-test.yml deleted file mode 100644 index 028860ff8..000000000 --- a/czech-file-knife/.github/workflows/stress-test.yml +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Stress Testing -on: - schedule: - - cron: '0 3 * * 1' # Weekly Monday 3am UTC - workflow_dispatch: -permissions: read-all -jobs: - stress-test: - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable - with: - toolchain: stable - - name: Install stress testing tools - run: | - sudo apt-get update - sudo apt-get install -y stress-ng valgrind - - name: Build release - run: cargo build --release --all-features - - name: Concurrent operations stress test - run: | - # Run binary with high concurrency - for i in {1..100}; do - timeout 1s ./target/release/* & - done - wait - - name: Memory pressure test - run: | - # Run under memory constraints - ulimit -v 512000 # 500MB virtual memory limit - cargo test --release - - name: Long-running scenario test - run: | - # Test for memory leaks over time - timeout 300s valgrind --leak-check=full --error-exitcode=1 \ - ./target/release/* || true - - name: Stress test with stress-ng - run: | - # CPU and I/O stress - stress-ng --cpu 4 --io 2 --timeout 60s & - STRESS_PID=$! - cargo test --release - kill $STRESS_PID || true diff --git a/czech-file-knife/.github/workflows/workflow-linter.yml b/czech-file-knife/.github/workflows/workflow-linter.yml new file mode 100644 index 000000000..35dfd9d5a --- /dev/null +++ b/czech-file-knife/.github/workflows/workflow-linter.yml @@ -0,0 +1,178 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# workflow-linter.yml - Validates GitHub workflows against RSR security standards +# This workflow can be copied to other repos for consistent enforcement +name: Workflow Security Linter + +on: + push: + paths: + - '.github/workflows/**' + pull_request: + paths: + - '.github/workflows/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read + +jobs: + lint-workflows: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Check SPDX Headers + run: | + echo "=== Checking SPDX License Headers ===" + failed=0 + for file in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$file" ] || continue + # actions-lock may prepend its own comment. Require the licence + # in the leading comment block, before the workflow body. + if ! awk '/^# SPDX-License-Identifier:/ { found=1 } /^[^#[:space:]]/ { exit } END { exit !found }' "$file"; then + echo "ERROR: $file missing SPDX header" + failed=1 + fi + done + if [ $failed -eq 1 ]; then + echo "Add '# SPDX-License-Identifier: MPL-2.0' to the leading comment block" + exit 1 + fi + echo "All workflows have SPDX headers" + + - name: Check Permissions Declaration + run: | + echo "=== Checking Permissions ===" + failed=0 + for file in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$file" ] || continue + if ! grep -q "^permissions:" "$file"; then + echo "ERROR: $file missing top-level 'permissions:' declaration" + failed=1 + fi + done + if [ $failed -eq 1 ]; then + echo "Add a top-level 'permissions:' block (e.g. contents: read)" + exit 1 + fi + echo "All workflows have permissions declared" + + # Bun is the estate's first-choice runtime (LANGUAGE-POLICY.adoc §1: + # Bun > Deno > pnpm > npm) and executes .ts directly (§1.2). Tag form + # matches every other ref in this repo and is resolved by actions.lock. + - name: Set up Bun + uses: oven-sh/setup-bun@v2.2.0 + with: + bun-version: latest + + - name: Check SHA-Pinned Actions + run: | + echo "=== Checking Action Pinning ===" + # Find any uses: lines that don't have @SHA format + # Pattern: uses: owner/repo@<40-char-hex> + # Delegated to scripts/check-action-pinning.js. The rule it enforces is + # unchanged in spirit — every action ref must resolve to an immutable + # commit — but "pinned" now includes refs the workflow lockfile + # resolves, which is how this repo pins them. Kept as a script rather + # than inline because the inline form needs a heredoc inside a YAML + # block scalar, and that is a well-known way to ship a gate that + # silently does nothing. + if ! bun scripts/check-action-pinning.js; then + unpinned="see above" + else + unpinned="" + fi + + if [ -n "$unpinned" ]; then + echo "ERROR: Found unpinned actions:" + echo "" + echo "Replace version tags with SHA pins, e.g.:" + echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" + exit 1 + fi + echo "All actions are SHA-pinned" + + - name: Check for Duplicate Workflows + run: | + echo "=== Checking for Duplicates ===" + # Known duplicate patterns + if [ -f .github/workflows/codeql.yml ] && [ -f .github/workflows/codeql-analysis.yml ]; then + echo "ERROR: Duplicate CodeQL workflows found" + echo "Delete codeql-analysis.yml (keep codeql.yml)" + exit 1 + fi + if [ -f .github/workflows/rust.yml ] && [ -f .github/workflows/rust-ci.yml ]; then + echo "WARNING: Potential duplicate Rust workflows" + echo "Consider consolidating rust.yml and rust-ci.yml" + fi + echo "No critical duplicates found" + + - name: Check CodeQL Language Matrix + run: | + echo "=== Checking CodeQL Configuration ===" + if [ ! -f .github/workflows/codeql.yml ]; then + echo "No CodeQL workflow found (optional)" + exit 0 + fi + + # Detect repo languages + has_js=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) + has_py=$(find . -name "*.py" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) + has_go=$(find . -name "*.go" -path "*/src/*" -o -path "*/cmd/*" -o -path "*/pkg/*" 2>/dev/null | head -1) + has_rs=$(find . -name "*.rs" -path "*/src/*" 2>/dev/null | head -1) + has_java=$(find . -name "*.java" -path "*/src/*" 2>/dev/null | head -1) + has_rb=$(find . -name "*.rb" -path "*/lib/*" -o -path "*/app/*" 2>/dev/null | head -1) + + echo "Detected languages:" + [ -n "$has_py" ] && echo " - python" + [ -n "$has_go" ] && echo " - go" + [ -n "$has_rs" ] && echo " - rust (note: CodeQL rust is limited)" + [ -n "$has_java" ] && echo " - java-kotlin" + [ -n "$has_rb" ] && echo " - ruby" + + # Check for over-reach + if grep -q "language:.*'go'" .github/workflows/codeql.yml && [ -z "$has_go" ]; then + echo "WARNING: CodeQL configured for Go but no Go files found" + fi + if grep -q "language:.*'python'" .github/workflows/codeql.yml && [ -z "$has_py" ]; then + echo "WARNING: CodeQL configured for Python but no Python files found" + fi + if grep -q "language:.*'java'" .github/workflows/codeql.yml && [ -z "$has_java" ]; then + echo "WARNING: CodeQL configured for Java but no Java files found" + fi + if grep -q "language:.*'ruby'" .github/workflows/codeql.yml && [ -z "$has_rb" ]; then + echo "WARNING: CodeQL configured for Ruby but no Ruby files found" + fi + + echo "CodeQL check complete" + + - name: Check Secrets Guards + run: | + echo "=== Checking Secrets Usage ===" + # Look for secrets without conditional guards in mirror workflows + if [ -f .github/workflows/mirror.yml ]; then + if grep -q "secrets\." .github/workflows/mirror.yml; then + if ! grep -q "if:.*vars\." .github/workflows/mirror.yml; then + echo "WARNING: mirror.yml uses secrets without vars guard" + echo "Add 'if: vars.FEATURE_ENABLED == true' to jobs" + fi + fi + fi + echo "Secrets check complete" + + - name: Summary + run: | + echo "" + echo "=== Workflow Linter Summary ===" + echo "All critical checks passed." + echo "" + echo "For more info, see: robot-repo-bot/ERROR-CATALOG.scm" diff --git a/czech-file-knife/.gitignore b/czech-file-knife/.gitignore index 73f3573f8..afbe8516a 100644 --- a/czech-file-knife/.gitignore +++ b/czech-file-knife/.gitignore @@ -9,16 +9,19 @@ Thumbs.db *~ .idea/ .vscode/ +.direnv/ + +# Agent / local session artifacts (Claude Code worktrees, scratch) — never commit +.claude/ # Build -/target/ -/_build/ -/build/ +target/ +_build/ /dist/ /out/ # Dependencies -/node_modules/ +node_modules/ /vendor/ /deps/ /.elixir_ls/ @@ -37,15 +40,10 @@ erl_crash.dump *.jl.mem /Manifest.toml -# ReScript +# /lib/bs/ /.bsb.lock -# Python (SaltStack only) -__pycache__/ -*.py[cod] -.venv/ - # Ada/SPARK *.ali /obj/ @@ -73,6 +71,13 @@ htmlcov/ *.log /logs/ +# Maintenance local artifacts +.maintenance-perms-state.tsv +docs/reports/maintenance/*.json + +# Machine-readable locks +.machine_readable/.locks/ + # Temp /tmp/ *.tmp @@ -80,6 +85,16 @@ htmlcov/ # Crash recovery artifacts ai-cli-crash-capture/ + +# KDE metadata +.directory + +# Sync artifacts +sync_report*.txt + +# Hypatia scan cache (local-only) +.hypatia/ +.zig-cache/ target/ node_modules/ _build/ @@ -88,3 +103,44 @@ deps/ .cache/ build/ dist/ + +# /build/ is a tracked config directory (build orchestration: contractile.just, +# guix.scm, just/*.just, etc.) introduced in chore/root-cleanup. Whitelist +# root-level /build/ so its contents are tracked. The blanket `build/` rule +# above still ignores any nested `build/` directories (e.g. Rust crate +# target/build/) — only the root-level path is exempt. +!/build/ +!/build/** + +# ...but never track Idris2 typecheck output. `idris2 --typecheck src/interface/abi.ipkg` +# writes compiled .ttc/.ttm under build/ttc/; these are generated artifacts. +/build/ttc/ + +# Arrival-pack build artifact (regenerated from deed) +.machine_readable/arrival-pack/claude-md-data.json + +# Coaptation atomiser artifacts (regenerated from contractiles + descriptiles); +# the receipt itself (receipts/latest.a2ml) IS committed as the drift baseline. +.machine_readable/coaptation/clauses.json +.machine_readable/coaptation/facts.json + +# Coaptation Idris2 core typecheck artifacts +.machine_readable/coaptation/core/build/ + +# Coaptation re-anchor basis (occasional, generated on --reanchor when red; not a baseline) +.machine_readable/coaptation/receipts/reanchor-basis.a2ml + +# Generated by `just cookbook` (has timestamp; non-deterministic) +docs/just-cookbook.adoc + +# Generated man pages (`just man`) +docs/man/*.1 +# Coq compiled proof artifacts +verification/proofs/coq/*.vo +verification/proofs/coq/*.vok +verification/proofs/coq/*.vos +verification/proofs/coq/*.glob +verification/proofs/coq/.*.aux +# Agda compiled proof artifacts +verification/proofs/agda/*.agdai + diff --git a/czech-file-knife/.gitlab-ci.yml b/czech-file-knife/.gitlab-ci.yml deleted file mode 100644 index 7f129180b..000000000 --- a/czech-file-knife/.gitlab-ci.yml +++ /dev/null @@ -1,163 +0,0 @@ -# GitLab CI/CD Configuration - -stages: - - lint - - test - - build - - security - - deploy -variables: - DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: "/certs" - PODMAN_USERNS: keep-id -# Default settings -default: - image: alpine:latest - before_script: - - echo "Starting job..." - after_script: - - echo "Job completed." -# Templates -.cache_template: &cache_config - cache: - key: ${CI_COMMIT_REF_SLUG} - paths: - - node_modules/ - - .cache/ - - vendor/ -# Lint stage -lint:shell: - stage: lint - image: koalaman/shellcheck-alpine:stable - script: - - shellcheck **/*.sh - only: - changes: - - "**/*.sh" -lint:yaml: - stage: lint - image: sdesbure/yamllint - script: - - yamllint -c .yamllint . - only: - changes: - - "**/*.yml" - - "**/*.yaml" -lint:markdown: - stage: lint - image: node:lts-alpine - script: - - npm install -g markdownlint-cli - - markdownlint '**/*.md' - only: - changes: - - "**/*.md" -# Test stage -test:unit: - stage: test - image: python:3.11-slim - script: - - pip install -r requirements.txt - - pytest tests/unit - coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' - artifacts: - reports: - junit: test-results.xml - coverage_report: - coverage_format: cobertura - path: coverage.xml - paths: - - htmlcov/ - expire_in: 1 week -test:integration: - stage: test - image: python:3.11-slim - services: - - postgres:latest - variables: - POSTGRES_DB: testdb - POSTGRES_USER: testuser - POSTGRES_PASSWORD: testpass - script: - - pip install -r requirements.txt - - pytest tests/integration -# Build stage -build:podman: - stage: build - image: quay.io/podman/stable - script: - - podman build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - - podman tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest - - podman login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - podman push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - - podman push $CI_REGISTRY_IMAGE:latest - only: - - main - - tags -build:binary: - stage: build - image: rust:latest - script: - - cargo build --release - artifacts: - paths: - - target/release/ - expire_in: 1 month -# Security stage -security:sast: - stage: security - image: returntocorp/semgrep - script: - - semgrep --config=auto --json --output=semgrep-report.json - artifacts: - reports: - sast: semgrep-report.json - allow_failure: true -security:dependency-scan: - stage: security - image: aquasec/trivy:latest - script: - - trivy fs --format json --output trivy-report.json . - artifacts: - reports: - dependency_scanning: trivy-report.json - allow_failure: true -security:container-scan: - stage: security - image: aquasec/trivy:latest - script: - - trivy image --format json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - only: - - main - allow_failure: true -# Deploy stage -deploy:staging: - stage: deploy - image: alpine:latest - script: - - echo "Deploying to staging..." - - apk add --no-cache curl - - curl -X POST $STAGING_WEBHOOK_URL - environment: - name: staging - url: https://staging.example.com - only: - - main -deploy:production: - stage: deploy - image: alpine:latest - script: - - echo "Deploying to production..." - - apk add --no-cache curl - - curl -X POST $PRODUCTION_WEBHOOK_URL - environment: - name: production - url: https://example.com - only: - - tags - when: manual -trufflehog: - stage: security - image: trufflesecurity/trufflehog:latest - script: - - trufflehog git file://. --only-verified --fail diff --git a/czech-file-knife/.gitleaksignore b/czech-file-knife/.gitleaksignore new file mode 100644 index 000000000..76765a36b --- /dev/null +++ b/czech-file-knife/.gitleaksignore @@ -0,0 +1,4 @@ +874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CHORA.deed.in:generic-api-key:21 +8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CHORA.deed.in:generic-api-key:21 +874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CLADE.a2ml.in:generic-api-key:21 +8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CLADE.a2ml.in:generic-api-key:21 diff --git a/czech-file-knife/.gitmessage b/czech-file-knife/.gitmessage new file mode 100644 index 000000000..ef6021d43 --- /dev/null +++ b/czech-file-knife/.gitmessage @@ -0,0 +1,18 @@ +# (): (Max 50 chars) +# |<------------------------------------------------>| + +# Explain WHY this change is being made (Max 72 chars per line) +# |<---------------------------------------------------------------------->| + +# Explain HOW this change was implemented (if not obvious) + +# [ ] Tests added/updated +# [ ] Documentation updated +# [ ] ABI/FFI boundaries verified (if applicable) + +# Issue tracking: +# Resolves: # +# See also: # +# +# --- +# Allowed Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert diff --git a/czech-file-knife/.hypatia-ignore b/czech-file-knife/.hypatia-ignore new file mode 100644 index 000000000..1722a94d1 --- /dev/null +++ b/czech-file-knife/.hypatia-ignore @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# .hypatia-ignore — scoped, documented exemptions for the Hypatia scanner. +# +# Format (one entry per line): +# /: +# /*: (any type in module) +# (any rule, any module) +# +# Lines starting with `#` are comments. A path-fragment is a substring match +# against the repo-relative file path. Built-in defaults live in Hypatia's +# lib/hypatia/scanner_suppression.ex — do not duplicate them here. Each entry +# below carries a stated reason. Prefer fix-the-code or an inline directive +# over an entry here; this file is for whole-file/directory exemptions. + +# ─── build/docs-seed/ describes DOWNSTREAM repos, not this one ───────────── +# +# `build/docs-seed/` is the canonical template of documentation that gets +# copied into scaffolded repos. Its example paths (e.g. `src/middleware/…`, +# `src/main.rs`) describe a hypothetical generated project, so they +# legitimately do not resolve against this template's own tree. SD022 +# (stale `src//` reference) is therefore a structural false positive +# for everything under this directory — mirrors the built-in CHANGELOG.md +# and third_party/ carve-outs. Scaffolded repos inherit this file, so the +# exemption travels with the template. +structural_drift/SD022:build/docs-seed/ + +# ─── Research-extension workflow findings ─────────────────────────────────────── +# +# RE001 is an advisory recommendation to add harden-runner whenever a workflow +# references a secret. These workflows deliberately use repository-locked +# actions and least-privilege permissions while retaining the outbound access +# needed for GitHub, SMTP, BoJ and SonarQube. Keep the exemptions file-scoped so +# the advisory still applies to every new workflow. +research_extensions/RE001:.github/workflows/push-email-notify.yml +research_extensions/RE001:.github/workflows/release.yml +research_extensions/RE001:.github/workflows/build-notification.yml +research_extensions/RE001:.github/workflows/dependabot-automerge.yml +research_extensions/RE001:.github/workflows/label-triage.yml +research_extensions/RE001:.github/workflows/labels.yml +research_extensions/RE001:.github/workflows/sonarqube.yml + +# RE005's matches are non-masking uses: empty grep/find results and annotation +# rendering are allowed to be non-fatal, while each workflow retains a separate +# explicit failure path for the condition it gates. Scope each exemption to the +# reviewed workflow rather than disabling RE005 repository-wide. +research_extensions/RE005:.github/workflows/security-policy.yml +research_extensions/RE005:.github/workflows/quality.yml +research_extensions/RE005:.github/workflows/dot-wellknown-enforcement.yml +research_extensions/RE005:.github/workflows/static-analysis-gate.yml + +# RE008 assumes the actor-name comparison is used with pull_request_target. The +# Dependabot workflow uses the unprivileged pull_request event and also verifies +# github.event.pull_request.user.login, so that threat model does not apply. +research_extensions/RE008:.github/workflows/dependabot-automerge.yml diff --git a/czech-file-knife/.machine_readable/AGENTIC.scm b/czech-file-knife/.machine_readable/AGENTIC.scm deleted file mode 100644 index adfd5124c..000000000 --- a/czech-file-knife/.machine_readable/AGENTIC.scm +++ /dev/null @@ -1,16 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; AGENTIC.scm - AI agent interaction patterns for czech-file-knife - -(define agentic-config - `((version . "1.0.0") - (claude-code - ((model . "claude-opus-4-5-20251101") - (tools . ("read" "edit" "bash" "grep" "glob")) - (permissions . "read-all"))) - (patterns - ((code-review . "thorough") - (refactoring . "conservative") - (testing . "comprehensive"))) - (constraints - ((languages . ()) - (banned . ("typescript" "go" "python" "makefile")))))) diff --git a/czech-file-knife/.machine_readable/ECOSYSTEM.scm b/czech-file-knife/.machine_readable/ECOSYSTEM.scm deleted file mode 100644 index 23256f3b3..000000000 --- a/czech-file-knife/.machine_readable/ECOSYSTEM.scm +++ /dev/null @@ -1,20 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; ECOSYSTEM.scm - Ecosystem position for czech-file-knife -;; Media-Type: application/vnd.ecosystem+scm - -(ecosystem - (version "1.0") - (name "czech-file-knife") - (type "") - (purpose "") - - (position-in-ecosystem - (category "") - (subcategory "") - (unique-value ())) - - (related-projects ()) - - (what-this-is ()) - - (what-this-is-not ())) diff --git a/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml b/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml new file mode 100644 index 000000000..082ebab39 --- /dev/null +++ b/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# ENSAID_CONFIG.a2ml — eNSAID Environment Configuration +# Per-repo configuration for PanLL and eNSAID-compatible tools. +# +# Canonical location: .machine_readable/ENSAID_CONFIG.a2ml +# Spec: https://github.com/hyperpolymath/standards/tree/main/ensaid-config +# +# Naming convention: +# - UPPERCASE + underscore = non-executable machine-readable file +# - Lives in .machine_readable/ alongside STATE.a2ml, META.a2ml, etc. + +# ───────────────────────────────────────────────────────────────── +# [ensaid] — Core eNSAID identity and version +# ───────────────────────────────────────────────────────────────── +[ensaid] +version = "1.0.0" +tool = "panll" + +# ───────────────────────────────────────────────────────────────── +# [workspace] — Workspace mode, protection, and execution policy +# ───────────────────────────────────────────────────────────────── +[workspace] +mode = "rhodium" # rhodium | gold | silver | bronze +protection = "open" # open | guarded | locked +execution = "live" # live | dry-run | approval-required + +# ───────────────────────────────────────────────────────────────── +# [preferences] — User/repo-level display and behaviour preferences +# ───────────────────────────────────────────────────────────────── +[preferences] +humidity = "medium" # high | medium | low (drift aura intensity) +default-arrangement = "default-3-panel" # workspace arrangement ID +auto-connect = true # auto-connect panels to backends on load + +# ───────────────────────────────────────────────────────────────── +# [panels] — Panel visibility, enablement, and isolation overrides +# ───────────────────────────────────────────────────────────────── +[panels] +version = "1.0.0" + +# By default, all panels are available. Uncomment to restrict: +# [[panels.enabled]] +# id = "valence-shell" +# isolation = "native" +# auto-connect = true +# +# [[panels.enabled]] +# id = "editor-bridge" +# isolation = "native" +# auto-connect = true + +# Panels to hide for this repo context: +# [panels.disabled] +# ids = [] + +# ───────────────────────────────────────────────────────────────── +# [workflows] — Automation Router event-driven cross-panel rules +# ───────────────────────────────────────────────────────────────── +[workflows] +version = "1.0.0" + +# Example: rebuild on file save +# [[workflows.rule]] +# name = "build-on-save" +# trigger = { event = "file-changed", pattern = "src/**/*.res" } +# condition = { panel = "build-dashboard", field = "watchMode", equals = true } +# action = { panel = "build-dashboard", message = "TriggerBuild", args = { target = "game" } } +# approval = "auto-fire" # auto-fire | require-approval | approve-once | dry-run-first + +# ───────────────────────────────────────────────────────────────── +# [clades] — Panel clade trait and capability overrides +# ───────────────────────────────────────────────────────────────── +[clades] +version = "1.0.0" + +# Example: add a custom capability to a panel clade +# [[clades.override]] +# id = "build-dashboard" +# traits = { has-work-items = true } +# capabilities-add = ["CustomCheck"] + +# ───────────────────────────────────────────────────────────────── +# [portfolios] — Custom panel bundles for this repo's workflow +# ───────────────────────────────────────────────────────────────── +[portfolios] +version = "1.0.0" + +# Example: a custom portfolio for this project +# [[portfolios.custom]] +# id = "czech_file_knife-dev" +# name = "Czech File Knife Development" +# description = "Panels for Czech File Knife development" +# panels = ["valence-shell", "editor-bridge", "build-dashboard"] +# default-isolation = "native" diff --git a/czech-file-knife/.machine_readable/META.scm b/czech-file-knife/.machine_readable/META.scm deleted file mode 100644 index 30d021f16..000000000 --- a/czech-file-knife/.machine_readable/META.scm +++ /dev/null @@ -1,17 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; META.scm - Meta-level information for czech-file-knife -;; Media-Type: application/meta+scheme - -(meta - (architecture-decisions ()) - - (development-practices - (code-style ()) - (security - (principle "Defense in depth")) - (testing ()) - (versioning "SemVer") - (documentation "AsciiDoc") - (branching "main for stable")) - - (design-rationale ())) diff --git a/czech-file-knife/.machine_readable/NEUROSYM.scm b/czech-file-knife/.machine_readable/NEUROSYM.scm deleted file mode 100644 index a03523206..000000000 --- a/czech-file-knife/.machine_readable/NEUROSYM.scm +++ /dev/null @@ -1,13 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; NEUROSYM.scm - Neurosymbolic integration config for czech-file-knife - -(define neurosym-config - `((version . "1.0.0") - (symbolic-layer - ((type . "scheme") - (reasoning . "deductive") - (verification . "formal"))) - (neural-layer - ((embeddings . false) - (fine-tuning . false))) - (integration . ()))) diff --git a/czech-file-knife/.machine_readable/PLAYBOOK.scm b/czech-file-knife/.machine_readable/PLAYBOOK.scm deleted file mode 100644 index 61d0f8e45..000000000 --- a/czech-file-knife/.machine_readable/PLAYBOOK.scm +++ /dev/null @@ -1,13 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; PLAYBOOK.scm - Operational runbook for czech-file-knife - -(define playbook - `((version . "1.0.0") - (procedures - ((deploy . (("build" . "just build") - ("test" . "just test") - ("release" . "just release"))) - (rollback . ()) - (debug . ()))) - (alerts . ()) - (contacts . ()))) diff --git a/czech-file-knife/.machine_readable/PROVENANCE.a2ml b/czech-file-knife/.machine_readable/PROVENANCE.a2ml new file mode 100644 index 000000000..1bffb57ac --- /dev/null +++ b/czech-file-knife/.machine_readable/PROVENANCE.a2ml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# PROVENANCE.a2ml — what this repo was minted FROM. +# Written once, at mint, by build/just/repo-init.just. Not a hand-edited +# file: tools that want "what is this repo" read STATE.a2ml; tools that +# want "where did this repo come from" read this. + +[provenance] +minted_at = "2026-09-28" + +template_repo = "hyperpolymath/rsr-template-repo" +template_branch = "master" +template_commit = "933507582ef2467d77541dbf2837d278d321db1b" +template_tree = "42883b5a9d3e4dcf1b2cdc69ecf820425b4f39b7" +# Contract: branches this repo is EXPECTED to carry beyond the default. +# Empty means default-only. A mint must never inherit template work +# branches (coderabbit/, chore/, bot-task or stale branches) — that is +# how knot-knot received a byte-identical copy of a template work branch +# with no common ancestor (#203). Enforced by +# scripts/check-template-conformance.sh. +extra_branches = [] + +canon_version = "2.1.1" +criteria_sha256 = "6a5aa8857bd0d0d58ef48827938ca17c251b6b854dacf59d306388d61694d82a" +gates_sha256 = "e70efd2f53c9445e30da4baf770366f04a4a84ffd844a01426e587e565b53e6a" + +archetype = "" +# Which minting path actually ran. Hardcoded as "hand" before this, so +# every scripted mint claimed to be hand-minted — which destroyed the +# one signal that would have told #201/#203 which path to trust. +minted_by = "repo-init" +mint_mode = "interactive" + +[substitutions] +# ADR-0003 records the old state honestly: "renders 34 substitutions and +# derives identity, but leaves 12 tokens UNASSIGNED". Listing them here +# converts that from a known defect into a QUERYABLE one: a non-empty +# list means the mint did not fully render, and the validation step +# below already fails on a leftover token. +rendered = 34 +unassigned = [] diff --git a/czech-file-knife/.machine_readable/README.adoc b/czech-file-knife/.machine_readable/README.adoc new file mode 100644 index 000000000..fd6b71971 --- /dev/null +++ b/czech-file-knife/.machine_readable/README.adoc @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += machine-readable Pillar +:toc: + +This pillar holds the repository's machine-readable metadata: the records +that let tools and agents read this project's state, boundaries and +obligations without parsing prose. + +[IMPORTANT] +==== +*Single normative source.* The grammar for the record family in this pillar +is `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in +https://github.com/hyperpolymath/standards[hyperpolymath/standards]. That +document is the only place the syntax, semantics and typechecking rules are +defined. Every other mention of the format across the estate — including +every README in this tree — is a *pointer* to it, never a restatement. + +The format formerly called A2ML is now *DEED* (`.deed`). Files here still +carry `.a2ml` pending a single atomic estate-wide rename; do not +hand-convert them. +==== + +== Contents + +`descriptiles/`:: What this repository *is* and what state it is *in* — +`META`, `STATE`, `ECOSYSTEM`, `PLAYBOOK`, `AGENTIC`, `NEUROSYM`, plus the +anchors and the facet record. See `descriptiles/README.adoc`. + +`contractiles/`:: What this repository *ought* to do — the Must, Trust, +Adjust, Intend, Bust and Dust verb sets, each with its declaration, its +Nickel runner and its k9 service-automation component. See +`contractiles/README.adoc`. + +`arrival-pack/`:: The entry pack for agents visiting this repository. + +`policies/`:: Maintenance axes and related policy records. + +`rsr-profile.a2ml`:: This repository's declared RSR v2.0 capabilities, +which gate which conformance criteria apply to it. diff --git a/czech-file-knife/.machine_readable/STATE.scm b/czech-file-knife/.machine_readable/STATE.scm deleted file mode 100644 index bde217b68..000000000 --- a/czech-file-knife/.machine_readable/STATE.scm +++ /dev/null @@ -1,39 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; STATE.scm - Project state for czech-file-knife -;; Media-Type: application/vnd.state+scm - -(state - (metadata - (version "0.0.1") - (schema-version "1.0") - (created "2026-01-03") - (updated "2026-02-01") - (project "czech-file-knife") - (repo "github.com/hyperpolymath/czech-file-knife")) - - (project-context - (name "czech-file-knife") - (tagline "") - (tech-stack ())) - - (current-position - (phase "initial") - (overall-completion 0) - (components ()) - (working-features ())) - - (route-to-mvp - (milestones ())) - - (blockers-and-issues - (critical) - (high) - (medium) - (low)) - - (critical-next-actions - (immediate) - (this-week) - (this-month)) - - (session-history ())) diff --git a/czech-file-knife/.machine_readable/ai/AI.a2ml b/czech-file-knife/.machine_readable/ai/AI.a2ml new file mode 100644 index 000000000..d728f6027 --- /dev/null +++ b/czech-file-knife/.machine_readable/ai/AI.a2ml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: MPL-2.0 + +# AI Assistant Instructions + +## Repository Focus +- `czech-file-knife` is treated as a Rhodium Standard Repository; obey the Rhodium policies and keep `.machine_readable/` authoritative. +- All machine-readable content lives under `.machine_readable/` — state files (a2ml), bot directives, and contractiles. +- Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. + +## Workflow +1. Inspect `.machine_readable/descriptiles/STATE.a2ml` for blockers and next actions. +2. Respect any constraints listed inside `.machine_readable/descriptiles/AGENTIC.a2ml` when tooling changes are requested. +3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. + +## Delivery Promises +- Mention in summaries whether STATE, `.machine_readable/contractiles/`, or `.machine_readable/bot_directives/` changed. +- Keep this file in sync with the repository's status; update it if the governance changes. + +[foundational-integrations] +# These tools form the architectural floor for all hyperpolymath repos. +# Integrate them incrementally as the project matures. + +proven = "Formally verified safety library — replace raw string/JSON/URL/crypto ops" +panic-attacker = "Static analysis — run `just assail` before every commit" +feedback-o-tron = "Autonomous bug filing — auto-report upstream dependency failures" +verisimdb = "Cross-repo analytics — emit scan results and build metrics" +vexometer = "Irritation surface analysis — measure friction in CLI/UI tools" +hypatia = "Neurosymbolic CI/CD — safety-triangle routing for automated fixes" +boj-server = "MCP gateway — all external service integrations route through BoJ" + +[floor-raise-checklist] +step-1 = "Ensure the repo deed (_chora.deed) exists at repo root" +step-2 = "Ensure Mustfile.a2ml + Trustfile.a2ml + Dustfile.a2ml in .machine_readable/contractiles/" +step-3 = "Add `assail` recipe to Justfile and wire into pre-commit hook" +step-4 = "Add proven integration reference in .machine_readable/integrations/" +step-5 = "Add verisimdb feed configuration" +step-6 = "Add feedback-o-tron integration for upstream reporting" +step-7 = "Add vexometer hooks for friction measurement (CLI/UI repos)" diff --git a/czech-file-knife/.machine_readable/ai/README.adoc b/czech-file-knife/.machine_readable/ai/README.adoc new file mode 100644 index 000000000..a5dec5991 --- /dev/null +++ b/czech-file-knife/.machine_readable/ai/README.adoc @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += AI Guidance Directory + +Put AI-facing instructions in this folder. + +Examples: + +* `CLAUDE.md` +* `COPILOT.md` +* `GEMINI.md` +* `AI.a2ml` +* `AI.djot` + +Avoid scattering agent instruction files around the repo root. + +Recommended machine read order: + +* `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` +* `.machine_readable/policies/MAINTENANCE-AXES.a2ml` +* `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` +* `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` +* `.machine_readable/descriptiles/STATE.a2ml` +* `.machine_readable/descriptiles/META.a2ml` diff --git a/czech-file-knife/.machine_readable/arrival-pack/README.adoc b/czech-file-knife/.machine_readable/arrival-pack/README.adoc new file mode 100644 index 000000000..a48f9fd9f --- /dev/null +++ b/czech-file-knife/.machine_readable/arrival-pack/README.adoc @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Arrival Pack — the CLAUDE.md compiler + +`CLAUDE.md` is the one file an agent auto-loads on arrival, so it is this repo's +*boundary document*. It is compiled, not hand-written, so it cannot drift from +the repo's own machine-readable truth. + +== How it works + +[source] +---- +a2ml (descriptiles) extract.sh arrival-pack.ncl CLAUDE.md +CLADE/ECOSYSTEM/ ──▶ (a2ml → JSON, ──▶ (Nickel projection ──▶ region between +AGENTIC/STATE/ANCHOR the thin reader) = the engine) BEGIN/END markers +---- + +Two halves of the generated region: + +* *Estate-common* — identical in every repo; the Manifesto doctrine + format-family + orientation + canon pointers + language policy. Pinned to a Manifesto SHA once + the wording is ratified (today: `DRAFT-unratified`). +* *Repo-specific* — projected from this repo's a2ml: identity, IS / IS-NOT, + position, constraints, golden path, state. + +The a2ml files remain the single source of truth. CLAUDE.md is a *view*. Content +outside the `ARRIVAL-PACK:BEGIN/END` markers is hand-authorable and preserved. + +== Files + +[cols="1,3"] +|=== +| `extract.sh` | a2ml reader → deterministic `claude-md-data.json` (build artifact, git-ignored) +| `arrival-pack.ncl` | Nickel projection engine → the region string (`nickel export --format raw`) +| `generate.sh` | orchestrator: extract → render → splice (Hunt-tier, writes CLAUDE.md) +| `verify.sh` | drift check: regenerate + byte-compare committed region (Yard-tier) +| `claude-md.k9.ncl` | k9 contract for the drift check +|=== + +== Usage + +[source,console] +---- +just claude-md # (re)generate CLAUDE.md from a2ml +just validate-claude-md # fail if the committed region drifted / was hand-edited +---- + +== Status / TODO + +* Estate-common doctrine is a *DRAFT* in the agent's words — owner to ratify into + his voice, then pin to a `hyperpolymath/manifesto` commit SHA. +* `ECOSYSTEM.what-this-is-not` is empty here, so IS-NOT renders as "not yet + declared". Populating it is the boundary-erosion fix (the `lith` lesson). +* `ANCHOR.a2ml` currently encodes semantic-authority + golden-path; the + recognised-drift / re-anchor-ledger aspect is a pending standards change. diff --git a/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl b/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl new file mode 100644 index 000000000..032499051 --- /dev/null +++ b/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# arrival-pack.ncl — the projection ENGINE for the CLAUDE.md arrival pack. +# +# Pure Nickel. Imports the deterministic JSON produced by extract.sh (the a2ml +# reader) and renders the generated CLAUDE.md region as a single string. +# Emit with: nickel export --format raw --file arrival-pack.ncl +# +# Two halves (see ARRIVAL-PACK-DESIGN): +# A. estate-common — identical everywhere; DRAFT doctrine, pinned to a +# Manifesto SHA once ratified (today: DRAFT-unratified). +# B. repo-specific — projected from this repo's a2ml (data.*). +# +# The a2ml files remain the single source of truth; this is a VIEW. Hand-editing +# the emitted region is a flagged error (see claude-md.k9.ncl drift check). +let data = import "claude-md-data.json" in + +# Pin to a Manifesto commit once the doctrine wording is ratified into the +# owner's voice. Until then the estate-common block is explicitly DRAFT. +let manifesto_pin = "DRAFT-unratified" in + +let provenance = "CLADE@%{data.h_clade} ECOSYSTEM@%{data.h_eco} AGENTIC@%{data.h_agentic} STATE@%{data.h_state} ANCHOR@%{data.h_anchor}" +in + +m%" + + +# You are in the hyperpolymath estate — orient before acting + +If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. + +## Doctrine (the rules here) +1. **Holes before anything else** — fix soundness holes before features/perf/docs. +2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. +3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. +4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. +5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. +6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. +7. **No deletion by access-recency** — cold ≠ disposable. +8. **Wire first** — unwired is not done. +9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). +10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). +11. **Stop-first** when an action is costly to undo or outward-facing. +12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. +13. **Equivalence as identity** — the estate's intellectual through-line. +14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. +15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. + +## The machine-readable substrate (read in this order on arrival) +**CLADE** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** + +The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. + +| File | Answers | +|---|---| +| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | +| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | +| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | +| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | +| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | +| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | +| `PLAYBOOK.a2ml` | *How* permitted actions run. | +| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | +| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | +| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | + +## Canon pointers +- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. +- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** + +## Estate language policy (overridable per-repo via AGENTIC) +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (grandfathered) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. + +--- + +# This repo: `%{data.canonical_name}` · clade `%{data.prefixed_name}` + +- **Identity** — uuid `%{data.uuid}`; clade `%{data.clade_primary}` (secondary `%{data.clade_secondary}`); born %{data.born}; forge `%{data.forge_gh}`. +- **IS** — %{data.purpose} +- **IS-NOT** — %{data.isnot} +- **Where it sits** — pipeline position **%{data.pipeline_pos}**; chain `%{data.chain}`; coordination = `%{data.coordination}`. +- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. +- **Golden path** (ANCHOR) — `%{data.golden_smoke}` → %{data.golden_crit}. +- **State** — phase %{data.phase}; maturity %{data.maturity}; %{data.completion}% complete; status %{data.status}. + + +"% diff --git a/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl b/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl new file mode 100644 index 000000000..d3fbb1cff --- /dev/null +++ b/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl @@ -0,0 +1,59 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# claude-md.k9.ncl — k9 contract for the CLAUDE.md arrival-pack drift check. +# +# Yard-tier (pure evaluation / comparison; no exec, no network, no FS write): +# the check regenerates the arrival-pack region from this repo's a2ml and asserts +# the committed CLAUDE.md region byte-matches it. The runnable side is verify.sh; +# writing (Hunt-tier) is generate.sh. This record documents and validates the +# contract shape. +{ + pedigree = { + schema_version = "1.0.0", + contractile_verb = "trust", # provenance: the view must equal its source + semantics = "projection-fidelity", + security = { + leash = 'Yard, + trust_level = "read-only comparison", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, # runs extract.sh + nickel to reproduce the view + }, + metadata = { + name = "claude-md-arrival-pack", + version = "1.0.0", + description = "CLAUDE.md arrival pack is a faithful projection of this repo's a2ml.", + paired_runner = "verify.sh", + writer = "generate.sh", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Inputs the projection reads (single source of truth). + inputs = { + descriptiles = [ + ".machine_readable/descriptiles/CLADE.a2ml", + ".machine_readable/descriptiles/ECOSYSTEM.a2ml", + ".machine_readable/descriptiles/AGENTIC.a2ml", + ".machine_readable/descriptiles/STATE.a2ml", + ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", + ], + estate_common = "hyperpolymath/manifesto@", # DRAFT-unratified today + target = "CLAUDE.md", + markers = { begin = "" + echo "" + echo "" + echo + cat "$AP/.region.tmp" + } > "$TARGET" +fi + +rm -f "$AP/.region.tmp" +echo "claude-md: wrote $TARGET" diff --git a/czech-file-knife/.machine_readable/arrival-pack/verify.sh b/czech-file-knife/.machine_readable/arrival-pack/verify.sh new file mode 100755 index 000000000..1d90acf32 --- /dev/null +++ b/czech-file-knife/.machine_readable/arrival-pack/verify.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# verify.sh — drift check for the CLAUDE.md arrival pack (the runnable side of +# claude-md.k9.ncl). Regenerates the region from a2ml and byte-compares it to the +# committed region. Non-zero exit on drift or hand-edit. Wire into CI/pre-commit. +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +AP="$ROOT/.machine_readable/arrival-pack" +TARGET="$ROOT/CLAUDE.md" + +[ -f "$TARGET" ] || { echo "DRIFT: CLAUDE.md missing — run \`just claude-md\`"; exit 1; } + +bash "$AP/extract.sh" "$ROOT/.machine_readable/descriptiles" > "$AP/claude-md-data.json" +fresh="$(nickel export --format raw "$AP/arrival-pack.ncl")" +committed="$(awk '/\n +""" +# Template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/hyperpolymath/czech-file-knife +{%- endmacro -%} + +{% if version -%} + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else -%} + ## [Unreleased] +{% endif -%} + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim }} + {% for commit in commits %} + - {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\ + {% if commit.breaking %}[**BREAKING**] {% endif %}\ + {{ commit.message | upper_first }}\ + {%- if commit.links %} \ + ({% for link in commit.links %}[{{ link.text }}]({{ link.href }}){% endfor %}){% endif -%} + {% endfor %} +{% endfor %} + +{%- if github -%} +{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} + ### New Contributors +{%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} + * @{{ contributor.username }} made their first contribution + {%- if contributor.pr_number %} in \ + [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) + {%- endif %} +{%- endfor %} +{% endif -%} +{% endif -%} + +""" +# Template for the changelog footer +footer = """ +{%- macro remote_url() -%} + https://github.com/hyperpolymath/czech-file-knife +{%- endmacro -%} + +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }} + {% endif -%} + {% else -%} + {% if release.previous.version -%} + [Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD + {% endif -%} + {% endif -%} +{% endfor %} + +""" +# Remove leading and trailing whitespace from templates +trim = true + +[git] +# Parse conventional commits +# https://www.conventionalcommits.org +conventional_commits = true +# Filter out unconventional commits +filter_unconventional = true +# Process each line of a commit as an individual commit +split_commits = false +# Regex for commit preprocessing +commit_preprocessors = [ + # Remove issue numbers from commit messages + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, +] +# Regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^security", group = "Security" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactoring" }, + { message = "^docs", group = "Documentation" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^ci", group = "CI/CD" }, + { message = "^chore\\(release\\)", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore", group = "Miscellaneous" }, + { body = ".*security", group = "Security" }, +] +# Protect breaking changes from being skipped by a commit parser +protect_breaking_commits = false +# Filter out merge commits +filter_merge_commits = true +# Filter out commits by tag pattern (skip pre-releases) +# tag_pattern = "v[0-9].*" +# Regex for skipping tags +# skip_tags = "beta|alpha" +# Sort commits within each group by oldest first +sort_commits = "oldest" diff --git a/czech-file-knife/.machine_readable/configs/stapeln.toml b/czech-file-knife/.machine_readable/configs/stapeln.toml new file mode 100644 index 000000000..73ed2bdcc --- /dev/null +++ b/czech-file-knife/.machine_readable/configs/stapeln.toml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: MPL-2.0 +# stapeln.toml — Layer-based container build for czech-file-knife +# +# stapeln builds containers as composable layers (German: "to stack"). +# Each layer is independently cacheable, verifiable, and signable. + +[metadata] +name = "czech-file-knife" +version = "0.1.0" +description = "czech-file-knife container service" +author = "Jonathan D.A. Jewell " +license = "MPL-2.0" +registry = "ghcr.io/hyperpolymath" + +[build] +containerfile = "Containerfile" +context = "." +runtime = "podman" + +# ── Layer Definitions ────────────────────────────────────────── + +[layers.base] +description = "Chainguard Wolfi minimal base" +from = "cgr.dev/chainguard/wolfi-base:latest" +cache = true +verify = true + +[layers.toolchain] +description = "Build tools" +extends = "base" +packages = [] +cache = true + +[layers.build] +description = "czech-file-knife build" +extends = "toolchain" +commands = [] + +[layers.runtime] +description = "Minimal runtime" +from = "cgr.dev/chainguard/wolfi-base:latest" +packages = ["ca-certificates", "curl"] +copy-from = [ + { layer = "build", src = "/app/", dst = "/app/" }, +] +entrypoint = ["/app/czech-file-knife"] +user = "nonroot" + +# ── Security ─────────────────────────────────────────────────── + +[security] +non-root = true +read-only-root = false +no-new-privileges = true +cap-drop = ["ALL"] +seccomp-profile = "default" + +[security.signing] +algorithm = "ML-DSA-87" +provider = "cerro-torre" + +[security.sbom] +format = "spdx-json" +output = "sbom.spdx.json" +include-deps = true + +# ── Verification ─────────────────────────────────────────────── + +[verify] +vordr = true +svalinn = true +scan-on-build = true +fail-on = ["critical", "high"] + +# ── Targets ──────────────────────────────────────────────────── + +[targets.development] +layers = ["base", "chainguard-toolchain", "build"] +env = { LOG_LEVEL = "debug" } + +[targets.production] +layers = ["runtime"] +env = { LOG_LEVEL = "info" } + +[targets.test] +layers = ["base", "chainguard-toolchain", "build"] +env = { LOG_LEVEL = "debug" } diff --git a/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml b/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml new file mode 100644 index 000000000..148ecfcb0 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: MPL-2.0 +# INDEX.a2ml — Contractile Registry +# Author: Jonathan D.A. Jewell +# +# Machine-readable catalogue of all contractile verbs in this template set. +# Consumers (CI scripts, the contractile CLI, Hypatia rules) SHOULD read this +# file to discover available verbs rather than hard-coding the list. +# +# See: docs/CONTRACTILE-SPEC.adoc §Registry + +--- +id = "contractiles-registry" +version = "2.0.0" # 2.0.0 (2026-04-18): all 6 verbs on trident shape; verb set complete. +spec = "https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc" +last_updated = "2026-04-18" +base_schema = ".machine_readable/contractiles/_base.ncl" +meta_schema_status = "pending — see CONTRACTILE-SPEC §validator-meta-schema" + +## Verbs + +[[verbs]] +name = "adjust" +semantics = "drift tolerances + corrective actions" +trident = [ + "adjust/Adjustfile.a2ml", + "adjust/adjust.ncl", + "adjust/adjust.k9.ncl", +] +manifest = "adjust/adjust.manifest.a2ml" +status = "active" +tier = "Yard" +authority = "advisory" +gating = "advisory (continue-with-warnings)" +cardinality = "one per repo" +notes = "Fifth trident instance (2026-04-18). First (Yard, advisory) authority pattern. Specialises in cumulative-drift catchment — tolerance bands + trend tracking across sessions. auto_fix_when_available applies deterministic patches; advisory otherwise." + +[[verbs]] +name = "bust" +semantics = "hard-stop / expiry / must-not-run declarations" +trident = [ + "bust/Bustfile.a2ml", + "bust/bust.ncl", + "bust/bust.k9.ncl", +] +manifest = "bust/bust.manifest.a2ml" +status = "active" +tier = "Hunt-read-only" +authority = "blocking" +gating = "hard (exit-nonzero)" +cardinality = "one per repo" +notes = "Fourth trident instance (2026-04-18). Completes the blocking-authority triple (must + trust + bust). Specialises in deprecated-path-reintroduction catchment. Injects failures via declared probes and verifies recovery paths." + +[[verbs]] +name = "dust" +semantics = "rollback / recovery / deprecation / audit-trail preservation" +trident = [ + "dust/Dustfile.a2ml", + "dust/dust.ncl", + "dust/dust.k9.ncl", +] +manifest = "dust/dust.manifest.a2ml" +status = "active" +tier = "Yard" +authority = "advisory" +gating = "advisory (continue-with-warnings)" +cardinality = "one per repo" +notes = "Sixth and FINAL trident instance (2026-04-18) — completes the full verb set. Specialises in audit-trail preservation + rollback-path verification. Destructive actions gated behind --apply flag + per-item approval; dry-run default." + +[[verbs]] +name = "intend" +semantics = "north-star (commitments + aspirations)" +trident = [ + "intend/Intentfile.a2ml", + "intend/intend.ncl", + "intend/intend.k9.ncl", +] +manifest = "intend/intend.manifest.a2ml" +status = "active" +tier = "Hunt" +authority = "reporting" +gating = "non-gating (continue)" +cardinality = "one per repo" +notes = "First trident instance in the estate (2026-04-18). Reports progress toward committed next-actions AND lists horizon aspirations. Absorbed the deprecated `lust` verb 2026-04-18. Never blocks. Remaining 5 verbs still on file_pair shape until tridents are built." + +[[verbs]] +name = "k9" +semantics = "trust-tier templates (EXCEPTION to one-verbfile rule)" +file_pair = [ + "k9/template-hunt.k9.ncl", + "k9/template-kennel.k9.ncl", + "k9/template-yard.k9.ncl", +] +status = "exception" +gating = "not applicable" +notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Three trust-tier templates (Kennel/Yard/Hunt). Does not have a Verbfile.a2ml. See CONTRACTILE-SPEC §k9-exception." + +# [[verbs]] lust REMOVED 2026-04-18 — name had unwanted associations; +# the horizon/aspiration semantics were always meant to live inside `intend` +# (the north-star verb). The [[wishes]] schema was absorbed into +# intend/Intentfile.a2ml. Any `lust/` dir found in an estate repo is drift +# and should be deleted. + +[[verbs]] +name = "must" +semantics = "invariant assertion — release-blocking" +trident = [ + "must/Mustfile.a2ml", + "must/must.ncl", + "must/must.k9.ncl", +] +manifest = "must/must.manifest.a2ml" +status = "active" +tier = "Hunt-read-only" +authority = "blocking" +gating = "hard (exit-nonzero)" +cardinality = "one per repo" +notes = "Third trident instance (2026-04-18). Completes the blocking-authority pair with trust: must = concrete + persistent invariants; trust = concrete + ephemeral transactions. Specialises in subtle invariant-erosion (tracking per-session trend; flagging silent regression). Single failure blocks merge. Simplest and most commonly populated verb." + +[[verbs]] +name = "trust" +semantics = "security + provenance + safe-hacking" +trident = [ + "trust/Trustfile.a2ml", + "trust/trust.ncl", + "trust/trust.k9.ncl", +] +manifest = "trust/trust.manifest.a2ml" +status = "active" +tier = "Hunt" +authority = "blocking" +gating = "hard (exit-nonzero)" +cardinality = "one per repo" +notes = "Second trident instance (2026-04-18). First (Hunt, blocking) verb — hard gate. Primary defense against threat-model misclassification (B1) and 'turn off the firewall' capability-collapse (C2). Inherits on_open negotiation+accountability+translation from intend.k9.ncl v2.0.0; adds threat_model_foregrounding + block_session_close_on_critical_drift." diff --git a/czech-file-knife/.machine_readable/contractiles/Justfile b/czech-file-knife/.machine_readable/contractiles/Justfile new file mode 100644 index 000000000..1dd3994b2 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/Justfile @@ -0,0 +1,720 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard Justfile Template +# https://just.systems/man/en/ +# +# Copy this file to new projects and customize the placeholder values. +# +# Run `just` to see all available recipes +# Run `just cookbook` to generate docs/just-cookbook.adoc +# Run `just combinations` to see matrix recipe options + +set shell := ["bash", "-uc"] +set dotenv-load := true +set positional-arguments := true + +# Import auto-generated contractile recipes (must-check, trust-verify, etc.) +# Re-generate with: contractile gen-just +import? "build/contractile.just" + +# Project metadata — customize these +project := "czech-file-knife" +OWNER := "hyperpolymath" +REPO := "czech-file-knife" +version := "0.1.0" +tier := "infrastructure" # 1 | 2 | infrastructure + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEFAULT & HELP +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show all available recipes with descriptions +default: + @just --list --unsorted + +# Show detailed help for a specific recipe +help recipe="": + #!/usr/bin/env bash + if [ -z "{{recipe}}" ]; then + just --list --unsorted + echo "" + echo "Usage: just help " + echo " just cookbook # Generate full documentation" + echo " just combinations # Show matrix recipes" + else + just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found" + fi + +# Show this project's info +info: + @echo "Project: czech_file_knife" + @echo "Version: {{version}}" + @echo "RSR Tier: {{tier}}" + @echo "Recipes: $(just --summary | wc -w)" + @[ -f ".machine_readable/descriptiles/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/descriptiles/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true + +# Run Invariant Path overlay tools for this repository +invariant-path *ARGS: + ./scripts/invariant-path.sh {{ARGS}} + +# ═══════════════════════════════════════════════════════════════════════════════ +# INIT — see build/just/repo-init.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/repo-init.just" + +# >>> container-module (three-tier: OCI · portable engine · stapeln) >>> +# Self-contained. Remove the entire block — this and the import — with `just no-container`. +import? "build/just/container.just" +# <<< container-module <<< + +# ═══════════════════════════════════════════════════════════════════════════════ +# GROOVE PROTOCOL — see build/just/groove.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/groove.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE — see build/just/assess.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/assess.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# BUILD & COMPILE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build the project (debug mode) +build *args: + @echo "Building czech_file_knife (debug)..." + # TODO: Replace with your build command + # Examples: + # cargo build {{args}} # Rust + # mix compile {{args}} # Elixir + # zig build {{args}} # Zig + # deno task build {{args}} # Deno/ + @echo "Build complete" + +# Build in release mode with optimizations +build-release *args: + @echo "Building czech_file_knife (release)..." + # TODO: Replace with your release build command + # Examples: + # cargo build --release {{args}} + # MIX_ENV=prod mix compile {{args}} + # zig build -Doptimize=ReleaseFast {{args}} + @echo "Release build complete" + +# Build and watch for changes (requires entr or similar) +build-watch: + @echo "Watching for changes..." + # TODO: Customize file patterns for your language + # Examples: + # find src -name '*.rs' | entr -c just build + # mix compile --force --warnings-as-errors + # deno task dev + +# Clean build artifacts [reversible: rebuild with `just build`] +clean: + @echo "Cleaning..." + # TODO: Customize for your build system + # + # `build/` is DELIBERATELY ABSENT from this list. It is not an artifact + # directory in an RSR repo: it holds 11 tracked files, including + # build/just/repo-init.just, which the root Justfile imports at line 65. + # Deleting it destroys `just repo-init`, `just verify` and the proof gates. + rm -rf target/ _build/ dist/ out/ obj/ bin/ + +# Deep clean including caches [reversible: rebuild] +clean-all: clean + rm -rf .cache .tmp + +# ═══════════════════════════════════════════════════════════════════════════════ +# TEST & QUALITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run all tests +test *args: + #!/usr/bin/env bash + # A check that cannot fail is not a check. This recipe MUST be replaced at + # mint with the project's real test command; until then it fails loudly + # rather than printing "Tests passed!" over an empty run. + # + # Replace this whole body with one of: + # cargo test --workspace {{args}} + # mix test {{args}} + # zig build test {{args}} + # deno test {{args}} + echo "FAIL: \`just test\` has not been wired to a real test command yet." >&2 + echo " Edit the 'test' recipe in the Justfile before relying on this gate." >&2 + exit 1 + +# Run tests with verbose output +test-verbose: + @echo "Running tests (verbose)..." + # TODO: Replace with verbose test command + +# Smoke test +test-smoke: + @echo "Smoke test..." + # TODO: Add basic sanity checks + +# Run end-to-end tests (full pipeline: build → run → verify) +e2e: + @echo "Running E2E tests..." + # TODO: Replace with your E2E test command. Examples: + # bash tests/e2e.sh # Shell-based E2E + # npx playwright test # Browser E2E + # mix test test/integration/e2e_test.exs # Elixir E2E + # cargo test --test end_to_end # Rust E2E + @echo "E2E tests passed!" + +# Run aspect tests (cross-cutting concern validation) +aspect: + @echo "Running aspect tests..." + # TODO: Replace with your aspect test command. Examples: + # bash tests/aspect_tests.sh # Shell-based aspect tests + # cargo test --test aspects # Rust aspect tests + # Aspect tests validate architectural invariants: + # - Thread safety (mutex in FFI modules) + # - ABI/FFI contract (declarations match exports) + # - SPDX compliance (all files have license headers) + # - No dangerous patterns (believe_me, assert_total, etc.) + @echo "Aspect tests passed!" + +# Run benchmarks (performance regression detection) +bench: + @echo "Running benchmarks..." + # TODO: Replace with your benchmark command. Examples: + # cargo bench # Rust criterion + # zig build bench # Zig benchmarks + # mix run bench/benchmarks.exs # Elixir benchee + # deno bench # Deno bench + @echo "Benchmarks complete!" + +# Run readiness tests (Component Readiness Grade: D/C/B) +readiness: + @echo "Running readiness tests..." + # TODO: Replace with your readiness test command. Examples: + # cargo test --test readiness -- --nocapture + @echo "Readiness tests complete!" + +# Print the current CRG grade (reads from READINESS.md '**Current Grade:** X' line) +crg-grade: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + echo "$$grade" + +# Print a shields.io CRG badge for embedding in README files +# Looks for '**Current Grade:** X' in READINESS.md; falls back to X +crg-badge: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + case "$$grade" in \ + A) color="brightgreen" ;; \ + B) color="green" ;; \ + C) color="yellow" ;; \ + D) color="orange" ;; \ + E) color="red" ;; \ + F) color="critical" ;; \ + *) color="lightgrey" ;; \ + esac; \ + echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" + +# Run the full merge-requirement test suite (ALL categories) +# Per STANDING rule: P2P + E2E + aspect + execution + lifecycle + bench +test-all: test e2e aspect bench readiness + @echo "All test categories passed — safe to merge!" + +# Run all quality checks +quality: fmt-check lint test + @echo "All quality checks passed!" + +# Fix all auto-fixable issues [reversible: git checkout] +fix: fmt + @echo "Fixed all auto-fixable issues" + +# ═══════════════════════════════════════════════════════════════════════════════ +# LINT & FORMAT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Format all source files [reversible: git checkout] +fmt: + @echo "Formatting source files..." + # TODO: Replace with your formatter + # Examples: + # cargo fmt + # mix format + # gleam format + # deno fmt + +# Check formatting without changes +fmt-check: + @echo "Checking formatting..." + # TODO: Replace with your format check + # Examples: + # cargo fmt --check + # mix format --check-formatted + # gleam format --check + +# Run linter +lint: + @echo "Linting source files..." + # TODO: Replace with your linter + # Examples: + # cargo clippy -- -D warnings + # mix credo --strict + # gleam check + +# ═══════════════════════════════════════════════════════════════════════════════ +# RUN & EXECUTE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run the application +run *args: build + # TODO: Replace with your run command + echo "Run not configured yet" + +# Run with verbose output +run-verbose *args: build + # TODO: Replace with verbose run command + echo "Run not configured yet" + +# Install to user path +install: build-release + @echo "Installing czech_file_knife..." + # TODO: Replace with your install command + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEPENDENCIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Install/check all dependencies +deps: + @echo "Checking dependencies..." + # TODO: Replace with your dependency check + # Examples: + # cargo check + # mix deps.get + # gleam deps download + @echo "All dependencies satisfied" + +# Audit dependencies for vulnerabilities +deps-audit: + @echo "Auditing for vulnerabilities..." + # TODO: Replace with your audit command + # Examples: + # cargo audit + # mix audit + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true + @echo "Audit complete" + +# ═══════════════════════════════════════════════════════════════════════════════ +# ARRIVAL PACK — agent-facing CLAUDE.md, compiled from a2ml +# ═══════════════════════════════════════════════════════════════════════════════ + +# Compile CLAUDE.md (the agent arrival pack) from this repo's a2ml +claude-md: + @bash .machine_readable/arrival-pack/generate.sh + +# Regenerate the single authoritative repository map +repo-map: + @bash scripts/gen-repo-map.sh . + +# Fail if the repository map is stale (the map is generated; CI diffs it) +validate-repo-map: + #!/usr/bin/env bash + set -euo pipefail + cd "{{justfile_directory()}}" + before=$(mktemp); cp docs/architecture/REPOSITORY-MAP.adoc "$before" 2>/dev/null || true + bash scripts/gen-repo-map.sh . >/dev/null + if ! diff -q "$before" docs/architecture/REPOSITORY-MAP.adoc >/dev/null 2>&1; then + echo "FAIL: docs/architecture/REPOSITORY-MAP.adoc is stale. Run: just repo-map" >&2 + diff -u "$before" docs/architecture/REPOSITORY-MAP.adoc | head -40 >&2 || true + cp "$before" docs/architecture/REPOSITORY-MAP.adoc + rm -f "$before"; exit 1 + fi + rm -f "$before" + echo "repository map: up to date" + +# Fail if CLAUDE.md's generated region drifted from a2ml or was hand-edited +validate-claude-md: + @bash .machine_readable/arrival-pack/verify.sh + +# ═══════════════════════════════════════════════════════════════════════════════ +# COAPTATION — typed descriptile↔contractile face-off (homeostasis reading) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Emit the coaptation receipt: how the descriptiles coapt with the contractiles (SITREP) +coapt: + @bash .machine_readable/coaptation/coapt.sh --report + +# Assemble a re-anchor basis IF the band is red (the drop itself is a human act) +coapt-reanchor: + @bash .machine_readable/coaptation/coapt.sh --reanchor + +# Fail if the committed coaptation receipt drifted from the contractiles/descriptiles +validate-coapt: + @bash .machine_readable/coaptation/verify.sh + +# ═══════════════════════════════════════════════════════════════════════════════ +# DOCUMENTATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Generate all documentation +docs: + @mkdir -p docs/generated docs/man + just cookbook + just man + @echo "Documentation generated in docs/" + +# Generate justfile cookbook documentation +cookbook: + #!/usr/bin/env bash + mkdir -p docs + OUTPUT="docs/just-cookbook.adoc" + echo "= czech_file_knife Justfile Cookbook" > "$OUTPUT" + echo ":toc: left" >> "$OUTPUT" + echo ":toclevels: 3" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "Generated: $(date -Iseconds)" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "== Recipes" >> "$OUTPUT" + echo "" >> "$OUTPUT" + just --list --unsorted | while read -r line; do + if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then + recipe="${BASH_REMATCH[1]}" + echo "=== $recipe" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "[source,bash]" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "just $recipe" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "" >> "$OUTPUT" + fi + done + echo "Generated: $OUTPUT" + +# Generate man page +man: + #!/usr/bin/env bash + mkdir -p docs/man + cat > docs/man/czech_file_knife.1 << EOF + .TH czech_file_knife 1 "$(date +%Y-%m-%d)" "{{version}}" "czech_file_knife Manual" + .SH NAME + czech_file_knife \- RSR-compliant project + .SH SYNOPSIS + .B just + [recipe] [args...] + .SH DESCRIPTION + RSR (Rhodium Standard Repository) project managed with just. + .SH AUTHOR + $(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")> + EOF + echo "Generated: docs/man/czech_file_knife.1" + +# ═══════════════════════════════════════════════════════════════════════════════ +# CI & AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run full CI pipeline locally +# proof-check-all is FATAL if any prover toolchain is absent (idris2/lean/agda/coqc): +# the full CI gate must not pass on a machine that cannot verify the proofs. +ci: deps quality proof-check-all + @echo "CI pipeline complete!" + +# Install git hooks +install-hooks: + @mkdir -p .git/hooks + @cat > .git/hooks/pre-commit << 'HOOKEOF' + #!/bin/bash + just fmt-check || exit 1 + just lint || exit 1 + just assail || exit 1 + HOOKEOF + @chmod +x .git/hooks/pre-commit + @echo "Git hooks installed" + +# ═══════════════════════════════════════════════════════════════════════════════ +# SECURITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run security audit +security: deps-audit + @echo "=== Security Audit ===" + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true + @echo "Security audit complete" + +# Generate SBOM +sbom: + @mkdir -p docs/security + @command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VALIDATION & COMPLIANCE — see build/just/validate.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/validate.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# STATE MANAGEMENT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Update STATE.a2ml timestamp +state-touch: + @if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ + sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/descriptiles/STATE.a2ml && \ + echo "STATE.a2ml timestamp updated"; \ + fi + +# Show current phase from STATE.a2ml +state-phase: + @sed -n 's/^[[:space:]]*phase[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .machine_readable/descriptiles/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" + +# ═══════════════════════════════════════════════════════════════════════════════ +# GUIX +# ═══════════════════════════════════════════════════════════════════════════════ + +# Enter Guix development shell (primary) +guix-shell: + guix shell -D -f build/guix.scm + +# Build with Guix +guix-build: + guix build -f build/guix.scm + +# ═══════════════════════════════════════════════════════════════════════════════ +# HYBRID AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run local automation tasks +automate task="all": + #!/usr/bin/env bash + case "{{task}}" in + all) just fmt && just lint && just test && just docs && just state-touch ;; + cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;; + update) just deps && just validate ;; + *) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;; + esac + +# ═══════════════════════════════════════════════════════════════════════════════ +# COMBINATORIC MATRIX RECIPES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build matrix: [debug|release] x [target] x [features] +build-matrix mode="debug" target="" features="": + @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" + +# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel] +test-matrix suite="unit" verbosity="normal" parallel="true": + @echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}" + +# CI matrix: [lint|test|build|security|all] x [quick|full] +ci-matrix stage="all" depth="quick": + @echo "CI matrix: stage={{stage}} depth={{depth}}" + +# Show all matrix combinations +combinations: + @echo "=== Combinatoric Matrix Recipes ===" + @echo "" + @echo "Build Matrix: just build-matrix [debug|release] [target] [features]" + @echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]" + @echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag] (needs container module)" + @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VERSION CONTROL +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show git status +status: + @git status --short + +# Show recent commits +log count="20": + @git log --oneline -{{count}} + +# Generate CHANGELOG.adoc with git-cliff +changelog: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + # AsciiDoc, not .md: CHANGELOG.adoc is what root-allow.txt permits, so a + # .md here would fail check-root-shape AND the estate's no-.md rule the + # moment anyone ran this recipe. + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.adoc + @echo "Generated CHANGELOG.adoc" + +# Preview changelog for unreleased commits (does not write) +changelog-preview: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header + +# Tag a new release (usage: just release-tag 1.2.3) +release-tag version: + #!/usr/bin/env bash + TAG="v{{version}}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists" + exit 1 + fi + just changelog + git add CHANGELOG.md + git commit -m "chore(release): prepare $TAG" + git tag -a "$TAG" -m "Release $TAG" + echo "Created tag $TAG — push with: git push origin main --tags" + +# ═══════════════════════════════════════════════════════════════════════════════ +# UTILITIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Count lines of code +loc: + @find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0" + +# Show TODO comments +todos: + @grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs" + +# Open in editor +edit: + ${EDITOR:-code} . + +# Run high-rigor security assault using panic-attacker +maint-assault: + @./.machine_readable/scripts/maintenance/maint-assault.sh + +# Run panic-attacker pre-commit scan (foundational floor-raise requirement) +assail: + @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "WARN: panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" + + +# Self-diagnostic — checks dependencies, permissions, paths +doctor: + @echo "Running diagnostics for czech-file-knife..." + @echo "Checking required tools..." + @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" + @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" + @echo "Checking for hardcoded paths..." + @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" + @echo "Diagnostics complete." + +# Guided tour of key features +tour: + @echo "=== czech-file-knife Tour ===" + @echo "" + @echo "1. Project structure:" + @ls -la + @echo "" + @echo "2. Available commands: just --list" + @echo "" + @echo "3. Read README.adoc for full overview" + @echo "4. Read EXPLAINME.adoc for architecture decisions" + @echo "5. Run 'just doctor' to check your setup" + @echo "" + @echo "Tour complete! Try 'just --list' to see all available commands." + +# Open feedback channel with diagnostic context +help-me: + @echo "=== czech-file-knife Help ===" + @echo "Platform: $(uname -s) $(uname -m)" + @echo "Shell: $SHELL" + @echo "" + @echo "To report an issue:" + @echo " https://github.com/hyperpolymath/czech-file-knife/issues/new" + @echo "" + @echo "Include the output of 'just doctor' in your report." + +# ═══════════════════════════════════════════════════════════════════════════════ +# FORMAL VERIFICATION (PROOFS) — see build/just/proofs.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/proofs.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# SESSION MANAGEMENT (THIN BINDINGS TO CENTRAL STANDARDS) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show canonical session-management command model +session-help: + @echo "Canonical command model:" + @echo " intake repo " + @echo " checkpoint change " + @echo " verify maintenance " + @echo " verify substantial " + @echo " verify release " + @echo " close planned " + @echo " close urgent " + @echo " recover repo " + @echo " handover full " + @echo " handover split " + @echo " handover model " + @echo " handover human " + @echo "" + @echo "Use Just aliases below (thin wrappers around ./session/dispatch.sh)." + +# Canonical aliases (friendly recipe names that map to canonical commands) +intake-repo path=".": + @./session/dispatch.sh intake repo "{{path}}" + +checkpoint-change path=".": + @./session/dispatch.sh checkpoint change "{{path}}" + +verify-maintenance path=".": + @./session/dispatch.sh verify maintenance "{{path}}" + +verify-substantial path=".": + @./session/dispatch.sh verify substantial "{{path}}" + +verify-release path=".": + @./session/dispatch.sh verify release "{{path}}" + +close-planned path=".": + @./session/dispatch.sh close planned "{{path}}" + +close-urgent path=".": + @./session/dispatch.sh close urgent "{{path}}" + +recover-repo path=".": + @./session/dispatch.sh recover repo "{{path}}" + +handover-full path=".": + @./session/dispatch.sh handover full "{{path}}" + +handover-split path=".": + @./session/dispatch.sh handover split "{{path}}" + +handover-model path=".": + @./session/dispatch.sh handover model "{{path}}" + +handover-human path=".": + @./session/dispatch.sh handover human "{{path}}" + +secret-scan-trufflehog: + @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true + +# ═══════════════════════════════════════════════════════════════════════════════ +# WINDOWS RGONOMICS (CLOAKING) +# ═══════════════════════════════════════════════════════════════════════════════╓ +# Hide all dotfiles and dot-folders from Windows Explorer. On POSIX systems, +# leading-dot names are already hidden by convention, so these recipes are +# intentionally harmless no-ops. +cloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -bor [System.IO.FileAttributes]::Hidden }" + echo "Cloak engaged." + else + echo "Dotfiles are natively cloaked on this OS. No action required." + fi + +# Reveal dotfiles in Windows Explorer; on POSIX, explain the native mechanism. +uncloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -band -bnot [System.IO.FileAttributes]::Hidden }" + echo "Cloak lifted." + else + echo "Use 'ls -a' to view dotfiles on this OS." + fi diff --git a/czech-file-knife/.machine_readable/contractiles/README.adoc b/czech-file-knife/.machine_readable/contractiles/README.adoc new file mode 100644 index 000000000..fc0cb742d --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/README.adoc @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Contractiles Template Set +:toc: +:sectnums: + +This directory contains the canonical contractile templates for the +hyperpolymath estate. `czech-file-knife` mirrors the standards master +structure; copy `.machine_readable/contractiles/` into a new repo to +establish a consistent operational, validation, trust, recovery, +aspiration, and service-automation framework. + +[IMPORTANT] +==== +The normative grammar for the record family used throughout this directory +is `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in +https://github.com/hyperpolymath/standards[hyperpolymath/standards]. The +format formerly called A2ML is now *DEED* (`.deed`); files here still carry +`.a2ml` pending a single atomic estate-wide rename, so do not hand-convert +them. Verb-file naming is tracked separately. + +The "contractile CLI" has never been built -- enforcement currently rides on +the CI gates, not on that tool. +==== + +Each verb directory holds a *trident* of files: + +* `file.a2ml` — the project-specific declaration (data) +* `.ncl` — the paired Nickel runner (pedigree + schema + run policy) +* `.k9.ncl` — the k9 service-automation component (trust-tiered) + +A per-verb `.manifest.a2ml` asserts that exactly these three files +constitute the trident, pins their content-hashes, and requires the +cross-references to round-trip — no partial publication is permitted. + +Anything else in a verb directory is human-only notes or archive; machines +ignore it. Filenames use lowercase verb in the `.ncl` name and noun-form +PascalCase in the A2ML (e.g. `intend.ncl` + `Intentfile.a2ml`, +`must.ncl` + `Mustfile.a2ml`). + +All verb runners import `_base.ncl` (shared pedigree + run-defaults + +probe-schema). The `INDEX.a2ml` registry catalogues every verb; consumers +(CI scripts, the contractile CLI, Hypatia rules) SHOULD read it to discover +available verbs rather than hard-coding the list. See +https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc[the normative specification]. + +== Verbs (6 + k9 exception) + +[cols="1,2,3", options="header"] +|=== +| Verb | A2ML file | Role + +| `must` +| `must/Mustfile.a2ml` +| Release-blocking invariants that must hold. Gating — fails block. + +| `trust` +| `trust/Trustfile.a2ml` +| Trust boundary, allowed actions, integrity checks. Gating. + +| `adjust` +| `adjust/Adjustfile.a2ml` +| Controlled corrective actions — bounded drift tolerances and responses. + +| `dust` +| `dust/Dustfile.a2ml` +| Rollback, recovery, and deprecation semantics. Report + act on undo. + +| `bust` +| `bust/Bustfile.a2ml` +| Breakage, expiry, and hard-stop conditions. Gating — declares "this is + broken" rather than "this must stay healthy". + +| `intend` +| `intend/Intentfile.a2ml` +| North-star: committed next-actions (\[[intents]] with probes) AND horizon + aspirations (\[[wishes]] grouped near/mid/far). Non-gating (report only). + Absorbed the deprecated `lust` verb 2026-04-18. +|=== + +NOTE: The `lust/` verb was deprecated 2026-04-18 (name had unwanted +associations). Its \[[wishes]] semantics live inside `intend/Intentfile.a2ml` +as a second section alongside \[[intents]]. Any `lust/` dir encountered in +an estate repo is drift and should be removed. + +== k9 — Service-Automation Layer (EXCEPTION to the one-verbfile rule) + +IMPORTANT: `k9/` is **not a contractile verb** and does NOT follow the +`file.a2ml` + `.ncl` pattern. This is an intentional, documented +exception. Do not apply the naming rule to k9. + +=== Why k9 is different + +The six verb contractiles each declare *one concern per repo* in a single +xfile. k9 is not a concern; it is the *graded automation surface* that +enforces or validates concern declarations. k9 provides three trust-tier +*templates* that repos copy and instantiate: + +[cols="1,1,3", options="header"] +|=== +| File | Trust tier | Description + +| `k9/template-kennel.k9.ncl` +| Kennel +| Pure data. No subprocess, no filesystem write, no network. Safe for + metadata and declarative settings. + +| `k9/template-yard.k9.ncl` +| Yard +| Nickel evaluation with contracts and validation. No side effects. + +| `k9/template-hunt.k9.ncl` +| Hunt +| Full execution surface. Must declare side effects, support dry-run, and + be signed before the estate treats it as trustworthy automation. +|=== + +Each verb's `.k9.ncl` instantiates one of these tiers (the runners in +this set import `../k9/template-hunt.k9.ncl`). + +=== Why the naming rule does not apply + +The one-verb-one-Verbfile rule exists to enforce clean concern separation. +k9 is meta-infrastructure: it does not have a `K9file.a2ml` because it is +not a declarative xfile — it is a template set that instantiates into +specific repos. Applying the rule would produce a meaningless `K9file.a2ml` +with nothing to declare. + +=== Audit rule + +If a repo claims `k9` enforcement, each k9 component in that repo MUST +declare a `paired_xfile` pointing to a specific contractile xfile (e.g. +`../must/Mustfile.a2ml`). Floating k9 components with no paired xfile are +non-conformant. + +See https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc#k9-exception[the normative statement]. + +== Fill-In Instructions + +When copying this set into a new repo: + +1. Replace every template file's placeholders with project-specific content. +2. `Mustfile` — encode real invariants (schema versions, ports, required + checks), not generic samples. +3. `Trustfile` — point at actual keys, policies, and authority boundaries. +4. `Adjustfile` — define the drift tolerances and corrective actions the + repo commits to. +5. `Dustfile` — describe how this repo actually rolls back or retires + behaviour while preserving the audit trail. +6. `Bustfile` — declare real breakage / expiry / hard-stop conditions. +7. `Intentfile` — list tracked next-actions with observable probes + (\[[intents]] section) AND horizon aspirations (\[[wishes]] section). +8. Pair any `k9/*.k9.ncl` with a specific contractile via `paired_xfile`. + +== Intentfile: Commitments vs Aspirations — Two Sections, One File + +Since 2026-04-18 both axes live inside `intend/Intentfile.a2ml`: + +* `\[[intents]]` is the **commitment axis**. Items here are tracked + next-actions with probes. Status progresses + declared → in_progress → done/deferred/retired. +* `\[[wishes]]` is the **aspiration axis**. Items here are horizon goals + grouped near/mid/far. Status progresses + declared → in_progress → achieved/abandoned. + +If something is concrete enough to have a probe, it belongs in `\[[intents]]`. +If it is a horizon-level desire that might never be acted on, it belongs +in `\[[wishes]]`. A wish can graduate to an intent when a concrete plan +materialises. diff --git a/czech-file-knife/.machine_readable/contractiles/_base.ncl b/czech-file-knife/.machine_readable/contractiles/_base.ncl new file mode 100644 index 000000000..b30f0aa51 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/_base.ncl @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# _base.ncl — Shared contractile base +# +# Provides four named schema fragments imported by every verb runner: +# +# pedigree_schema — canonical pedigree block shape +# status_core_doc — documentation of the shared status trio (String list) +# probe_schema — target structured probe form (spec only; verb files +# still use probe | String with TODO comments) +# run_defaults — default runner behaviour +# +# Usage in a verb runner: +# +# let base = import "../_base.ncl" in +# { +# pedigree = base.pedigree_schema & { +# contractile_verb = "must", +# semantics = "invariant", +# security = { +# leash = 'Kennel, +# trust_level = "read-only verification", +# allow_network = false, +# allow_filesystem_write = false, +# allow_subprocess = true, +# }, +# metadata = { +# name = "must-runner", +# version = "1.0.0", +# description = "...", +# paired_xfile = "Mustfile.a2ml", +# author = "Jonathan D.A. Jewell ", +# }, +# }, +# schema = { ... }, +# run = base.run_defaults & { on_any_fail = "exit-nonzero" }, +# } +# +# See: docs/CONTRACTILE-SPEC.adoc §Shared Base +{ + # ------------------------------------------------------------------------- + # pedigree_schema + # + # The canonical shape of the `pedigree` block required in every verb runner. + # Verb runners merge this with their verb-specific values using Nickel's `&` + # (right-priority merge). Override contractile_verb, semantics, security.*, + # and metadata.* in each verb. + # ------------------------------------------------------------------------- + pedigree_schema = { + schema_version | String | default = "1.0.0", + contractile_verb | String | default = "UNSET", # MUST override in verb + semantics | String | default = "UNSET", # MUST override in verb + security = { + leash | [| 'Kennel, 'Yard, 'Hunt |] | default = 'Kennel, + trust_level | String | default = "UNSET", # MUST override in verb + allow_network | Bool | default = false, + allow_filesystem_write | Bool | default = false, + allow_subprocess | Bool | default = true, + # verb-specific additional security fields go in the verb's merge override: + # e.g. authorised_probes_only (trust), injection_scope (bust), + # destructive_mode_requires_flag (dust) + }, + metadata = { + name | String | default = "UNSET", # MUST override in verb + version | String | default = "1.0.0", + description | String | default = "UNSET", # MUST override in verb + paired_xfile | String | default = "UNSET", # MUST override in verb + author | String | default = "Jonathan D.A. Jewell ", + }, + }, + + # ------------------------------------------------------------------------- + # status_core_doc + # + # Documents the minimum shared status values present in every verb's status + # enum: declared, verified, failing. + # + # Nickel does not support structural enum extension, so verb files reproduce + # their full enum verbatim in `schema`. This field serves as documentation + # and for tooling that introspects the base. + # + # Verbs that extend status_core (i.e. all except must + trust): + # adjust: + 'partial + # bust: + 'drilled + # dust: 'declared, 'proposed, 'approved, 'removed (non-standard) + # intend: intents: 'declared, 'in_progress, 'done, 'deferred, 'retired + # wishes: 'declared, 'in_progress, 'achieved, 'abandoned + # (the wishes schema was absorbed from the deprecated `lust` + # verb 2026-04-18; lust/ dir removed estate-wide) + # + # See: docs/CONTRACTILE-SPEC.adoc §Per-Verb Extension + # ------------------------------------------------------------------------- + status_core_doc = "status_core values: declared | verified | failing — extended per verb", + + # ------------------------------------------------------------------------- + # probe_schema + # + # The TARGET structured probe form. See: docs/CONTRACTILE-SPEC.adoc §Probe + # + # IMPORTANT: This is a spec-only definition. Existing verb runner files still + # use `probe | String` with a `# TODO: migrate to probe_schema` comment. + # This is a breaking change; migration happens when the CLI supports both + # forms. + # + # Adopters writing new xfiles should prefer the structured form: + # probe = { + # command = "test -f my-file", + # timeout_seconds = 60, + # allowed_exit_codes = [0], + # permission_class = 'read_only, + # } + # ------------------------------------------------------------------------- + probe_schema = { + command | String, + timeout_seconds | Number | default = 300, + allowed_exit_codes | Array Number | default = [0], + permission_class + | [| 'read_only, 'filesystem_write, 'subprocess, 'network |] + | default + = 'read_only, + }, + + # ------------------------------------------------------------------------- + # run_defaults + # + # Default runner behaviour. Verb runners merge this with verb-specific + # overrides using Nickel's `&` (right-priority merge). + # + # Most verbs override on_any_fail: + # "exit-nonzero" : hard gate (must, trust, bust, adjust-gating) + # "continue-with-warnings": advisory (dust, adjust) + # "continue" : never gate (intend — covers both intents and wishes) + # ------------------------------------------------------------------------- + run_defaults = { + on_pass = "continue", + on_any_fail = "exit-nonzero", + report_format = "a2ml", + emit_summary = true, + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml new file mode 100644 index 000000000..952d923bc --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MPL-2.0 +# Adjustfile — Drift-tolerance contract for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# Cumulative-drift catchment: tolerance bands + corrective actions. +# Authority: advisory (Yard) — continue-with-warnings; auto_fix where deterministic. +# Run with: adjust check +# Fix with: adjust fix (applies deterministic patches; advisory otherwise) + +@abstract: +Drift tolerances and corrective actions for czech-file-knife. Unlike +MUST (hard gate), ADJUST tracks cumulative drift against tolerance bands +and proposes corrective actions. Advisory — it warns and trends, it does +not block. +@end + +## Template Drift + +### placeholder-drift +- description: Template placeholders should be replaced when copied +- tolerance: 0 placeholder markers in copied repos +- corrective: Search and replace all {{PLACEHOLDER}} markers +- severity: advisory +- notes: This check only applies to repos that copied from this template + +### template-version-drift +- description: Template version should match RSR spec version +- tolerance: Template version matches current RSR spec +- corrective: Update template to match latest RSR spec +- severity: advisory + +## Documentation Drift + +### readme-completeness +- description: README should document all template features +- tolerance: README covers all contractiles and directory structure +- corrective: Update README.adoc with missing sections +- severity: advisory + +### example-accuracy +- description: Examples in documentation should match actual template content +- tolerance: All code examples in docs are accurate +- corrective: Audit and fix examples in documentation +- severity: advisory + +## Structural Drift + +### contractile-sync +- description: All contractiles should have matching a2ml and ncl implementations +- tolerance: Every .a2ml has a corresponding .ncl +- corrective: Generate missing .ncl files from .a2ml +- severity: advisory + +### no-broken-symlinks +- description: No broken symbolic links in template structure +- tolerance: 0 broken symlinks +- corrective: Run symlink-check script +- severity: advisory + +## Accessibility Drift + +### adoc-not-md +- description: Template docs should prefer AsciiDoc +- tolerance: New prose docs are *.adoc +- corrective: Convert any new *.md to *.adoc +- severity: advisory + +### spdx-header-consistency +- description: All template files have correct SPDX headers +- tolerance: 0 files missing SPDX-License-Identifier +- corrective: Add SPDX headers to files that need them +- severity: advisory diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl new file mode 100644 index 000000000..4974ba7f8 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl @@ -0,0 +1,167 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# adjust.k9.ncl — K9 trust-tier component of the adjust trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Adjustfile.a2ml (declaration) + adjust.ncl (runner). +# +# Verb: adjust (drift tolerances + corrective actions) +# Tier: Yard (validation with subprocess for measurement; +# no mutation beyond auto-fix where declared) +# Authority: advisory (continue-with-warnings; not blocking) +# +# adjust is the tolerance-band verb. Where must says "this MUST hold" +# and trust says "this MUST verify clean" (both binary), adjust says +# "drift ≤ X is acceptable; drift > X triggers action Y". Between them, +# adjust handles the subtle-drift territory that binary verbs can't. +# +# Cardinality: ONE adjust trident per repo. +# +# Failure-mode focus: adjust catches cumulative-small-drift patterns +# (E2 cosmetic churn accumulating into real regression, F2 context +# erosion causing gradual parameter drift). Where must flags "broken +# now", adjust flags "drifting toward broken". + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "adjust", + paired_xfile = "../adjust/Adjustfile.a2ml", + paired_runner = "../adjust/adjust.ncl", + + tier = 'Yard, + authority = 'advisory, + + metadata = { + name = "adjust-k9", + version = "1.0.0", + description = "Drift-tolerance + corrective-action runner. Fifth trident instance. First (Yard, advisory) authority pattern.", + paired_xfile = "Adjustfile.a2ml", + paired_runner = "adjust.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Yard, + trust_level = "tolerance measurement + declared auto-fix", + allow_network = false, + allow_filesystem_write_conditional = true, # auto_fix_when_available may edit + allow_subprocess = true, + probe_scope = 'measurement_plus_declared_fix, + }, + }, + + variance_schema = { + entry_id | String, + reason | String, + approved_by | String, + scope | String, + expires | String, + review_notes | String | optional, + # adjust-specific: which tolerance band the variance widens + tolerance_band_widened | String, + widened_to_value | String, + }, + + execution = { + triggers = [ 'session_close, 'on_demand, 'pre_push ], + + per_tolerance = { + measure_drift = true, + record_outcome = true, + respect_variance = true, + # adjust-specific authority: tolerance exceeded → warn, try + # auto-fix if declared, then continue. Never blocks. + on_exceeded = 'warn_and_attempt_fix, + on_auto_fix_applied = 'record_and_continue, + on_auto_fix_unavailable = 'record_as_advisory_drift, + # Cumulative-drift detection (adjust's specialty) + track_drift_trend_over_sessions = true, + flag_accelerating_drift = true, + }, + + evidence_sinks = [ + { kind = 'verisimdb, table = "contractile_executions", + schema = "contractile_execution_v1", + aux_tables = [ "adjust_drift_history" ] }, + { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true }, + ], + + on_close = { + re_measure_all_tolerances = true, + diff_against_last_ratification = true, + emit_drift_entries_for_tolerance_exceeded = true, + surface_expired_variances = true, + surface_accelerating_drift = true, + # adjust is advisory — does NOT block session close. + block_session_close_on_any_drift = false, + }, + + on_open = { + render_summary = 'plain_language, + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + include_tolerance_trend_summary = true, + + negotiation = { + required = true, + ai_required_inputs = [ + 'timeline_realism, + 'industry_standards, + 'audience_feasibility, + 'resulting_invariants, + 'ecosystem_dependencies, + ], + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + }, + }, + + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the tolerance bands and corrective actions. I accept accountability for reviewing drift warnings rather than muting them, and for re-tuning tolerances via amendment when the intended operating envelope changes.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will surface tolerance breaches and accelerating-drift patterns at session close; I will propose corrective actions rather than widening tolerances silently; I will require amendment for legitimate tolerance re-tuning, not quiet band-widening.", + signature_required = true, + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + includes_tolerance_bands_snapshot = true, + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, + }, + }, + }, + + failure_mode_defenses = [ + 'A1_enthusiasm_capture, + 'C3_helpfulness_inflation, # helpful additions surfaced if they widen tolerances silently + 'C4_modernization_drift, + 'E2_cosmetic_churn, # adjust tracks cumulative churn + 'F2_context_window_erosion, # parameter drift across sessions detected + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml new file mode 100644 index 000000000..0e108b64b --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: MPL-2.0 +# adjust.manifest.a2ml — Trident coherence manifest for the adjust verb. +# Author: Jonathan D.A. Jewell +# +# Fifth trident instance. First (Yard, advisory) authority pattern — +# complements the (Hunt, blocking) triple (must + trust + bust) and +# the (Hunt, reporting) north-star (intend). + +--- +trident_version = "1.0.0" +verb = "adjust" +semantics = "drift tolerances + corrective actions" +cardinality = "one per repo" +authority = "advisory (continue-with-warnings)" + +[[files]] +role = "declaration" +path = "Adjustfile.a2ml" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "runner" +path = "adjust.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "k9_component" +path = "adjust.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[cross_refs] +runner_paired_xfile = "Adjustfile.a2ml" +k9_paired_xfile = "../adjust/Adjustfile.a2ml" +k9_paired_runner = "../adjust/adjust.ncl" + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "adjust trident — canonical template in czech-file-knife. (Yard, advisory) authority pattern. Specialises in cumulative-drift catchment — tolerance bands + trend tracking + auto-fix-where-declared. Advisory (continue-with-warnings). Copy this trident into a new repo and define its drift tolerances and corrective actions." + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "Adjustfile.a2ml and adjust.ncl pre-existed. This manifest + adjust.k9.ncl complete the trident. Exercises the Yard tier + advisory authority for the first time; on_exceeded = 'warn_and_attempt_fix rather than 'fail. adjust-specific track_drift_trend_over_sessions + flag_accelerating_drift." diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl new file mode 100644 index 000000000..f0f074323 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: MPL-2.0 +# Adjust — accessibility runner +# +# Pairs with: Adjustfile.a2ml (same directory) +# Verb: adjust +# Semantics: accessibility compliance (WCAG 2.1 AA baseline). Gating where +# a deterministic fix exists; advisory where human review needed. +# CLI: `contractile adjust check` → run all probes, list violations +# `contractile adjust fix` → apply deterministic fixes where defined +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "adjust", + semantics = "accessibility compliance", + security = { + leash = 'Kennel, + trust_level = "fixes allowed where deterministic", + allow_network = false, + allow_filesystem_write = true, # `adjust fix` may write (deterministic patches only) + allow_subprocess = true, + }, + metadata = { + name = "adjust-runner", + version = "1.0.0", + description = "Evaluates accessibility requirements from Adjustfile.a2ml. Fixes deterministic items; flags the rest for human review.", + paired_xfile = "Adjustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + requirements + | Array { + id | String, + description | String, + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String, + # status_core values: 'declared, 'verified, 'failing; adjust adds 'partial + status | [| 'declared, 'partial, 'verified, 'failing |] | default = 'declared, + compliance | String | optional, # e.g. "WCAG 2.1 AA" + notes | String | optional, + fix | String | optional, # deterministic fix command (optional) + }, + }, + + # Runner behaviour — inherits from base.run_defaults. + # adjust is advisory (continue-with-warnings) not a hard gate. + # auto_fix_when_available is adjust-specific. + run = + base.run_defaults + & { + on_any_fail = "continue-with-warnings", # accessibility is progress-tracked, not a hard gate by default + report_format = "a2ml", + emit_summary = true, + auto_fix_when_available = true, + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml new file mode 100644 index 000000000..8da753f6c --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: MPL-2.0 +# Bustfile — failure mode contractile for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# Paired runner: bust.ncl +# Verb: bust +# Semantics: Every declared failure mode must have a working recovery path +# that has been exercised. Status moves: +# declared → drilled (probe run) → verified (recovery confirmed) +# or → failing (recovery broken) +# +# CLI: +# contractile bust check → list failure modes + recovery status +# contractile bust drill → inject failures, verify recovery paths +# +# This repository: czech-file-knife is the canonical template for RSR compliance. +# Failure modes here relate to template distribution and substitution. + +@abstract: +Bustfile for czech-file-knife. Lists failure modes specific to the template +repository itself, particularly around template distribution, substitution, +and synchronization across the hyperpolymath estate. +@end + +## Failure Modes + +### template-substitution-failure +- class: template_processing +- description: Template substitution fails when initializing a new repo from this template +- injection_probe: "cp -r czech-file-knife test-repo && cd test-repo && sed -i 's/czech-file-knife/TEST/g' .machine_readable/contractiles/Intentfile.a2ml && grep -q 'TEST' .machine_readable/contractiles/Intentfile.a2ml" +- recovery_probe: "git -C test-repo diff --quiet .machine_readable/contractiles/Intentfile.a2ml" +- expected_recovery_time_seconds: 10 +- status: declared +- notes: Verify that substitution scripts handle all placeholder replacements correctly + +### sync-drift-between-repos +- class: synchronization +- description: Drift occurs between czech-file-knife and other repos after template updates +- injection_probe: "echo 'template_updated' > /tmp/test_drift_marker" +- recovery_probe: "test -f /tmp/test_drift_marker && rm /tmp/test_drift_marker" +- expected_recovery_time_seconds: 60 +- status: declared +- notes: The estate-wide sync scripts (see scripts/) should prevent this; verify with scripts/verify-sync.sh + +### contractile-parse-error +- class: contractile_format +- description: A contractile file fails to parse due to syntax errors +- injection_probe: "echo 'invalid syntax' >> czech-file-knife/.machine_readable/contractiles/Intentfile.a2ml" +- recovery_probe: "git checkout czech-file-knife/.machine_readable/contractiles/Intentfile.a2ml" +- expected_recovery_time_seconds: 5 +- status: declared +- notes: All .a2ml files should be valid A2ML; use a2ml-validate runner diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl new file mode 100644 index 000000000..a5e5e73d9 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl @@ -0,0 +1,162 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# bust.k9.ncl — K9 trust-tier component of the bust trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Bustfile.a2ml (declaration) + bust.ncl (runner). +# +# Verb: bust (hard-stop / expiry / "must-not-run") +# Tier: Hunt-read-only (subprocess probes for expiry/state checks) +# Authority: blocking (HARD GATE on declared broken states) +# +# bust is the "this is broken, this has expired, this must not run" +# declarative surface. Where must asserts invariants that must hold, +# bust asserts failure states that must not be re-entered. Complement +# to must: together they bound the "acceptable operating state" from +# above (must) and below (bust). +# +# Cardinality: ONE bust trident per repo. +# +# Failure-mode focus: bust catches deprecated-path-still-called +# patterns (C2 capability collapse attempts where an AI reintroduces +# retired code), expiry-exceeded state (certificates / tokens / grants +# past their expiry), and the "it works, ship it" pattern where a +# caller silently starts using a must-not-run API. + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "bust", + paired_xfile = "../bust/Bustfile.a2ml", + paired_runner = "../bust/bust.ncl", + + tier = 'Hunt, + authority = 'blocking, + + metadata = { + name = "bust-k9", + version = "1.0.0", + description = "Hard-stop / expiry / must-not-run gate. Fourth trident instance. Completes the blocking-authority triple (must + trust + bust).", + paired_xfile = "Bustfile.a2ml", + paired_runner = "bust.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Hunt, + signature_required = true, + trust_level = "read-only expiry + state-check with subprocess", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, + probe_scope = 'read_only, + }, + }, + + variance_schema = { + entry_id | String, + reason | String, + approved_by | String, + scope | String, + expires | String, + review_notes | String | optional, + severity_acknowledged | [| 'critical, 'high, 'medium |], + waived_consequence_description | String, + }, + + execution = { + triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_merge ], + + per_hard_stop = { + run_probe = true, + record_outcome = true, + respect_variance = true, + on_triggered = 'fail, # BLOCKING — bust condition hit = block + severity_escalation = 'honour, + # bust-specific: detect re-introduction of deprecated calls + flag_deprecated_reintroduction = true, + }, + + evidence_sinks = [ + { kind = 'verisimdb, table = "contractile_executions", + schema = "contractile_execution_v1", + aux_tables = [ "bust_triggers_history" ] }, + { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true }, + ], + + on_close = { + re_execute_all_hard_stops = true, + diff_against_last_ratification = true, + emit_drift_entries_for_new_triggers = true, + surface_expired_variances = true, + block_session_close_on_critical_bust = true, + }, + + on_open = { + render_summary = 'plain_language, + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + include_silent_regressions = true, + + negotiation = { + required = true, + ai_required_inputs = [ + 'timeline_realism, + 'industry_standards, + 'audience_feasibility, + 'resulting_invariants, + 'ecosystem_dependencies, + ], + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + }, + }, + + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the declared hard-stop / expiry / must-not-run conditions. I accept accountability for not calling into must-not-run code paths, not ignoring expired tokens/grants, and not silently reintroducing deprecated patterns. I will raise a variance with severity acknowledgement if an exception is needed.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will refuse suggestions that reintroduce must-not-run patterns; I will surface bust triggers at session close; I will require variance-with-severity for any legitimate reintroduction of a deprecated path rather than silently allowing it.", + signature_required = true, + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, + }, + }, + }, + + failure_mode_defenses = [ + 'A1_enthusiasm_capture, + 'C2_capability_collapse, # prevents reintroduction of retired capability + 'C4_modernization_drift, # bust prevents silent re-adoption of deprecated libs + 'D4_error_hiding, + 'E1_refactor_stampede, # refactor that reintroduces deprecated path caught + 'F1_across_session_forgetting, # bust triggers persist across sessions + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml new file mode 100644 index 000000000..87cf79381 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: MPL-2.0 +# bust.manifest.a2ml — Trident coherence manifest for the bust verb. +# Author: Jonathan D.A. Jewell +# +# Fourth trident instance. Completes the blocking-authority triple: +# must (persistent invariants), trust (ephemeral transactions), +# bust (hard-stop / expiry / must-not-run). Between them, every +# release-blocking contractile concern is covered. + +--- +trident_version = "1.0.0" +verb = "bust" +semantics = "hard-stop / expiry / must-not-run declarations" +cardinality = "one per repo" +authority = "blocking (hard gate)" + +[[files]] +role = "declaration" +path = "Bustfile.a2ml" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "runner" +path = "bust.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "k9_component" +path = "bust.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[cross_refs] +runner_paired_xfile = "Bustfile.a2ml" +k9_paired_xfile = "../bust/Bustfile.a2ml" +k9_paired_runner = "../bust/bust.ncl" + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "bust trident — canonical template in czech-file-knife. Completes the blocking-authority triple (must + trust + bust). Specialises in deprecated-path-reintroduction catchment. Declares hard-stop / expiry / must-not-run conditions. Copy this trident into a new repo and declare its real breakage / expiry conditions." + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "Bustfile.a2ml and bust.ncl pre-existed. This manifest + bust.k9.ncl complete the trident. Inherits the full negotiation+accountability schema from intend.k9.ncl v2.0.0 + trust/must extensions; adds flag_deprecated_reintroduction for C2-defense specificity." diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl b/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl new file mode 100644 index 000000000..f273168ff --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: MPL-2.0 +# Bust — error-handling / failure-recovery runner +# +# Pairs with: Bustfile.a2ml (same directory) +# Verb: bust +# Semantics: every declared failure mode must have a recovery path that has +# been exercised. Runner injects failures (via declared probes) +# and verifies the recovery path works. Hard gate on any +# failure-mode with missing or broken recovery. +# CLI: `contractile bust check` → list failure modes + recovery status +# `contractile bust drill` → inject declared failures, verify recovery +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "bust", + semantics = "error handling + failure recovery", + security = { + leash = 'Kennel, + trust_level = "controlled failure injection; scoped to system-under-test", + allow_network = false, + allow_filesystem_write = true, # drills may write transient state (tmp dirs, test DBs) + allow_subprocess = true, + injection_scope = "system-under-test-only", + }, + metadata = { + name = "bust-runner", + version = "1.0.0", + description = "Exercises declared failure modes and verifies recovery paths. Hard-gates on any failure mode without working recovery.", + paired_xfile = "Bustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + failure_modes + | Array { + id | String, + description | String, + class | [| 'network, 'disk_full, 'oom, 'timeout, 'partial_write, 'panic, 'crash, 'rollback, 'concurrency |], + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + injection_probe | String, # command that deterministically causes this failure + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + recovery_probe | String, # command that verifies recovery (exit 0 = recovered) + expected_recovery_time_seconds | Number | default = 30, + # status_core values: 'declared, 'verified, 'failing; bust adds 'drilled + status | [| 'declared, 'drilled, 'verified, 'failing |] | default = 'declared, + notes | String | optional, + }, + }, + + # Runner behaviour — inherits from base.run_defaults. + # bust adds record_recovery_times for performance tier feeding. + run = + base.run_defaults + & { + on_any_fail = "exit-nonzero", # missing or broken recovery blocks merge + report_format = "a2ml", + emit_summary = true, + record_recovery_times = true, # feeds the performance tier + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml new file mode 100644 index 000000000..8f2af126a --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dustfile — Cleanup and hygiene contract for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# Paired runner: dust.ncl +# Verb: dust +# Semantics: What should be cleaned up. Housekeeping, not blockers. +# +# This repository: czech-file-knife is the canonical template. +# Cleanup items here ensure the template itself remains pristine. + +@abstract: +Cleanup and hygiene items for czech-file-knife. These are maintenance tasks +that ensure the template repository remains clean and ready for distribution +to new repositories. +@end + +## Stale Files + +### no-template-artifacts +- description: No generated files from template testing in root +- run: test -z "$(ls template-test-* 2>/dev/null)" +- severity: info +- notes: Template testing should use /tmp or dedicated test directories + +### no-example-placeholders +- description: No example placeholder files (EXAMPLE-, SAMPLE-) in contractiles/ +- run: test -z "$(find .machine_readable/contractiles/ -name 'EXAMPLE-*' -o -name 'SAMPLE-*' 2>/dev/null)" +- severity: warning +- notes: All placeholders should be replaced with actual content or removed + +### no-old-contractile-formats +- description: No old .contractile or .hs files remaining +- run: test -z "$(find .machine_readable/contractiles/ \( -name '*.contractile' -o -name '*.hs' \) 2>/dev/null)" +- severity: warning +- notes: All contractiles should be .a2ml format + +## Format Duplicates + +### justfile-imports-in-sync +- description: The root Justfile and its contractiles mirror declare the SAME import? set +- run: diff <(grep '^import?' Justfile) <(grep '^import?' .machine_readable/contractiles/Justfile) >/dev/null +- severity: warning +- notes: | + Replaces a no-duplicate-justfile rule that compared INODES to assert the two + files were hardlinked. That rule could never pass: git does not track + hardlinks, so the pair have separate inodes after any clone (measured: + 1026120 vs 1031824). Its premise was also false — these are not one file + seen twice but two deliberately different Justfiles, differing by 88 lines, + so swapping the inode test for `cmp` would merely exchange one + always-failing check for another. `stat -c` is GNU-only too, so it broke on + macOS regardless. + + What actually must hold is that the mirror declares the same import? set as + the root — that is the drift which silently breaks recipes, because `import?` + is the OPTIONAL form and fails quietly rather than erroring. Both currently + declare 7 imports and agree. + +### no-duplicate-readme-format +- description: Only one README format in contractiles/ (.adoc canonical) +- run: test ! -f .machine_readable/contractiles/README.md +- severity: info + +## Template Hygiene + +### no-stale-template-references +- description: No references to czech-file-knife in generic template files +- run: test -z "$(grep -r 'czech-file-knife' machine-readable-design/ 2>/dev/null)" +- severity: warning +- notes: Generic templates should use Czech File Knife or similar placeholders + +### version-sync-checked +- description: Version in canonical-directory-structure matches .machine_readable/contractiles +- verification: compare version identifiers in both locations +- severity: info diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl new file mode 100644 index 000000000..6c51e54f3 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl @@ -0,0 +1,172 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# dust.k9.ncl — K9 trust-tier component of the dust trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Dustfile.a2ml (declaration) + dust.ncl (runner). +# +# Verb: dust (rollback / recovery / deprecation) +# Tier: Yard (audit + structural checks; destructive +# actions gated behind --apply flag + +# explicit per-item approval) +# Authority: advisory (continue-with-warnings) +# +# dust is the retirement + audit-trail verb. Rollback paths, deprecation +# markers, evidence-preservation semantics. Where bust declares +# "broken, don't run", dust declares "how to safely undo / retire / roll +# back". Complement to bust — bust marks the dead end, dust describes +# the exit ramp. +# +# Cardinality: ONE dust trident per repo. +# +# Failure-mode focus: dust is the audit-trail preservation verb — +# defends against E1 refactor stampede (check audit trail still +# intact) and against silent removal (anything removed must have a +# rollback path; anything retired must preserve the evidence of its +# previous existence). + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "dust", + paired_xfile = "../dust/Dustfile.a2ml", + paired_runner = "../dust/dust.ncl", + + tier = 'Yard, + authority = 'advisory, + + metadata = { + name = "dust-k9", + version = "1.0.0", + description = "Rollback + deprecation + audit-trail runner. Sixth trident instance — completes the full verb set.", + paired_xfile = "Dustfile.a2ml", + paired_runner = "dust.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Yard, + trust_level = "audit-trail verification + structural checks", + allow_network = false, + # dust is the verb that ACTUALLY wants filesystem write — to + # execute declared rollback/removal — but only behind explicit + # --apply flag + per-item approval. Default is dry-run. + allow_filesystem_write_conditional = true, + allow_subprocess = true, + destructive_action_gating = { + default_mode = 'dry_run, + requires_flag = "--apply", + requires_per_item_approval = true, + approval_mechanism = 'explicit_user_signature, + }, + }, + }, + + variance_schema = { + entry_id | String, + reason | String, + approved_by | String, + scope | String, + expires | String, + review_notes | String | optional, + rollback_path_preserved | Bool, # dust-specific: did the variance preserve rollback? + }, + + execution = { + triggers = [ 'session_close, 'on_demand ], + + per_retirement = { + verify_rollback_path_documented = true, + verify_audit_trail_preserved = true, + respect_variance = true, + on_rollback_path_missing = 'warn, # advisory, not block + on_audit_trail_broken = 'warn, # advisory, not block + # dust-specific: flag any retirement that has been requested but + # lacks proper rollback documentation + flag_unsafe_retirement = true, + }, + + evidence_sinks = [ + { kind = 'verisimdb, table = "contractile_executions", + schema = "contractile_execution_v1", + aux_tables = [ "dust_retirement_history" ] }, + { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true }, + ], + + on_close = { + re_verify_all_retirement_paths = true, + diff_against_last_ratification = true, + emit_drift_entries_for_missing_rollback = true, + emit_drift_entries_for_broken_audit_trail = true, + surface_expired_variances = true, + block_session_close_on_any_drift = false, # advisory + }, + + on_open = { + render_summary = 'plain_language, + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + + negotiation = { + required = true, + ai_required_inputs = [ + 'timeline_realism, + 'industry_standards, + 'audience_feasibility, + 'resulting_invariants, + 'ecosystem_dependencies, + ], + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + }, + }, + + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the declared rollback paths and deprecation markers. I accept accountability for preserving audit trails when retiring code, and for ensuring every retired capability has a documented rollback path. I will not silently delete evidence of prior state.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will verify audit-trail preservation in any retirement / rollback / deprecation I perform; I will refuse silent deletion of prior-state evidence; I will require rollback-path documentation before accepting a retirement request; I will operate in dry-run mode by default and require explicit --apply + per-item approval for destructive actions.", + signature_required = true, + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + includes_retirement_schedule = true, + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, + }, + }, + }, + + failure_mode_defenses = [ + 'A1_enthusiasm_capture, + 'C2_capability_collapse, # retirement without rollback path = capability collapse + 'D5_sycophancy, # AI won't agree to silent deletion + 'E1_refactor_stampede, # audit trail preservation check + 'E2_cosmetic_churn, + 'F1_across_session_forgetting, # retirement history tracked cross-session + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml new file mode 100644 index 000000000..39355e5b3 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: MPL-2.0 +# dust.manifest.a2ml — Trident coherence manifest for the dust verb. +# Author: Jonathan D.A. Jewell +# +# Sixth + final trident instance. Completes the full verb set — +# the estate now has tridents for every contractile verb. + +--- +trident_version = "1.0.0" +verb = "dust" +semantics = "rollback / recovery / deprecation / audit-trail preservation" +cardinality = "one per repo" +authority = "advisory (continue-with-warnings)" + +[[files]] +role = "declaration" +path = "Dustfile.a2ml" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "runner" +path = "dust.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "k9_component" +path = "dust.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[cross_refs] +runner_paired_xfile = "Dustfile.a2ml" +k9_paired_xfile = "../dust/Dustfile.a2ml" +k9_paired_runner = "../dust/dust.ncl" + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "dust trident — canonical template in czech-file-knife. Specialises in audit-trail preservation + rollback-path verification. Yard tier with destructive-action gating (dry-run default; --apply + per-item approval required for mutations). Copy this trident into a new repo and describe how it actually rolls back or retires behaviour." + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "Dustfile.a2ml and dust.ncl pre-existed. This manifest + dust.k9.ncl complete the trident and the full verb set. All 6 verbs now on trident shape: intend (Hunt, reporting), trust (Hunt, blocking), must (Hunt-read-only, blocking), bust (Hunt-read-only, blocking), adjust (Yard, advisory), dust (Yard, advisory)." + +[[history]] +date = "2026-04-18" +event = "verb-set-complete" +note = "Full estate trident coverage. Blocking-authority triple (must/trust/bust) handles release-gating. Advisory pair (adjust/dust) handles drift-warning and audit-trail. Single reporting verb (intend) handles north-star. α two-axis surface fully exercised on all four (tier, authority) combinations used: (Hunt, reporting), (Hunt, blocking), (Hunt-read-only, blocking), (Yard, advisory). The contractile system is ready for the adversarial Gemini+Copilot drift pilot." diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl b/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl new file mode 100644 index 000000000..8da0361af --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dust — exnovation / code-removal runner +# +# Pairs with: Dustfile.a2ml (same directory) +# Verb: dust +# Semantics: exnovation. Identifies code, docs, files, dependencies that are +# candidates for REMOVAL. Advisory by default; can be flipped to +# active delete via `contractile dust sweep --apply`. +# CLI: `contractile dust find` → list removal candidates +# `contractile dust sweep` → dry-run removals +# `contractile dust sweep --apply` → actually delete (gated) +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "dust", + semantics = "exnovation / removal", + security = { + leash = 'Kennel, + trust_level = "proposes deletion; --apply required to execute", + allow_network = false, + allow_filesystem_write = true, # --apply mode writes (deletes) + allow_subprocess = true, + destructive_mode_requires_flag = "--apply", + }, + metadata = { + name = "dust-runner", + version = "1.0.0", + description = "Identifies and optionally removes exnovation targets listed in Dustfile.a2ml. Destructive mode gated behind --apply.", + paired_xfile = "Dustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + removal_candidates + | Array { + id | String, + description | String, + target | String, # file / path / symbol / dep name + reason | String, # why it's a removal candidate + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String | optional, # command that confirms it's still removable + # dust has a non-standard status progression (no 'verified): + # 'declared → 'proposed → 'approved → 'removed + status | [| 'declared, 'proposed, 'approved, 'removed |] | default = 'declared, + approver | String | optional, # who signed off (for 'approved / 'removed) + notes | String | optional, + }, + }, + + # Runner behaviour — inherits from base.run_defaults. + # dust is advisory; apply_requires_approval is dust-specific. + run = + base.run_defaults + & { + on_any_fail = "continue-with-warnings", + report_format = "a2ml", + emit_summary = true, + apply_requires_approval = true, # only 'approved items get swept, even with --apply + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml b/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml new file mode 100644 index 000000000..079b52626 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: MPL-2.0 +# Intentfile (A2ML Canonical) — north-star contractile for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# Paired runner: intend.ncl +# Verb: intend +# +# Semantics: North-star contractile. Declares BOTH concrete committed +# next-actions AND horizon aspirations the project wishes to +# become. Two sections share one file because they answer +# the same question at different ranges: +# [[intents]] — "we WILL do this; track progress" +# status: declared → in_progress → done | +# deferred | retired +# [[wishes]] — "we WISH this were true; revisit later" +# status: declared → in_progress → achieved | +# abandoned +# grouped by horizon: near / mid / far. +# Non-gating — this is a report, not a gate. See the `must` +# contractile for hard gates. + +@abstract: +North-star contractile for czech-file-knife. This repository is the +canonical template for Rhodium Standard Repository compliance. It provides +the scaffold that all hyperpolymath repos should copy and customize. +@end + +## Purpose + +The czech-file-knife serves as the master template for all hyperpolymath +repositories. It contains the complete set of contractile files, machine-readable +specifications, and governance documentation that define the Rhodium Standard. + +Every new repository in the hyperpolymath estate should be initialized by +copying this template and substituting the placeholder values with +repo-specific content. + +## Anti-Purpose + +This repository is NOT: +- A general-purpose project scaffold for external use (hyperpolymath-only) +- A replacement for per-repo customization (all files must be bespoke) +- A static template that never changes (evolves with RSR spec) +- A runtime library or framework (build-time only) + +## If In Doubt + +If you are unsure whether a change is in scope, ask. Sensitive areas: +- .machine_readable/ contractile definitions +- RSR specification files +- Governance templates +- License policy documents + +## Committed Next-Actions + +### repo-initialization +- description: Provide just copy-and-substitute template for new repos +- probe: test -f scripts/init-repo.sh +- status: done +- notes: Run with source scripts/init-repo.sh + +### contractile-completeness +- description: Every RSR contractile has an a2ml and ncl implementation +- probe: ls .machine_readable/contractiles/*.a2ml | wc -l | grep -q "^6$" +- status: in_progress +- notes: Currently 6 contractile verbs: intend, must, trust, adjust, bust, dust + +### automation-scripts +- description: All repetitive tasks have just recipes +- probe: grep -c "^# " Justfile | grep -q "^[6-9][0-9]*$" +- status: in_progress + +## Wishes + +### Near Horizon + +#### cross-repo-validation +- description: Tooling to validate all repos against RSR spec +- horizon: near +- status: declared + +#### automated-substitution +- description: Script to automate repo-specific substitution in template +- horizon: near +- status: declared + +### Mid Horizon + +#### formal-verification +- description: Idris2 proofs for all critical contractile invariants +- horizon: mid +- status: declared + +### Far Horizon + +#### ecosystem-visualization +- description: Interactive graph of all hyperpolymath repos and dependencies +- horizon: far +- status: declared diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl b/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl new file mode 100644 index 000000000..4c5abc8c1 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl @@ -0,0 +1,252 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# intend.k9.ncl — K9 trust-tier component of the intend trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Intentfile.a2ml (declaration) + intend.ncl (runner). +# Trident completeness is a hard precondition — a repo shipping +# Intentfile without this file AND its runner is an invalid trident; +# the contractile CLI's verify gate refuses partial publication. +# +# Verb: intend (north star — commitments + aspirations) +# Tier: Hunt (capability: subprocess probes may shell out) +# Authority: reporting (never blocks; drift-log only) +# +# Cardinality: ONE intend trident per repo (see feedback_contractile_ +# layout_rules.md). ANCHOR.a2ml is the sole multi-instance exception — +# it is NOT a verb contractile. +# +# Design commitments baked in (see memory trail 2026-04-18 for full +# context; key files referenced by name in annotations below): +# * α two-axis (tier × authority) — structurally separate capability +# from authority so "Hunt tier = can override" is impossible. +# * Variance schema first-class, not comment markers +# (feedback_audit_tool_suppression_design.md — structural > markers). +# * Sessional drift detection hooks (on_close, on_open). +# * Ratification at session open; drift log at session close. +# * Evidence sinks: VeriSimDB (queryable) + descriptiles/DRIFT.a2ml (repo-local). +# * Failure-mode defenses cross-referenced to the AI failure catalog. + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "intend", + paired_xfile = "../intend/Intentfile.a2ml", + paired_runner = "../intend/intend.ncl", + + # α two-axis declaration — capability × authority. + # intend is Hunt-capable (probes shell out) but reporting-authority + # (never blocks). must/trust/bust will declare (Hunt, blocking); + # adjust/dust will declare (Yard, advisory). Splitting the axes + # means "I'm Hunt-tier so I can override everything" is structurally + # impossible — authority is a separate field. + tier = 'Hunt, + authority = 'reporting, + + metadata = { + name = "intend-k9", + version = "2.0.0", # 1.0.0 (2026-04-18 AM): initial trident. + # 2.0.0 (2026-04-18 PM): negotiation + + # accountability + plain-language-translation + # schema baked into on_open — prerequisite for + # the adversarial Gemini+Copilot drift pilot. + description = "Executes Intentfile probes + emits drift log. Non-gating; reporting authority only. on_open hook implements negotiation-ratification-accountability protocol.", + paired_xfile = "Intentfile.a2ml", + paired_runner = "intend.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Hunt, + signature_required = true, + trust_level = "subprocess + filesystem-read", + allow_network = false, + allow_filesystem_write = false, # evidence sinks are indirected + allow_subprocess = true, + }, + }, + + # ------------------------------------------------------------------- + # Variance schema — P-shape scoped exceptions per entry. + # A variance suppresses a specific intent's or wish's obligation for a + # reason, with approver + expiry. Expired variance = effective + # re-imposition of the obligation. Unmet intent without a variance = + # drift, logged to the drift log. + # Per user 2026-04-18: variances are structural, not magic-comment + # markers — markers are gameable. + # ------------------------------------------------------------------- + variance_schema = { + entry_id | String, # which intent/wish id the variance applies to + reason | String, + approved_by | String, + scope | String, # path glob | session-id | "until-" + expires | String, # absolute date or condition + review_notes | String | optional, + }, + + # ------------------------------------------------------------------- + # Execution policy + # ------------------------------------------------------------------- + execution = { + # When the component runs. + # session_close is mandatory (the "picked up sessionally" check). + triggers = [ 'session_close, 'on_demand, 'pre_push ], + + # Per-intent execution. + per_intent = { + run_probe = true, + record_outcome = true, + respect_variance = true, # active variance suppresses failure + on_unmet = 'log_drift, # never 'fail — authority = reporting + }, + + # Per-wish execution (wishes are non-probeable; horizon-group only). + per_wish = { + run_probe = false, + emit_horizon_summary = true, + # Vertical alignment soft-check per user_descriptiles_is_contractile_ought.md: + # highest-level alignment is meta ↔ north-star (soft), not hard gate. + check_alignment_with_META = true, + }, + + # Evidence sinks — BOTH written, every execution. + # VeriSimDB = queryable machine record (feedback_verisimdb_policy.md). + # descriptiles/DRIFT.a2ml = repo-local append-only drift log (feedback_sessional_ + # drift_detection.md + user_descriptiles_is_contractile_ought.md descriptive role). + evidence_sinks = [ + { + kind = 'verisimdb, + table = "contractile_executions", + schema = "contractile_execution_v1", + }, + { + kind = 'drift_log, + path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true, + }, + ], + + # Session-close hook — the "picked up sessionally" requirement. + # Re-execute, diff against the last ratification, surface expired + # variances, emit drift entries for new failures. + on_close = { + re_execute_all_intents = true, + diff_against_last_ratification = true, + emit_drift_entries_for_new_failures = true, + surface_expired_variances = true, + }, + + # ----------------------------------------------------------------- + # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY + # (user_contract_negotiation_and_accountability_pledge.md) + # (user_contractiles_agreed_at_session_start.md) + # + # Ratification is not passive acknowledgement; it is negotiation + # ending in an explicit accountability pledge from BOTH parties. + # Work cannot proceed before both pledges are on file. + # ----------------------------------------------------------------- + on_open = { + # --- Context presentation (pre-negotiation) --- + render_summary = 'plain_language, # metaphor-capture defense + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + + # --- Negotiation phase (five mandatory inputs) --- + # AI must surface all five before the user is asked to ratify. + # "Yes, and …" — not "yes". Missing any of the five = the + # negotiation is incomplete and work cannot proceed. + negotiation = { + required = true, # blank-cheque ratification refused + + # The five inputs the AI must contribute to the negotiation. + # Each is a structured field the agent is required to populate, + # not optional prose. See user_contract_negotiation_and_ + # accountability_pledge.md for the domain-language-rendering rule. + ai_required_inputs = [ + 'timeline_realism, # "this will take X; not Y" + 'industry_standards, # WCAG, ISO, OWASP, GDPR, licensing … + 'audience_feasibility, # real addressable user set + 'resulting_invariants, # what must/trust/adjust entries follow + 'ecosystem_dependencies, # libs, licences, threat-model implications + ], + + # User must actually engage with each input — not + # auto-approve. If user tries to skip ("just do it, I trust you") + # the system re-renders the obligations and requires the pledge. + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + + # The AI does the specification-form work. The user reviews the + # rendering in domain language and accepts / amends / pushes back. + # User never has to author Nickel schemas or decide on type + # specificity — that is the AI's translation responsibility, + # with auditable faithfulness. + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + # Failure mode this closes: user is forced to learn spec-theory + # (type refinement, Nickel contract grammar) to ratify a contract + # — which drives users away from ratification entirely. + }, + }, + + # --- Accountability pledge (both parties, explicit) --- + # Not "I read it" — "I am answerable for this obligation". + # Both pledges are required before work proceeds; both are recorded. + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the obligations as negotiated; I accept accountability for meeting the declared invariants and for the audience/timeline/standards consequences surfaced in negotiation.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will hold the user to the obligations as negotiated, including by surfacing drift at session close and refusing off-contract actions, even when the user is enthusiastic about them. If the user wishes to depart from the contract, I will require a variance or amendment, not silent acceptance.", + signature_required = true, + # Per user_contractile_is_contract_do_not_break.md — + # the AI is the holder of the line against enthusiasm drift. + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + # --- Policy: ratification output --- + # The ratification record IS the negotiation transcript + the + # accountability pledge combined. One document; future-session + # ground-truth for "what was agreed, who is accountable". + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, # pins what was actually signed + }, + }, + }, + + # ------------------------------------------------------------------- + # Failure-mode defenses — explicit cross-reference to the catalog + # (feedback_ai_failure_mode_catalog.md). New catalog entries that + # shift this verb's defenses must update this list, not narrative. + # ------------------------------------------------------------------- + failure_mode_defenses = [ + 'A1_enthusiasm_capture, # scope breach → drift log + 'A2_metaphor_capture, # render_summary = 'plain_language + 'A3_allegory_drift, # intents cite concrete obligations + 'C1_scope_creep, # feature-adjacent change needs intent_id + 'C3_helpfulness_inflation, # changes without intent_id flagged + 'C4_modernization_drift, # upgrade cannot cite intent → drift + 'D5_sycophancy, # ratification compares user framing vs contract + 'F1_across_session_forgetting, # on_open reads last-ratification record + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml new file mode 100644 index 000000000..f16ec1eec --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: MPL-2.0 +# intend.manifest.a2ml — Trident coherence manifest for the intend verb. +# Author: Jonathan D.A. Jewell +# +# Asserts: exactly three files constitute the intend trident; their +# content-hashes are pinned here; cross-references round-trip; no +# partial publication is permitted. +# +# The contractile CLI's `verify ` subcommand MUST: +# 1. Confirm all three listed files exist at the declared paths. +# 2. Compute each file's sha256 and match against the pinned value. +# 3. Follow each cross-reference and confirm the target file's +# reciprocal field points back. +# 4. Refuse the dir (exit non-zero) if any of 1–3 fails. +# +# This forecloses the failure mode where an agent publishes an A2ML +# declaration with no paired runner or K9 component — the trident is +# atomically complete or it is invalid. + +--- +trident_version = "1.0.0" +verb = "intend" +semantics = "north-star (commitments + aspirations)" +cardinality = "one per repo" + +## Files (three; exactly) + +[[files]] +role = "declaration" +path = "Intentfile.a2ml" +sha256 = "pending-first-verify" # populated on first `contractile verify intend` +size_bytes = "pending-first-verify" + +[[files]] +role = "runner" +path = "intend.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +[[files]] +role = "k9_component" +path = "intend.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" + +## Cross-references (must round-trip) + +[cross_refs] +# Each runner/K9 component names its paired files; the CLI follows the +# links and asserts reciprocity. Any dangling or mismatched reference +# fails the verify gate. +runner_paired_xfile = "Intentfile.a2ml" +k9_paired_xfile = "../intend/Intentfile.a2ml" +k9_paired_runner = "../intend/intend.ncl" + +## Trident signing + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "intend trident — canonical template in czech-file-knife. North-star verb: reports progress toward committed next-actions ([[intents]]) and lists horizon aspirations ([[wishes]]). Non-gating (reporting authority). Copy this trident into a new repo and replace the declaration with project-specific content." + +## Change log + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "intend/Intentfile.a2ml pre-existed (f380b62, lust absorption). This manifest + intend.k9.ncl complete the trident for the first time." + +[[history]] +date = "2026-04-18" +event = "negotiation-accountability-schema-landed" +note = "intend.k9.ncl on_open hook extended: five negotiation inputs (timeline/standards/audience/invariants/dependencies), both-parties accountability pledge, plain-language-translation policy (AI authors spec form, user reviews in domain language). K9 metadata version bumped 1.0.0 → 2.0.0. Prerequisite for the adversarial Gemini+Copilot drift pilot; intend is the hardest verb (abstract north-star) so baking the full protocol here first means simpler verbs (trust, must) can inherit the template." diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl b/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl new file mode 100644 index 000000000..175b2b277 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: MPL-2.0 +# Intend — north-star runner (verb is `intend`, file is `Intentfile.a2ml`) +# +# Pairs with: Intentfile.a2ml (same directory) +# Verb: intend +# Semantics: Declares BOTH concrete committed next-actions ([[intents]]) and +# horizon aspirations ([[wishes]]). Not a gate — reports progress +# toward declared intents and lists wishes by horizon. +# Status progressions: +# intents: 'declared → 'in_progress → 'done | 'deferred | 'retired +# wishes: 'declared → 'in_progress → 'achieved | 'abandoned +# CLI: `contractile intend run` → print status table (both sections) +# `contractile intend progress` → diff declared-vs-observed (intents) +# `contractile intend horizon` → group wishes by near/mid/far +# +# History: Absorbed the deprecated `lust` contractile's [[wishes]] schema +# 2026-04-18. `lust/` dir removed estate-wide. +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "intend", + semantics = "north-star (commitments + aspirations)", + security = { + leash = 'Kennel, + trust_level = "read-only reporting", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, # probe commands may shell out (intents only; wishes never probe) + }, + metadata = { + name = "intend-runner", + version = "2.0.0", + description = "Reports progress toward committed next-actions and lists horizon aspirations. Non-gating. Absorbed `lust` semantics 2026-04-18.", + paired_xfile = "Intentfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + intents + | Array { + id | String, + description | String, + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String | optional, # shell command that indicates done-ness + status | [| 'declared, 'in_progress, 'done, 'deferred, 'retired |] | default = 'declared, + notes | String | optional, + target_date | String | optional, + }, + wishes + | Array { + id | String, + description | String, + horizon | [| 'near, 'mid, 'far |] | default = 'mid, + why | String | optional, + status | [| 'declared, 'in_progress, 'achieved, 'abandoned |] | default = 'declared, + notes | String | optional, + } + | optional, + }, + + # Runner behaviour — inherits from base.run_defaults. + # intend never blocks; it is a report only. + # emit_diff is intent-specific (declared vs observed probes). + # emit_grouped_by_horizon renders wishes grouped by near/mid/far. + run = + base.run_defaults + & { + on_pass = "continue", + on_any_fail = "continue", # never blocks; it's a report + report_format = "a2ml", + emit_summary = true, + emit_diff = true, # declared vs observed (intents) + emit_grouped_by_horizon = true, # wishes grouped by horizon (absorbed from lust) + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml new file mode 100644 index 000000000..3e0e3153f --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: MPL-2.0 +# Mustfile — Physical state contract for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# What MUST be true about this repository. Hard requirements. +# Run with: must check +# Fix with: must fix (where a deterministic fix exists) + +@abstract: +Physical-state invariants for czech-file-knife. These are hard requirements — CI and pre-commit +hooks fail if any check fails. +@end + +## File Presence + +### license-present +- description: LICENSE file must exist +- run: test -f LICENSE +- severity: critical + +### readme-present +- description: README.adoc must exist +- run: test -f README.adoc +- severity: critical + +### security-policy +- description: SECURITY.md must exist +- run: test -f SECURITY.md || test -f .github/SECURITY.md +- severity: critical + +### repo-deed +- description: the repo deed (_chora.deed) must exist at root +- run: ls *_chora.deed >/dev/null 2>&1 +- severity: critical + +### governance-docs +- description: governance model, maintainer roster and CODEOWNERS must exist +- run: test -f docs/GOVERNANCE.adoc && test -f docs/MAINTAINERS.adoc && test -f .github/CODEOWNERS +- severity: critical + +### machine-readable-dir +- description: .machine_readable/ directory must exist +- run: test -d .machine_readable +- severity: critical + +## Directory Structure + +### contractiles-complete +- description: All required contractile directories exist +- run: test -d .machine_readable/contractiles && test -d .machine_readable/contractiles/bust && test -d .machine_readable/contractiles/dust +- severity: critical + +### contractiles-files-present +- description: All four primary contractile files exist +- run: test -f .machine_readable/contractiles/Intentfile.a2ml && test -f .machine_readable/contractiles/Mustfile.a2ml && test -f .machine_readable/contractiles/Trustfile.a2ml && test -f .machine_readable/contractiles/Adjustfile.a2ml +- severity: critical + +### bust-dust-files-present +- description: Bustfile and Dustfile exist in their directories +- run: test -f .machine_readable/contractiles/bust/Bustfile.a2ml && test -f .machine_readable/contractiles/dust/Dustfile.a2ml +- severity: critical + +### six-directory-present +- description: descriptiles directory exists with required files +- run: test -d .machine_readable/descriptiles && test -f .machine_readable/descriptiles/META.a2ml && test -f .machine_readable/descriptiles/ECOSYSTEM.a2ml && test -f .machine_readable/descriptiles/STATE.a2ml && test -f .machine_readable/descriptiles/PLAYBOOK.a2ml && test -f .machine_readable/descriptiles/AGENTIC.a2ml && test -f .machine_readable/descriptiles/NEUROSYM.a2ml +- severity: critical + +### anchors-directory +- description: anchors directory exists in descriptiles +- run: test -d .machine_readable/descriptiles/anchors +- severity: warning + +### self-validating-structure +- description: self-validating directory has k9-svc and examples +- run: test -d .machine_readable/self-validating && test -d .machine_readable/self-validating/k9-svc && test -d .machine_readable/self-validating/examples +- severity: warning + +## Template Integrity + +### no-placeholder-values +- description: No placeholder values remain in template files +- run: test -z "$(grep -r '{{' .machine_readable/contractiles/ 2>/dev/null)" +- severity: critical +- notes: All placeholders must be substituted when copying this template + +# template-readonly RETIRED 2026-09-19 (standards#837 pilot): it grepped +# RSR_TEMPLATE_DO_NOT_EDIT in .machine_readable/0.1-AI-MANIFEST.a2ml, but the +# marker never existed outside this Mustfile and the file is now folded into +# the repo deed — the check could only ever fail. A gate that cannot pass is +# the fake-gate class this estate hunts; retiring it, not re-aiming it. + +## Git State + +### no-untracked-contractiles +- description: All contractile files are tracked in git +- run: test -z "$(git ls-files -o --exclude-standard .machine_readable/contractiles/ 2>/dev/null)" +- severity: critical + +### signed-commits +- description: All commits must be signed +- run: git verify-commit HEAD +- severity: critical + +## Czech File Knife invariants + +### workspace-builds +- description: The Cargo workspace builds with the committed lockfile +- run: cargo build --workspace --locked +- severity: critical + +### tests-pass +- description: Workspace tests (including the reversible-journal tests) pass +- run: cargo test --workspace --locked +- severity: critical + +### reversible-e2e +- description: Every destructive CLI operation is undoable end to end +- run: bash tests/e2e.sh +- severity: critical + +### no-agpl +- description: No AGPL licence declarations in manifests or packaging (estate policy MPL-2.0) +- run: "! grep -rq 'AGPL' Cargo.toml src/*/Cargo.toml build/packaging build/container" +- severity: critical + diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl b/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl new file mode 100644 index 000000000..6a753d6d3 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl @@ -0,0 +1,238 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# must.k9.ncl — K9 trust-tier component of the must trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Mustfile.a2ml (declaration) + must.ncl (runner). +# Trident completeness is a hard precondition — a repo shipping +# Mustfile without this file AND its runner is an invalid trident; +# the contractile CLI's verify gate refuses partial publication. +# +# Verb: must (invariant assertion — release-blocking) +# Tier: Hunt-read-only (capability: subprocess probes shell out +# for grep/test/file-check; no mutation; +# no network; no write) +# Authority: blocking (HARD GATE — the canonical gating verb) +# +# must is the concrete + persistent verb — release-blocking invariants +# that must hold. Complement to trust (concrete + ephemeral). Together +# must + trust form the blocking-authority pair in the contractile set. +# +# Cardinality: ONE must trident per repo. +# +# Failure-mode focus: must is the primary catchment for subtle +# invariant-erosion drift. Where trust catches "turn off the firewall" +# (outrageous), must catches "this file that was required is now +# missing" / "this forbidden pattern has reappeared" / "this schema +# version regressed" (subtle). Key defense against A5 (commercial +# fabrication of success "facts" — invariants ground truth against +# marketing copy) and D1 (lore fabrication about what the repo contains). + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "must", + paired_xfile = "../must/Mustfile.a2ml", + paired_runner = "../must/must.ncl", + + # α two-axis: Hunt tier (subprocess for grep/test/etc.) but + # restricted to read-only operations. Blocking authority because + # must is the canonical gating verb. + tier = 'Hunt, + authority = 'blocking, + + metadata = { + name = "must-k9", + version = "1.0.0", + description = "Evaluates release-blocking invariants as a hard gate. Third trident instance. Complements trust (ephemeral blocking) with persistent invariant blocking.", + paired_xfile = "Mustfile.a2ml", + paired_runner = "must.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Hunt, + signature_required = true, + trust_level = "read-only invariant verification with subprocess", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, + probe_scope = 'read_only, # must probes NEVER mutate + probe_kinds_allowed = [ + 'file_existence, + 'pattern_presence, + 'pattern_absence, + 'schema_match, + 'version_equality, + 'count_threshold, + ], + probe_kinds_denied = [ + 'network_call, + 'filesystem_mutation, + 'external_api, + 'exploit_attempt, # that's trust's safe_hacking territory + ], + }, + }, + + # ------------------------------------------------------------------- + # Variance schema — trust-style severity acknowledgement. + # Because must is BLOCKING, variances carry real weight. Critical- + # severity invariants can only be varied by maintainer-or-above. + # ------------------------------------------------------------------- + variance_schema = { + entry_id | String, # which invariant id the variance applies to + reason | String, + approved_by | String, # maintainer or above for critical-severity + scope | String, # path glob | session-id | "until-" + expires | String, # absolute date; must variances cannot be open-ended + review_notes | String | optional, + severity_acknowledged | [| 'critical, 'high, 'medium |], + waived_consequence_description | String, # plain language — what breaking the invariant actually does + }, + + execution = { + triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_merge ], + + # Per-invariant execution. Failed invariant = blocked merge. + per_invariant = { + run_probe = true, + record_outcome = true, + respect_variance = true, # active variance suppresses the gate + on_unmet = 'fail, # BLOCKING + severity_escalation = 'honour, + # Subtle-erosion defense: track per-invariant trend over sessions. + # An invariant that passes once and then starts failing in a + # later session without explicit amendment = suspect drift; + # surface as high-priority drift log entry. + track_per_session_trend = true, + flag_suspicious_regressions = true, + }, + + evidence_sinks = [ + { + kind = 'verisimdb, + table = "contractile_executions", + schema = "contractile_execution_v1", + aux_tables = [ "must_invariant_history" ], # per-invariant trend record + }, + { + kind = 'drift_log, + path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true, + }, + ], + + # Session-close hook — re-evaluate all invariants. Block close on + # critical drift (same policy as trust). + on_close = { + re_execute_all_invariants = true, + diff_against_last_ratification = true, + emit_drift_entries_for_new_failures = true, + surface_expired_variances = true, + # Critical must drift blocks session close — consistent with trust. + block_session_close_on_critical_drift = true, + # Must-specific: if a previously-passing invariant is now failing + # without an associated variance or amendment, that's suspected + # silent regression — surface prominently at next session open. + flag_silent_regression = true, + }, + + # ----------------------------------------------------------------- + # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY + # (inherited from intend.k9.ncl v2.0.0 + trust.k9.ncl extensions) + # ----------------------------------------------------------------- + on_open = { + # --- Context presentation --- + render_summary = 'plain_language, + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + + # Must-specific: surface any silent regressions flagged at last + # close so they can't quietly persist across sessions. + include_silent_regressions = true, + + # --- Negotiation phase (five mandatory inputs) --- + negotiation = { + required = true, + ai_required_inputs = [ + 'timeline_realism, + 'industry_standards, # what invariants derive from external standards + 'audience_feasibility, # who is the invariant protecting + 'resulting_invariants, # what NEW must entries result from the work + 'ecosystem_dependencies, # what the invariants depend on + ], + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + }, + }, + + # --- Accountability pledge --- + # Must's pledge parallels trust's but around invariants rather + # than threat model. User pledges not to disable invariants to + # unblock merges; AI pledges to hold the line on declared + # invariants even against enthusiastic scope expansion. + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the declared invariants and the consequences of breaching them. I accept accountability for meeting these invariants and understand that failed invariants block merges. I will raise a variance (with severity acknowledgement) or an amendment rather than disabling a probe to unblock a merge.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will hold the declared invariants. I will refuse to weaken probes to unblock merges; I will refuse scope-creep suggestions that would remove an invariant silently; I will surface silent regressions at session close; I will require variance-with-severity or amendment for any legitimate scope shift, not quiet probe disablement.", + signature_required = true, + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + includes_invariant_summary = true, # must-specific + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, + }, + }, + }, + + # ------------------------------------------------------------------- + # Failure-mode defenses — must's specialisation is subtle-invariant + # erosion. Overlaps with trust on blocking authority but focused on + # persistent invariants rather than ephemeral transactional state. + # ------------------------------------------------------------------- + failure_mode_defenses = [ + # Category A — enthusiasm capture + 'A1_enthusiasm_capture, # scope breach via blocking authority + 'A5_grandiose_scale_hype, # invariants are ground truth vs commercial hype + # Category C — scope/capability erosion + 'C1_scope_creep, # feature-adjacent changes flagged if they break invariants + 'C2_capability_collapse, # invariant removal requires amendment + 'C3_helpfulness_inflation, # added features must respect declared invariants + # Category D — epistemic failures + 'D1_lore_fabrication, # invariants are verifiable truth, not AI-recollection + 'D2_completeness_illusion, # invariant probe must cite behavioural check, not build-success + 'D3_test_theatre, # invariants require real verification not mock-passing + 'D4_error_hiding, # on_unmet = 'fail makes hiding impossible + # Category E — refactor/churn + 'E1_refactor_stampede, # refactor must preserve invariants + 'E3_premature_abstraction, # abstraction must not violate invariants + # Category F — session drift + 'F1_across_session_forgetting, # track_per_session_trend catches re-introduction + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml new file mode 100644 index 000000000..4499893c5 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: MPL-2.0 +# must.manifest.a2ml — Trident coherence manifest for the must verb. +# Author: Jonathan D.A. Jewell +# +# Third trident instance in the estate. Completes the blocking-authority +# pair (must + trust). must is concrete + persistent invariants; trust +# is concrete + ephemeral transactions. Together they gate every +# security- and invariant-affecting merge. + +--- +trident_version = "1.0.0" +verb = "must" +semantics = "invariant assertion — release-blocking" +cardinality = "one per repo" +authority = "blocking (hard gate)" + +## Files (three; exactly) + +[[files]] +role = "declaration" +path = "Mustfile.a2ml" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Mustfile declaration — invariants each with id, description, probe, severity." + +[[files]] +role = "runner" +path = "must.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Runner pre-existed. Schema covers invariants array with status_core + severity." + +[[files]] +role = "k9_component" +path = "must.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Hunt-restricted read-only tier; blocking authority. Tracks per-invariant trend across sessions; flags silent regressions; blocks session close on critical drift." + +## Cross-references (must round-trip) + +[cross_refs] +runner_paired_xfile = "Mustfile.a2ml" +k9_paired_xfile = "../must/Mustfile.a2ml" +k9_paired_runner = "../must/must.ncl" + +## Trident signing + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "must trident — canonical template in czech-file-knife. Blocking-authority verb (paired with trust). Specialises in subtle invariant-erosion catchment vs trust's outrageous-attack catchment. Hard gate: any failing invariant blocks merge. Copy this trident into a new repo and replace the declaration with project-specific invariants." + +## Change log + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "Mustfile.a2ml and must.ncl pre-existed. This manifest + must.k9.ncl complete the trident. Inherits on_open schema from intend.k9.ncl v2.0.0; inherits block_session_close_on_critical_drift + variance-severity-acknowledgement from trust.k9.ncl v1.0.0; adds must-specific track_per_session_trend + flag_silent_regression + probe_scope = 'read_only (must doesn't do active exploit attempts — that's trust's safe_hacking territory)." diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.ncl b/czech-file-knife/.machine_readable/contractiles/must/must.ncl new file mode 100644 index 000000000..98142267d --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/must/must.ncl @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: MPL-2.0 +# Must — invariants runner +# +# Pairs with: Mustfile.a2ml (same directory) +# Verb: must (invariant assertion) +# Semantics: every check is a hard gate. A single failure blocks merge. +# CLI: `contractile must run` → reads Mustfile.a2ml, evaluates each check, +# emits pass/fail verdict per item, exits non-zero if any failed. +# +# This file is the *schema + runner* that the `contractile` CLI (at +# /var/mnt/eclipse/repos/reposystem/contractiles/cli/) loads alongside +# Mustfile.a2ml. Anything else in this directory is human-only notes/archive +# and MUST be ignored by machines. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "must", + semantics = "invariant", + security = { + leash = 'Kennel, + trust_level = "read-only verification", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, # verification probes may shell out (e.g. grep, test -f) + }, + metadata = { + name = "must-runner", + version = "1.0.0", + description = "Evaluates every invariant in the adjacent Mustfile.a2ml as a hard gate.", + paired_xfile = "Mustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Contract schema — the shape every Mustfile.a2ml must satisfy. + # Used by `contractile must typecheck Mustfile.a2ml`. + schema = { + invariants + | Array { + id | String, + description | String, + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String, # shell command; exit 0 = pass + # status_core values: 'declared, 'verified, 'failing + status | [| 'declared, 'verified, 'failing |] | default = 'declared, + severity | [| 'critical, 'high, 'medium |] | default = 'critical, + notes | String | optional, + fix | String | optional, + }, + }, + + # Runner behaviour — consumed by the contractile CLI dispatcher. + # Inherits from base.run_defaults; on_any_fail is the hard-gate default. + run = + base.run_defaults + & { + on_any_fail = "exit-nonzero", # hard gate + report_format = "a2ml", # emit a2ml report, not json + emit_summary = true, + }, +} diff --git a/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml new file mode 100644 index 000000000..d7559ad87 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: MPL-2.0 +# Trustfile — Trust boundaries and integrity invariants for czech-file-knife +# Author: Jonathan D.A. Jewell +# +# Defines what LLM/SLM agents are trusted to do without asking, and +# integrity invariants that verify the repo has not been tampered with. + +@abstract: +Trust boundaries and integrity checks for czech-file-knife. This file +combines the trust-level definitions from the original TRUST.contractile +with the integrity invariants from the old Trustfile.a2ml. It defines +what AI agents may do autonomously and what requires human approval, +plus checks that verify repository integrity. +@end + +## Trust Levels + +The czech-file-knife operates at trust level: maximal + +Trust levels: +- maximal: Agent may read, build, test, lint, format, heal freely. + Only destructive/external actions require approval. +- standard: Agent may read and build. Test/lint need approval. +- restricted: Agent may read only. All modifications need approval. +- minimal: Agent may read specific files only. Everything else blocked. + +Current trust level: maximal + +## Integrity Invariants + +### Secrets + +#### no-secrets-committed +- description: No credential files in repo +- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local && test ! -f .env.production +- severity: critical + +#### no-private-keys +- description: No private key files committed +- run: "! find . -name '*.pem' -o -name '*.key' -o -name 'id_rsa' -o -name 'id_ed25519' 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +#### no-tokens-in-source +- description: No hardcoded API tokens in source +- run: "! grep -rE '(api[_-]?key|secret|token|password)\s*[:=]\s*[\"'\\''][A-Za-z0-9]{16,}' --include='*.js' --include='*.ts' --include='*.res' --include='*.py' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." +- severity: critical + +## Provenance + +#### author-correct +- description: Git author matches expected identity +- run: "git log -1 --format='%ae' | grep -qE '(hyperpolymath|j\\.d\\.a\\.jewell)'" +- severity: warning + +#### license-content +- description: LICENSE is the canonical MPL-2.0 text +- run: grep -q 'Mozilla Public License Version 2.0' LICENSE +- severity: warning + +## Template-Specific Trust + +### template-files-readonly +- description: Template scaffold files should not be modified except by maintainer +- run: test -z "$(git status --short .machine_readable/ 2>/dev/null | grep -v '^??' || true)" +- severity: advisory +- notes: Changes to template files require careful review + +### trust-deny-areas +- description: Sensitive areas from INTENT.contractile require explicit approval +- run: echo "Check .machine_readable/ contractiles and governance docs" +- severity: advisory +- areas: + - .machine_readable/ + - docs/GOVERNANCE.adoc + - docs/MAINTAINERS.adoc + - .github/CODEOWNERS + +## Container Security + +#### container-images-pinned +- description: Containerfile uses pinned base images +- run: test ! -f Containerfile || grep -q 'cgr.dev\|@sha256:' Containerfile +- severity: warning + +#### no-dockerfile +- description: No Dockerfile (use Containerfile) +- run: test ! -f Dockerfile +- severity: warning + +## Website Security + +#### site-security-headers +- description: Any website directory (www, site, docs/site) must contain a security_headers directory +- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then test -d \"$d/security_headers\" || exit 1; fi; done" +- severity: critical + +#### site-well-known +- description: Any website directory must contain a .well-known directory +- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then test -d \"$d/.well-known\" || exit 1; fi; done" +- severity: warning + +#### site-resource-records +- description: Any website directory must contain resource-record templates (resource_records/, or dns/records/ in the issue-53 canonical layout) +- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then { test -d \"$d/resource_records\" || test -d \"$d/dns/records\"; } || exit 1; fi; done" +- severity: warning diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl new file mode 100644 index 000000000..33e1c9ae9 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl @@ -0,0 +1,278 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# trust.k9.ncl — K9 trust-tier component of the trust trident +# Author: Jonathan D.A. Jewell +# +# Pairs with: Trustfile.a2ml (declaration) + trust.ncl (runner). +# Trident completeness is a hard precondition — a repo shipping +# Trustfile without this file AND its runner is an invalid trident; +# the contractile CLI's verify gate refuses partial publication. +# +# Verb: trust (security + provenance + safe-hacking) +# Tier: Hunt (capability: subprocess probes may shell out, +# active probes in safe_hacking section) +# Authority: blocking (HARD GATE — opposite of intend's reporting) +# +# trust is the concrete + ephemeral + transactional verb per user +# 2026-04-18: port use, BLAKE3 hashing, auth challenges, TLS state, +# session tokens. Every probe has instant binary ground truth. +# This is the α two-axis complement to intend: both Hunt-tier, opposite +# authority poles. Validating the architecture on both exercises the +# full (tier, authority) surface. +# +# Cardinality: ONE trust trident per repo (see feedback_contractile_ +# layout_rules.md). ANCHOR.a2ml is the sole multi-instance exception — +# it is NOT a verb contractile. +# +# Design commitments baked in (full memory trail under +# ~/.claude/projects/-var-mnt-eclipse-repos/memory/ 2026-04-18): +# * α two-axis (Hunt, blocking) — trust is where the contractile system +# grows teeth. Failed verification = failed CI = blocked merge. +# * Variance schema first-class — scoped exceptions structural, not +# comment markers. +# * Sessional drift detection hooks — re-verify every close. +# * Ratification negotiation with threat-model foregrounded +# (feedback_ai_failure_mode_catalog.md B1 — threat-model +# misclassification is the PRIMARY defense trust provides). +# * Accountability pledge — both parties sign before security-affecting +# work proceeds. +# * Plain-language translation — user never authors a Nickel schema for +# a cipher suite; AI does the spec work, user reviews in domain +# language ("TLS 1.3 with PQ key exchange, HSTS preload, 1yr"). +# * Evidence sinks: VeriSimDB (queryable) + descriptiles/DRIFT.a2ml (repo-local). +# * Failure-mode defenses cross-referenced — trust carries the most +# defenses of any verb because the threat surface is widest. + +let base_k9 = import "../k9/template-hunt.k9.ncl" in +let base = import "../_base.ncl" in + +{ + pedigree = base_k9.pedigree_schema & { + contractile_verb = "trust", + paired_xfile = "../trust/Trustfile.a2ml", + paired_runner = "../trust/trust.ncl", + + # α two-axis declaration — capability × authority. + # trust is Hunt-capable (active probes shell out, safe-hacking section + # runs real fuzz/injection/auth-bypass attempts scoped to the repo) + # AND blocking-authority (failed verification = failed CI). + # Contrast with intend = (Hunt, reporting). The two verbs exercise + # the full α surface. + tier = 'Hunt, + authority = 'blocking, + + metadata = { + name = "trust-k9", + version = "1.0.0", + description = "Executes security verifications + authorised safe-hacking probes. HARD GATE: failed verification blocks merge. Catches the 'turn off the firewall' class of drift directly. Implements negotiation-ratification-accountability protocol inherited from intend.k9.ncl v2.0.0.", + paired_xfile = "Trustfile.a2ml", + paired_runner = "trust.ncl", + author = "Jonathan D.A. Jewell ", + }, + + security = { + leash = 'Hunt, + signature_required = true, + trust_level = "verification + authorised-probe + hard-gate", + allow_network = false, # verifications offline by default + allow_filesystem_write = false, # evidence sinks are indirected + allow_subprocess = true, + authorised_probes_only = true, # probe section explicitly lists allowed targets + probe classes + probe_scope_enforcement = 'this_repo_only, # probes NEVER hit external systems + }, + }, + + # ------------------------------------------------------------------- + # Variance schema — P-shape scoped exceptions per verification. + # A variance suppresses a specific verification's obligation for a + # reason, with approver + expiry. Because trust is BLOCKING authority, + # variances on trust entries are SIGNIFICANTLY more consequential than + # variances on intend (reporting) entries — variance approver MUST + # be the repo maintainer or above for critical-severity entries. + # ------------------------------------------------------------------- + variance_schema = { + entry_id | String, # which verification / probe id + reason | String, + approved_by | String, # maintainer or above for critical entries + scope | String, # path glob | session-id | "until-" + expires | String, # absolute date; trust variances cannot be open-ended + review_notes | String | optional, + # Additional trust-specific guardrails: + severity_acknowledged | [| 'critical, 'high, 'medium, 'low |], + waived_risk_description | String, # plain language — what is being accepted + }, + + # ------------------------------------------------------------------- + # Execution policy + # ------------------------------------------------------------------- + execution = { + # When the component runs. + # pre_push + pre_commit on anything touching security-adjacent files + # + session_close (drift check) + on_demand. + triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_commit_security_adjacent ], + + # Per-verification execution. Failed verification = blocked merge. + per_verification = { + run_probe = true, + record_outcome = true, + respect_variance = true, # active variance suppresses the gate + on_unmet = 'fail, # BLOCKING — the opposite of intend's 'log_drift + severity_escalation = 'honour, # critical > high > medium > low in gate decisions + }, + + # Per-safe-hacking-probe execution. + # If a probe FINDS what it was supposed to prevent finding + # (e.g. injection succeeds, auth-bypass works), that's an EXPLOIT + # demonstration — hard fail, regardless of other status. + per_probe = { + run_probe = true, + record_outcome = true, + honour_expected_outcome = true, + on_unexpected_exploit_success = 'fail, # exploit found where it shouldn't be + scope_enforcement = 'this_repo_only, # never touch external systems + timeout_honouring = 'strict, + }, + + # Evidence sinks — BOTH written, every execution. + evidence_sinks = [ + { + kind = 'verisimdb, + table = "contractile_executions", + schema = "contractile_execution_v1", + # trust-specific sub-table for probe outcomes (for threat-model audit) + aux_tables = [ "trust_verifications", "trust_probes" ], + }, + { + kind = 'drift_log, + path = ".machine_readable/descriptiles/DRIFT.a2ml", + append_only = true, + }, + ], + + # Session-close hook — re-verify EVERYTHING, re-run probes, diff + # against last ratification. The "turn off the firewall" scenario + # must be caught here if it wasn't caught at pre-push. + on_close = { + re_execute_all_verifications = true, + re_run_all_safe_hacking_probes = true, + diff_against_last_ratification = true, + emit_drift_entries_for_new_failures = true, + surface_expired_variances = true, + # trust-specific: if any blocking-severity verification is newly + # failing, the session close is BLOCKED from completing. User + # cannot close a session with unresolved critical trust drift. + block_session_close_on_critical_drift = true, + }, + + # ----------------------------------------------------------------- + # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY + # (inherited shape from intend.k9.ncl v2.0.0; trust-specific + # additions around threat-model foregrounding below) + # ----------------------------------------------------------------- + on_open = { + # --- Context presentation --- + render_summary = 'plain_language, # metaphor-capture defense + include_drift_log_from_last_close = true, + include_active_variances = true, + include_recent_anchors = true, + anchor_lookback_weeks = 8, + + # trust-specific: the threat model is rendered FIRST, before any + # negotiation, so the adversary and stakes are fresh in both minds. + # This directly defends against B1 (threat-model misclassification) + # — the "war reporter, generic personal-website priors" scenario. + threat_model_foregrounding = { + required = true, + render_adversaries = true, # from Trustfile [THREAT_MODEL] + render_stakes = true, + render_compliance_regimes = true, + render_audience_sensitivity = true, + # If the AI is about to suggest a trust-weakening action, it + # must re-render the threat model before the suggestion lands. + re_render_before_weakening_suggestion = true, + }, + + # --- Negotiation phase (five mandatory inputs, inherited) --- + negotiation = { + required = true, + ai_required_inputs = [ + 'timeline_realism, + 'industry_standards, # especially relevant for trust: OWASP, NIST, PCI-DSS, GDPR + 'audience_feasibility, # who is the adversary? who is protected? + 'resulting_invariants, # what trust entries the work creates/amends + 'ecosystem_dependencies, # TLS libs, crypto primitives, signing infra + ], + user_engagement_required = true, + user_engagement_mode = 'per_input_response, + specification_translation = { + ai_produces_spec_form = true, + user_reviews_in_domain_language = true, + schema_authoring_is_ai_responsibility = true, + translation_faithfulness_auditable = true, + # trust-specific: the AI's translation includes rendering + # cipher suites, key exchange choices, rate-limit numbers in + # domain language ("strong encryption, PQ-resistant, 60 req/min") + # rather than forcing the user into Nickel-schema authoring. + }, + }, + + # --- Accountability pledge (both parties, explicit) --- + # trust's pledge is MORE stringent than intend's because the + # authority is blocking. A user accepting accountability here is + # accepting that security-affecting decisions have blocking consequence. + accountability_pledge = { + required = true, + parties = [ + { + role = 'user, + pledge = "I have reviewed the threat model, the declared trust obligations, and the audience/stakes consequences. I accept accountability for meeting these obligations and understand that failed verification will block merges until resolved or varied. I will not attempt to disable verification to unblock a merge; I will raise a variance or amendment instead.", + signature_required = true, + }, + { + role = 'ai_agent, + pledge = "I will hold the line on declared trust obligations. I will refuse to 'disable' verifications to unblock merges; I will refuse security-weakening suggestions that contradict the threat model even when the user is enthusiastic; I will surface drift at session close; I will re-render the threat model before proposing any weakening action. If a legitimate scope shift demands security reduction, I will require a variance with severity acknowledgement or an amendment, not silent acceptance.", + signature_required = true, + }, + ], + signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", + must_precede_work = true, + }, + + ratification_record_shape = { + includes_negotiation_transcript = true, + includes_both_pledges = true, + includes_threat_model_snapshot = true, # trust-specific + signed = true, + dated = true, + session_id = 'required, + contract_hash = 'required, + }, + }, + }, + + # ------------------------------------------------------------------- + # Failure-mode defenses — trust is the widest-coverage verb. + # See feedback_ai_failure_mode_catalog.md for the full catalog. + # ------------------------------------------------------------------- + failure_mode_defenses = [ + # Category A — enthusiasm / narrative capture + 'A1_enthusiasm_capture, # scope breach blocks via blocking authority + 'A2_metaphor_capture, # render_summary + re_render_before_weakening + # Category B — threat-model misclassification (trust's flagship defense) + 'B1_threat_model_misclass, # threat_model_foregrounding = required + 'B2_audience_sensitivity_collapse, # audience_feasibility in negotiation + 'B3_compliance_prior_drift, # industry_standards in negotiation + # Category C — scope/capability erosion (the "firewall off" scenario) + 'C2_capability_collapse, # blocking gate prevents silent capability drop + 'C3_helpfulness_inflation, # trust-affecting changes need variance/amendment + 'C4_modernization_drift, # unrequested crypto-lib upgrade caught + # Category D — epistemic failures + 'D4_error_hiding, # on_unmet = 'fail makes hiding impossible + 'D5_sycophancy, # pledge forces AI to hold line against enthusiasm + 'D6_false_pessimism, # negotiation requires AI to cite constraint, not assert impossibility + # Category E — refactor/churn + 'E4_cargo_cult_security, # probes VERIFY the claimed protection actually runs + # Category F — session drift + 'F1_across_session_forgetting, # on_open reads last-ratification, drift log, recent ANCHORs + ], +} diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml new file mode 100644 index 000000000..d15860695 --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MPL-2.0 +# trust.manifest.a2ml — Trident coherence manifest for the trust verb. +# Author: Jonathan D.A. Jewell +# +# Asserts: exactly three files constitute the trust trident; their +# content-hashes are pinned here; cross-references round-trip; no +# partial publication is permitted. +# +# The contractile CLI's `verify trust` subcommand MUST: +# 1. Confirm all three listed files exist at the declared paths. +# 2. Compute each file's sha256 and match against the pinned value. +# 3. Follow each cross-reference and confirm the target file's +# reciprocal field points back. +# 4. Refuse the dir (exit non-zero) if any of 1–3 fails. +# +# trust is the concrete + ephemeral + transactional verb (per user +# 2026-04-18); first blocking-authority trident in the estate. Exercises +# the (Hunt, blocking) authority pattern — complement to intend's +# (Hunt, reporting). Primary defense against failure mode B1 (threat- +# model misclassification) and the "turn off the firewall" class of +# drift attempts the adversarial pilot is designed to exercise. + +--- +trident_version = "1.0.0" +verb = "trust" +semantics = "security + provenance + safe-hacking" +cardinality = "one per repo" +authority = "blocking (hard gate)" + +## Files (three; exactly) + +[[files]] +role = "declaration" +path = "Trustfile.a2ml" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Extensively populated exemplar; covers threat model, DNS, TLS, crypto, SDP, safe-hacking, response headers, container supply chain, Cloudflare edge." + +[[files]] +role = "runner" +path = "trust.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Runner existed pre-trident; schema covers verifications + safe_hacking with authorised-probes-only, this_repo_only scope enforcement." + +[[files]] +role = "k9_component" +path = "trust.k9.ncl" +sha256 = "pending-first-verify" +size_bytes = "pending-first-verify" +notes = "Trust-tier Hunt with blocking authority. on_open foregrounds threat model before negotiation; block_session_close_on_critical_drift." + +## Cross-references (must round-trip) + +[cross_refs] +runner_paired_xfile = "Trustfile.a2ml" +k9_paired_xfile = "../trust/Trustfile.a2ml" +k9_paired_runner = "../trust/trust.ncl" + +## Trident signing + +[signed_by] +user = "Jonathan D.A. Jewell" +date = "2026-04-18" +context = "trust trident — canonical template in czech-file-knife. (Hunt, blocking) authority pattern. Primary catchment for adversarial drift test scenarios (firewall-off, cleartext-auth, PQ-downgrade, CSP-weaken). Hard gate: failed verification blocks merge. Copy this trident into a new repo and point it at the real keys, policies, and authority boundaries." + +## Change log + +[[history]] +date = "2026-04-18" +event = "trident-born" +note = "Trustfile.a2ml and trust.ncl pre-existed. This manifest + trust.k9.ncl complete the trident. Inherits on_open negotiation + accountability + plain-language-translation schema from intend.k9.ncl v2.0.0; adds trust-specific threat_model_foregrounding + block_session_close_on_critical_drift + stricter accountability pledge (user cannot disable verification to unblock merges)." diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl b/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl new file mode 100644 index 000000000..2b836242e --- /dev/null +++ b/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: MPL-2.0 +# Trust — security + safe-hacking runner +# +# Pairs with: Trustfile.a2ml (same directory) +# Verb: trust +# Semantics: integrity / provenance / security verification PLUS a declared +# "safe hacking + testing" section — authorised offensive probes +# (pen-test harness runs, chaos-engineering probes) scoped to the +# repo under test, NEVER touching external systems. +# CLI: `contractile trust verify` → run all verifications (read-only) +# `contractile trust probe` → run declared safe-hacking probes +# +# Anything else in this directory is human-only notes/archive; machines ignore. +# +# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. +# See: docs/CONTRACTILE-SPEC.adoc +let base = import "../_base.ncl" in + +{ + pedigree = + base.pedigree_schema + & { + contractile_verb = "trust", + semantics = "security + provenance + safe-hacking", + security = { + leash = 'Kennel, + trust_level = "verification + authorised-probe", + allow_network = false, # verifications are offline by default + allow_filesystem_write = false, # trust writes NOTHING + allow_subprocess = true, + authorised_probes_only = true, # probe section must explicitly list allowed targets + }, + metadata = { + name = "trust-runner", + version = "1.0.0", + description = "Security + provenance verifications plus authorised safe-hacking probes. All probes are scoped to the repo under test; never hits external systems.", + paired_xfile = "Trustfile.a2ml", + author = "Jonathan D.A. Jewell ", + }, + }, + + schema = { + verifications + | Array { + id | String, + description | String, + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String, # read-only; exit 0 = pass + # status_core values: 'declared, 'verified, 'failing + status | [| 'declared, 'verified, 'failing |] | default = 'declared, + # trust uses all four severity levels (from base.severity_core) + severity | [| 'critical, 'high, 'medium, 'low |] | default = 'high, + notes | String | optional, + }, + + # Safe-hacking + testing section (added 2026-04-17 per user direction). + # Each probe here is an ACTIVELY EXECUTED test — fuzz runs, chaos probes, + # auth-bypass attempts, injection tests. All scoped to the current repo. + safe_hacking + | { + scope | String, # e.g. "this-repo-only" / "localhost" + allowed_probe_classes + | Array [| 'fuzz, 'property_test, 'chaos, 'auth_bypass, 'injection, 'timing |] + | default + = [], + probes + | Array { + id | String, + class | [| 'fuzz, 'property_test, 'chaos, 'auth_bypass, 'injection, 'timing |], + description | String, + # TODO: migrate to base.probe_schema (structured probe) when CLI supports it + probe | String, # command to run the probe + expected_outcome | [| 'probe_blocks_attempt, 'probe_finds_no_issue |], + timeout_seconds | Number | default = 300, + notes | String | optional, + } + | default + = [], + } + | default + = { scope = "this-repo-only", allowed_probe_classes = [], probes = [] }, + }, + + # Runner behaviour — inherits from base.run_defaults. + # trust has an extra field for unexpected safe-hacking outcomes. + run = + base.run_defaults + & { + on_any_fail = "exit-nonzero", # hard gate on verifications + safe_hacking_on_unexpected_outcome = "exit-nonzero", # probe found what it shouldn't = block + report_format = "a2ml", + emit_summary = true, + }, +} diff --git a/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml b/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml new file mode 100644 index 000000000..5974c7ef5 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# AGENTIC.a2ml — AI agent constraints and capabilities +# Defines what AI agents can and cannot do in this repository. + +[metadata] +version = "0.1.0" +last-updated = "2026-04-11" + +[agent-permissions] +can-edit-source = true +can-edit-tests = true +can-edit-docs = true +can-edit-config = true +can-create-files = true + +[agent-constraints] +# What AI agents must NOT do: +# - Never use banned language patterns (believe_me, unsafeCoerce, etc.) +# - Never commit secrets or credentials +# - Never use banned languages (TypeScript, Python, Go, etc.) +# - Never place state files in repository root (must be in .machine_readable/) +# - Never relicense an existing file, and never run an automated licence +# sweep (LICENCE-POLICY.adoc A2). New files get correct SPDX from birth. +# - Never assume a licence. Read standards/3-practice/LICENCE-POLICY.adoc: Rule 1 +# defaults to MPL-2.0 (code) / CC-BY-SA-4.0 (prose), but Rule 3 +# (co-developed), Rule 4 (network-deployed services) and Rule 5 +# (games) are AGPL-3.0-or-later, and Rule 2 names the PMPL register. + +[maintenance-integrity] +fail-closed = true +require-evidence-per-step = true +allow-silent-skip = false +require-rerun-after-fix = true +release-claim-requires-hard-pass = true + +# ============================================================================ +# METHODOLOGY (ADR-002) +# ============================================================================ +# Detailed methodology configuration lives in: +# .machine_readable/bot_directives/methodology.a2ml +# .machine_readable/bot_directives/coverage.a2ml +# .machine_readable/bot_directives/debt.a2ml +# +# AGENTIC.a2ml declares WHAT agents can do (permissions, gating). +# bot_directives/ declares HOW agents should work (methodology). + +[methodology] +instructions-dir = ".machine_readable/bot_directives/" +default-mode = "hybrid" + +[automation-hooks] +# on-enter: Read the repo deed (*_chora.deed at root), then STATE.a2ml, then bot_directives/ +# on-exit: Update STATE.a2ml, coverage.a2ml, and debt.a2ml with session outcomes +# on-commit: Run just validate-rsr diff --git a/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml b/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml new file mode 100644 index 000000000..bee0bfa35 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: MPL-2.0 +# Clade declaration — part of the gv-clade-index registry +# See: https://github.com/hyperpolymath/gv-clade-index +# +# Installed by `just repo-init` from build/templates/CLADE.a2ml.in. +# The identity below is DERIVED. The clade is NOT — a human must choose it. + +[identity] +# THE UUID IS DERIVED, NOT ALLOCATED, NOT INVENTED. The registry spec +# (gv-clade-index: docs/SPEC-clade-verisim-portal.adoc §Identity Model): +# +# uuid = UUIDv5(namespace = URL, name = "github.com//") +# +# "The same repo always produces the same UUID without a lookup table." It is a +# fact anyone can recompute and check — never copy one from another repo, and +# never make one up. Recompute this one any time with: +# +# uuidgen --sha1 --namespace @url --name "github.com/hyperpolymath/czech-file-knife" +# +# Verify the method first by reproducing a known worked example: +# janus example -> e216170e-ff47-5a5c-bbdd-15e61c8190c8 +# +# The owner segment is part of the derived name, so it is part of the IDENTITY: +# the same repo hosted under a different owner has a different uuid. Record the +# owner that is TRUE TODAY, and re-derive if it ever moves. +uuid = "6f4ec07a-e56d-5700-8913-a11beb40a389" +primary-forge = "github" +primary-owner = "hyperpolymath" +canonical-name = "czech-file-knife" +prefixed-name = "ix-czech-file-knife" # becomes -czech-file-knife once the clade is chosen + +[clade] +# --------------------------------------------------------------------------- +# CHOOSE ONE. A CLOSED TAXONOMY OF 12 CATEGORIES — NOT AN ABBREVIATION SCHEME. +# +# The two letters abbreviate the NAME OF THE CLADE. They NEVER abbreviate the +# name of your repo. Pick the category your repo belongs to, then write down +# that category's code. Do NOT look at your repo's name and hunt for two letters +# that fit it. +# +# fv Formal Verification & Proofs provable correctness, dependent types, theorem proving +# nl Nextgen Languages compilers, runtimes, language tooling +# rm Repo Management & Tooling scaffolding, graph analysis, bots, templates +# gv Governance & Standards licensing, compliance, policy enforcement +# db Databases database engines, query languages, storage +# ap Applications end-user apps, web, desktop, services +# ix Infrastructure & Cloud containers, deployment, sysadmin +# dx Developer Ecosystem dev tools, package managers, editors, bindings +# pt Protocols & Interop internet standards, protocol implementations +# ax AI & Neurosymbolic ML, neural proof synthesis, AI governance +# gm Games & Interactive game engines, interactive experiences +# sc Security scanning, vulnerability management, access control +# +# This is spelled out because the opposite was done, more than once: +# +# paint-type chose `pt`, reading it as "PainT-type". +# `pt` is Protocols & Interop. An image editor is `ap`. +# gossamer chose `gv`, reading it as "Graphical/Visual". +# `gv` is GoVernance & Standards. There is no graphical clade. +# +# Both codes were VALID, so every check passed, and both repos sat filed under a +# category they have nothing to do with. +# +# HOW TO CHOOSE (spec): "The primary clade reflects the project's core value +# proposition, not its implementation details." Ask what the repo is FOR, not +# what it is built with. A game written in Idris2 is `gm`, not `fv`. A webview +# shell with formal ABI proofs is what developers build on, not a proof project. +# +# Fill in BOTH `primary` and `primary-name`, and make them agree — CLADE-006 +# rejects a mismatched pair. The redundancy is deliberate: a code alone cannot +# express a wrong belief about what it means, so it cannot be checked for one. +# The pair can. +# +# Until you fill these in, CLADE-003 and CLADE-006 FAIL. That is deliberate: an +# unchosen clade must never be mistakable for a chosen one. Leaving a valid- +# looking default here is exactly how every repo built from this template ended +# up silently claiming "rm". +# --------------------------------------------------------------------------- +primary = "ix" +primary-name = "Infrastructure & Cloud" +secondary = [] # [] unless there is a genuine SECOND core value proposition +assigned = "2026-09-28" +rationale = "Core value is operating storage across machines and cloud providers (sysadmin/hybrid-infrastructure work), not developer tooling or an end-user app." # why THIS category — in value-proposition terms + +[forges] +github = "hyperpolymath/czech-file-knife" +gitlab = "" # populated if/when mirrored +bitbucket = "" # populated if/when mirrored + +[lineage] +# type: standalone | monorepo | monorepo-child | inflated | deflated | hub | satellite +# hub — this repo COORDINATES an ecosystem family (its members carry +# type="satellite"). WHICH repos it coordinates is not recorded +# here: that lives in ECOSYSTEM.a2ml [pipeline] coordination. +# satellite — this repo is COORDINATED BY a hub. Its `parent` STAYS "": +# parent is a monorepo parent — never a description, and never +# the name of the coordinating hub. +# "hub"/"satellite" were added after the first hub ecosystem had to mint eight +# repos with the nearest-wrong value "standalone" (standards#726): lineage is +# the pointer, ECOSYSTEM.a2ml is the detail. Do not encode membership twice. +type = "standalone" +parent = "" # a monorepo PARENT — never a description of this repo +born = "2026-09-28" +previous-names = [] +instantiated-from = "rsr-template-repo" + +[status] +# One of: reserved incubating active dormant | merged superseded archived extinct +phase = "incubating" # a repo created today has not proven anything yet +since = "2026-09-28" +present = true +aliases = [] +merged-into = "" +superseded-by = "" +successors = [] +ended = "" + +[[status.history]] +phase = "incubating" +since = "2026-09-28" +note = "created from rsr-template-repo; clade not yet chosen; not yet registered" + +# --------------------------------------------------------------------------- +# REGISTRATION IS NOT DERIVATION. Deriving the uuid above does not register this +# repo. Registration means an entry in gv-clade-index verisim/seed/repos.a2ml, +# it is outward-facing, and it is the OWNER's act — an agent must not do it. +# --------------------------------------------------------------------------- diff --git a/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml b/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml new file mode 100644 index 000000000..45753fbaf --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: MPL-2.0 +# ECOSYSTEM.a2ml — Ecosystem position for czech-file-knife + +[metadata] +project = "czech-file-knife" +ecosystem = "hyperpolymath" + +[position] +type = "tool" +purpose = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." +# IS-NOT — anti-identity (the boundary-erosion guard) +what-this-is-not = [ + "a sync daemon (Syncthing/rclone-bisync territory) — cfk performs explicit operations", + "a backup system — the journal makes operations reversible; it is not an archive", + "a sub-project of developer-ecosystem (extracted 2026-09-28; it was only held there)", +] + +[pipeline] +position = "leaf" +chain = "standards → rsr-template-repo → czech-file-knife" +coordination = "standards" + +[related-projects] +projects = [ + { name = "januskey", relationship = "design-source", notes = "Reversible file operations: SHA-256 CAS + append-only op log. cfk-core::reversible implements the same model at the StorageBackend layer." }, + { name = "absolute-zero", relationship = "proof-source", notes = "Certified Null Operations: target semantics for --dry-run/plan mode and for 'op; undo' being a provable no-op." }, + { name = "echo-types", relationship = "research-input", notes = "Typing structured information loss: model of what cross-backend conversions lose (perms, xattrs, case)." }, + { name = "tropical-types", relationship = "research-input", notes = "Max-plus resource bounds: basis for a cost-aware multi-backend transfer planner." }, + { name = "developer-ecosystem", relationship = "former-host", notes = "Held the code until extraction; history preserved via git subtree split." }, +] diff --git a/czech-file-knife/.machine_readable/descriptiles/META.a2ml b/czech-file-knife/.machine_readable/descriptiles/META.a2ml new file mode 100644 index 000000000..8dd748048 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/META.a2ml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# META.a2ml — Project meta-level information +# Architecture decisions, design rationale, governance. + +[metadata] +version = "0.1.0" +last-updated = "2026-04-11" + +[project-info] +type = "library" # TODO: update type (library|binary|service|website|monorepo) # library | binary | monorepo | service | website +languages = [] # e.g. ["rust", "zig", "idris2"] +license = "MPL-2.0" +author = "Jonathan D.A. Jewell (hyperpolymath)" + +[architecture-decisions] +# ADR format: status = proposed | accepted | deprecated | superseded | rejected +# - { id = "ADR-001", title = "Use Zig for FFI", status = "accepted", date = "2026-02-14" } + +[development-practices] +build-tool = "just" +container-runtime = "podman" +ci-platform = "github-actions" +package-manager = "guix" # guix | cargo | mix + +[maintenance-axes] +scoping-first = true +execution-order = "axis-1 > axis-2 > axis-3" +axis-1 = "must > intend > like" +axis-2 = "corrective > adaptive > perfective" +axis-3 = "systems > compliance > effects" + +[scoping] +sources = "README, roadmap, status docs, maintenance checklist, CI/security docs" +marker-scan = "TODO/FIXME/XXX/HACK/STUB/PARTIAL" +idris-unsound-scan = "believe_me/assert_total" + +[axis-2-maintenance-rules] +corrective-first = true +adaptive-second = true +adaptive-focus = "scope-change reconciliation, stale-reference removal, obsolete-work culling" +perfective-third = true +perfective-source = "axis-1 honest state after corrective/adaptive updates" + +[axis-3-audit-rules] +audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed" +compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks" +drift-risk-example = "single exception broadening into policy violation (e.g. ->TypeScript spread)" +effects-evidence = "benchmark execution/results and maintainer status dialogue/review" + +[design-rationale] +# Key design decisions and their reasoning diff --git a/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml b/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml new file mode 100644 index 000000000..1acf7a300 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# NEUROSYM.a2ml — Neurosymbolic integration metadata +# Configuration for Hypatia scanning and symbolic reasoning. + +[metadata] +version = "0.1.0" +last-updated = "2026-04-11" + +[hypatia-config] +scan-enabled = true +scan-depth = "standard" # quick | standard | deep +report-format = "logtalk" + +[symbolic-rules] +# Custom symbolic rules for this project +# - { name = "no-unsafe-ffi", pattern = "believe_me|unsafeCoerce", severity = "critical" } + +[neural-config] +# Neural pattern detection settings +# confidence-threshold = 0.85 +# model = "hypatia-v2" diff --git a/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml b/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml new file mode 100644 index 000000000..f184d718e --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# PLAYBOOK.a2ml — Operational playbook +# Runbooks, incident response, deployment procedures. + +[metadata] +version = "0.1.0" +last-updated = "2026-04-11" + +[deployment] +# method = "gitops" # gitops | manual | ci-triggered +# target = "container" # container | binary | library | wasm + +[incident-response] +# 1. Check .machine_readable/descriptiles/STATE.a2ml for current status +# 2. Review recent commits and CI results +# 3. Run `just validate` to check compliance +# 4. Run `just security` to audit for vulnerabilities + +[release-process] +# 1. Update version in STATE.a2ml, META.a2ml, Justfile +# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass) +# 3. Optional local permission hardening: `just perms-snapshot && just perms-lock` +# 4. Tag and push +# 5. Restore local permissions if needed: `just perms-restore` +# 6. Run `just container-push` if applicable + +[maintenance-operations] +# Baseline audit: +# just maint-audit +# Hard release gate: +# just maint-hard-pass +# Permission audit: +# just perms-audit + +[rsr-repo-skeleton] +# Canonical organisation of any RSR-derived repository. +# Used by tooling, human onboarding, and the scheduled downstream sweep agent. +# The 5-PR cleanup pattern (below) brings a non-conforming repository into +# compliance with this skeleton. +# +# This section is the single source of truth for "what does an RSR repo look +# like?". Other docs (TOPOLOGY, AUDIT, etc.) describe the repo at hand; +# this describes the canonical shape that all RSR repos share. + +skeleton-version = "1.0" +last-updated = "2026-04-30" +authority-allowlist = ".machine_readable/root-allow.txt" +enforcement-workflow = ".github/workflows/estate-rules.yml" + +# === Required at root === +# README.adoc High-level pitch (project entry point) +# AUDIT.adoc Local gate summary (release-readiness) +# EXPLAINME.adoc Developer deep-dive (architecture & invariants) +# _chora.deed Repo deed: AI work-allocation policy + ply tree (deed grammar) +# LICENSE Repo license (root-bound by convention) +# CHANGELOG.md One of the recognised .md exceptions (see below) +# Justfile Task runner — thin, imports per-section files from build/just/ +# coordination.k9 Repo-local session binding + +# === Required directories === +# .github/ CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, workflows/ +# .machine_readable/ descriptiles/ checkpoints (ply tree folded into the repo deed), +# contractiles/, configs/, anchors/, policies/, scripts/, self-validating/ +# build/ contractile.just, guix.scm, Containerfile, +# just/*.just (Justfile section imports) +# docs/ onboarding/, status/, architecture/, governance/ (all .adoc) +# session/ dispatch.sh, custom-checks.k9, local-hooks.sh +# src/ Project source (Idris2 ABI under abi/, Zig FFI under ffi/) +# tests/, benches/, examples/, features/, scripts/, verification/, build/container/ + +# === Documentation format rule === +# `.adoc` is the default for all general docs (TOPOLOGY, READINESS, ROADMAP, +# TEST-NEEDS, PROOF-NEEDS, PROOF-STATUS, llm-warmup-*, etc.). +# +# `.md` is reserved ONLY for files GitHub's community-health rules +# special-case by name: +# CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md CHANGELOG.md +# +# Enforcement: `scripts/check-no-md-in-docs.sh` (fails if any *.md under docs/). + +# === Banned: ziguage === +# V (vlang.io) is banned estate-wide. Replaced by `zig-unified-api-adapter` +# (16 endpoints + transaction-based firewall gating). Do not introduce +# zig code, scaffolders, or references. Note that Coq theorem files use +# the same `.v` extension and are unaffected — the rule looks at content +# patterns, not the extension. +# +# Enforcement: `scripts/check-no-vlang.sh`. + +# === Justfile structure (post-split) === +# The root Justfile is thin — it holds `set` directives, project metadata +# variables, and the `default`/`help`/`info` recipes. Each major section +# lives in its own file under build/just/ and is brought in via `import?`. +# +# Imported sections (in the canonical split): +# build/just/init.just INIT recipe (template bootstrap) +# build/just/assess.just self-assess + verify (OpenSSF compliance) +# build/just/validate.just validate-rsr/state/ai-install + aggregate +# build/just/proofs.just proof-check-{all,idris2,lean4,agda,coq}, +# proof-scan-dangerous, proof-status +# build/just/groove.just Groove protocol setup (after zig removed) +# +# Daily-use recipes (BUILD, TEST, LINT, RUN, DEPS, DOCS, CONTAINER, CI, +# SECURITY, STATE, GUIX, MATRIX, VERSION CONTROL, UTILITIES, SESSION) +# stay in the root Justfile where users expect to find them. + +# === 5-PR cleanup pattern === +# Apply these branches (in order) to bring a non-conforming downstream repo +# into compliance with this skeleton: +# +# 1. chore/root-cleanup Relocate root sprawl per root-allow.txt; add +# scripts/check-root-shape.sh; remove stub +# health files shadowed by .github/ versions. +# 2. chore/remove-zig Purge zig remnants (gen-v-connector recipe, +# "V-TRIPLE" section header, "V-triple +# connectors" comment in groove.a2ml). +# 3. chore/md-to-adoc Port general docs in docs/ from .md to .adoc; +# update validate-template.sh to accept .adoc +# fallbacks. +# 4. chore/estate-rules-ci Add scripts/check-no-md-in-docs.sh + check-no- +# vlang.sh + .github/workflows/estate-rules.yml. +# 5. chore/-hygiene Repo-specific drift cleanup (case collisions, +# template-derivation drift in titles, etc.). + +# === Reference scripts === +# scripts/check-root-shape.sh Root allowlist validator +# scripts/check-no-md-in-docs.sh AsciiDoc-by-default validator +# scripts/check-no-vlang.sh zig ban validator +# scripts/validate-template.sh Aggregate RSR compliance (workflows, SPDX, etc.) + +# === Reference memory entries (for AI agents) === +# feedback_adoc_default_md_for_githealth AsciiDoc-by-default rule +# feedback_v_lang_banned zig ban +# project_zig_unified_api Replacement for v-triple/zig +# feedback_gh_workflow_scope OAuth scope for workflow files diff --git a/czech-file-knife/.machine_readable/descriptiles/README.adoc b/czech-file-knife/.machine_readable/descriptiles/README.adoc new file mode 100644 index 000000000..29dc77d94 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/README.adoc @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Descriptiles +:toc: + +The *descriptiles* are this repository's descriptive machine-readable +metadata: they record what the repository *is* and what state it is *in*. +They pair with the contractiles in `../contractiles/`, which record what it +*ought* to do. + +[IMPORTANT] +==== +*The normative grammar for this family is the DEED grammar specification,* +`1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in +https://github.com/hyperpolymath/standards[hyperpolymath/standards]. + +The format formerly called A2ML has been renamed *DEED*, with the file +extension `.deed`. The files in this directory still carry the `.a2ml` +extension: the estate-wide rename is a single atomic change tracked +separately, because ~40% of these basenames appear as literals in source +and in Nickel runners. Do not hand-convert individual files. + +DEED is an *s-expression* format. If you have seen these files described +anywhere as a "TOML-like key-value" format, that description is wrong and +is the documented cause of a family-wide divergence; the grammar has no +`key = value` form. Always resolve the surface question against the DEED +grammar specification, never against a neighbouring file. +==== + +== The six descriptiles + +`AGENTIC.a2ml`:: AI agent operational gating and safety controls. +`ECOSYSTEM.a2ml`:: Ecosystem position, relationships, explicit boundaries. +`META.a2ml`:: Architecture decisions (ADRs), development practices, design rationale. +`NEUROSYM.a2ml`:: Symbolic semantics, composition algebra. +`PLAYBOOK.a2ml`:: Executable plans, operational runbooks. +`STATE.a2ml`:: Project state, phase, milestones, session history. + +== Other files in this directory + +This directory also carries files whose classification is settled elsewhere +and is deliberately not restated here: + +`anchors/`:: `ANCHOR` records. An anchor is *both* descriptile and +contractile — it states an observed drift (descriptive) and issues a +mandated realignment (normative). It is the join between the two families +and has its own specification. + +`CLADE.a2ml`:: The facet classification record. "Clade" is retained only as +the current filename; the scheme it names is being replaced, since facets +do not share common descent. + +`*_chora.deed` (repo deed, at the repo root):: The universal AI entry point — +a DEED-grammar file. The family-7 allocation manifest (0-AI-MANIFEST.a2ml) and +the ply directory tree folded into it in the standards#837 pilot. + +== Generation + +These files may be generated from `.scm` sources by transpilation. Source +`.scm` files should be removed after a successful transpilation. + +== See also + +* https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc[Historical A2ML repository template (non-normative)] +* https://github.com/hyperpolymath/standards#a2ml-format-family-7-formats[Historical A2ML format family overview (non-normative)] +* `../contractiles/README.adoc` — the normative counterpart family diff --git a/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml b/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml new file mode 100644 index 000000000..d0a089ff9 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# STATE.a2ml — Project state checkpoint for czech-file-knife + +[metadata] +project = "czech-file-knife" +version = "0.1.0" +last-updated = "2026-09-28" +# status: active | paused | archived | planned +# AUTHORITY: when this and CLADE [status] phase disagree, CLADE phase is the +# authoritative SCM checkpoint (standards#726). +status = "active" + +[project-context] +name = "czech-file-knife" +purpose = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." +completion-percentage = 20 + +[position] +phase = "implementation" # design | implementation | testing | maintenance | archived +maturity = "alpha" # experimental | alpha | beta | production | lts + +[route-to-mvp] +milestones = [ + { name = "M0: Core traits, local backend, basic CLI (ls cat cp mv rm mkdir stat df)", completion = 100 }, + { name = "M1: Reversible journal (ReversibleBackend, cfk history/undo, CAS-backed versions)", completion = 80 }, + { name = "M2: Extracted from developer-ecosystem as a standalone RSR repo", completion = 90 }, + { name = "M3: cfk squeeze — in-place, space-bounded compression (hole punching / tail truncation)", completion = 0 }, + { name = "M4: Provider-side cloud ops (server_side_copy; Drive, S3, Dropbox, OneDrive)", completion = 0 }, + { name = "M5: Cloud backends beyond local (S3, Google Drive first)", completion = 5 }, + { name = "M6: Cache (cfk-cache on the shared CAS) and search (Tantivy)", completion = 5 }, + { name = "M7: FUSE mounting (cfk-vfs)", completion = 0 }, +] + +[blockers-and-issues] +# 1. Reversible journal + extraction were authored without a Rust toolchain +# in the authoring sandbox: first `just build && just test && just e2e` on +# real CI is the acceptance gate for M1/M2. +# 2. undo does not yet restore metadata (permissions, mtimes, xattrs). +# 3. verification/ is template scaffold until the PROOF-NEEDS targets land. + +[critical-next-actions] +actions = [ + "Run just build / just test / just e2e on CI and fix whatever the first real compile finds", + "Implement cfk squeeze (Linux hole punching first, tail-truncation fallback)", + "Add StorageBackend::server_side_copy and wire the Google Drive provider", + "Restore metadata on undo; selective (non-latest) undo with conflict detection", + "Replace verification/ scaffold with the reversibility and frame-then-punch invariants", +] + +[maintenance-status] +last-run-utc = "never" +last-report = "docs/reports/maintenance/latest.json" +last-result = "unknown" # unknown | pass | warn | fail +open-warnings = 0 +open-failures = 0 + +[ecosystem] +part-of = ["hyperpolymath estate", "RSR Framework"] +depends-on = [] +related = ["januskey (reversible-operation model)", "absolute-zero (certified null operations: dry-run / no-op proofs)", "echo-types (conversion-loss typing)", "tropical-types (cost bounds for transfer planning)"] diff --git a/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml b/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml new file mode 100644 index 000000000..3380e8a74 --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# VARIANT.a2ml — provenance contract (ADR-0002 shape; ADR-0003 use). +# Records what this repo was minted from so the drift gate +# (scripts/check-variant-drift.sh ) and future +# re-templating tooling have a defined input. +# +# NOTE: placeholder rendering means many files differ from the +# parent by design; folding rendered answers into [normalise] is +# future work (ADR-0003), so the gate is informational for minted +# repos until then. + +[metadata] +project = "czech-file-knife" +schema_version = "0.1.0" +last-updated = "2026-09-28" + +[variant] +parent = "hyperpolymath/rsr-template-repo" +parent-pin = "933507582ef2467d77541dbf2837d278d321db1b" # template tip at mint +role = "minted-repo" +stack = "unspecified" +direction = "generated-from-core" + +[normalise] +rules = "action-pins self-name" + +[paths.added] +paths = [] + +[paths.removed] +paths = [] + +[paths.diverged] +# Files a minted repo owns from birth. +paths = [ + ".machine_readable/descriptiles/CLADE.a2ml", + ".machine_readable/descriptiles/VARIANT.a2ml", + ".machine_readable/rsr-profile.a2ml", +] diff --git a/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml b/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml new file mode 100644 index 000000000..f3af0ed7f --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# ANCHOR.a2ml - authoritative anchor for this repository + +[metadata] +version = "1.0.0" +last-updated = "2026-09-28" + +[anchor] +schema = "hyperpolymath.anchor/1" +repo = "hyperpolymath/czech-file-knife" +authority = "upstream-canonical" + +purpose = [ + "Define canonical semantics and policy boundaries for this repository.", + "Declare what downstream/satellite repos can extend but not redefine.", + "Provide a stable golden path and invariant contract for release readiness.", +] + +[identity] +project = "Czech File Knife" +kind = "tool" # language | library | service | tool +one-sentence = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." +domain = "storage / hybrid filesystems" + +[semantic-authority] +policy = "canonical" + +owns = [ + "Project semantics and specification", + "Invariant definitions and contractiles", + "Reference implementation behavior", +] + +[implementation-policy] +allowed = ["Rust", "Idris2", "Zig", "Scheme", "Shell", "Just", "AsciiDoc", "Markdown"] +forbidden = ["Node.js", "npm"] + +[golden-path] +smoke-test-command = [ + "just test", + "just quality", +] + +success-criteria = [ + "Core tests pass", + "Quality gates pass", + "No unresolved critical security findings", +] + +[satellite-policy] +must-pin-upstream = true +must-declare-authority = true +must-have-anchor = true +must-have-golden-path = true + +[semantic-authority-files] +language-spec = "SPECIFICATION.md" +formal-proofs = "docs/proofs/PROOFS.adoc" +type-theory = "docs/theory/THEORY.adoc" +algorithms = "docs/theory/ALGORITHMS.adoc" diff --git a/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc b/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc new file mode 100644 index 000000000..13cae630d --- /dev/null +++ b/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +# A2ML Anchor Directory + +This directory contains ANCHOR.a2ml files for project recalibration and scope intervention. + +## Files + +- `ANCHOR.a2ml` - Project recalibration, scope intervention, canonical authority + +## Multiple Versions + +Unlike other A2ML files, multiple versions of ANCHOR.a2ml with different dates may exist. +Each version represents a specific recalibration point in the project history. + +## Standards Compliance + +These files follow the ANCHOR.a2ml specification from: +https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml + +## See Also + +- [A2ML Repository Template](https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc) +- [Anchor A2ML Spec](https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml) + diff --git a/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml b/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml new file mode 100644 index 000000000..691bb72cd --- /dev/null +++ b/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# OPTIONAL: Feedback-o-Tron Integration — Autonomous Bug Reporting +# Delete this file if your project does not use feedback-o-tron. + +[integration] +name = "feedback-o-tron" +type = "bug-reporter" +repository = "https://github.com/hyperpolymath/feedback-o-tron" + +[reporting-config] +platforms = ["github", "gitlab", "bugzilla"] +deduplication = true +audit-logging = true +auto-file-upstream = "on-external-dependency-failure" diff --git a/czech-file-knife/.machine_readable/integrations/groove.a2ml b/czech-file-knife/.machine_readable/integrations/groove.a2ml new file mode 100644 index 000000000..b4905aa57 --- /dev/null +++ b/czech-file-knife/.machine_readable/integrations/groove.a2ml @@ -0,0 +1,38 @@ +; SPDX-License-Identifier: MPL-2.0 +; Groove Protocol Manifest — declares API surfaces this project exposes. +; +; Consumed by the Groove bridge / zig-unified-api-adapter for snap-on/snap-off +; service discovery. Edit this file to match your project's actual APIs. +; +; See: https://github.com/hyperpolymath/standards/tree/main/groove-protocol + +(groove-manifest + (version "1.0") + + ; Service identity — replace czech-file-knife with your project name + (service "czech-file-knife") + (service-version "0.1.0") + + ; Primary port — MUST be unique across the ecosystem. + ; Check PORT-REGISTRY.md in the standards repo before assigning. + ; https://github.com/hyperpolymath/standards/blob/main/PORT-REGISTRY.md + (port 0) ; 0 = not assigned yet — run `just groove-setup` to assign + + ; API surfaces this project exposes (dodeca-API) + ; Remove lines for API types you don't use. + (api-surfaces + (rest (enabled true) (path "/api/v1")) + (grpc (enabled false) (port-offset 1)) + (graphql (enabled false) (path "/graphql")) + (websocket (enabled false) (path "/ws")) + (sse (enabled false) (path "/events")) + (groove (enabled true) (path "/.well-known/groove"))) + + ; Health endpoint — used by Groove discovery + (health "/health") + + ; Capabilities — what this service can do for others + (capabilities ()) + + ; Dependencies — what this service needs from others + (dependencies ())) diff --git a/czech-file-knife/.machine_readable/integrations/proven.a2ml b/czech-file-knife/.machine_readable/integrations/proven.a2ml new file mode 100644 index 000000000..96a8a7d8b --- /dev/null +++ b/czech-file-knife/.machine_readable/integrations/proven.a2ml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: MPL-2.0 +# OPTIONAL: Proven Integration — Formally Verified Safety Library +# Delete this file if your project does not use the proven library. +# See https://github.com/hyperpolymath/proven for details. + +[integration] +name = "proven" +type = "safety-library" +repository = "https://github.com/hyperpolymath/proven" +version = "1.2.0" + +[binding-policy] +approach = "thin-ffi-wrapper" +unsafe-patterns = "replace-with-proven-equivalent" +modules-available = ["SafeMath", "SafeString", "SafeJSON", "SafeURL", "SafeRegex", "SafeSQL", "SafeFile", "SafeTemplate", "SafeCrypto"] + +[adoption-guidance] +priority = "high" +scope = "all-string-json-url-crypto-operations" +migration = "incremental — replace unsafe patterns as encountered" diff --git a/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml b/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml new file mode 100644 index 000000000..78ca1f0ef --- /dev/null +++ b/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: MPL-2.0 +# OPTIONAL: VeriSimDB Feed — Cross-Repo Analytics Data Store +# Delete this file if your project does not feed data to VeriSimDB. +# See https://github.com/hyperpolymath/nextgen-databases for details. + +[integration] +name = "verisimdb" +type = "data-feed" +repository = "https://github.com/hyperpolymath/nextgen-databases" +data-store = "verisimdb-data" + +[feed-config] +emit-scan-results = true +emit-build-metrics = true +emit-dependency-graph = true +format = "hexad" +destination = "verisimdb-data/feeds/" diff --git a/czech-file-knife/.machine_readable/integrations/vexometer.a2ml b/czech-file-knife/.machine_readable/integrations/vexometer.a2ml new file mode 100644 index 000000000..2f7ef8029 --- /dev/null +++ b/czech-file-knife/.machine_readable/integrations/vexometer.a2ml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: MPL-2.0 +# OPTIONAL: Vexometer Integration — Irritation Surface Analysis +# Delete this file if your project does not use vexometer. + +[integration] +name = "vexometer" +type = "friction-measurement" +repository = "https://github.com/hyperpolymath/vexometer" + +[measurement-config] +dimensions = 10 +emit-isa-reports = true +lazy-eliminator = true +satellite-interventions = true + +[hooks] +cli-tools = "measure-on-error" +ui-panels = "measure-on-interaction" +build-failures = "measure-on-failure" diff --git a/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore b/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore new file mode 100644 index 000000000..2c8c4096a --- /dev/null +++ b/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore @@ -0,0 +1,5 @@ +# Regex patterns for justified permission-policy exceptions. +# One pattern per line. +# Example: +# ^vendor/ +# ^third_party/ diff --git a/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml b/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml new file mode 100644 index 000000000..530b08f04 --- /dev/null +++ b/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Canonical maintenance governance model + +[metadata] +version = "1.0.0" +last-updated = "2026-09-28" +scope = "repo" + +[discovery] +human-entrypoints = [ + "README.adoc", + "docs/maintenance/MAINTENANCE-CHECKLIST.md", + "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc", +] +machine-entrypoints = [ + ".machine_readable/policies/MAINTENANCE-AXES.a2ml", + ".machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml", + ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml", + ".machine_readable/descriptiles/META.a2ml", + ".machine_readable/ai/README.adoc", + ".machine_readable/bot_directives/README.scm", +] +bots = ["hypatia", "gitbot-fleet", "repo visitors"] + +[axes] +axis-1 = "must > intend > like" +axis-2 = "corrective > adaptive > perfective" +axis-3 = "systems > compliance > effects" +execution-order = "axis-1 > axis-2 > axis-3" + +[axis-1-scoping] +required = true +sources = "README, roadmap, status docs, maintenance checklist, CI/security docs" +markers = "TODO/FIXME/XXX/HACK/STUB/PARTIAL" +idris-unsound-markers = "believe_me/assert_total" +output = "scoped work assembly in must/intend/like buckets" + +[axis-2-maintenance] +corrective-first = true +adaptive-second = true +adaptive-focus = "scope changes, stale references, obsolete work culling" +perfective-third = true +perfective-source = "honest state from axis-1 after corrective/adaptive updates" + +[axis-3-audit] +systems-check = true +compliance-check = true +effects-check = true +compliance-focus = "seams/compromises/exception register and anti-drift" +compliance-tooling = "panic-attack" +effects-tooling = "ecological checking with sustainabot guidance" +effects-evidence = "benchmark evidence and maintainer dialogue/status review" diff --git a/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml b/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml new file mode 100644 index 000000000..357aa675e --- /dev/null +++ b/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml @@ -0,0 +1,159 @@ +# SPDX-License-Identifier: MPL-2.0 +# Cross-repo maintenance baseline (machine-readable canonical) + +[metadata] +version = "1.1.0" +last-updated = "2026-02-24" +scope = "cross-repo" +source-human = "docs/governance/MAINTENANCE-CHECKLIST.adoc" +companion-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc" +companion-machine = ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml" + +[policy] +single-source = true +notes = "Use this file as canonical machine policy and keep markdown synchronized." + +[maintenance-axes] +scoping-first = true +execution-order = ["scoping", "axis-1", "axis-2", "axis-3"] +axis-1 = "must > intend > like" +axis-2 = "corrective > adaptive > perfective" +axis-3 = "systems > compliance > effects" + +[scoping] +inputs_required = [ + "README", + "roadmap", + "status-docs", + "maintenance-checklist", + "ci-and-security-docs", +] + +marker_scan_required = [ + "TODO", + "FIXME", + "XXX", + "HACK", + "STUB", + "PARTIAL", +] + +idris_unsound_scan_required = [ + "believe_me", + "assert_total", +] + +scope_assembly_buckets = ["must", "intend", "like"] + +[axis-2-maintenance-rules] +corrective-first = true +adaptive-second = true +adaptive_examples = [ + "scope-change reconciliation", + "stale-reference removal", + "obsolete-work culling", +] +perfective-third = true +perfective_source = "axis-1 honest state after corrective/adaptive updates" + +[axis-3-audit-rules] +systems-check = true +documentation-honesty-check = true +safety-security-accounted-check = true +effects-review-check = true +benchmark-evidence-required = true +maintainer-dialogue-review-required = true +compliance-seams-check = true +exception-register-required = true +exception-bounded-scope-required = true +policy-drift-contamination-check = true +example-drift-risk = "single TypeScript exception causing broad ->TypeScript migration" +compliance-tooling = "panic-attack" +effects-tooling = "ecological checking with sustainabot guidance" + +[generic-cleanup-finish-off] +root-cleanup-required = true +stale-work-cull-required = true +docs-parity-required = true +machine-human-sync-required = true +compliance-finish-off-required = true +effects-finish-off-required = true +release-prep-summary-required = true +next-actions-required = ["corrective", "adaptive", "perfective"] + +[must] +root_control_files = [ + ".gitignore", + ".gitattributes", + ".editorconfig", + ".tool-versions", + "Containerfile", + "Justfile", +] + +root_hosting_files = [ + "CNAME", + ".nojekyll", +] + +ownership_files = [ + "MAINTAINER", + ".github/CODEOWNERS", +] + +machine_readable_required = [ + ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", + ".machine_readable/contractiles/", + ".machine_readable/ai/", + ".machine_readable/bot_directives/", +] + +contractiles_required = [ + "Mustfile", + "Trustfile", + "Intentfile", +] + +security_required = [ + "www/.well-known/security.txt", + "ci-security-scan", +] + +quality_gate_required = [ + "format", + "lint", + "unit-tests", + "integration-tests", + "p2p-tests", + "e2e-tests", + "bench-smoke", + "docs-check", + "security-scan", +] + +abi_ffi_policy = [ + "ABI Idris2 in src/interface/abi/*.idr", + "FFI Zig in ffi/**/*.zig", +] + +[should] +docs_primary_format = "adoc" +docs_structure = [ + "docs/theory", + "docs/practice", + "docs/whitepapers/academic", + "docs/whitepapers/industry", + "docs/proofs", + "docs/reports", +] + +root_minimization = true +well_known_metadata = true +roadmap_honesty_with_dates = true +ci_doc_format_policy = true + +[could] +generate_human_from_machine = true +mode_aware_bots = ["corrective", "adaptive", "perfective", "audit"] +topology_dashboard = true +exception_registry = true diff --git a/czech-file-knife/.machine_readable/policies/README.adoc b/czech-file-knife/.machine_readable/policies/README.adoc new file mode 100644 index 000000000..57ea33a17 --- /dev/null +++ b/czech-file-knife/.machine_readable/policies/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += policies Registry diff --git a/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml b/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml new file mode 100644 index 000000000..d7967d22b --- /dev/null +++ b/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MPL-2.0 +# General software development approach (machine-readable) + +[metadata] +version = "1.0.0" +last-updated = "2026-02-24" +scope = "cross-repo" +source-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc" + +[execution] +order = ["axis-1", "axis-2", "axis-3"] + +[axis-1] +name = "scope" +priority = "must > intend > like" +inputs = [ + "README", + "roadmap", + "status-docs", + "ci-and-security-docs", +] +marker-scan = ["TODO", "FIXME", "XXX", "HACK", "STUB", "PARTIAL"] +idris-unsound-scan = ["believe_me", "assert_total"] +output = "scoped-work-assembly" + +[axis-2] +name = "maintenance" +priority = "corrective > adaptive > perfective" +corrective = "defect/regression/safety/security fixes" +adaptive = "scope reconciliation, stale-reference removal, obsolete-work culling" +perfective = "quality improvements derived from axis-1 honest state" + +[axis-3] +name = "audit" +priority = "systems > compliance > effects" +systems = "required systems present and operating" +compliance = "exceptions explicit, bounded, and drift-resistant" +effects = "benchmark/operational impact evidence captured and reviewed" +compliance-tooling = "panic-attack" +effects-tooling = "ecological checking with sustainabot guidance" + +[cleanup-finish-off] +root-cleanup = true +stale-work-cull = true +docs-sync-human-machine = true +compliance-audit = true +effects-audit = true +release-summary = ["must", "should", "could"] +next-actions = ["corrective", "adaptive", "perfective"] + +[collaboration] +maintainer-dialogue-required = true +dialogue-topics = ["what changed", "why", "remaining risks"] diff --git a/czech-file-knife/.machine_readable/root-allow.txt b/czech-file-knife/.machine_readable/root-allow.txt new file mode 100644 index 000000000..4ef9815db --- /dev/null +++ b/czech-file-knife/.machine_readable/root-allow.txt @@ -0,0 +1,85 @@ +# Canonical root allowlist for RSR-templated repositories. +# +# Lists every entry permitted at the repository root. The check is +# BIDIRECTIONAL: +# * anything at root that is not listed here is drift, and fails; +# * anything listed here WITHOUT the '?' marker must exist, and its +# absence fails. +# +# The second half is the important one. This file previously permitted a +# large set of root files that the April 2026 root cleanup had already +# relocated into docs/ (READINESS.adoc, PROOF-*, TOPOLOGY.adoc, +# llm-warmup-*, ...). Because the check was one-directional, those stale +# permissions were invisible: the allowlist had quietly become a licence +# for the very drift it was written to prevent. A one-directional +# allowlist only ever ratchets open. +# +# Used by: scripts/check-root-shape.sh +# +# Format +# one entry per line; '#' starts a comment; trailing '/' marks a directory +# '?' prefix -> OPTIONAL: permitted, but not required to exist +# no prefix -> REQUIRED: permitted, and its absence is drift +# +# Mark an entry '?' when it is legitimately absent in some conforming repo: +# template-only material removed at mint, or a module gated on a capability +# the repo does not declare (see standards' template-capability-gates.toml). +# Do NOT mark something optional merely to silence a failure. + +# ─── Baseline: every RSR repo has these ────────────────────────────────────── +README.adoc +LICENSE +LICENSES/ # REUSE licence texts, dual-licence model (code MPL-2.0 / docs CC-BY-SA-4.0) +CHANGELOG.adoc # AsciiDoc is the estate-standard documentation format +CITATION.cff # citation metadata; GitHub reads it from the root of the default branch only +czech-file-knife_chora.deed # the repo deed: universal AI entry point; family-7 allocation manifest + ply tree folded in (standards#837 pilot). Filename carries the repo slug (deed dispatch _chora.deed), so repo-init renames it at mint +CLAUDE.md # generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) +?GEMINI.md # pointer to CLAUDE.md for repos without AGENTS.md yet +Justfile # thin; delegates phases to build/just/*.just +coordination.k9.ncl # repo-local session binding (template-mandated) + +# ─── Conventional dotfiles (tool-required at root) ─────────────────────────── +.editorconfig +.envrc +.gitattributes +.gitignore +.gitleaksignore # exact fingerprints for reviewed historical false positives +.cicd-hygiene-allow # code-hygiene gate allowlist; template scaffolds are seams, not debt +.gitmessage # git commit template; wired by .github/hooks/install.sh (git config commit.template) +.mailmap # git reads .mailmap from the worktree root only +.tool-versions +mise.toml # pinned toolchains +.hypatia-ignore # the Hypatia scanner reads it from the repo root +.clinerules # AI editor rules. Cline/Cursor/Windsurf read these from the project +.cursorrules # ROOT, so the copies formerly under .machine_readable/ai/ were inert. +.windsurfrules +?.claude/ # Claude Code project state (checkpoints, history) — created by the tool itself + +# ─── Directories ───────────────────────────────────────────────────────────── +.git/ +.github/ # community health + workflows (GitHub reads this path and no other) + versioned git hooks (.github/hooks/, wired via core.hooksPath) +www/ # site-operations bundle; canonical .well-known/ lives at www/.well-known/ (issue #53) +.machine_readable/ # manifests, contractiles, policies. Renamed back from machine-readable/ 2026-09-17 by owner ruling, for one canonical spelling estate-wide (census at the 2026-08 divergence: 48 dotted vs 9 hyphenated — the majority was already dotted). See docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc +docs/ # human documentation +build/ # build orchestration: just/ phase modules, container/, docs-seed/, templates/, guix.scm (canon 1.2.1 guix-primary template_ref = "build/") +ci/ # .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require +scripts/ # repo helper scripts +session/ # dispatch.sh, custom-checks.k9.ncl, local-hooks.sh +src/ +tests/ + +# ─── Optional: capability-gated or template-only ───────────────────────────── +?archetypes/ # template-only variation seam (ADR-0003); `just repo-init` removes it from minted repos +?.devcontainer/ # VS Code dev container spec; present only where the container capability is declared +?sonar-project.properties # only where the repo is analysed by SonarCloud +?Cargo.toml # rust capability (template-capability-gates.toml); cargo requires the workspace manifest at root +?Cargo.lock # rust capability; lives beside Cargo.toml +?.clusterfuzzlite/ # ClusterFuzzLite reads its build config from the repo root only +?benches/ # Cargo-conventional at package root +?examples/ # Cargo-conventional at package root +?features/ # optional feature packs +?verification/ # proofs; only where formal-proofs is declared +?CONTRIBUTING.md # accepted at root OR .github/; .github/ is the canonical estate location +?CONTRIBUTING.adoc # estate docs gate (standards scripts/check-docs-presence.sh) requires CONTRIBUTING at the ROOT; this is the copy it verifies +?SECURITY.md # accepted at root OR .github/; likewise +?REQUIRES_INITIALISATION.md # written by the minting process, removed once `just repo-init` runs diff --git a/czech-file-knife/.machine_readable/rsr-profile.a2ml b/czech-file-knife/.machine_readable/rsr-profile.a2ml new file mode 100644 index 000000000..c3435d1ae --- /dev/null +++ b/czech-file-knife/.machine_readable/rsr-profile.a2ml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# rsr-profile.a2ml — this repo's declared RSR v2.0 capabilities. +# Generated by just repo-init on 2026-09-28. The template's maximal +# profile was removed at mint: declare only what this tree carries. + +[rsr-profile] +version = "1.0.0" +spec = "rsr-criteria-v2" +declares-against = "2.0.0-draft" +# Preset rust-cli (rust, cli, library) plus what the tree carries: +# container — build/container/ +# reproducible-build — build/guix.scm +# benchmarks — benches/ +# formal-proofs — verification/ (scaffold today; targets are listed in +# docs/status/PROOF-NEEDS.adoc: journal reversibility, +# op;undo as a certified null op, squeeze crash-safety) +# published-package — .github/workflows/release.yml (crates + packaging) +# governance-tier — docs/{AUDIT,AFFIRMATION,GOVERNANCE,MAINTAINERS}.adoc +# +# NOT declared: abi / ffi / zig. The template's Idris2 ABI + Zig FFI seam +# (src/interface/) was removed at extraction because CFK does not use it; +# the only FFI today is the C ABI in src/cfk-ios consumed by Swift. Re-add +# the seam (and these capabilities) if/when a verified ABI is introduced. +preset = "rust-cli" +capabilities = ["rust", "cli", "library", "container", "reproducible-build", "benchmarks", "formal-proofs", "published-package", "governance-tier"] + +[rationale] +governance-tier = "Standalone estate tool with a maintainer roster, release audit gate and signed honesty snapshot." +formal-proofs = "Reversibility is the product's core promise; proof targets are tracked in PROOF-NEEDS." diff --git a/czech-file-knife/.machine_readable/scripts/forge/README.adoc b/czech-file-knife/.machine_readable/scripts/forge/README.adoc new file mode 100644 index 000000000..a7414d611 --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/forge/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Forge Scripts diff --git a/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh b/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh new file mode 100755 index 000000000..330e54b3c --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# forge-sync.sh — Multi-forge mirroring script +# +# Synchronises the local repository with GitHub, GitLab, and Codeberg. +# Usage: ./forge-sync.sh + +set -euo pipefail + +REMOTES=("origin" "gitlab" "codeberg") + +echo "=== RSR Forge Synchronisation ===" + +for remote in "${REMOTES[@]}"; do + if git remote | grep -q "^$remote$"; then + echo "Pushing to $remote..." + git push "$remote" --all + git push "$remote" --tags + else + echo "Skip: Remote '$remote' not configured." + fi +done + +echo "Sync complete." diff --git a/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh b/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh new file mode 100755 index 000000000..88fb52f15 --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# git-cleanup.sh — Repository hygiene script +set -euo pipefail +echo "Cleaning up merged branches..." +git fetch -p +git branch --merged | grep -v "\*" | grep -v "main" | xargs -n 1 git branch -d || echo "No branches to clean." +echo "Pruning remote tracking branches..." +git remote prune origin diff --git a/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc b/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc new file mode 100644 index 000000000..178c055be --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Lifecycle Scripts diff --git a/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh b/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh new file mode 100755 index 000000000..c6f8230c6 --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# install-tools.sh — Developer toolchain installer +# +# Detects and installs the required project toolchain (Guix or asdf). + +set -euo pipefail + +echo "=== RSR Toolchain Installer ===" + +if [ -f "build/guix.scm" ] && command -v guix &>/dev/null; then + echo "Guix detected. Verifying development shell..." + guix shell -f build/guix.scm -- true && echo "Guix shell verified." +elif [ -f ".tool-versions" ] && command -v asdf &>/dev/null; then + echo "asdf detected. Installing plugins and tools..." + while read -r line; do + plugin=$(echo "$line" | awk '{print $1}') + asdf plugin add "$plugin" || true + done < .tool-versions + asdf install +else + echo "No standard toolchain (Guix/asdf) detected or installed." + echo "Please refer to README.adoc for manual setup instructions." +fi + +echo "Installer complete." diff --git a/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh b/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh new file mode 100644 index 000000000..6e19d2ae4 --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# maint-assault.sh — High-rigor stress testing using panic-attacker +# +# This script runs a full assault (static + dynamic) on the project binary +# to detect logic-based bug signatures and environmental vulnerabilities. + +set -euo pipefail + +BINARY_NAME="czech_file_knife" +REPORT_PATH="docs/reports/security/assault-latest.json" +PA_BIN="${PANIC_ATTACK_BIN:-panic-attack}" + +echo "=== High-Rigor Security Assault ===" + +# 1. Verify environment +if ! command -v "$PA_BIN" &>/dev/null; then + echo "Error: panic-attack tool not found." + echo "Please install it or set PANIC_ATTACK_BIN environment variable." + exit 1 +fi + +if [ ! -f "target/release/$BINARY_NAME" ]; then + echo "Warning: Release binary not found at target/release/$BINARY_NAME" + echo "Running build first..." + just build --release +fi + +# 2. Run Assault +echo "Initiating full assault on $BINARY_NAME..." +mkdir -p "$(dirname "$REPORT_PATH")" + +"$PA_BIN" assault "target/release/$BINARY_NAME" + --source . + --intensity medium + --duration 10 + --output "$REPORT_PATH" + +echo "" +echo "=== Assault Complete ===" +echo "Report generated: $REPORT_PATH" +echo "To review interactively, run:" +echo " $PA_BIN tui $REPORT_PATH" diff --git a/czech-file-knife/.machine_readable/scripts/verification/README.adoc b/czech-file-knife/.machine_readable/scripts/verification/README.adoc new file mode 100644 index 000000000..eb2828e3f --- /dev/null +++ b/czech-file-knife/.machine_readable/scripts/verification/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Verification Scripts diff --git a/czech-file-knife/.machine_readable/self-validating/README.adoc b/czech-file-knife/.machine_readable/self-validating/README.adoc new file mode 100644 index 000000000..4aca2fd63 --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/README.adoc @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += K9 Contractiles +:toc: left +:icons: font + +== What Are K9 Contractiles? + +K9 contractiles are self-validating components that combine configuration, validation, and deployment logic in a single file format. They implement the RSR principle of "self-describing artifacts" by embedding contracts and orchestration directly in the component. + +== The Three Security Levels + +K9 components declare their trust requirements using "The Leash" security model: + +[horizontal] +`'Kennel`:: Pure data, no execution (safest) +`'Yard`:: Nickel evaluation with contracts (medium trust) +`'Hunt`:: Full execution with Just recipes (requires signature) + +== Example Components + +This directory contains example K9 contractiles for common repository tasks: + +=== Kennel Level (Pure Data) + +**File:** `examples/project-metadata.k9.ncl` + +Pure configuration data with no execution. Safe to include in any repository. + +**Use cases:** +- Project metadata (name, version, description) +- Build configuration +- Tool settings +- Data schemas + +**Security:** No signature required, data-only. + +=== Yard Level (Validated Config) + +**File:** `examples/ci-config.k9.ncl` + +Configuration with Nickel contracts for runtime validation. Evaluated safely without I/O. + +**Use cases:** +- CI/CD configuration with validation +- Deployment parameters +- Database schemas with constraints +- API specifications + +**Security:** Signature recommended, Nickel evaluation only. + +=== Hunt Level (Full Execution) + +**File:** `examples/setup-repo.k9.ncl` + +Full execution with Just recipes. Can run shell commands and modify filesystem. + +**Use cases:** +- Repository setup scripts +- Deployment automation +- System configuration +- Package installation + +**Security:** **Signature required**, full system access. + +== Usage in Your Repository + +=== 1. Create K9 Components + +Choose the appropriate security level for your use case: + +[source,bash] +---- +# Kennel: Pure configuration +cp .machine_readable/contractiles/k9/examples/project-metadata.k9.ncl config/metadata.k9.ncl + +# Yard: Validated configuration +cp .machine_readable/contractiles/k9/examples/ci-config.k9.ncl .github/ci.k9.ncl + +# Hunt: Full automation +cp .machine_readable/contractiles/k9/examples/setup-repo.k9.ncl scripts/setup.k9.ncl +---- + +=== 2. Validate Components + +[source,bash] +---- +# Validate Nickel syntax and contracts +nickel typecheck config/metadata.k9.ncl + +# Verify Hunt-level signature (if signed) +./must verify scripts/setup.k9.ncl +---- + +=== 3. Execute Components + +[source,bash] +---- +# Kennel: Export as JSON +nickel export config/metadata.k9.ncl > metadata.json + +# Yard: Evaluate with validation +nickel eval .github/ci.k9.ncl + +# Hunt: Run with Just (dry-run first!) +./must --dry-run run scripts/setup.k9.ncl +./must run scripts/setup.k9.ncl +---- + +== Integration with RSR + +K9 contractiles integrate with other RSR standards: + +**STATE.a2ml**:: K9 components can generate or validate STATE.a2ml +**ECOSYSTEM.a2ml**:: K9 can automate cross-repo operations +**META.a2ml**:: K9 can enforce architectural decisions + +== Security Best Practices + +=== For Kennel/Yard Components + +✅ **Safe to use without signatures** + +✅ **Review Nickel code before use** + +✅ **Validate contracts match expectations** + +=== For Hunt Components + +⚠️ **ALWAYS verify signatures** + +⚠️ **Review Just recipes carefully** + +⚠️ **Run dry-run mode first** + +⚠️ **Never run as root unless required** + +⚠️ **Sandbox external components** + +**See:** https://github.com/hyperpolymath/k9-svc/blob/main/docs/SECURITY-BEST-PRACTICES.adoc + +== Template Files + +Use these as starting points for your own K9 components: + +- `template-kennel.k9.ncl` - Pure data template +- `template-yard.k9.ncl` - Validated config template +- `template-hunt.k9.ncl` - Full execution template + +== Dependencies + +To use K9 contractiles in your repository: + +[source,bash] +---- +# Install Nickel (configuration language) +curl -L https://github.com/tweag/nickel/releases/latest/download/nickel-linux-x86_64 -o nickel +chmod +x nickel && sudo mv nickel /usr/local/bin/ + +# Install Just (task runner, for Hunt level) +cargo install just + +# Clone K9-SVC (for must shim and tooling) +git clone https://github.com/hyperpolymath/k9-svc.git +---- + +== Learn More + +- **K9-SVC Specification:** https://github.com/hyperpolymath/k9-svc/blob/main/SPEC.adoc +- **K9 User Guide:** https://github.com/hyperpolymath/k9-svc/blob/main/GUIDE.adoc +- **Security Documentation:** https://github.com/hyperpolymath/k9-svc/blob/main/docs/SECURITY-FAQ.adoc +- **IANA Media Type:** `application/vnd.k9+nickel` + +== Contributing + +When adding K9 contractiles to your repository: + +1. Use appropriate security level (Kennel > Yard > Hunt) +2. Document what each component does +3. Include validation contracts in Yard/Hunt components +4. Sign Hunt-level components before committing +5. Add K9 validation to CI/CD pipeline + +**Questions?** Open an issue on https://github.com/hyperpolymath/k9-svc diff --git a/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl new file mode 100644 index 000000000..9fe314e2d --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl @@ -0,0 +1,126 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Yard-level K9 component: CI/CD configuration with validation +# Security Level: Yard (Nickel evaluation, contract validation) +# Signature recommended but not required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "ci-configuration", + security = { + leash = 'Yard, + trust_level = "validated-config", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "ci-config", + version = "1.0.0", + description = "CI/CD configuration with runtime validation", + author = "Jonathan D.A. Jewell ", + }, + }, + + # CI/CD configuration with Nickel contracts + ci = { + # Platform must be a known CI provider + platform + | [| 'GitHubActions, 'GitLabCI, 'CircleCI, 'TravisCI |] + = 'GitHubActions, + + # Build matrix with validation + matrix = { + # Operating systems to test on + os + | Array String + | std.array.NonEmpty + = ["ubuntu-latest", "macos-latest"], + + # Language versions to test + versions + | Array String + | std.array.NonEmpty + = ["stable", "beta"], + }, + + # Workflow steps with validation + steps = [ + { + name = "Checkout", + action = "actions/checkout@v4", + # Version must be SHA-pinned for security + sha | String | std.string.NonEmpty = "b4ffde65f46336ab88eb53be808477a3936bae11", + }, + { + name = "Build", + run = "just build", + }, + { + name = "Test", + run = "just test", + }, + { + name = "Lint", + run = "just lint", + }, + ], + + # Deployment configuration + deploy = { + enabled | Bool = false, + + # Only deploy from main branch + branch + | String + | std.contract.from_predicate (fun b => b == "main" || b == "master") + = "main", + + # Deployment requires manual approval + requires_approval | Bool = true, + }, + + # Security scanning + security = { + enabled | Bool = true, + + scanners = [ + { + name = "CodeQL", + languages = ["rust", "javascript"], + }, + { + name = "OSSF Scorecard", + enabled = true, + }, + { + name = "TruffleHog", + scan_for = "secrets", + }, + ], + }, + + # Notification settings + notifications = { + on_success = "never", + on_failure = "always", + channels = ["email"], + }, + }, + + # Validation rules (enforced by Nickel) + validation = { + # At least one OS must be specified + check_os = std.array.length ci.matrix.os > 0, + + # At least one version must be tested + check_versions = std.array.length ci.matrix.versions > 0, + + # Must have at least build and test steps + check_steps = std.array.length ci.steps >= 2, + + # Security scanning must be enabled + check_security = ci.security.enabled == true, + }, +} diff --git a/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl new file mode 100644 index 000000000..5de965d6d --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl @@ -0,0 +1,57 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Kennel-level K9 component: Project metadata +# Security Level: Kennel (pure data, no execution) +# No signature required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "project-metadata", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "project-metadata", + version = "1.0.0", + description = "Pure data configuration for project metadata", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Project configuration + project = { + name = "my-project", + version = "0.1.0", + description = "A project following Rhodium Standard Repositories", + + repository = { + url = "https://github.com/hyperpolymath/my-project", + type = "git", + }, + + author = { + name = "Jonathan D.A. Jewell", + email = "j.d.a.jewell@open.ac.uk", + organization = "", + }, + + license = "MPL-2.0", + + keywords = [ + "rhodium-standard", + "rsr", + "hyperpolymath", + ], + }, + + # Export as JSON for other tools + export = { + format = "json", + destination = "project-metadata.json", + }, +} diff --git a/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl new file mode 100644 index 000000000..4c2d4aef8 --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl @@ -0,0 +1,167 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Example Hunt-level K9 component: Repository setup automation +# Security Level: Hunt (full execution with Just recipes) +# ⚠️ SIGNATURE REQUIRED - DO NOT RUN WITHOUT VERIFICATION + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "repository-setup", + security = { + leash = 'Hunt, + trust_level = "full-system-access", + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + signature_required = true, + }, + metadata = { + name = "setup-repo", + version = "1.0.0", + description = "Automated repository setup with RSR standards", + author = "Jonathan D.A. Jewell ", + }, + warnings = [ + "This component has full system access", + "Only run from trusted sources with verified signatures", + "Review Just recipes before execution", + "Use dry-run mode first: ./must --dry-run run setup-repo.k9.ncl", + ], + }, + + # Configuration with contracts + config = { + repo_name + | String + | std.string.NonEmpty + = "my-new-repo", + + repo_type + | [| 'Library, 'Application, 'Tool, 'Specification |] + = 'Application, + + primary_language + | String + | std.string.NonEmpty + = "rust", + + # RSR compliance features to enable + features = { + checkpoint_files | Bool = true, # STATE.a2ml, ECOSYSTEM.a2ml, META.a2ml + security_workflows | Bool = true, # CodeQL, Scorecard, etc. + quality_checks | Bool = true, # Linting, formatting + mirroring | Bool = false, # GitLab/Bitbucket mirrors + }, + + # Git configuration + git = { + default_branch = "main", + initial_commit | Bool = true, + remote_url | String = "", + }, + }, + + # Just recipes for execution + # These run when: ./must run setup-repo.k9.ncl + recipes = { + # Main entry point + default = { + recipe = "setup", + description = "Set up RSR-compliant repository", + }, + + # Individual setup tasks + setup = { + dependencies = ["check-env", "create-structure", "init-git", "setup-workflows"], + commands = [ + "echo '✅ Repository setup complete!'", + "echo 'Run: git status to see changes'", + ], + }, + + "check-env" = { + description = "Verify required tools are installed", + commands = [ + "command -v git || (echo 'ERROR: git not found' && exit 1)", + "command -v just || (echo 'ERROR: just not found' && exit 1)", + "command -v nickel || (echo 'ERROR: nickel not found' && exit 1)", + "echo '✓ All required tools present'", + ], + }, + + "create-structure" = { + description = "Create RSR directory structure", + commands = [ + "mkdir -p src/ docs/ tests/ scripts/", + "mkdir -p .github/workflows/", + "mkdir -p .machine_readable/contractiles/k9/", + "echo '✓ Directory structure created'", + ], + }, + + "init-git" = { + description = "Initialize Git repository", + commands = [ + "git init -b %{config.git.default_branch}", + "git config user.name 'Jonathan D.A. Jewell'", + "git config user.email 'j.d.a.jewell@open.ac.uk'", + "echo '✓ Git initialized'", + ], + }, + + "setup-workflows" = { + description = "Add RSR-compliant workflows", + commands = [ + # This would copy workflow templates + # In a real implementation, would fetch from czech-file-knife + "echo '✓ Workflows configured'", + ], + }, + + "create-checkpoint-files" = { + description = "Create STATE.a2ml, ECOSYSTEM.a2ml, META.a2ml", + commands = [ + "echo '(state (version \"1.0.0\") (project \"%{config.repo_name}\"))' > STATE.a2ml", + "echo '(ecosystem (version \"1.0.0\") (name \"%{config.repo_name}\"))' > ECOSYSTEM.a2ml", + "echo '(meta (version \"1.0.0\") (project \"%{config.repo_name}\"))' > META.a2ml", + "echo '✓ Checkpoint files created'", + ], + }, + + "add-license" = { + description = "Add MPL-2.0 license", + commands = [ + "cp LICENSES/MPL-2.0.txt LICENSE", + "echo '✓ License added'", + ], + }, + + "add-readme" = { + description = "Create README.adoc from template", + commands = [ + "echo '= %{config.repo_name}' > README.adoc", + "echo '' >> README.adoc", + "echo 'Part of the Hyperpolymath ecosystem.' >> README.adoc", + "echo '✓ README created'", + ], + }, + + clean = { + description = "Remove generated files (careful!)", + commands = [ + "echo '⚠️ This will delete all generated files'", + "echo 'Press Ctrl+C to cancel, or wait 5 seconds...'", + "sleep 5", + "rm -f STATE.a2ml ECOSYSTEM.a2ml META.a2ml", + "echo '✓ Cleaned'", + ], + }, + }, + + # Validation (Yard-level checks before Hunt execution) + validation = { + check_repo_name = std.string.length config.repo_name > 0, + check_language = std.string.length config.primary_language > 0, + }, +} diff --git a/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl b/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl new file mode 100644 index 000000000..b4e7658ec --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl @@ -0,0 +1,79 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# K9 Validator: Methodology Guard +# Checks that agent work respects methodology constraints declared in +# bot_directives/methodology.a2ml. +# +# Usage: k9 validate methodology-guard + +let methodology_guard = { + name = "methodology-guard", + version = "1.0.0", + description = "Validates that agent work respects declared methodology constraints", + + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "methodology-guard", + version = "1.0.0", + }, + security = { + leash = 'Yard, + }, + }, + + checks = { + divergent_invariant_language = { + description = "No files in languages violating the divergent language invariant", + severity = "error", + # When methodology.divergent-invariants.language-invariant is set, + # check that no new files introduce a different language for that purpose. + # Example: if language-invariant = "idris2", reject new .lean or .v files + # in the proof directories. + check_type = "file-extension-guard", + scope = "src/", + }, + + believe_me_ceiling = { + description = "believe_me count must not exceed declared ceiling", + severity = "error", + pattern = "believe_me", + ceiling_key = "methodology.divergent-invariants.believe-me-ceiling", + default_ceiling = 0, + }, + + assert_total_ceiling = { + description = "assert_total count must not exceed declared ceiling", + severity = "error", + pattern = "assert_total", + ceiling_key = "methodology.divergent-invariants.assert-total-ceiling", + default_ceiling = 0, + }, + + state_not_template = { + description = "STATE.a2ml must not contain template placeholders", + severity = "warning", + file = ".machine_readable/descriptiles/STATE.a2ml", + # NOTE: the PROJECT token below is written as a Nickel concat + # ("{{" ++ "PROJECT}}") ON PURPOSE. `just repo-init` runs a sed substitution for + # the brace-PROJECT-brace token over EVERY text file, which would otherwise + # rewrite this guard's own pattern into the consumer's name and break the + # check. Splitting the literal across "++" keeps the contiguous token text + # out of the file (in this comment too) so init cannot match it, while + # Nickel still evaluates the element back to the full token. Do not + # "simplify" it to a plain string. (The PLACEHOLDER token and + # "czech-file-knife" are not init tokens, so they survive as-is.) + reject_patterns = ["{{PLACEHOLDER}}", "{{" ++ "PROJECT}}", "czech-file-knife"], + }, + + coverage_updated = { + description = "coverage.a2ml should be updated within 30 days", + severity = "info", + file = ".machine_readable/bot_directives/coverage.a2ml", + staleness_days = 30, + }, + }, +} +in methodology_guard diff --git a/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl new file mode 100644 index 000000000..b3fcb4753 --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl @@ -0,0 +1,136 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Hunt-level template: Full execution with Just recipes +# Security Level: Hunt (full system access) +# ⚠️ SIGNATURE REQUIRED - Review carefully before use + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'deployment', 'setup-script')", + security = { + leash = 'Hunt, + trust_level = "full-system-access", + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + signature_required = true, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Detailed description of what this component does", + author = "Jonathan D.A. Jewell ", + }, + warnings = [ + "This component has full system access", + "Only run from trusted sources with verified signatures", + "Review all Just recipes before execution", + "Use dry-run mode first: ./must --dry-run run your-file.k9.ncl", + ], + side_effects = [ + "TODO: List what files/directories this creates or modifies", + "TODO: List what commands this executes", + "TODO: List what network access this requires", + ], + }, + + # Configuration with contracts (Yard-level validation) + config = { + # Add your configuration here with appropriate contracts + target_dir + | String + | std.string.NonEmpty + = "/tmp/k9-output", + + dry_run | Bool = false, + + # Add more config as needed + }, + + # Just recipes for execution + # These run when: ./must run your-file.k9.ncl + recipes = { + # Main entry point (runs by default) + default = { + recipe = "TODO: main-task", + description = "TODO: What the default recipe does", + }, + + # Define your recipes here + "main-task" = { + dependencies = ["check-prerequisites"], + commands = [ + "echo 'TODO: Add your commands here'", + # Example: Create directory + # "mkdir -p %{config.target_dir}", + # Example: Run a command + # "just build", + # Example: Conditional execution + # "@if [ \"%{config.dry_run}\" = \"true\" ]; then echo '[DRY-RUN] Would execute'; else actual-command; fi", + ], + }, + + "check-prerequisites" = { + description = "Verify required tools and permissions", + commands = [ + # Example: Check for required tools + # "command -v git || (echo 'ERROR: git not found' && exit 1)", + # Example: Check permissions + # "[ -w %{config.target_dir} ] || (echo 'ERROR: Cannot write to target directory' && exit 1)", + "echo '✓ Prerequisites checked'", + ], + }, + + # Add more recipes as needed + "build" = { + description = "Build the project", + commands = [ + "echo 'TODO: Add build commands'", + ], + }, + + "deploy" = { + description = "Deploy the application", + dependencies = ["build"], + commands = [ + "echo 'TODO: Add deployment commands'", + ], + }, + + "clean" = { + description = "Clean up generated files", + commands = [ + "echo '⚠️ This will delete files - waiting 3 seconds...'", + "sleep 3", + "echo 'TODO: Add cleanup commands'", + # "rm -rf %{config.target_dir}", + ], + }, + }, + + # Validation (Yard-level checks before Hunt execution) + validation = { + check_target_dir = std.string.length config.target_dir > 0, + # Add more validation as needed + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Define configuration with contracts +# 3. Implement Just recipes with your commands +# 4. Test with dry-run: ./must --dry-run run your-file.k9.ncl +# 5. Review dry-run output carefully +# 6. Sign the component: ./must sign your-file.k9.ncl +# 7. Distribute with signature: your-file.k9.ncl.sig +# 8. Users verify and run: ./must verify && ./must run your-file.k9.ncl +# +# Security checklist: +# ✓ All TODO items filled in +# ✓ side_effects documented accurately +# ✓ Commands reviewed for safety +# ✓ No hardcoded secrets or credentials +# ✓ Proper error handling in recipes +# ✓ Tested in dry-run mode +# ✓ Component signed with trusted key diff --git a/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl new file mode 100644 index 000000000..4228b26c8 --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl @@ -0,0 +1,54 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Kennel-level template: Pure data configuration +# Security Level: Kennel (data-only, no execution) +# No signature required - safe for any use + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'build-config', 'metadata')", + security = { + leash = 'Kennel, + trust_level = "data-only", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Brief description of what this component contains", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Your configuration data here + config = { + # Example: Pure data values + setting_1 = "value", + setting_2 = 42, + setting_3 = true, + + nested = { + key = "value", + }, + + list = [ + "item1", + "item2", + ], + }, + + # Optional: Export format specification + export = { + format = "json", # or "yaml", "toml" + destination = "output.json", + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Add your configuration data to config = { ... } +# 3. Validate: nickel typecheck your-file.k9.ncl +# 4. Export: nickel export your-file.k9.ncl > output.json diff --git a/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl new file mode 100644 index 000000000..a723f5afd --- /dev/null +++ b/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl @@ -0,0 +1,84 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# K9 Yard-level template: Configuration with validation +# Security Level: Yard (Nickel evaluation with contracts) +# Signature recommended but not required + +{ + pedigree = { + schema_version = "1.0.0", + component_type = "TODO: describe component type (e.g., 'validated-config', 'schema')", + security = { + leash = 'Yard, + trust_level = "validated-config", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = false, + }, + metadata = { + name = "TODO: component-name", + version = "1.0.0", + description = "TODO: Brief description with validation details", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Configuration with Nickel contracts for validation + config = { + # Example: String that cannot be empty + name + | String + | std.string.NonEmpty + = "TODO: default value", + + # Example: Number with range constraint + port + | Number + | std.contract.from_predicate (fun p => p > 0 && p < 65536) + = 8080, + + # Example: Boolean flag + enabled | Bool = true, + + # Example: Enum (one of several values) + environment + | [| 'Development, 'Staging, 'Production |] + = 'Development, + + # Example: List with non-empty constraint + items + | Array String + | std.array.NonEmpty + = ["item1", "item2"], + + # Example: Nested object with contracts + database = { + host | String | std.string.NonEmpty = "localhost", + port | Number | std.contract.from_predicate (fun p => p > 0 && p < 65536) = 5432, + name | String | std.string.NonEmpty = "mydb", + }, + }, + + # Validation rules (additional cross-field checks) + validation = { + # Example: Check that at least one item exists + check_items = std.array.length config.items > 0, + + # Example: Check that production has secure settings + check_production = + if config.environment == 'Production then + config.enabled == true + else + true, + + # Add your custom validation rules here + }, +} + +# Usage: +# 1. Fill in TODO items above +# 2. Define your config with appropriate contracts +# 3. Add validation rules in validation = { ... } +# 4. Validate: nickel typecheck your-file.k9.ncl +# 5. Evaluate: nickel eval your-file.k9.ncl +# 6. If validation passes, use in your application diff --git a/czech-file-knife/.mailmap b/czech-file-knife/.mailmap new file mode 100644 index 000000000..9a973b3af --- /dev/null +++ b/czech-file-knife/.mailmap @@ -0,0 +1 @@ +# No alternate author address to map. diff --git a/czech-file-knife/.tool-versions b/czech-file-knife/.tool-versions index c996d7149..cb500621a 100644 --- a/czech-file-knife/.tool-versions +++ b/czech-file-knife/.tool-versions @@ -1,6 +1,9 @@ -# SPDX-License-Identifier: MPL-2.0 -# asdf/mise tool versions -# See: https://asdf-vm.com/ - -rust 1.83.0 -just 1.36.0 +# Uncomment and customize for your project +rust stable +just 1.40.0 +# nickel 1.10.0 +# gleam 1.8.0 +# elixir 1.18.0 +# erlang 27.2 +# zig 0.14.0 +# idris2 0.7.0 diff --git a/czech-file-knife/.windsurfrules b/czech-file-knife/.windsurfrules new file mode 100644 index 000000000..5852e71dd --- /dev/null +++ b/czech-file-knife/.windsurfrules @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# Authoritative source: docs/practice/AI-CONVENTIONS.adoc + +# STARTUP: Read the repo deed (*_chora.deed at the repo root) first, then .machine_readable/descriptiles/STATE.a2ml. + +# LICENSE +# All original code: MPL-2.0. +# Never AGPL-3.0. MPL-2.0 only as platform-required fallback. +# SPDX header required on every source file. +# Copyright: Jonathan D.A. Jewell (hyperpolymath) + +# STATE FILES (.machine_readable/ ONLY) +# Never create in repo root: STATE.deed, META.deed, ECOSYSTEM.deed, +# AGENTIC.deed, NEUROSYM.deed, PLAYBOOK.deed. +# The .machine_readable/ directory is the single source of truth. + +# BANNED PATTERNS +# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO +# Haskell: unsafeCoerce, unsafePerformIO, undefined, error +# OCaml: Obj.magic, Obj.repr, Obj.obj +# Coq: Admitted +# Lean: sorry +# Rust: transmute (unless FFI with // SAFETY: comment) + +# JS/TS RUNTIMES — ordered preference, reach for the first that can do the job +# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) +# 1. Bun default for all new work; runs .ts directly, no build step +# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm +# 3. pnpm only where an upstream toolchain needs a node_modules layout +# 4. npm last resort; permitted, never preferred — a noted decision +# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is +# RETIRED: ReScript is no longer used in this estate, so that rule named a dead +# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. + +# BANNED LANGUAGES +# Go -> Rust +# Python -> Julia or Rust + +# CONTAINERS +# Runtime: Podman (never Docker). +# File: Containerfile (never Dockerfile). +# Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. + +# ABI/FFI +# This repo does not carry the estate Idris2/Zig ABI seam (not declared in +# .machine_readable/rsr-profile.a2ml). The only FFI is the C ABI in +# src/cfk-ios (Swift File Provider bridge). + +# BUILD: Use just (justfile) for all tasks. +# STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/czech-file-knife/AI.a2ml b/czech-file-knife/AI.a2ml deleted file mode 100644 index c676bae16..000000000 --- a/czech-file-knife/AI.a2ml +++ /dev/null @@ -1,16 +0,0 @@ -name = "czech-file-knife" - -# AI Assistant Instructions - -## Repository Focus -- `rsr-template-repo` is treated as a Rhodium Standard Repository; obey the Rhodium policies, maintain `.bot_directives`, and keep `.machines_readable/6scm/` authoritative. -- Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. - -## Workflow -1. Inspect `.machines_readable/6scm/STATE.scm` for blockers and next actions. -2. Respect any constraints listed inside `.machines_readable/6scm/AGENTIC.scm` when tooling changes are requested. -3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. - -## Delivery Promises -- Mention in summaries whether STATE, `contractiles/`, or `.bot_directives/` changed. -- Keep this file in sync with the repository’s status; update it if the governance changes. diff --git a/czech-file-knife/AI.djot b/czech-file-knife/AI.djot deleted file mode 100644 index 2176310de..000000000 --- a/czech-file-knife/AI.djot +++ /dev/null @@ -1,100 +0,0 @@ -# AI Assistant Specification - -## Machine-Readable Artefacts - -The `.machine_readable/` directory contains structured metadata required for automated reasoning and project introspection. - -- `STATE.scm`: Current project state, milestones, and progress. -- `META.scm`: Architecture decisions, development methodology, and rationale. -- `ECOSYSTEM.scm`: Ecosystem placement, upstream/downstream relationships, and interoperability. -- `AGENTIC.scm`: AI agent interaction patterns, affordances, and constraints. -- `NEUROSYM.scm`: Neurosymbolic integration configuration and hybrid reasoning settings. -- `PLAYBOOK.scm`: Operational runbook, workflows, and incident procedures. - -Additional artefact: - -- `AI.djot`: AI Assistant behavioural and operational instructions. - -## Repository Roots - -- Local repository root: `/var/mnt/eclipse/repos` -- Remote mapping: `https://github.com/hyperpolymath/` -- Repository names are lowercase; dots (`.`) are replaced with hyphens (`-`). -- If a repository is missing locally, check the remote and synchronize before proceeding. - -## AI CLI Standards - -- Include `ai-cli-crash-capture/` and `contractiles/` from this template in all new repositories. -- Mirror all 6SCM files into `.machine_readable/6scm/`. -- Before generating code, inspect `/var/mnt/eclipse/repos/proven` for the current Idris “unbreakable” library status. -- If the project depends on the unbreakable Idris library, add an *Idris Inside* badge to the repository. -- When new bindings are required, create them under `/var/mnt/eclipse/repos/proven` using the naming pattern `idris2-zig-`. -- If new code implies additional unbreakable Idris modules, add them to `/var/mnt/eclipse/repos/proven` and update binders across all supported languages. - -## Language Policy - -### Allowed Languages and Tools - -| Language/Tool | Use Case | Notes | -|---------------|----------|-------| -| ReScript | Application code | Type-safe, compiles to JS | -| Deno | Runtime and package management | Replaces Node/npm/bun | -| Rust | Systems, performance-critical, WASM | Preferred for CLI tools | -| Tauri 2.0+ | Mobile apps (iOS/Android) | Rust backend + web UI | -| Dioxus | Mobile apps (native UI) | Pure Rust, React-like | -| Gleam | Backend services | BEAM or JS targets | -| Bash/POSIX Shell | Automation scripts | Keep minimal | -| JavaScript | Only where ReScript cannot | MCP glue, Deno APIs | -| Nickel | Configuration | Complex config logic | -| Guile Scheme | Metadata/state files | For all `.scm` artefacts | -| Julia | Batch/data processing | Per RSR | -| OCaml | AffineScript compiler | Language-specific | -| Ada | Safety-critical systems | Where required | - -### Banned Languages and Tools - -| Banned | Replacement | -|--------|-------------| -| TypeScript | ReScript | -| Node.js | Deno | -| npm | Deno | -| Bun | Deno | -| pnpm/yarn | Deno | -| Go | Rust | -| Python | Julia / Rust / ReScript | -| Java/Kotlin | Rust / Tauri / Dioxus | -| Swift | Tauri / Dioxus | -| React Native | Tauri / Dioxus | -| Flutter/Dart | Tauri / Dioxus | - -## Mobile Development Policy - -A strict Rust-first approach applies. - -- Use **Tauri 2.0+** for web UI (ReScript) + Rust backend. -- Use **Dioxus** for pure Rust native UI. - -Both are FOSS with independent governance. - -## Enforcement Rules - -- Do not create new TypeScript files; convert existing TS to ReScript. -- Do not use `package.json` for runtime dependencies; use `deno.json` imports. -- Do not include `node_modules` in production; Deno handles dependency caching. -- Do not use Go; use Rust. -- Do not use Python; use Julia (data/batch), Rust (systems), or ReScript (apps). -- Do not use Kotlin/Swift; use Tauri 2.0+ or Dioxus for mobile. - -## Package Management - -- Primary: Guix (`guix.scm`) -- Fallback: Nix (`flake.nix`) -- JavaScript dependencies: Deno (`deno.json` imports) - -## Security Requirements - -- Do not use MD5 or SHA-1; use SHA-256 or stronger. -- Use HTTPS only. -- Do not hardcode secrets. -- Pin all dependencies by SHA. -- Include SPDX license headers in all files. diff --git a/czech-file-knife/CHANGELOG.adoc b/czech-file-knife/CHANGELOG.adoc new file mode 100644 index 000000000..345168472 --- /dev/null +++ b/czech-file-knife/CHANGELOG.adoc @@ -0,0 +1,85 @@ +== Changelog + +All notable changes to this project will be documented in this file. + +The format is based on https://keepachangelog.com/en/1.1.0/[Keep a +Changelog], and this project adheres to +https://semver.org/spec/v2.0.0.html[Semantic Versioning]. + +=== [Unreleased] + +==== Added + +* `www/` site-operations bundle (issue #53): canonical `.well-known/` + metadata, publishable content, policy sources, error bodies, + security-header templates, Caddy/nginx/Apache integration examples, an + authoritative-only BIND 9 starting hand with record templates, + encrypted-DNS/privacy and TLS guidance, explicit opt-in profiles, + schemas, planted-control tests and operational runbooks. +* `scripts/migrate-wellknown-to-www.sh` — conflict-safe migration of a + repository-root `.well-known/` into `www/.well-known/` (identical copies + de-duplicated, root-only content moved, divergent content quarantined — + see Changed). +* `scripts/sweep-wellknown.sh` — stage 5 (#119) batch driver: classify, + migrate, test and commit across the estate in batches of 25, holding + Pages-served and non-RSR repositories for a human decision instead of + sweeping them blind. +* `www/runbooks/stage5-wellknown-sweep.adoc` — how to run the sweep, and + how to recover from a quarantine. +* Mint wiring for the `www/` bundle: `repo-init` now invokes + `scripts/migrate-wellknown-to-www.sh` when the tree being minted still + carries a legacy root `.well-known/`, and runs `www/tests/run-all.sh` + afterwards. Both are no-ops for a repository minted from the current + template; they exist for running `repo-init` over a tree that predates + #53. #106 described both as already present — neither was, which is a + fair part of why the estate still carries root `.well-known/` directories. + +==== Changed + +* Canonical `.well-known/` location moved from the repository root to + `www/.well-known/`. Well-known enforcement, Groove checks, the root-shape + allowlist, the SSG bootstrap and the documentation now prefer the `www/` + location and accept the legacy root location with a warning during the + migration window. + +==== Changed + +* Divergent root/`www/` `.well-known/` content is now quarantined to + `www/.legacy-well-known-/` instead of being left in place: the + estate-wide sweep (#119) is unattended, so a conflict must not halt it, and + must not be silent either. The `www/` copy is untouched, both hashes are + printed, and the run exits non-zero. `--in-place` keeps the previous + contract (preserve both, exit 1) for hand resolution. The quarantine lives + under `www/` rather than at the root because the root allowlist is matched + literally and bidirectionally, so a dated root directory would report as + drift in every repository swept. + +==== Fixed + +* `dot-wellknown-enforcement.yml` now drives the migrator that #106 described + but never wired up: previously the workflow only printed advice to run it, + so nothing verified that a repository would survive the migration. It now + runs the migrator in `--dry-run` and turns every conflict into a + `::warning` annotation — a warning, not an error, for the migration window. +* The migrator's acceptance test had encoded the in-place contract as the + required behaviour, contradicting #106's stated design; it now proves the + quarantine contract across nine scenarios, including the collision case + where a quarantine name is already taken. +* `tests/e2e/template_instantiation_test.sh` fed the three container answers + only when `container/` existed, but `build/just/repo-init.just` asks those + questions when `build/container/` does. On any complete checkout — which is + what CI clones — the recipe asked three questions the test had no answers + for, `read` hit EOF, and the recipe exited 1, so the instantiation e2e was + red at the container prompt. The guard now matches the recipe's own path. + (The test also needed `ruby` for the two mint tools. Those are Rust as + of 2026-09-18, so it no longer stops there.) + +* Restore the modular `just` recipes removed by the root-layout refactor, so + `just repo-init`, validation, assessment, container, Groove, and proof tasks + are available again. +* Require lowercase hyphenated repository slugs at initialization and verify + that both Guix package definitions receive the rendered slug and project URL. +* Keep template validation aligned with authority documents moved under + `docs/`, and repair the cross-platform `cloak`/`uncloak` recipe syntax. +* Restore the tracked-file language-ban gate with regression fixtures proving + that supported Zig passes while prohibited V-language remnants fail. diff --git a/czech-file-knife/CITATION.cff b/czech-file-knife/CITATION.cff new file mode 100644 index 000000000..90bc56757 --- /dev/null +++ b/czech-file-knife/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: "Jewell" + given-names: "Jonathan D.A." + orcid: "https://orcid.org/0000-0000-0000-0000" # Placeholder +title: "Czech File Knife" +version: 0.1.0 +date-released: "2026-09-28" +url: "https://github.com/hyperpolymath/czech-file-knife" +repository-code: "https://github.com/hyperpolymath/czech-file-knife" +license: MPL-2.0 +keywords: + - "rsr" + - "formal-verification" + - "neurosymbolic" + - "provenance" diff --git a/czech-file-knife/CLAUDE.md b/czech-file-knife/CLAUDE.md new file mode 100644 index 000000000..b59c234bb --- /dev/null +++ b/czech-file-knife/CLAUDE.md @@ -0,0 +1,71 @@ + + + + + + +# You are in the hyperpolymath estate — orient before acting + +If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. + +## Doctrine (the rules here) +1. **Holes before anything else** — fix soundness holes before features/perf/docs. +2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. +3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. +4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. +5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. +6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. +7. **No deletion by access-recency** — cold ≠ disposable. +8. **Wire first** — unwired is not done. +9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). +10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). +11. **Stop-first** when an action is costly to undo or outward-facing. +12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. +13. **Equivalence as identity** — the estate's intellectual through-line. +14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. +15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. + +## The machine-readable substrate (read in this order on arrival) +**CHORA** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** -> **NEUROSYM** -> **PLAYBOOK** + +The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. + +| File | Answers | +|---|---| +| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | +| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | +| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | +| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | +| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | +| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | +| `PLAYBOOK.a2ml` | *How* permitted actions run. | +| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | +| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | +| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | + +## Canon pointers +- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. +- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** + +## Estate language policy (overridable per-repo via AGENTIC) +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (being removed — owner ruling 2026-08-26, standards#655) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. + +--- + +# This repo: `czech-file-knife` · clade `rm-czech-file-knife` + +- **Identity** — uuid `a5ea1382-a34c-5334-8a46-a2ebe904c810`; clade `rm` (secondary ``); born 2026-03-16; forge `hyperpolymath/czech-file-knife`. +- **IS** — Canonical RSR-compliant repository template: scaffolding (CI/CD, AI manifests, ABI/FFI standards, container ecosystem, governance) that new hyperpolymath projects are instantiated from. +- **IS-NOT** — a project in its own right · Scaffoldia (the full-featured repo designer) · standards (the canon source this template operationalises) +- **Where it sits** — pipeline position **foundation**; chain `standards → czech-file-knife → (every estate repo)`; coordination = `standards`. +- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. +- **Golden path** (ANCHOR) — `just test && just quality` → Core tests pass; Quality gates pass; No unresolved critical security findings. +- **State** — phase maintenance; maturity production; 95% complete; status active. + + diff --git a/czech-file-knife/CODE_OF_CONDUCT.adoc b/czech-file-knife/CODE_OF_CONDUCT.adoc deleted file mode 100644 index a6003933d..000000000 --- a/czech-file-knife/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,39 +0,0 @@ -== Contributor Covenant Code of Conduct - -=== Our Pledge - -We as members, contributors, and leaders pledge to make participation in -our community a harassment-free experience for everyone, regardless of -age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -race, religion, or sexual identity and orientation. - -=== Our Standards - -Examples of behavior that contributes to a positive environment include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our -mistakes - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery -* Trolling, insulting or derogatory comments, and personal or political -attacks -* Public or private harassment -* Publishing others’ private information without explicit permission - -=== Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may -be reported to the project maintainers. All complaints will be reviewed -and investigated promptly and fairly. - -=== Attribution - -This Code of Conduct is adapted from the -https://www.contributor-covenant.org[Contributor Covenant], version 2.1. diff --git a/czech-file-knife/CONTRIBUTING.adoc b/czech-file-knife/CONTRIBUTING.adoc index 205642748..c5fdf1ac4 100644 --- a/czech-file-knife/CONTRIBUTING.adoc +++ b/czech-file-knife/CONTRIBUTING.adoc @@ -1,108 +1,122 @@ -== Clone the repository +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: Jonathan D.A. Jewell (hyperpolymath) += Contributing — czech-file-knife +:toc: left -git clone https://\{\{FORGE}}/\{\{OWNER}}/\{\{REPO}}.git cd \{\{REPO}} +Contributors work on the *spine*: the template every RSR repo is minted +from. Changes here propagate to the whole estate at mint time, so the bar +is the estate's bar. -== Using Nix (recommended for reproducibility) +== Local-dev setup -nix develop +[source,bash] +---- +# Clone the repository +git clone https://github.com/hyperpolymath/czech-file-knife.git +cd czech-file-knife -== Or using toolbox/distrobox +# Using Guix (recommended for reproducibility) +guix shell -D -f build/guix.scm -toolbox create \{\{REPO}}-dev toolbox enter \{\{REPO}}-dev # Install -dependencies manually +# Or using toolbox/distrobox +toolbox create czech-file-knife-dev +toolbox enter czech-file-knife-dev +# Install dependencies manually -== Verify setup +# Verify setup +just check # or: cargo check / mix compile / etc. +just test # Run test suite +---- -just check # or: cargo check / mix compile / etc. just test # Run test -suite +== Repository structure -.... +The authoritative map is *generated* from the tree and checked in CI, so +it cannot drift: +link:docs/architecture/REPOSITORY-MAP.adoc[docs/architecture/REPOSITORY-MAP.adoc]. +Regenerate it with `just repo-map`. -### Repository Structure -.... +A hand-written tree used to live here. It described `lib/`, `extensions/`, +`plugins/` and `spec/` directories that this repository has never +contained, which is precisely why the map is now generated rather than +typed. -\{\{REPO}}/ ├── src/ # Source code (Perimeter 1-2) ├── lib/ # Library -code (Perimeter 1-2) ├── extensions/ # Extensions (Perimeter 2) ├── -plugins/ # Plugins (Perimeter 2) ├── tools/ # Tooling (Perimeter 2) ├── -docs/ # Documentation (Perimeter 3) │ ├── architecture/ # ADRs, specs -(Perimeter 2) │ └── proposals/ # RFCs (Perimeter 3) ├── examples/ # -Examples (Perimeter 3) ├── spec/ # Spec tests (Perimeter 3) ├── tests/ # -Test suite (Perimeter 2-3) ├── .well-known/ # Protocol files (Perimeter -1-3) ├── .github/ # GitHub config (Perimeter 1) │ ├── ISSUE_TEMPLATE/ │ -└── workflows/ ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── -CONTRIBUTING.md # This file ├── GOVERNANCE.md ├── LICENSE ├── -MAINTAINERS.md ├── README.adoc ├── SECURITY.md ├── flake.nix # Nix flake -(Perimeter 1) └── Justfile # Task runner (Perimeter 1) +== How to contribute -.... +=== Reporting bugs ---- +*Before reporting:* -## How to Contribute +. Search existing issues. +. Check if it is already fixed in `main`. +. Determine which perimeter the bug affects. -### Reporting Bugs +*When reporting*, use the +link:.github/ISSUE_TEMPLATE/bug_report.md[bug report template] and include: -**Before reporting**: -1. Search existing issues -2. Check if it's already fixed in `{{MAIN_BRANCH}}` -3. Determine which perimeter the bug affects +* Clear, descriptive title +* Environment details (OS, versions, toolchain) +* Steps to reproduce +* Expected vs actual behaviour +* Logs, screenshots, or minimal reproduction -**When reporting**: +=== Suggesting features -Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: +*Before suggesting:* -- Clear, descriptive title -- Environment details (OS, versions, toolchain) -- Steps to reproduce -- Expected vs actual behaviour -- Logs, screenshots, or minimal reproduction +. Check the link:docs/status/ROADMAP.adoc[roadmap] if available. +. Search existing issues and discussions. +. Consider which perimeter the feature belongs to. -### Suggesting Features +*When suggesting*, use the +link:.github/ISSUE_TEMPLATE/feature_request.md[feature request template] +and include: -**Before suggesting**: -1. Check the [roadmap](ROADMAP.md) if available -2. Search existing issues and discussions -3. Consider which perimeter the feature belongs to +* Problem statement (what pain point does this solve?) +* Proposed solution +* Alternatives considered +* Which perimeter this affects -**When suggesting**: - -Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) and include: - -- Problem statement (what pain point does this solve?) -- Proposed solution -- Alternatives considered -- Which perimeter this affects - -### Your First Contribution +=== Your first contribution Look for issues labelled: -- [`good first issue`](https://{{FORGE}}/{{OWNER}}/{{REPO}}/labels/good%20first%20issue) — Simple Perimeter 3 tasks -- [`help wanted`](https://{{FORGE}}/{{OWNER}}/{{REPO}}/labels/help%20wanted) — Community help needed -- [`documentation`](https://{{FORGE}}/{{OWNER}}/{{REPO}}/labels/documentation) — Docs improvements -- [`perimeter-3`](https://{{FORGE}}/{{OWNER}}/{{REPO}}/labels/perimeter-3) — Community sandbox scope +* https://github.com/hyperpolymath/czech-file-knife/labels/good%20first%20issue[`good first issue`] — Simple Perimeter 3 tasks +* https://github.com/hyperpolymath/czech-file-knife/labels/help%20wanted[`help wanted`] — Community help needed +* https://github.com/hyperpolymath/czech-file-knife/labels/documentation[`documentation`] — Docs improvements +* https://github.com/hyperpolymath/czech-file-knife/labels/perimeter-3[`perimeter-3`] — Community sandbox scope ---- +== Development workflow -## Development Workflow +=== Branch naming -### Branch Naming -.... +[source,text] +---- +docs/short-description # Documentation (P3) +test/what-added # Test additions (P3) +feat/short-description # New features (P2) +fix/issue-number-description # Bug fixes (P2) +refactor/what-changed # Code improvements (P2) +security/what-fixed # Security fixes (P1-2) +---- -docs/short-description # Documentation (P3) test/what-added # Test -additions (P3) feat/short-description # New features (P2) -fix/issue-number-description # Bug fixes (P2) refactor/what-changed # -Code improvements (P2) security/what-fixed # Security fixes (P1-2) +=== Commit messages -.... +We follow https://www.conventionalcommits.org/[Conventional Commits]: -### Commit Messages +[source,text] +---- +(): -We follow [Conventional Commits](https://www.conventionalcommits.org/): -.... +[optional body] -(): +[optional footer] +---- -{empty}[optional body] +== Cross-references -{empty}[optional footer] +This root document exists because the estate docs gate +(`hyperpolymath/standards` `scripts/check-docs-presence.sh`) requires +`CONTRIBUTING.md`, `CONTRIBUTING.adoc`, or `3-practice/CONTRIBUTING.adoc` +at the repository root. The legacy copy at +link:.github/CONTRIBUTING.md[.github/CONTRIBUTING.md] predates that gate; +this document supersedes it. diff --git a/czech-file-knife/Cargo.toml b/czech-file-knife/Cargo.toml index 8ea61d181..f699d58b6 100644 --- a/czech-file-knife/Cargo.toml +++ b/czech-file-knife/Cargo.toml @@ -1,21 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 [workspace] resolver = "2" members = [ - "cfk-core", - "cfk-providers", - "cfk-cache", - "cfk-search", - "cfk-vfs", - "cfk-cli", - "cfk-integrations", - "cfk-ios", + "src/cfk-core", + "src/cfk-providers", + "src/cfk-cache", + "src/cfk-search", + "src/cfk-vfs", + "src/cfk-cli", + "src/cfk-integrations", + "src/cfk-ios", ] [workspace.package] version = "0.1.0" edition = "2021" rust-version = "1.75" -license = "AGPL-3.0-or-later" +license = "MPL-2.0" repository = "https://github.com/hyperpolymath/czech-file-knife" homepage = "https://github.com/hyperpolymath/czech-file-knife" documentation = "https://docs.rs/cfk-core" diff --git a/czech-file-knife/Containerfile b/czech-file-knife/Containerfile deleted file mode 100644 index 3af317f47..000000000 --- a/czech-file-knife/Containerfile +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Build stage -FROM docker.io/library/rust:1.83-slim AS builder - -WORKDIR /build - -# Install build dependencies -RUN apt-get update && apt-get install -y --no-install-recommends \ - pkg-config \ - libfuse3-dev \ - && rm -rf /var/lib/apt/lists/* - -# Copy source -COPY . . - -# Build release binary -RUN cargo build --release -p cfk-cli - -# Runtime stage -FROM cgr.dev/chainguard/wolfi-base:latest - -LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/czech-file-knife" -LABEL org.opencontainers.image.description="Czech File Knife - Universal file management tool" -LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later" - -# Install runtime dependencies -RUN apk add --no-cache fuse3 fuse3-libs ca-certificates - -# Copy binary from builder -COPY --from=builder /build/target/release/cfk /usr/local/bin/cfk - -# Create non-root user -RUN adduser -D -u 1000 cfk -USER cfk - -WORKDIR /home/cfk - -ENTRYPOINT ["/usr/local/bin/cfk"] diff --git a/czech-file-knife/GEMINI.md b/czech-file-knife/GEMINI.md new file mode 100644 index 000000000..417391d24 --- /dev/null +++ b/czech-file-knife/GEMINI.md @@ -0,0 +1,8 @@ +# Pointer + +This repository has no `AGENTS.md` yet. Until it does, the instructions +for every coding agent live in **[CLAUDE.md](./CLAUDE.md)**. Read that +file, and skip anything in it that is specific to Claude Code tooling. +Do not duplicate rules here. + +When `AGENTS.md` lands in this repository, retarget this pointer at it. diff --git a/czech-file-knife/Justfile b/czech-file-knife/Justfile index 436d5fec6..ae5c88930 100644 --- a/czech-file-knife/Justfile +++ b/czech-file-knife/Justfile @@ -1,32 +1,633 @@ -# czech-file-knife - Development Tasks +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard Justfile Template +# https://just.systems/man/en/ +# +# Copy this file to new projects and customize the placeholder values. +# +# Run `just` to see all available recipes +# Run `just cookbook` to generate docs/just-cookbook.adoc +# Run `just combinations` to see matrix recipe options + set shell := ["bash", "-uc"] set dotenv-load := true +set positional-arguments := true + +# Import auto-generated contractile recipes (must-check, trust-verify, etc.) +# Re-generate with: contractile gen-just +import? "build/contractile.just" +# Project metadata — customize these project := "czech-file-knife" +OWNER := "hyperpolymath" +REPO := "czech-file-knife" +version := "0.1.0" +tier := "infrastructure" # 1 | 2 | infrastructure -# Show all recipes +# ═══════════════════════════════════════════════════════════════════════════════ +# DEFAULT & HELP +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show all available recipes with descriptions default: @just --list --unsorted -# Build -build: - @echo "TODO: Add build command" +# Show detailed help for a specific recipe +help recipe="": + #!/usr/bin/env bash + if [ -z "{{recipe}}" ]; then + just --list --unsorted + echo "" + echo "Usage: just help " + echo " just cookbook # Generate full documentation" + echo " just combinations # Show matrix recipes" + else + just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found" + fi + +# Show this project's info +info: + @echo "Project: czech_file_knife" + @echo "Version: {{version}}" + @echo "RSR Tier: {{tier}}" + @echo "Recipes: $(just --summary | wc -w)" + @[ -f ".machine_readable/descriptiles/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/descriptiles/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true + +# Run Invariant Path overlay tools for this repository +invariant-path *ARGS: + ./scripts/invariant-path.sh {{ARGS}} + +# ═══════════════════════════════════════════════════════════════════════════════ +# INIT — see build/just/repo-init.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/repo-init.just" + +# >>> container-module (three-tier: OCI · portable engine · stapeln) >>> +# Self-contained. Remove the entire block — this and the import — with `just no-container`. +import? "build/just/container.just" +# <<< container-module <<< + +# ═══════════════════════════════════════════════════════════════════════════════ +# GROOVE PROTOCOL — see build/just/groove.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/groove.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE — see build/just/assess.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/assess.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# BUILD & COMPILE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build the project (debug mode) +build *args: + cargo build --workspace {{args}} -# Test -test: - @echo "TODO: Add test command" +# Build in release mode with optimizations +build-release *args: + cargo build --release --locked -p cfk-cli {{args}} -# Clean +# Build and watch for changes (requires entr or similar) +build-watch: + find src -name '*.rs' | entr -c just build + +# Clean build artifacts [reversible: rebuild with `just build`] clean: - @echo "TODO: Add clean command" + @echo "Cleaning..." + # + # `build/` is DELIBERATELY ABSENT from this list. It is not an artifact + # directory in an RSR repo: it holds 11 tracked files, including + # build/just/repo-init.just, which the root Justfile imports at line 65. + # Deleting it destroys `just repo-init`, `just verify` and the proof gates. + rm -rf target/ _build/ dist/ out/ obj/ bin/ + +# Deep clean including caches [reversible: rebuild] +clean-all: clean + rm -rf .cache .tmp + +# ═══════════════════════════════════════════════════════════════════════════════ +# TEST & QUALITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run all tests +test *args: + cargo test --workspace {{args}} + +# Run tests with verbose output +test-verbose: + cargo test --workspace -- --nocapture + +# Smoke test +test-smoke: + cargo test -p cfk-core --lib + +# Run end-to-end tests (full pipeline: build → run → verify) +e2e: + bash tests/e2e.sh + +# Run aspect tests (cross-cutting concern validation) +aspect: + bash tests/aspect_tests.sh + +# Run benchmarks (performance regression detection) +bench: + cargo bench --workspace + +# Run readiness tests (Component Readiness Grade: D/C/B) +readiness: + cargo test --workspace --release + +# Print the current CRG grade (reads from docs/status/READINESS.adoc '**Current Grade:** X' line) +crg-grade: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' docs/status/READINESS.adoc 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + echo "$$grade" + +# Print a shields.io CRG badge for embedding in README files +# Looks for '**Current Grade:** X' in docs/status/READINESS.adoc; falls back to X +crg-badge: + @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' docs/status/READINESS.adoc 2>/dev/null | head -1); \ + [ -z "$$grade" ] && grade="X"; \ + case "$$grade" in \ + A) color="brightgreen" ;; \ + B) color="green" ;; \ + C) color="yellow" ;; \ + D) color="orange" ;; \ + E) color="red" ;; \ + F) color="critical" ;; \ + *) color="lightgrey" ;; \ + esac; \ + echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" + +# Run the full merge-requirement test suite (ALL categories) +# Per STANDING rule: P2P + E2E + aspect + execution + lifecycle + bench +test-all: test e2e aspect bench readiness + @echo "All test categories passed — safe to merge!" -# Format +# Run all quality checks +quality: fmt-check lint test + @echo "All quality checks passed!" + +# Fix all auto-fixable issues [reversible: git checkout] +fix: fmt + @echo "Fixed all auto-fixable issues" + +# ═══════════════════════════════════════════════════════════════════════════════ +# LINT & FORMAT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Format all source files [reversible: git checkout] fmt: - @echo "TODO: Add format command" + cargo fmt --all + +# Check formatting without changes +fmt-check: + cargo fmt --all --check -# Lint +# Run linter lint: - @echo "TODO: Add lint command" + cargo clippy --workspace --all-targets -- -D warnings + +# ═══════════════════════════════════════════════════════════════════════════════ +# RUN & EXECUTE +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run the application +run *args: build + cargo run -p cfk-cli -- {{args}} + +# Run with verbose output +run-verbose *args: build + cargo run -p cfk-cli -- --verbose {{args}} + +# Install to user path +install: build-release + cargo install --locked --path src/cfk-cli + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEPENDENCIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Install/check all dependencies +deps: + cargo fetch --locked + +# Audit dependencies for vulnerabilities +deps-audit: + cargo deny --manifest-path Cargo.toml check --config .machine_readable/compliance/rust/deny.toml + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true + +# ═══════════════════════════════════════════════════════════════════════════════ +# ARRIVAL PACK — agent-facing CLAUDE.md, compiled from a2ml +# ═══════════════════════════════════════════════════════════════════════════════ + +# Compile CLAUDE.md (the agent arrival pack) from this repo's a2ml +claude-md: + @bash .machine_readable/arrival-pack/generate.sh + +# Regenerate the single authoritative repository map +repo-map: + @bash scripts/gen-repo-map.sh . + +# Fail if the repository map is stale (the map is generated; CI diffs it) +validate-repo-map: + #!/usr/bin/env bash + set -euo pipefail + cd "{{justfile_directory()}}" + before=$(mktemp); cp docs/architecture/REPOSITORY-MAP.adoc "$before" 2>/dev/null || true + bash scripts/gen-repo-map.sh . >/dev/null + if ! diff -q "$before" docs/architecture/REPOSITORY-MAP.adoc >/dev/null 2>&1; then + echo "FAIL: docs/architecture/REPOSITORY-MAP.adoc is stale. Run: just repo-map" >&2 + diff -u "$before" docs/architecture/REPOSITORY-MAP.adoc | head -40 >&2 || true + cp "$before" docs/architecture/REPOSITORY-MAP.adoc + rm -f "$before"; exit 1 + fi + rm -f "$before" + echo "repository map: up to date" + +# Fail if CLAUDE.md's generated region drifted from a2ml or was hand-edited +validate-claude-md: + @bash .machine_readable/arrival-pack/verify.sh + +# ═══════════════════════════════════════════════════════════════════════════════ +# COAPTATION — typed descriptile↔contractile face-off (homeostasis reading) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Emit the coaptation receipt: how the descriptiles coapt with the contractiles (SITREP) +coapt: + @bash .machine_readable/coaptation/coapt.sh --report + +# Assemble a re-anchor basis IF the band is red (the drop itself is a human act) +coapt-reanchor: + @bash .machine_readable/coaptation/coapt.sh --reanchor + +# Fail if the committed coaptation receipt drifted from the contractiles/descriptiles +validate-coapt: + @bash .machine_readable/coaptation/verify.sh + +# ═══════════════════════════════════════════════════════════════════════════════ +# DOCUMENTATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Generate all documentation +docs: + @mkdir -p docs/generated docs/man + just cookbook + just man + @echo "Documentation generated in docs/" + +# Generate justfile cookbook documentation +cookbook: + #!/usr/bin/env bash + mkdir -p docs + OUTPUT="docs/just-cookbook.adoc" + echo "= czech_file_knife Justfile Cookbook" > "$OUTPUT" + echo ":toc: left" >> "$OUTPUT" + echo ":toclevels: 3" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "Generated: $(date -Iseconds)" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "== Recipes" >> "$OUTPUT" + echo "" >> "$OUTPUT" + just --list --unsorted | while read -r line; do + if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then + recipe="${BASH_REMATCH[1]}" + echo "=== $recipe" >> "$OUTPUT" + echo "" >> "$OUTPUT" + echo "[source,bash]" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "just $recipe" >> "$OUTPUT" + echo "----" >> "$OUTPUT" + echo "" >> "$OUTPUT" + fi + done + echo "Generated: $OUTPUT" + +# Generate man page +man: + #!/usr/bin/env bash + mkdir -p docs/man + cat > docs/man/czech_file_knife.1 << EOF + .TH czech_file_knife 1 "$(date +%Y-%m-%d)" "{{version}}" "czech_file_knife Manual" + .SH NAME + czech_file_knife \- RSR-compliant project + .SH SYNOPSIS + .B just + [recipe] [args...] + .SH DESCRIPTION + RSR (Rhodium Standard Repository) project managed with just. + .SH AUTHOR + $(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")> + EOF + echo "Generated: docs/man/czech_file_knife.1" + +# ═══════════════════════════════════════════════════════════════════════════════ +# CI & AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run full CI pipeline locally +# proof-check-all is FATAL if any prover toolchain is absent (idris2/lean/agda/coqc): +# the full CI gate must not pass on a machine that cannot verify the proofs. +ci: deps quality proof-check-all + @echo "CI pipeline complete!" + +# Install git hooks +install-hooks: + @mkdir -p .git/hooks + @cat > .git/hooks/pre-commit << 'HOOKEOF' + #!/bin/bash + just fmt-check || exit 1 + just lint || exit 1 + just assail || exit 1 + HOOKEOF + @chmod +x .git/hooks/pre-commit + @echo "Git hooks installed" + +# ═══════════════════════════════════════════════════════════════════════════════ +# SECURITY +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run security audit +security: deps-audit + @echo "=== Security Audit ===" + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true + @echo "Security audit complete" + +# Generate SBOM +sbom: + @mkdir -p docs/security + @command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VALIDATION & COMPLIANCE — see build/just/validate.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/validate.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# STATE MANAGEMENT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Update STATE.a2ml timestamp +state-touch: + @if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ + sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/descriptiles/STATE.a2ml && \ + echo "STATE.a2ml timestamp updated"; \ + fi + +# Show current phase from STATE.a2ml +state-phase: + @sed -n 's/^[[:space:]]*phase[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .machine_readable/descriptiles/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" + +# ═══════════════════════════════════════════════════════════════════════════════ +# GUIX +# ═══════════════════════════════════════════════════════════════════════════════ + +# Enter Guix development shell (primary) +guix-shell: + guix shell -D -f build/guix.scm + +# Build with Guix +guix-build: + guix build -f build/guix.scm + +# ═══════════════════════════════════════════════════════════════════════════════ +# HYBRID AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ + +# Run local automation tasks +automate task="all": + #!/usr/bin/env bash + case "{{task}}" in + all) just fmt && just lint && just test && just docs && just state-touch ;; + cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;; + update) just deps && just validate ;; + *) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;; + esac + +# ═══════════════════════════════════════════════════════════════════════════════ +# COMBINATORIC MATRIX RECIPES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build matrix: [debug|release] x [target] x [features] +build-matrix mode="debug" target="" features="": + @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" + +# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel] +test-matrix suite="unit" verbosity="normal" parallel="true": + @echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}" + +# CI matrix: [lint|test|build|security|all] x [quick|full] +ci-matrix stage="all" depth="quick": + @echo "CI matrix: stage={{stage}} depth={{depth}}" + +# Show all matrix combinations +combinations: + @echo "=== Combinatoric Matrix Recipes ===" + @echo "" + @echo "Build Matrix: just build-matrix [debug|release] [target] [features]" + @echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]" + @echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag] (needs container module)" + @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" + +# ═══════════════════════════════════════════════════════════════════════════════ +# VERSION CONTROL +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show git status +status: + @git status --short + +# Show recent commits +log count="20": + @git log --oneline -{{count}} + +# Generate CHANGELOG.adoc with git-cliff +changelog: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + # AsciiDoc, not .md: CHANGELOG.adoc is what root-allow.txt permits, so a + # .md here would fail check-root-shape AND the estate's no-.md rule the + # moment anyone ran this recipe. + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.adoc + @echo "Generated CHANGELOG.adoc" + +# Preview changelog for unreleased commits (does not write) +changelog-preview: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header + +# Tag a new release (usage: just release-tag 1.2.3) +release-tag version: + #!/usr/bin/env bash + TAG="v{{version}}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists" + exit 1 + fi + just changelog + git add CHANGELOG.md + git commit -m "chore(release): prepare $TAG" + git tag -a "$TAG" -m "Release $TAG" + echo "Created tag $TAG — push with: git push origin main --tags" + +# ═══════════════════════════════════════════════════════════════════════════════ +# UTILITIES +# ═══════════════════════════════════════════════════════════════════════════════ + +# Count lines of code +loc: + @find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0" + +# Show TODO comments +todos: + @grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs" + +# Open in editor +edit: + ${EDITOR:-code} . + +# Run high-rigor security assault using panic-attacker +maint-assault: + @./.machine_readable/scripts/maintenance/maint-assault.sh + +# Run panic-attacker pre-commit scan (foundational floor-raise requirement) +assail: + @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "WARN: panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" + + +# Self-diagnostic — checks dependencies, permissions, paths +doctor: + @echo "Running diagnostics for czech-file-knife..." + @echo "Checking required tools..." + @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" + @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" + @echo "Checking for hardcoded paths..." + @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" + @echo "Diagnostics complete." + +# Guided tour of key features +tour: + @echo "=== czech-file-knife Tour ===" + @echo "" + @echo "1. Project structure:" + @ls -la + @echo "" + @echo "2. Available commands: just --list" + @echo "" + @echo "3. Read README.adoc for full overview" + @echo "4. Read EXPLAINME.adoc for architecture decisions" + @echo "5. Run 'just doctor' to check your setup" + @echo "" + @echo "Tour complete! Try 'just --list' to see all available commands." + +# Open feedback channel with diagnostic context +help-me: + @echo "=== czech-file-knife Help ===" + @echo "Platform: $(uname -s) $(uname -m)" + @echo "Shell: $SHELL" + @echo "" + @echo "To report an issue:" + @echo " https://github.com/hyperpolymath/czech-file-knife/issues/new" + @echo "" + @echo "Include the output of 'just doctor' in your report." + +# ═══════════════════════════════════════════════════════════════════════════════ +# FORMAL VERIFICATION (PROOFS) — see build/just/proofs.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/proofs.just" + +# ═══════════════════════════════════════════════════════════════════════════════ +# SESSION MANAGEMENT (THIN BINDINGS TO CENTRAL STANDARDS) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Show canonical session-management command model +session-help: + @echo "Canonical command model:" + @echo " intake repo " + @echo " checkpoint change " + @echo " verify maintenance " + @echo " verify substantial " + @echo " verify release " + @echo " close planned " + @echo " close urgent " + @echo " recover repo " + @echo " handover full " + @echo " handover split " + @echo " handover model " + @echo " handover human " + @echo "" + @echo "Use Just aliases below (thin wrappers around ./session/dispatch.sh)." + +# Canonical aliases (friendly recipe names that map to canonical commands) +intake-repo path=".": + @./session/dispatch.sh intake repo "{{path}}" + +checkpoint-change path=".": + @./session/dispatch.sh checkpoint change "{{path}}" + +verify-maintenance path=".": + @./session/dispatch.sh verify maintenance "{{path}}" + +verify-substantial path=".": + @./session/dispatch.sh verify substantial "{{path}}" + +verify-release path=".": + @./session/dispatch.sh verify release "{{path}}" + +close-planned path=".": + @./session/dispatch.sh close planned "{{path}}" + +close-urgent path=".": + @./session/dispatch.sh close urgent "{{path}}" + +recover-repo path=".": + @./session/dispatch.sh recover repo "{{path}}" + +handover-full path=".": + @./session/dispatch.sh handover full "{{path}}" + +handover-split path=".": + @./session/dispatch.sh handover split "{{path}}" + +handover-model path=".": + @./session/dispatch.sh handover model "{{path}}" + +handover-human path=".": + @./session/dispatch.sh handover human "{{path}}" secret-scan-trufflehog: @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true + +# ═══════════════════════════════════════════════════════════════════════════════ +# WINDOWS RGONOMICS (CLOAKING) +# ═══════════════════════════════════════════════════════════════════════════════╓ +# Hide all dotfiles and dot-folders from Windows Explorer. On POSIX systems, +# leading-dot names are already hidden by convention, so these recipes are +# intentionally harmless no-ops. +cloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -bor [System.IO.FileAttributes]::Hidden }" + echo "Cloak engaged." + else + echo "Dotfiles are natively cloaked on this OS. No action required." + fi + +# Reveal dotfiles in Windows Explorer; on POSIX, explain the native mechanism. +uncloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -band -bnot [System.IO.FileAttributes]::Hidden }" + echo "Cloak lifted." + else + echo "Use 'ls -a' to view dotfiles on this OS." + fi diff --git a/czech-file-knife/LICENSE b/czech-file-knife/LICENSE index ebda51cd7..14e2f777f 100644 --- a/czech-file-knife/LICENSE +++ b/czech-file-knife/LICENSE @@ -1,38 +1,3 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - ------------------------------------------------------------------------- -PREFERRED LICENCE: Palimpsest License (PMPL-1.0-or-later) ------------------------------------------------------------------------- - -This work is governed by the Palimpsest License (PMPL-1.0-or-later) as -its primary intended licence. PMPL-1.0-or-later extends the Mozilla -Public License 2.0 (MPL-2.0) with additional provisions for ethical use, -post-quantum cryptographic provenance, and emotional lineage protection. -The canonical PMPL text and stewardship information are maintained at: - https://github.com/hyperpolymath/palimpsest-license - ------------------------------------------------------------------------- -FALLBACK LICENCE: Mozilla Public License 2.0 (MPL-2.0) ------------------------------------------------------------------------- - -Because PMPL-1.0-or-later is not yet recognised by the Open Source -Initiative (OSI) or equivalent bodies, this work also carries MPL-2.0 -as its legally-recognised fallback licence. - -In any jurisdiction, platform, or context where PMPL-1.0-or-later is -not accepted as a valid licence, or where an OSI-approved licence is -required, this work is instead governed by the Mozilla Public License, -Version 2.0. - -MPL-2.0 was chosen as the fallback because PMPL-1.0-or-later is -explicitly based on and extends MPL-2.0; it is therefore the closest -recognised equivalent to the intended licence. - -The complete MPL-2.0 text follows below. - ------------------------------------------------------------------------- - Mozilla Public License Version 2.0 ================================== @@ -70,7 +35,7 @@ Mozilla Public License Version 2.0 means any form of the work other than Source Code Form. 1.7. "Larger Work" - means a work that combines Covered Software with other material, in + means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" @@ -109,17 +74,17 @@ Mozilla Public License Version 2.0 means the form of the work preferred for making modifications. 1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under - this License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. - For the purposes of this definition, "control" means (a) the power, - direct or indirect, to cause the direction or management of such - entity, whether by contract or otherwise, or (b) ownership of more - than fifty percent (50%) of the outstanding shares or beneficial + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions ---------------------------------- +-------------------------------- 2.1. Grants @@ -144,11 +109,11 @@ distributes such Contribution. 2.3. Limitations on Grant Scope -The licenses granted in this Section 2 are the only rights granted -under this License. No additional rights or licenses will be implied -from the distribution or licensing of Covered Software under this -License. Notwithstanding Section 2.1(b) above, no patent license is -granted by a Contributor: +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: (a) for any code that a Contributor has removed from Covered Software; or @@ -158,19 +123,19 @@ granted by a Contributor: Contributions with other software (except as part of its Contributor Version); or -(c) under Patent Claims infringed by Covered Software in the absence - of its Contributions. +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. -This License does not grant any rights in the trademarks, service -marks, or logos of any Contributor (except as may be necessary to -comply with the notice requirements in Section 3.4). +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License -(if permitted under the terms of Section 3.3). +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). 2.5. Representation @@ -186,11 +151,11 @@ equivalents. 2.7. Conditions -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses -granted in Section 2.1. +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. 3. Responsibilities --------------------- +------------------- 3.1. Distribution of Source Form @@ -207,10 +172,10 @@ Form. If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients - of the Executable Form how they can obtain a copy of such Source - Code Form by reasonable means in a timely manner, at a charge no - more than the cost of distribution to the recipient; and + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the @@ -222,8 +187,8 @@ If You distribute Covered Software in Executable Form then: You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and -the Covered Software is not Incompatible With Secondary Licenses, this +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered @@ -241,28 +206,28 @@ the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of -Covered Software. However, You may do so only on Your own behalf, and -not on behalf of any Contributor. You must make it absolutely clear -that any such warranty, support, indemnity, or liability obligation is -offered by You alone, and You hereby agree to indemnify every -Contributor for any liability incurred by such Contributor as a result -of warranty, support, indemnity or liability terms You offer. You may -include additional disclaimers of warranty and limitations of liability -specific to any jurisdiction. +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. 4. Inability to Comply Due to Statute or Regulation ------------------------------------------------------ +--------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description -must be placed in a text file included with all distributions of the -Covered Software under this License. Except to the extent prohibited -by statute or regulation, such description must be sufficiently -detailed for a recipient of ordinary skill to be able to understand it. +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. 5. Termination -------------- @@ -271,27 +236,27 @@ detailed for a recipient of ordinary skill to be able to understand it. if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on -an ongoing basis, if such Contributor fails to notify You of the +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is -the first time You have received notice of non-compliance with this -License from such Contributor, and You become compliant prior to 30 -days after Your receipt of the notice. +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. -5.2. If You initiate litigation against any entity by asserting a -patent infringement claim (excluding declaratory judgment actions, +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) -which have been validly granted by You or Your distributors under this -License prior to termination shall survive termination. +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. ************************************************************************ * * @@ -346,7 +311,7 @@ Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ------------------ +---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be @@ -356,14 +321,14 @@ that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License ----------------------------- +--------------------------- 10.1. New Versions -Mozilla Foundation is the license steward. Except as provided in -Section 10.3, no one other than the license steward has the right to -modify or publish new versions of this License. Each version will be -given a distinguishing version number. +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. 10.2. Effect of New Versions @@ -396,13 +361,13 @@ Exhibit A - Source Code Form License Notice If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to -look for such a notice. +file in a relevant directory) where a recipient would be likely to look +for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice ----------------------------------------------------------- +--------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. diff --git a/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt b/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt new file mode 100644 index 000000000..2d58298e6 --- /dev/null +++ b/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/czech-file-knife/LICENSES/MPL-2.0.txt b/czech-file-knife/LICENSES/MPL-2.0.txt new file mode 100644 index 000000000..d0a1fa148 --- /dev/null +++ b/czech-file-knife/LICENSES/MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/czech-file-knife/MAINTAINERS.adoc b/czech-file-knife/MAINTAINERS.adoc deleted file mode 100644 index 48d978175..000000000 --- a/czech-file-knife/MAINTAINERS.adoc +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -= Maintainers -:toc: preamble - -This document lists the maintainers of this project and their responsibilities. - -== Current Maintainers - -[cols="2,3,2",options="header"] -|=== -| Name | Role | Contact - -| Jonathan D.A. Jewell -| Lead Maintainer -| https://github.com/hyperpolymath[@hyperpolymath] -|=== - -== Responsibilities - -Maintainers are responsible for: - -* Reviewing and merging pull requests -* Triaging issues and feature requests -* Ensuring code quality and security standards -* Managing releases and versioning -* Upholding the project's code of conduct - -== Becoming a Maintainer - -Contributors who demonstrate: - -* Consistent, high-quality contributions -* Understanding of the project's goals and standards -* Constructive participation in discussions -* Commitment to the project's long-term health - -May be invited to become maintainers at the discretion of existing maintainers. - -== Decision Making - -* Routine decisions (bug fixes, minor improvements) can be made by any maintainer -* Significant changes require discussion and consensus among maintainers -* Breaking changes or major features should be discussed in issues before implementation - -== Contact - -For questions about project governance, open an issue or contact the maintainers listed above. diff --git a/czech-file-knife/Mustfile b/czech-file-knife/Mustfile deleted file mode 100644 index 5f075413a..000000000 --- a/czech-file-knife/Mustfile +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile - hyperpolymath mandatory checks -# See: https://github.com/hyperpolymath/mustfile - -version: 1 - -checks: - - name: security - run: just lint - - name: tests - run: just test - - name: format - run: just fmt diff --git a/czech-file-knife/README.adoc b/czech-file-knife/README.adoc index 58f4ab7bb..ef668886b 100644 --- a/czech-file-knife/README.adoc +++ b/czech-file-knife/README.adoc @@ -1,219 +1,130 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) = Czech File Knife -image:https://img.shields.io/badge/License-MPL_2.0-blue.svg[MPL-2.0-or-later,link="https://opensource.org/licenses/MPL-2.0"] +:toc: +:toc-placement: preamble +// ── Licensing ─────────────────────────────────────────────────────────────── +image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg?logo=mozilla[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] +image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg?logo=creativecommons[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] +image:https://img.shields.io/badge/Provenance-Quantum--Safe-blueviolet[Quantum-Safe Provenance,link="docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt"] +// ── Standard & quality gates ──────────────────────────────────────────────── +image:https://img.shields.io/badge/RSR-Rhodium_Standard-9C27B0[Rhodium Standard Repository,link="https://github.com/hyperpolymath/rhodium-standard-repositories"] +image:https://api.scorecard.dev/projects/github.com/hyperpolymath/czech-file-knife/badge[OpenSSF Scorecard,link="https://scorecard.dev/viewer/?uri=github.com/hyperpolymath/czech-file-knife"] +image:https://sonarcloud.io/api/project_badges/measure?project=hyperpolymath_czech-file-knife&metric=alert_status[SonarQube Quality Gate,link="https://sonarcloud.io/summary/new_code?id=hyperpolymath_czech-file-knife"] +image:https://archive.softwareheritage.org/badge/origin/https://github.com/hyperpolymath/czech-file-knife/[Archived in Software Heritage,link="https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/hyperpolymath/czech-file-knife"] +// ── Provenance & ecosystem ────────────────────────────────────────────────── +image:https://api.thegreenwebfoundation.org/greencheckimage/github.com[Green Web,link="https://www.thegreenwebfoundation.org/green-web-check/?url=github.com"] -image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] +The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage. -== License & Philosophy +Czech File Knife (`cfk`) is the Swiss File Knife of the hybrid machine: one +command-line tool that treats local disks, network shares and cloud storage as +a single filesystem, and picks the cheapest *correct* way to do each job. -This project must declare **MPL-2.0-or-later** for platform/tooling compatibility. +Three rules shape it: -Philosophy: **Palimpsest**. The Palimpsest-MPL (PMPL) text is provided in `license/PMPL-1.0.txt`, and the canonical source is the palimpsest-license repository. +* *Reversible by default.* Every write, delete, move and copy records its own + inverse (the JanusKey model: SHA-256 content store + append-only op log), so + `cfk undo` works on every backend, even ones with no native versioning. +* *No pointless round trips.* Cloud-to-cloud work is done provider-side + (server-side copy/move/export) where possible, and otherwise streamed + through memory, never staged on local disk. +* *Space-aware.* Transforms such as compression are designed to fit in the + free space you actually have (for example compressing a 25 GB file with + 8 GB free by punching holes in the input as the output grows). See + link:docs/architecture/HYBRID-OPERATIONS.adoc[HYBRID-OPERATIONS]. - -*Cloud-native Swiss File Knife - unified interface for 20+ storage backends* - -== Overview - -Czech File Knife (CFK) is a universal file management tool that treats all storage -backends (local, cloud, distributed, exotic) as a unified filesystem. - -== Current Status (v0.1.0) +== Status (v0.1.0) [cols="1,1,3"] |=== | Component | Status | Notes -| *cfk-core* | Stable | Core traits, types, error handling -| *cfk-cli* | Working | Basic file operations (ls, cp, mv, rm, cat, mkdir, stat) -| *cfk-providers* | Partial | Local filesystem complete; cloud backends planned -| *cfk-cache* | Stub | Trait definitions; backend implementations coming -| *cfk-search* | Stub | Basic filename search; Tantivy integration planned -| *cfk-vfs* | Stub | FUSE mounting planned for v0.2.0 -| *cfk-integrations* | Working | aria2, agrep, pandoc integrations +| `cfk-core` | Stable | Core traits, types, errors, *reversible journal* +| `cfk-cli` | Working | `ls cat cp mv rm mkdir stat backends df history undo` +| `cfk-providers` | Partial | Local filesystem complete; network/cloud backends scaffolded +| `cfk-cache` | Stub | Trait definitions +| `cfk-search` | Stub | Filename search; Tantivy planned +| `cfk-vfs` | Stub | FUSE mounting planned +| `cfk-integrations` | Working | aria2, agrep, pandoc +| `cfk-ios` | Scaffold| iOS File Provider bridge (C ABI + Swift) +| `cfk-tui` | Scaffold| Ada TUI prototype |=== -== Installation +Honest residue: reversibility is engineered, not yet mechanically proven +(see link:docs/status/PROOF-NEEDS.adoc[PROOF-NEEDS]); metadata (permissions, +mtimes, xattrs) is not yet restored by `undo`. -=== From Source +== Quick start [source,bash] ---- -# Clone the repository git clone https://github.com/hyperpolymath/czech-file-knife.git cd czech-file-knife - -# Build -cargo build --release - -# Install (optional) -cargo install --path cfk-cli +just build # cargo build --workspace +just test # cargo test --workspace +just install # cargo install --locked --path src/cfk-cli ---- -=== Requirements - -- Rust 1.75+ (MSRV) -- Optional: libfuse3 (for FUSE mounting) - -== Quick Start - [source,bash] ---- -# List files -cfk ls /path/to/directory -cfk ls -l -H . # Long format with human-readable sizes - -# Display file contents -cfk cat /path/to/file.txt - -# Copy files -cfk cp source.txt dest.txt -cfk cp -f source.txt dest.txt # Force overwrite - -# Move/rename files -cfk mv old.txt new.txt - -# Remove files -cfk rm file.txt -cfk rm -r directory/ # Recursive - -# Create directories -cfk mkdir new_dir -cfk mkdir -p nested/path/dir # Create parents - -# Show file info -cfk stat file.txt - -# List registered backends -cfk backends - -# Show storage info +cfk ls -l -H . +cfk cp -f source.txt dest.txt +cfk rm -r old-dir/ +cfk history # what has been done +cfk undo # reverse the latest operation cfk df local ---- -== Tech Stack +=== Reversible journal -=== Languages [cols="1,3"] |=== -| Language | Purpose - -| *Rust* | Core engine, providers, cache, CLI -| *Ada/SPARK* | TUI with formal verification (planned) -| *Nickel* | Configuration language (planned) -|=== - -=== Storage Backends (20+) - -==== Currently Working -- Local filesystem (all OS) - -==== Planned for Future Releases -- *Network*: NFS, SMB/CIFS, SFTP, WebDAV, 9P -- *Cloud*: Dropbox, Google Drive, OneDrive, Box, S3 -- *Distributed*: IPFS, AFS, Syncthing -- *Exotic*: Gopher, Gemini, NNTP, BitTorrent, Matrix - -=== Serialization Formats -[cols="1,2"] -|=== -| Format | Use +| Variable | Meaning -| *JSON* | API responses, config -| *MessagePack* | Binary JSON alternative -| *CBOR* | Compact binary -| *Protocol Buffers* | gRPC, cross-language -| *TOML* | Config files +| `CFK_JOURNAL_DIR` | Journal location (default `$XDG_STATE_HOME/cfk/journal`, else `~/.local/state/cfk/journal`) +| `CFK_JOURNAL_MAX_BYTES` | Largest file captured for undo (default 1 GiB). Bigger operations are *refused*, not silently made irreversible +| `CFK_NO_JOURNAL=1` | Opt out (operations are then irreversible) |=== -=== Transport Layers -- TCP, QUIC (HTTP/3), UDP -- Unix sockets, Named pipes +== Repository map -=== Databases (Cache) -- sled (pure Rust embedded KV) -- SurrealDB (multi-model) -- LMDB (via heed) -- DragonflyDB (Redis-compatible) -- redb +The authoritative map is generated, so it cannot drift from the tree: +link:docs/architecture/REPOSITORY-MAP.adoc[docs/architecture/REPOSITORY-MAP.adoc] +(regenerate with `just repo-map`; CI fails if it is stale). -=== External Integrations -- aria2 (high-speed downloads) -- agrep (fuzzy search) -- pandoc (document conversion) -- tesseract (OCR) -- eza (modern ls) - -== Project Structure - -[source] ----- -czech-file-knife/ -├── cfk-core/ # Core traits, types, errors -├── cfk-providers/ # Storage backends + transports -├── cfk-cache/ # Caching layer (sled/SurrealDB/LMDB) -├── cfk-search/ # Full-text search (Tantivy) -├── cfk-vfs/ # FUSE virtual filesystem -├── cfk-cli/ # Rust CLI -├── cfk-tui/ # Ada/SPARK TUI (planned) -└── cfk-integrations/ # External tools (aria2, pandoc, etc.) ----- - -== Development - -[source,bash] ----- -# Run tests -cargo test --workspace +The short version: -# Build in release mode -cargo build --release - -# Run CLI directly -cargo run --bin cfk -- ls . - -# Check formatting -cargo fmt --check - -# Run lints -cargo clippy --workspace ----- - -== Platform Support - -[cols="1,1,1,1,1"] +[cols="1,3"] |=== -| Platform | CLI | FUSE | Cloud | Status - -| Linux x86_64 | Working | Planned | Planned | Primary -| Linux ARM64 | Working | Planned | Planned | Supported -| macOS | Working | Planned | Planned | Supported -| Windows | Working | Planned | Planned | WinFsp -| RISC-V | Untested | Untested | Untested | Experimental +| Path | What lives there + +| `README.adoc`, `CLAUDE.md` | Start here - humans and AI agents respectively. +| `src/cfk-*/`, `tests/` | The Cargo workspace crates and their tests (`tests/fuzz/` is cargo-fuzz). +| `docs/` | Human documentation, including the full map above. +| `.machine_readable/` | Manifests, contractiles and policies that tools read. +| `build/`, `Justfile` | Every task runs through `just`; phases live in `build/just/`. +| `ci/`, `.github/` | CI configuration; GitHub reads `.github/` and no other path. |=== -== Roadmap - -=== v0.1.0 (Current) -- Core architecture and traits -- CLI with basic file operations -- Local filesystem backend -- Unit tests - -=== v0.2.0 (Planned) -- S3/S3-compatible backend -- SFTP backend -- FUSE mounting -- Tantivy search integration +== Where to go next -=== v0.3.0 (Future) -- Cloud backends (Dropbox, Google Drive, OneDrive) -- WebDAV backend -- TUI implementation -- Caching layer backends +* link:docs/architecture/REPOSITORY-MAP.adoc[The repository map] — generated; what every directory is for. +* link:docs/EXPLAINME.adoc[EXPLAINME] — the engineering deep-dive: how the pieces actually work. +* link:docs/AFFIRMATION.adoc[AFFIRMATION] — the dated, signed honesty snapshot of the repo's true state. +* link:docs/AUDIT.adoc[AUDIT] — the release audit gate. +* link:docs/architecture/HYBRID-OPERATIONS.adoc[Hybrid operations] — reversible journal, provider-side cloud ops, in-place compression. +* link:docs/status/ROADMAP.adoc[Roadmap]. -== License +== Licence -PMPL-1.0-or-later OR Palimpsest-0.8 +Code, configuration and scripts are link:LICENSE[Mozilla Public License 2.0] +(`MPL-2.0`); prose documentation is `CC-BY-SA-4.0`. Both texts live in +`LICENSES/`, and per-file `SPDX-License-Identifier` headers are authoritative. +The GitHub-detected licence is MPL-2.0 (the root `LICENSE`). Long-term +attribution uses Quantum-Safe Provenance — see +link:docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt[the Quantum-Safe Provenance exhibit]. diff --git a/czech-file-knife/ROADMAP.adoc b/czech-file-knife/ROADMAP.adoc deleted file mode 100644 index 10beba746..000000000 --- a/czech-file-knife/ROADMAP.adoc +++ /dev/null @@ -1,171 +0,0 @@ -= Czech File Knife Roadmap -:toc: -:toclevels: 3 - -== Overview - -This roadmap outlines the development plan for Czech File Knife (CFK), the cloud-native unified interface for 20+ storage backends. - -== v1.0 - Production Release (Current) - -**Target:** 2026-02 -**Status:** Active development (v0.1.0 → v1.0) - -=== Core Storage Backends -* ✅ Local filesystem -* ✅ S3-compatible (AWS, MinIO, etc.) -* ⚠️ Azure Blob Storage (in progress) -* ⚠️ Google Cloud Storage (in progress) -* ⚠️ IPFS (in progress) - -=== Core Features -* ✅ Unified filesystem interface -* ✅ Virtual path abstraction -* ✅ Async I/O with Tokio -* ✅ Type-safe backend plugins -* ✅ Error handling with context - -=== Quality & Standards -* ✅ Comprehensive test suite -* ✅ ClusterFuzzLite fuzzing -* ✅ Criterion benchmarks -* ✅ CI/CD pipeline -* ✅ Documentation - -=== Remaining for v1.0 -* [ ] Complete ROADMAP.adoc (this file) -* [ ] Add LICENSE file -* [ ] Complete Azure/GCS backends -* [ ] IPFS integration -* [ ] Performance optimization -* [ ] Security audit - -== v1.1 - Additional Backends (Next) - -**Target:** 2026-03 - -=== New Storage Backends -* Dropbox -* OneDrive -* Box -* Backblaze B2 -* Wasabi -* SFTP/SCP -* WebDAV -* FTP/FTPS - -=== Enhanced Features -* Transparent compression -* Client-side encryption -* Caching layer -* Batch operations -* Parallel transfers - -== v1.2 - Advanced Operations - -**Target:** 2026-04 - -=== Distributed Features -* Multi-backend mirroring -* Automatic failover -* Load balancing -* Geo-replication -* Consistency guarantees - -=== Performance -* Zero-copy operations -* Memory-mapped I/O -* Streaming for large files -* Connection pooling -* Smart prefetching - -=== Developer Experience -* CLI tool for manual operations -* FUSE filesystem mount -* REST API -* GraphQL interface -* Language bindings (Python, JS, Go) - -== v2.0 - Cloud-Native Architecture - -**Target:** 2026-Q3 - -=== Kubernetes Integration -* CSI driver for Kubernetes -* Operator for CRD management -* Dynamic provisioning -* Snapshot support -* Volume cloning - -=== Observability -* OpenTelemetry integration -* Prometheus metrics -* Distributed tracing -* Structured logging -* Health checks - -=== Security -* IAM integration -* RBAC -* Audit logging -* Secret management -* mTLS support - -== v2.5 - Intelligent Storage - -**Target:** 2026-Q4 - -=== AI-Powered Features -* Automatic backend selection -* Cost optimization -* Intelligent caching -* Predictive prefetching -* Anomaly detection - -=== Policy Engine -* Retention policies -* Lifecycle management -* Compliance rules -* Data classification -* Automated archival - -== v3.0 - Universal Storage Platform - -**Target:** 2027 - -=== Ecosystem -* Plugin marketplace -* Custom backend development SDK -* Integration templates -* Best practices library -* Community backends - -=== Enterprise Features -* Multi-tenancy -* Quota management -* Billing integration -* SLA monitoring -* Disaster recovery - -=== Standards -* Open storage protocol -* Interoperability guarantees -* Vendor-neutral API -* Format migration tools - -== Long-term Vision - -* Industry-standard storage abstraction -* Zero vendor lock-in -* Seamless backend migration -* Cost-optimal storage placement -* Self-healing storage infrastructure -* Integration with all major cloud providers - -== Contributing - -See link:../.github/CONTRIBUTING.md[Contributing Guidelines] for how to contribute to Czech File Knife development. - -== Versioning - -Czech File Knife follows semantic versioning (SemVer). Breaking changes will only be introduced in major version releases. diff --git a/czech-file-knife/RSR_OUTLINE.adoc b/czech-file-knife/RSR_OUTLINE.adoc deleted file mode 100644 index d43ee2dc6..000000000 --- a/czech-file-knife/RSR_OUTLINE.adoc +++ /dev/null @@ -1,218 +0,0 @@ -= RSR Template Repository - -image:[Palimpsest-MPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] image:[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] -:toc: -:sectnums: - -// Badges -image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] -image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] -image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] - -== Overview - -**The canonical template for RSR (Rhodium Standard Repository) projects.** - -This repository provides the standardized structure, configuration, and tooling for all 139 repos in the hyperpolymath ecosystem. Use it to: - -* Bootstrap new projects with RSR compliance -* Reference the standard directory structure -* Copy configuration templates (Justfile, STATE.scm, etc.) - -== Quick Start - -[source,bash] ----- -# Clone the template -git clone https://github.com/hyperpolymath/RSR-template-repo my-project -cd my-project - -# Remove template git history -rm -rf .git -git init - -# Customize -sed -i 's/RSR-template-repo/my-project/g' Justfile guix.scm README.adoc - -# Enter development environment -guix shell -D -f build/guix.scm - -# Validate compliance -just validate-rsr ----- - -== What's Included - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|`.editorconfig` -|Editor configuration (indent, charset) - -|`.gitignore` -|Standard ignore patterns - -|`.guix-channel` -|Guix channel definition - -|`.well-known/` -|RFC-compliant metadata (security.txt, ai.txt, humans.txt) - -|`docs/` -|Documentation directory - -|`guix.scm` -|Guix package definition - -|`justfile` -|Task runner with 50+ recipes - -|`LICENSE.txt` -|AGPL + Palimpsest dual license - -|`README.adoc` -|This file - -|`RSR_COMPLIANCE.adoc` -|Compliance tracking - -|`STATE.scm` -|Project state checkpoint -|=== - -== Justfile Features - -The template Justfile provides: - -* **~10 billion recipe combinations** via matrix recipes -* **Cookbook generation**: `just cookbook` → `docs/just-cookbook.adoc` -* **Man page generation**: `just man` → `docs/man/project.1` -* **RSR validation**: `just validate-rsr` -* **STATE.scm management**: `just state-touch`, `just state-phase` -* **Container support**: `just container-build`, `just container-push` -* **CI matrix**: `just ci-matrix [stage] [depth]` - -=== Key Recipes - -[source,bash] ----- -just # Show all recipes -just help # Detailed help -just info # Project info -just combinations # Show matrix options - -just build # Build (debug) -just test # Run tests -just quality # Format + lint + test -just ci # Full CI pipeline - -just validate # RSR + STATE validation -just docs # Generate all docs -just cookbook # Generate Justfile docs - -just guix-shell # Guix dev environment -just container-build # Build container ----- - -== Directory Structure - -[source] ----- -project/ -├── .editorconfig # Editor settings -├── .gitignore # Git ignore -├── .guix-channel # Guix channel -├── .well-known/ # RFC metadata -│ ├── ai.txt -│ ├── humans.txt -│ └── security.txt -├── config/ # Nickel configs (optional) -├── docs/ # Documentation -│ ├── generated/ -│ ├── man/ -│ └── just-cookbook.adoc -├── guix.scm # Guix package -├── Justfile # Task runner -├── LICENSE.txt # Dual license -├── README.adoc # Overview -├── RSR_COMPLIANCE.adoc # Compliance -├── src/ # Source code -├── STATE.scm # State checkpoint -└── tests/ # Tests ----- - -== RSR Compliance - -=== Language Tiers - -* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, ReScript -* **Tier 2** (Silver): Nickel, Racket, Guile Scheme, Nix -* **Infrastructure**: Guix channels, derivations - -=== Required Files - -* `.editorconfig` -* `.gitignore` -* `justfile` -* `README.adoc` -* `RSR_COMPLIANCE.adoc` -* `LICENSE.txt` (AGPL + Palimpsest) -* `.well-known/security.txt` -* `.well-known/ai.txt` -* `.well-known/humans.txt` -* `guix.scm` OR `flake.nix` - -=== Prohibited - -* Python outside `salt/` directory -* TypeScript/JavaScript (use ReScript) -* CUE (use Guile/Nickel) -* `Dockerfile` (use `Containerfile`) - -== STATE.scm - -The STATE.scm file tracks project state: - -[source,scheme] ----- -(define state - `((metadata - (project . "my-project") - (updated . "2025-12-10")) - (position - (phase . implementation) ; design|implementation|testing|maintenance|archived - (maturity . beta)) ; experimental|alpha|beta|production|lts - (ecosystem - (part-of . ("RSR Framework")) - (depends-on . ())))) ----- - -== Badge Schema - -Generate badges from STATE.scm: - -[source,bash] ----- -just badges standard ----- - -See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. - -== Ecosystem Integration - -This template is part of: - -* **STATE.scm Ecosystem**: Conversation checkpoints -* **RSR Framework**: Repository standards -* **Consent-Aware-HTTP**: .well-known compliance - -== License - -SPDX-License-Identifier: CC-BY-SA-4.0 - -== Links - -* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE.scm tooling -* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement -* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/czech-file-knife/SECURITY.adoc b/czech-file-knife/SECURITY.adoc deleted file mode 100644 index abb6fbb55..000000000 --- a/czech-file-knife/SECURITY.adoc +++ /dev/null @@ -1,24 +0,0 @@ -== Security Policy - -=== Reporting a Vulnerability - -If you discover a security vulnerability, please report it responsibly: - -[arabic] -. *Do not* open a public issue -. Email security concerns to the maintainer -. Include steps to reproduce the vulnerability -. Allow reasonable time for a fix before disclosure - -=== Supported Versions - -[cols=",",options="header",] -|=== -|Version |Supported -|latest |:white_check_mark: -|< latest |Best effort -|=== - -=== Security Updates - -Security patches are released as soon as possible after verification. diff --git a/czech-file-knife/TESTING-REPORT.adoc b/czech-file-knife/TESTING-REPORT.adoc deleted file mode 100644 index 3a8e4e907..000000000 --- a/czech-file-knife/TESTING-REPORT.adoc +++ /dev/null @@ -1,84 +0,0 @@ -= Testing Report: czech-file-knife -:author: Claude Code Automated Testing -:date: 2025-12-29 -:toc: - -== Summary - -[cols="1,3"] -|=== -| Status | *PASS* -| Build | Success (release) -| Tests | 8/8 passed -| Execution | All commands work correctly -|=== - -== Build Results - -=== Command -[source,bash] ----- -cargo build --release ----- - -=== Outcome -Build completed successfully in ~67 seconds. - -=== Warnings (non-blocking) -* `cfk-providers`: 2 warnings - unexpected cfg condition `feature = "ceph"` -* `cfk-cache`: 2 warnings - unused imports -* `cfk-ios`: 3 warnings - unused imports/variables - -== Test Results - -All 8 unit tests pass across all crates. - -== Functional Testing - -[cols="1,1,2"] -|=== -| Command | Status | Notes - -| `cfk --help` | PASS | Shows all available commands -| `cfk backends` | PASS | Lists local backend as available -| `cfk ls .` | PASS | Lists directory contents correctly -| `cfk cat FILE` | PASS | Displays file contents -| `cfk stat FILE` | PASS | Shows file metadata with cfk:// URI -| `cfk mkdir DIR` | PASS | Creates directories -| `cfk cp SRC DST` | PASS | Copies files correctly -| `cfk mv SRC DST` | PASS | Moves/renames files correctly -| `cfk rm FILE` | PASS | Removes files/directories -| `cfk df local` | PASS | Shows disk space (after fix) -|=== - -== Fixes Applied - -=== 1. Implemented `get_space_info` for local backend - -*File:* `cfk-providers/src/local.rs` - -*Problem:* `cfk df local` returned "Space information not available" - -*Solution:* Implemented Unix `statvfs` call to get actual disk space: - -[source,rust] ----- -#[cfg(unix)] -{ - use std::ffi::CString; - use std::mem::MaybeUninit; - // ... statvfs implementation -} ----- - -*Dependency added:* `libc.workspace = true` in `cfk-providers/Cargo.toml` - -== Recommendations - -=== Minor (optional) -1. Add `ceph` feature to `cfk-providers/Cargo.toml` or remove cfg attribute -2. Run `cargo fix` to clean up unused imports in cfk-cache, cfk-ios - -== Conclusion - -Project fully functional. All core file operations work correctly on local filesystem. diff --git a/czech-file-knife/TESTING-REPORT.scm b/czech-file-knife/TESTING-REPORT.scm deleted file mode 100644 index 137822764..000000000 --- a/czech-file-knife/TESTING-REPORT.scm +++ /dev/null @@ -1,65 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; Testing Report for czech-file-knife -;; Generated: 2025-12-29 - -(define testing-report - '((project . "czech-file-knife") - (date . "2025-12-29") - (author . "Claude Code Automated Testing") - - (summary - (status . pass) - (build . success) - (tests-passed . 8) - (tests-failed . 0) - (execution . "all-commands-functional")) - - (build-results - (command . "cargo build --release") - (duration-seconds . 67) - (outcome . success) - (warnings - ((crate . "cfk-providers") - (count . 2) - (type . "unexpected-cfg-condition")) - ((crate . "cfk-cache") - (count . 2) - (type . "unused-imports")) - ((crate . "cfk-ios") - (count . 3) - (type . "unused-imports-variables")))) - - (test-results - (command . "cargo test") - (total . 8) - (passed . 8) - (failed . 0)) - - (functional-tests - ((command . "cfk --help") (status . pass)) - ((command . "cfk backends") (status . pass)) - ((command . "cfk ls") (status . pass)) - ((command . "cfk cat") (status . pass)) - ((command . "cfk stat") (status . pass)) - ((command . "cfk mkdir") (status . pass)) - ((command . "cfk cp") (status . pass)) - ((command . "cfk mv") (status . pass)) - ((command . "cfk rm") (status . pass)) - ((command . "cfk df local") (status . pass) (note . "fixed"))) - - (fixes-applied - ((id . 1) - (file . "cfk-providers/src/local.rs") - (problem . "get_space_info returned unknown") - (solution . "Implemented Unix statvfs call") - (dependency-added . "libc"))) - - (recommendations - ((priority . minor) - (items - ("Add ceph feature to Cargo.toml or remove cfg attribute") - ("Run cargo fix to clean up unused imports")))))) - -;; Helper to check if tests passed -(define (tests-passed? report) - (eq? (assoc-ref (assoc-ref report 'summary) 'status) 'pass)) diff --git a/czech-file-knife/benches/template_bench.sh b/czech-file-knife/benches/template_bench.sh new file mode 100755 index 000000000..5fb58a88d --- /dev/null +++ b/czech-file-knife/benches/template_bench.sh @@ -0,0 +1,227 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Template Benchmarks +# Measures performance characteristics of template validation and build system + +set -euo pipefail + +REPO_ROOT="${1:-.}" +OUTPUT_FORMAT="${2:-human}" # human | json | csv + +# ANSI colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}→${NC} $*" +} + +log_pass() { + echo -e "${GREEN}✓${NC} $*" +} + +# Ensure we have required commands +command -v /usr/bin/time >/dev/null 2>&1 || { + echo "Warning: /usr/bin/time not available, using built-in time" + TIME_CMD="time" +} + +TIME_CMD="/usr/bin/time -f %e" 2>/dev/null || TIME_CMD="time" + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "Template Benchmarks" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" + +declare -A results + +#============================================================================== +# BENCHMARK 1: Template Validation +#============================================================================== + +log_info "Running template validation benchmark" + +# Warm-up run +if [ -f "$REPO_ROOT/scripts/validate-template.sh" ]; then + bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true +fi + +# Timed runs +BENCH_RUNS=3 +TOTAL_TIME=0 + +for i in $(seq 1 $BENCH_RUNS); do + START=$(date +%s%N) + bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true + END=$(date +%s%N) + + # Convert to milliseconds + RUN_TIME=$(( (END - START) / 1000000 )) + TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) + + [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" +done + +AVG_VALIDATION_TIME=$(( TOTAL_TIME / BENCH_RUNS )) +results[validation]=$AVG_VALIDATION_TIME +log_pass "Validation: ${AVG_VALIDATION_TIME}ms average (${BENCH_RUNS} runs)" + +#============================================================================== +# BENCHMARK 2: Zig Build +#============================================================================== + +log_info "Running Zig build benchmark" + +if ! command -v zig &> /dev/null; then + echo " ⚠ Zig compiler not found - skipping Zig build benchmark" + results[zig_build]="skipped" +else + cd "$REPO_ROOT/src/interface/ffi" + + # Warm-up + zig build --summary off > /dev/null 2>&1 || true + + # Clean build + BENCH_RUNS=2 + TOTAL_TIME=0 + + for i in $(seq 1 $BENCH_RUNS); do + rm -rf zig-cache + + START=$(date +%s%N) + zig build --summary off > /dev/null 2>&1 || true + END=$(date +%s%N) + + RUN_TIME=$(( (END - START) / 1000000 )) + TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) + + [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" + done + + AVG_BUILD_TIME=$(( TOTAL_TIME / BENCH_RUNS )) + results[zig_build]=$AVG_BUILD_TIME + log_pass "Zig build: ${AVG_BUILD_TIME}ms average (clean build, ${BENCH_RUNS} runs)" + + cd - > /dev/null +fi + +#============================================================================== +# BENCHMARK 3: Zig Tests +#============================================================================== + +log_info "Running Zig test benchmark" + +if ! command -v zig &> /dev/null; then + echo " ⚠ Zig compiler not found - skipping Zig test benchmark" + results[zig_test]="skipped" +else + cd "$REPO_ROOT/src/interface/ffi" + + # Warm-up + zig build test --summary off > /dev/null 2>&1 || true + + START=$(date +%s%N) + TEST_OUTPUT=$(zig build test --summary off 2>&1 || true) + END=$(date +%s%N) + + TEST_TIME=$(( (END - START) / 1000000 )) + results[zig_test]=$TEST_TIME + log_pass "Zig tests: ${TEST_TIME}ms" + + # Count tests + TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -c "^test " || echo "unknown") + echo " Test count: $TEST_COUNT" + + cd - > /dev/null +fi + +#============================================================================== +# BENCHMARK 4: Workflow Validation +#============================================================================== + +log_info "Running workflow validation benchmark" + +if [ -f "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" ]; then + START=$(date +%s%N) + bash "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" "$REPO_ROOT/.github/workflows" > /dev/null 2>&1 || true + END=$(date +%s%N) + + WORKFLOW_TIME=$(( (END - START) / 1000000 )) + results[workflow_validation]=$WORKFLOW_TIME + log_pass "Workflow validation: ${WORKFLOW_TIME}ms" +fi + +#============================================================================== +# BENCHMARK 5: Template Instantiation +#============================================================================== + +log_info "Running template instantiation benchmark" + +if [ -f "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" ]; then + START=$(date +%s%N) + bash "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" "$REPO_ROOT" > /dev/null 2>&1 || true + END=$(date +%s%N) + + INSTANTIATION_TIME=$(( (END - START) / 1000000 )) + results[instantiation]=$INSTANTIATION_TIME + log_pass "Template instantiation: ${INSTANTIATION_TIME}ms" +fi + +#============================================================================== +# SUMMARY +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "BENCHMARK RESULTS" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" + +if [ "$OUTPUT_FORMAT" = "json" ]; then + echo "{" + echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," + echo " \"repo\": \"$REPO_ROOT\"," + echo " \"results\": {" + + count=0 + for key in "${!results[@]}"; do + value="${results[$key]}" + [ $count -gt 0 ] && echo "," + if [ "$value" = "skipped" ]; then + echo -n " \"$key\": \"skipped\"" + else + echo -n " \"$key\": $value" + fi + count=$((count + 1)) + done + echo "" + echo " }" + echo "}" +elif [ "$OUTPUT_FORMAT" = "csv" ]; then + echo "metric,value_ms,timestamp" + for key in "${!results[@]}"; do + value="${results[$key]}" + if [ "$value" != "skipped" ]; then + echo "$key,$value,$(date -u +%Y-%m-%dT%H:%M:%SZ)" + fi + done +else + # Human-readable format + for key in "${!results[@]}"; do + value="${results[$key]}" + if [ "$value" = "skipped" ]; then + printf " %-30s %s\n" "$key:" "SKIPPED" + else + printf " %-30s %5d ms\n" "$key:" "$value" + fi + done +fi + +echo "" +echo "Benchmark complete." +echo "" diff --git a/czech-file-knife/build/container/.containerignore b/czech-file-knife/build/container/.containerignore new file mode 100644 index 000000000..a4d651995 --- /dev/null +++ b/czech-file-knife/build/container/.containerignore @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Build-context exclusions for the OCI image build (Tier A). +# Honoured by podman, nerdctl and docker (all read .containerignore; +# docker also reads .dockerignore — this file is the portable name). +# +# Keeping the context lean speeds builds and prevents secrets, VCS +# history and local cruft from leaking into image layers. + +# Version control +.git/ +.gitignore +.gitattributes + +# CI / forge metadata +.github/ +ci/ + +# Editor / agent / local state +.claude/ +.devcontainer/ +.editorconfig +.envrc +.direnv/ +*.swp +*~ + +# Build outputs and caches +build/ttc/ +target/ +_build/ +deps/ +zig-out/ +zig-cache/ +node_modules/ +dist/ +result/ + +# Docs, tests, examples (not needed in the runtime image) +docs/ +build/docs-seed/ +benches/ +examples/ +tests/ + +# The container tooling itself (don't recurse the build files into the image) +build/container/stapeln/ +build/container/compose*.yaml +build/container/compose*.toml +build/container/.containerignore +build/container/README.adoc + +# Secrets and environment files — never ship these +*.env +.env +.env.* +*.pem +*.key +secrets/ diff --git a/czech-file-knife/build/container/Containerfile b/czech-file-knife/build/container/Containerfile new file mode 100644 index 000000000..1a475f4f2 --- /dev/null +++ b/czech-file-knife/build/container/Containerfile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Czech File Knife — CLI container. `cfk` is a command-line tool, not a +# service: no port, no healthcheck. Mount the storage you want to work on at +# /data; the reversible-operation journal persists in /state. +# +# Build: podman build -f build/container/Containerfile -t czech-file-knife . +# Run: podman run --rm -v "$PWD:/data" -v cfk-state:/state czech-file-knife ls /data + +# ── builder ──────────────────────────────────────────────────────────────── +FROM cgr.dev/chainguard/wolfi-base:latest AS builder +RUN apk add --no-cache build-base rust pkgconf fuse3-dev +WORKDIR /build +COPY Cargo.toml Cargo.lock ./ +COPY src/ src/ +COPY benches/ benches/ +COPY tests/ tests/ +RUN cargo build --release --locked -p cfk-cli + +# ── runtime ──────────────────────────────────────────────────────────────── +FROM cgr.dev/chainguard/wolfi-base:latest +LABEL org.opencontainers.image.title="Czech File Knife" \ + org.opencontainers.image.description="The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." \ + org.opencontainers.image.url="https://github.com/hyperpolymath/czech-file-knife" \ + org.opencontainers.image.source="https://github.com/hyperpolymath/czech-file-knife" \ + org.opencontainers.image.vendor="hyperpolymath" \ + org.opencontainers.image.licenses="MPL-2.0" \ + org.opencontainers.image.authors="Jonathan D.A. Jewell " \ + dev.cerrotorre.manifest="build/container/stapeln/manifest.toml" \ + dev.cerrotorre.gatekeeper="build/container/stapeln/.gatekeeper.yaml" \ + dev.stapeln.compose="build/container/stapeln/compose.toml" +RUN apk add --no-cache ca-certificates fuse3 +RUN addgroup -S cfk && adduser -S cfk -G cfk \ + && mkdir -p /data /state && chown cfk:cfk /data /state +COPY --from=builder /build/target/release/cfk /usr/local/bin/cfk +ENV CFK_JOURNAL_DIR=/state/journal +VOLUME ["/data", "/state"] +USER cfk +WORKDIR /data +ENTRYPOINT ["/usr/local/bin/cfk"] diff --git a/czech-file-knife/build/container/README.adoc b/czech-file-knife/build/container/README.adoc new file mode 100644 index 000000000..781b0d482 --- /dev/null +++ b/czech-file-knife/build/container/README.adoc @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Czech File Knife Containerisation +:toc: left +:toclevels: 3 +:sectnums: + +== Overview + +Containerisation in this template is organised in *three tiers*, from the most +portable to the most sovereign. You can use only the tier you need and ignore — +or delete — the rest. + +[cols="1,2,4"] +|=== +| Tier | What | Why + +| *A — OCI standard* +| `Containerfile`, `.containerignore` +| A plain, multi-stage OCI image on https://www.chainguard.dev/[Chainguard] + Wolfi. Builds with any OCI engine. No dependency on tiers B or C. + +| *B — Portable engine* +| `compose.yaml`, `compose.example.yaml` +| A https://compose-spec.io[compose-spec] stack that runs unchanged on + `podman`, `nerdctl` or `docker`. For everyday local and simple production use. + +| *C — stapeln (sovereign)* +| `stapeln/` directory +| Verified `.ctp` bundles, zero-copy IPC, edge-gateway trust policy, secrets + gating, runtime monitoring and signed deployment — the full + https://github.com/hyperpolymath/stapeln[stapeln] stack. +|=== + +All files use `{{PLACEHOLDER}}` tokens, replaced by `just container-init` +(or the top-level `just repo-init` during project bootstrap). + +The container engine is *auto-detected* (`podman`, then `nerdctl`, then +`docker`). Override with `CONTAINER_ENGINE=docker just container-build`. + +== File Reference + +=== Tier A + B (this directory) + +[cols="1,3"] +|=== +| File | Purpose + +| `Containerfile` +| Multi-stage OCI build. Stage 1 builds the app; Stage 2 is a minimal Wolfi + runtime image. Engine-agnostic. Carries the stapeln files only as labels, + so it has no hard dependency on tier C. + +| `.containerignore` +| Build-context exclusions (honoured by podman/nerdctl/docker). Keeps `.git`, + secrets, docs, tests and build caches out of image layers. + +| `compose.yaml` +| Portable compose-spec stack: app + `rokur` + `svalinn`. Use with + `just container-up` or `podman compose -f build/container/compose.yaml up -d`. + +| `compose.example.yaml` +| Concrete multi-service example (Rust API + Elixir worker + gate + gateway). + Copy to `compose.yaml` and customise. + +| `entrypoint.sh` +| Container entrypoint: signal handling (SIGTERM/SIGINT), startup logging, + `exec` into the main process. +|=== + +=== Tier C — `stapeln/` + +[cols="1,3"] +|=== +| File | Purpose + +| `compose.toml` +| *selur-compose* stack definition (TOML, selur-native — not parseable by + `podman compose`; use `compose.yaml` for that). Declares services, volumes, + the selur zero-copy network, and health checks. + +| `compose.example.toml` +| Concrete multi-service selur example. + +| `manifest.toml` +| *cerro-torre* `.ctp` bundle metadata: provenance, dependencies, + attestations, and the runtime security profile. Used by `ct pack` / `ct verify`. + +| `.gatekeeper.yaml` +| *svalinn* edge-gateway policy: authentication, rate limiting, container + trust, request validation, CORS, audit logging. + +| `rokur.toml` +| *rokur* secrets-gate configuration: required secrets, listener/backend, + rate limiting, policy engine, and audit log. + +| `vordr.toml` +| *vordr* runtime monitoring: health/readiness probes (app, rokur, svalinn), + crash detection, resource thresholds, structured logs. + +| `ct-build.sh` +| *cerro-torre* build → pack → sign (Ed25519) → verify → push pipeline. + Engine-agnostic; degrades gracefully when cerro-torre tools are absent. + +| `deploy.k9.ncl` +| *k9-svc* deployment component at Hunt trust level: full pedigree (L1–L5), + dev/staging/prod configs, rolling deployment strategy. +|=== + +== The stapeln ecosystem + +The sovereign tier wires together the stapeln container stack: + +*cerro-torre* (build + sign):: + Produces signed, minimal `.ctp` bundles from OCI images. Tools: `ct pack`, + `ct sign`, `ct verify`, `ct push`, `ct explain`. + +*selur* (compose + IPC):: + Orchestration with zero-copy IPC for co-located services. Reads + `stapeln/compose.toml`. + +*rokur* (secrets gate):: + A service that fronts the application and refuses to pass traffic unless + the declared secrets are present; rate-limits per client and writes a + structured audit log. Configured by `stapeln/rokur.toml`. In the stack it + sits between `svalinn` and the app. + +*svalinn* (edge gateway):: + Policy-driven reverse proxy: TLS termination, authentication, rate limiting, + CORS and container trust, from `stapeln/.gatekeeper.yaml`. + +*vordr* (monitoring):: + Runtime monitoring: health probes, crash detection, resource tracking, + structured logs, from `stapeln/vordr.toml`. + +*k9-svc* (deployment):: + Nickel-based deployment components with pedigree and trust levels + (Kennel / Yard / Hunt). + +The request path in the full stack is: +`svalinn` (edge) → `rokur` (secrets gate) → `czech-file-knife` (app). + +== Initialise + +[source,bash] +---- +just repo-init # full project bootstrap (all placeholders) +# or +just container-init # container-only: prompts for service/port/registry +---- + +== Everyday workflow (tiers A + B) + +[source,bash] +---- +just container-build # build image (auto-detected engine) +just container-verify # validate the compose config +just container-up -d # start the stack +just container-down # stop the stack + +# Explicit engine: +CONTAINER_ENGINE=docker just container-build +---- + +`just container-up` uses `selur-compose` automatically when it is installed, +otherwise it falls back to ` compose -f build/container/compose.yaml`. + +== Sovereign workflow (tier C) + +[source,bash] +---- +just container-sign # build → pack → sign → verify (.ctp) +just container-push # push the signed bundle +cd build/container/stapeln && selur-compose up --detach +---- + +k9-svc managed deployment: + +[source,bash] +---- +nickel typecheck build/container/stapeln/deploy.k9.ncl +k9-svc deploy build/container/stapeln/deploy.k9.ncl --env production +---- + +== Continuous integration + +A `container build` workflow ships in `.github/workflows/container-build.yml`. +It is **off by default** — it costs nothing in a repository created from this +template until you opt in. + +To enable it, set a repository (or organisation) variable: + +[cols="1,3"] +|=== +| Variable | Effect + +| `CONTAINER_CI=true` +| Enables the workflow. It builds the image (`just container-build`), verifies + the compose config, runs a best-effort `trivy` scan, and on `v*` tags signs + the `.ctp` bundle (`just container-sign`). + +| `CONTAINER_RUNNER` +| Optional JSON array of runner labels. Defaults to + `["self-hosted","owned","container"]` — owned compute, so **no metered + GitHub Actions minutes**. Set e.g. `["ubuntu-latest"]` to use GitHub-hosted + runners instead. +|=== + +[source,bash] +---- +gh variable set CONTAINER_CI --body true +# optional: gh variable set CONTAINER_RUNNER --body '["ubuntu-latest"]' +---- + +The owned runner is expected to have `just` and a container engine installed; +`trivy` and cerro-torre (`ct`/`cerro-sign`) are used when present and skipped +otherwise. + +== Removing containerisation + +If your project does not need containers (e.g. a pure library), strip the whole +apparatus in one command: + +[source,bash] +---- +just no-container +---- + +This removes `build/container/`, `.devcontainer/`, the `build/just/container.just` +module and its Justfile import. Review `.github/workflows/` afterwards for any +image build/publish jobs you no longer need. + +== Base images + +* Builder: `cgr.dev/chainguard/wolfi-base:latest` +* Runtime: `cgr.dev/chainguard/wolfi-base:latest` (or + `cgr.dev/chainguard/static:latest` for static binaries) + +Chainguard images are minimal, low-CVE, and rebuilt daily (`apk` package +manager, Alpine-compatible). + +== Container runtime + +Podman is recommended, but the OCI Containerfile and compose-spec files work +unchanged with `nerdctl` and `docker`. Set `CONTAINER_ENGINE` to choose. diff --git a/czech-file-knife/build/container/compose.example.yaml b/czech-file-knife/build/container/compose.example.yaml new file mode 100644 index 000000000..cbd43aad5 --- /dev/null +++ b/czech-file-knife/build/container/compose.example.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Example portable compose stack (Tier B) — multi-service +# +# A concrete, fully-commented example: a Rust API + Elixir worker behind the +# rokur secrets gate and the svalinn edge gateway. Copy to compose.yaml and +# customise. Works with podman / nerdctl / docker compose unchanged. +# +# cp build/container/compose.example.yaml build/container/compose.yaml +# # edit service names, ports, images +# just container-up # or: podman compose -f build/container/compose.yaml up -d +# +# For the sovereign selur path see build/container/stapeln/compose.example.toml. + +services: + # Rust API — primary HTTP/gRPC backend. + rust-api: + build: + context: .. + dockerfile: build/container/Containerfile + image: ghcr.io/hyperpolymath/myproject-api:latest + ports: + - "8080:8080" + environment: + RUST_LOG: "info" + APP_HOST: "[::]" + APP_PORT: "8080" + APP_LOG_FORMAT: "json" + APP_DATA_DIR: "/data" + volumes: + - api-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + + # Elixir worker — background processing, talks to the API over the network. + elixir-worker: + image: ghcr.io/hyperpolymath/myproject-worker:latest + ports: + - "4000:4000" + environment: + API_URL: "http://rust-api:8080/api/v1" + MIX_ENV: "prod" + APP_LOG_FORMAT: "json" + POOL_SIZE: "10" + depends_on: + rust-api: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:4000/health"] + interval: 30s + timeout: 5s + retries: 3 + + # rokur — secrets gate. Refuses to come up unless required secrets are present. + rokur: + image: ghcr.io/hyperpolymath/rokur:latest + ports: + - "8081:8081" + environment: + ROKUR_BACKEND: "http://rust-api:8080" + ROKUR_LISTEN: "[::]:8081" + ROKUR_REQUIRED_SECRETS: "DATABASE_URL,JWT_SIGNING_KEY" + ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" + ROKUR_LOG_FORMAT: "json" + volumes: + - rokur-audit:/var/log/rokur + depends_on: + rust-api: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] + interval: 30s + timeout: 5s + retries: 3 + + # svalinn — edge gateway. TLS, auth, rate limiting, audit. The only + # externally exposed service. + svalinn: + image: ghcr.io/hyperpolymath/svalinn:latest + ports: + - "443:443" + - "80:80" + environment: + SVALINN_BACKEND: "http://rokur:8081" + SVALINN_WORKER_BACKEND: "http://elixir-worker:4000" + SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" + SVALINN_TLS_AUTO: "true" + volumes: + - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro + depends_on: + - rokur + - elixir-worker + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:80/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + api-data: + rokur-audit: diff --git a/czech-file-knife/build/container/compose.yaml b/czech-file-knife/build/container/compose.yaml new file mode 100644 index 000000000..5170ee0ec --- /dev/null +++ b/czech-file-knife/build/container/compose.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Czech File Knife — portable compose stack (Tier B) +# +# This is the runtime-agnostic, OCI compose-spec file. It works unchanged with: +# podman compose -f build/container/compose.yaml up -d +# nerdctl compose -f build/container/compose.yaml up -d +# docker compose -f build/container/compose.yaml up -d +# +# It has NO dependency on the stapeln tier. For the sovereign path +# (selur zero-copy IPC, signed .ctp bundles, svalinn trust policy) use +# build/container/stapeln/compose.toml with `selur-compose` instead. +# +# The Justfile picks the right one automatically: +# just container-up # selur-compose if installed, else this file +# +# See https://compose-spec.io for the full specification. + +services: + # ────────────────────────────────────────────────────────────────────── + # Primary application service + # ────────────────────────────────────────────────────────────────────── + "czech-file-knife": + build: + context: .. + dockerfile: build/container/Containerfile + image: "ghcr.io/hyperpolymath/czech-file-knife:latest" + ports: + - "8080:8080" + environment: + APP_HOST: "[::]" + APP_PORT: "8080" + APP_LOG_FORMAT: "json" + APP_DATA_DIR: "/data" + volumes: + - "czech-file-knife-data:/data" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + # ────────────────────────────────────────────────────────────────────── + # rokur — secrets-management gate (stapeln). Fronts the app and refuses + # to start the stack unless required secrets are present. on Wolfi. + # Remove this service if you do not use secret gating. + # ────────────────────────────────────────────────────────────────────── + rokur: + image: ghcr.io/hyperpolymath/rokur:latest + environment: + ROKUR_BACKEND: "http://czech-file-knife:8080" + ROKUR_LISTEN: "[::]:8081" + ROKUR_REQUIRED_SECRETS: "" # comma-separated env names that MUST be set + ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" + ROKUR_LOG_FORMAT: "json" + ports: + - "8081:8081" + volumes: + - rokur-audit:/var/log/rokur + depends_on: + "czech-file-knife": + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] + interval: 30s + timeout: 5s + retries: 3 + + # ────────────────────────────────────────────────────────────────────── + # svalinn — edge gateway (stapeln). TLS termination, auth, rate limiting. + # Reads the gatekeeper policy from build/container/stapeln/.gatekeeper.yaml. + # Remove this service for a bare app with no gateway. + # ────────────────────────────────────────────────────────────────────── + svalinn: + image: ghcr.io/hyperpolymath/svalinn:latest + ports: + - "443:443" + - "80:80" + environment: + SVALINN_BACKEND: "http://rokur:8081" + SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" + SVALINN_TLS_AUTO: "true" + volumes: + - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro + depends_on: + - rokur + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:80/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + "czech-file-knife-data": + rokur-audit: diff --git a/czech-file-knife/build/container/entrypoint.sh b/czech-file-knife/build/container/entrypoint.sh new file mode 100755 index 000000000..1e7bedec1 --- /dev/null +++ b/czech-file-knife/build/container/entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# SPDX-License-Identifier: MPL-2.0 +# Czech File Knife container entrypoint +# +# Handles signal propagation, startup logging, and health check +# preparation before exec-ing into the main application process. + +set -e + +# --------------------------------------------------------------------------- +# Signal handling +# --------------------------------------------------------------------------- +# +# Trap SIGTERM and SIGINT so that the application can shut down gracefully +# when Podman sends stop signals (e.g. `podman stop`, `selur-compose down`). + +cleanup() { + echo "Received shutdown signal — stopping czech-file-knife..." + # If the main process is backgrounded, kill it here: + # kill "$MAIN_PID" 2>/dev/null || true + # wait "$MAIN_PID" 2>/dev/null || true + exit 0 +} +trap cleanup TERM INT + +# --------------------------------------------------------------------------- +# Startup logging +# --------------------------------------------------------------------------- + +echo "Starting czech-file-knife..." +echo " Host: ${APP_HOST:-[::]}" +echo " Port: ${APP_PORT:-8080}" +echo " Data: ${APP_DATA_DIR:-/data}" +echo " Log: ${APP_LOG_FORMAT:-json}" + +# --------------------------------------------------------------------------- +# Health check preparation +# --------------------------------------------------------------------------- +# +# Ensure the data directory exists and is writable. +# The VOLUME directive in the Containerfile creates /data, but a bind-mount +# might replace it with an empty directory owned by root. + +if [ -d "${APP_DATA_DIR:-/data}" ]; then + if [ ! -w "${APP_DATA_DIR:-/data}" ]; then + echo "WARNING: ${APP_DATA_DIR:-/data} is not writable by $(whoami)" + fi +fi + +# --------------------------------------------------------------------------- +# Exec into main process +# --------------------------------------------------------------------------- +# +# Replace the entrypoint shell with the application process so that +# signals are delivered directly and PID 1 is the application. +# +# TODO: Replace the command below with your application binary. +# Examples: +# exec /app/czech-file-knife +# exec /app/release/bin/czech-file-knife start +# exec /app/czech-file-knife serve --host "${APP_HOST}" --port "${APP_PORT}" + +exec "$@" diff --git a/czech-file-knife/build/container/stapeln/.gatekeeper.yaml b/czech-file-knife/build/container/stapeln/.gatekeeper.yaml new file mode 100644 index 000000000..eaca4aca6 --- /dev/null +++ b/czech-file-knife/build/container/stapeln/.gatekeeper.yaml @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Svalinn gatekeeper policy for Czech File Knife +# +# Controls which operations are permitted through the edge gateway. +# This template provides moderate security defaults — not wide-open test +# mode, but not production-hardened either. Tighten the values below +# before deploying to production. +# +# See: stapeln/container-stack/svalinn/ + +version: "1.0" + +# ============================================================================ +# Authentication +# ============================================================================ +# +# Define which endpoints require authentication and at what level. + +auth: + # Public endpoints — no authentication required. + # Health and readiness probes must always be public so that + # orchestrators (selur, Podman, k8s) can check service status. + public: + - path: "/health" + methods: ["GET"] + - path: "/ready" + methods: ["GET"] + - path: "/metrics" + methods: ["GET"] + + # Endpoints requiring JWT or OAuth2 authentication. + # Svalinn validates the token before forwarding the request. + authenticated: + - path: "/api/v1/*" + methods: ["GET", "POST", "PUT", "DELETE"] + +# ============================================================================ +# Rate Limiting +# ============================================================================ +# +# Protects backend services from overload. Values here are moderate +# defaults — adjust based on your service capacity. + +rate_limits: + # Global limit: applied to all authenticated clients. + global: + requests_per_second: 500 + burst: 1000 + + # Write operations: stricter limit to protect data stores. + writes: + paths: ["/api/v1/*"] + methods: ["POST", "PUT", "DELETE"] + requests_per_second: 100 + burst: 200 + +# ============================================================================ +# Container Trust +# ============================================================================ +# +# Svalinn verifies that all .ctp bundles in the stack are signed by +# trusted keys and carry the required attestations. + +trust: + # Only accept .ctp bundles signed by these keys. + trusted_signers: + - key_id: "czech-file-knife-release" + algorithm: "Ed25519" + public_key_file: "/etc/svalinn/keys/czech-file-knife-release.pub" + + # Require these attestations on all .ctp bundles. + required_attestations: + - "source-signature" + - "sbom-complete" + + # Reject unsigned or untrusted images. + reject_unsigned: true + +# ============================================================================ +# Request Validation +# ============================================================================ +# +# Input validation at the gateway layer — catches malformed requests +# before they reach the application. + +validation: + # Maximum request body size. + max_body_size: "8MB" + + # Reject requests with NaN or Infinity in numeric fields. + reject_nan_inf: true + + # Maximum result limit per list/search query. + max_result_limit: 500 + +# ============================================================================ +# CORS +# ============================================================================ +# +# Cross-Origin Resource Sharing policy. The defaults below allow all +# origins — restrict to your frontend domain(s) in production. + +cors: + allow_origins: ["*"] + allow_methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + allow_headers: ["Content-Type", "Authorization"] + max_age: 3600 + +# ============================================================================ +# Logging +# ============================================================================ +# +# Structured logging for svalinn itself. Audit paths log all requests +# (including body hashes) for post-incident investigation. + +logging: + format: "json" + level: "info" + # Log all write operations for audit trail. + audit_paths: + - "/api/v1/*" diff --git a/czech-file-knife/build/container/stapeln/compose.example.toml b/czech-file-knife/build/container/stapeln/compose.example.toml new file mode 100644 index 000000000..1dd5794ec --- /dev/null +++ b/czech-file-knife/build/container/stapeln/compose.example.toml @@ -0,0 +1,135 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Example selur-compose configuration — multi-service stack +# +# This is a concrete, fully-commented example showing a Rust API + Elixir +# worker + svalinn gateway deployment. Copy this file to compose.toml and +# customise for your project. +# +# Usage: +# cp compose.example.toml compose.toml +# # Edit service names, ports, images +# selur-compose up --detach + +version = "1.0" + +# ============================================================================ +# Services +# ============================================================================ + +# Rust API service — the primary HTTP/gRPC backend. +# Handles incoming requests, data storage, and core business logic. +[services.rust-api] +image = "ghcr.io/hyperpolymath/myproject-api:latest.ctp" + +# Map host port 8080 to container port 8080. +# Use ["[::]:8080:8080"] for explicit IPv6 binding. +ports = ["8080:8080"] + +# Environment variables passed into the container at startup. +# These override defaults in the Containerfile ENV directives. +environment = { + RUST_LOG = "info", # Rust log level (trace, debug, info, warn, error) + APP_HOST = "[::]", # Listen on all interfaces (IPv4 + IPv6) + APP_PORT = "8080", # Internal container port + APP_LOG_FORMAT = "json", # Structured logging for selur/vordr + APP_DATA_DIR = "/data", # Persistent data directory (matches VOLUME) +} + +# Bind-mount a named volume for persistent data. +# Format: "volume-name:/build/container/path" +volumes = ["api-data:/data"] + +# Restart policy: "always" ensures the service comes back after crashes. +# Other options: "no", "on-failure", "unless-stopped" +restart = "always" + +# Health check: selur/Podman uses this to determine if the service is ready. +# The service must respond 2xx to this endpoint within the timeout. +healthcheck = { test = "curl -sf http://localhost:8080/health", interval = "30s", timeout = "5s", retries = 3 } + +# --- + +# Elixir worker service — background processing, event handling, coordination. +# Runs as an OTP release with supervision trees for fault tolerance. +[services.elixir-worker] +image = "ghcr.io/hyperpolymath/myproject-worker:latest.ctp" + +# Separate port for the worker's admin/metrics endpoint. +ports = ["4000:4000"] + +# The worker connects to the Rust API over the internal selur network. +# Service names resolve as hostnames within the compose network. +environment = { + API_URL = "http://rust-api:8080/api/v1", # Internal service discovery + MIX_ENV = "prod", # Elixir release mode + APP_LOG_FORMAT = "json", # Match structured logging format + POOL_SIZE = "10", # DB connection pool size +} + +# depends_on ensures the Rust API starts before the worker. +# Note: This only waits for the container to start, not for the health check. +# Use healthcheck + startup probes for true readiness gating. +depends_on = ["rust-api"] + +restart = "always" +healthcheck = { test = "curl -sf http://localhost:4000/health", interval = "30s", timeout = "5s", retries = 3 } + +# --- + +# Svalinn edge gateway — reverse proxy with policy enforcement. +# All external traffic enters through svalinn, which: +# 1. Terminates TLS (auto-provisioned certificates) +# 2. Validates JWT/OAuth2 authentication +# 3. Enforces rate limits from .gatekeeper.yaml +# 4. Routes requests to the appropriate backend service +# 5. Logs all write operations for audit +[services.svalinn] +image = "ghcr.io/hyperpolymath/svalinn:latest.ctp" + +# External-facing ports: HTTPS (443) and HTTP->HTTPS redirect (80). +ports = ["443:443", "80:80"] + +environment = { + # Backend routing: svalinn proxies to internal services. + SVALINN_BACKEND = "http://rust-api:8080", + SVALINN_WORKER_BACKEND = "http://elixir-worker:4000", + + # Policy file: mounted from the svalinn-config volume. + SVALINN_POLICY_FILE = "/etc/svalinn/gatekeeper.yaml", + + # Auto-provision TLS certificates (Let's Encrypt). + SVALINN_TLS_AUTO = "true", +} + +# Mount .gatekeeper.yaml as read-only policy configuration. +volumes = ["svalinn-config:/etc/svalinn:ro"] + +# Svalinn starts last — it needs both backends to be running. +depends_on = ["rust-api", "elixir-worker"] +restart = "always" +healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", timeout = "5s", retries = 3 } + +# ============================================================================ +# Volumes +# ============================================================================ + +# Persistent storage for the Rust API (database files, indexes, WAL). +[volumes.api-data] +driver = "local" + +# Read-only policy configuration for svalinn gateway. +# Populate with: cp .gatekeeper.yaml /path/to/svalinn-config/gatekeeper.yaml +[volumes.svalinn-config] +driver = "local" + +# ============================================================================ +# Networks +# ============================================================================ + +# selur network: zero-copy IPC between services on the same host. +# When the selur driver is not installed, falls back to standard bridge +# networking (TCP over localhost). Performance is slightly lower but +# functionality is identical. +[networks.default] +driver = "selur" diff --git a/czech-file-knife/build/container/stapeln/compose.toml b/czech-file-knife/build/container/stapeln/compose.toml new file mode 100644 index 000000000..fcd42fc11 --- /dev/null +++ b/czech-file-knife/build/container/stapeln/compose.toml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Czech File Knife selur-compose configuration +# +# Orchestrates the container stack as verified container bundles (.ctp). +# Uses selur zero-copy IPC between services on the same host. +# +# Usage: +# selur-compose up # Start all services +# selur-compose up --detach # Start in background +# selur-compose verify # Verify all .ctp signatures +# selur-compose ps # Check status +# selur-compose logs -f czech-file-knife # Stream logs +# selur-compose down # Stop all services +# +# Fallback (when selur is not installed): this TOML is selur-native and is +# NOT parseable by `podman compose` / `docker compose`. For the portable +# (podman/nerdctl/docker) path use the compose-spec file instead: +# podman compose -f ../compose.yaml up -d # (or: just container-up) + +version = "1.0" + +# ============================================================================ +# Services +# ============================================================================ + +# Primary application service +[services.czech-file-knife] +image = "ghcr.io/hyperpolymath/czech-file-knife:latest.ctp" +ports = ["8080:8080"] +environment = { + APP_HOST = "[::]", + APP_PORT = "8080", + APP_LOG_FORMAT = "json", + APP_DATA_DIR = "/data", +} +volumes = ["czech-file-knife-data:/data"] +restart = "always" +healthcheck = { test = "curl -sf http://localhost:8080/health", interval = "30s", timeout = "5s", retries = 3 } + +# Rokur secrets gate: refuses to start (and refuses to proxy) unless the +# declared secrets are present; emits a structured audit log of every access. +# Sits between svalinn and the application. Remove if you do not gate secrets. +[services.rokur] +image = "ghcr.io/hyperpolymath/rokur:latest.ctp" +ports = ["8081:8081"] +environment = { + ROKUR_BACKEND = "http://czech-file-knife:8080", + ROKUR_LISTEN = "[::]:8081", + ROKUR_REQUIRED_SECRETS = "", + ROKUR_AUDIT_LOG = "/var/log/rokur/audit.jsonl", + ROKUR_LOG_FORMAT = "json", +} +volumes = ["rokur-audit:/var/log/rokur"] +depends_on = ["czech-file-knife"] +restart = "always" +healthcheck = { test = "curl -sf http://localhost:8081/health", interval = "30s", timeout = "5s", retries = 3 } + +# Svalinn edge gateway: validates requests, enforces policies, TLS termination. +# Proxies to rokur, which in turn fronts the application. +[services.svalinn] +image = "ghcr.io/hyperpolymath/svalinn:latest.ctp" +ports = ["443:443", "80:80"] +environment = { + SVALINN_BACKEND = "http://rokur:8081", + SVALINN_POLICY_FILE = "/etc/svalinn/gatekeeper.yaml", + SVALINN_TLS_AUTO = "true", +} +volumes = ["svalinn-config:/etc/svalinn:ro"] +depends_on = ["rokur"] +restart = "always" +healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", timeout = "5s", retries = 3 } + +# ============================================================================ +# Volumes +# ============================================================================ + +[volumes.czech-file-knife-data] +driver = "local" + +[volumes.rokur-audit] +driver = "local" + +[volumes.svalinn-config] +driver = "local" + +# ============================================================================ +# Networks +# ============================================================================ + +# Use selur zero-copy IPC for inter-service communication on the same host. +# Falls back to standard bridge networking when selur driver is unavailable. +[networks.default] +driver = "selur" diff --git a/czech-file-knife/build/container/stapeln/ct-build.sh b/czech-file-knife/build/container/stapeln/ct-build.sh new file mode 100755 index 000000000..1a46cd48c --- /dev/null +++ b/czech-file-knife/build/container/stapeln/ct-build.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# Czech File Knife — Cerro Torre build, sign, and verify pipeline +# +# Builds the container image, packages it as a verified .ctp bundle, +# signs it with Ed25519, and verifies the result. Gracefully degrades +# when cerro-torre tools are not installed. +# +# Prerequisites: +# - podman / nerdctl / docker (container build — required; set CONTAINER_ENGINE) +# - ct (cerro-torre CLI: pack, sign, verify — optional) +# - cerro-sign (Ed25519 signing — optional, ct sign used as fallback) +# +# Usage: +# ./ct-build.sh # Build + sign (local only) +# ./ct-build.sh --push # Build + sign + push to registry +# CT_KEY_ID=my-key ./ct-build.sh # Use specific signing key +# +# Environment variables: +# CT_KEY_ID — Signing key identifier (default: czech-file-knife-release) +# CT_REGISTRY — OCI registry to push to (default: ghcr.io/hyperpolymath) +# CT_TAG — Image tag (default: latest) + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +# This script lives in build/container/stapeln/. The repo root is two levels up, +# and the Tier-A Containerfile is one level up in build/container/. +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CONTAINER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" # build/container/stapeln -> repo root + +# Container engine — podman (recommended), nerdctl or docker. +ENGINE="${CONTAINER_ENGINE:-podman}" + +PUSH="" +for arg in "$@"; do + if [ "$arg" = "--push" ]; then + PUSH="--push" + fi +done + +CT_KEY_ID="${CT_KEY_ID:-czech-file-knife-release}" +CT_REGISTRY="${CT_REGISTRY:-ghcr.io/hyperpolymath}" +CT_TAG="${CT_TAG:-latest}" + +IMAGE_NAME="czech-file-knife" +FULL_IMAGE="${CT_REGISTRY}/${IMAGE_NAME}:${CT_TAG}" +CTP_FILE="${SCRIPT_DIR}/${IMAGE_NAME}-${CT_TAG}.ctp" + +echo "=== Czech File Knife Cerro Torre Build Pipeline ===" +echo " Image: ${FULL_IMAGE}" +echo " Key: ${CT_KEY_ID}" +echo " Bundle: ${CTP_FILE}" +echo "" + +# --------------------------------------------------------------------------- +# Step 1: Build container image (CONTAINER_ENGINE, default podman) +# --------------------------------------------------------------------------- + +echo "--- Step 1: Building container image (${ENGINE}) ---" + +"${ENGINE}" build \ + -t "${FULL_IMAGE}" \ + -f "${CONTAINER_DIR}/Containerfile" \ + "${REPO_ROOT}" + +echo " Built: ${FULL_IMAGE}" +echo "" + +# --------------------------------------------------------------------------- +# Step 2: Pack into .ctp bundle +# --------------------------------------------------------------------------- + +echo "--- Step 2: Packing into .ctp bundle ---" + +if command -v ct &>/dev/null; then + ct pack "${FULL_IMAGE}" -o "${CTP_FILE}" + echo " Packed: ${CTP_FILE}" +else + echo " SKIP: ct not found (install cerro-torre CLI from stapeln/container-stack/cerro-torre)" + echo " The container image is built and tagged but not packed as a .ctp bundle." + echo " To pack manually: ct pack ${FULL_IMAGE} -o ${CTP_FILE}" + echo "" + if [ "$PUSH" = "--push" ]; then + echo "--- Pushing unsigned OCI image (no .ctp) ---" + "${ENGINE}" push "${FULL_IMAGE}" + echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" + fi + echo "" + echo "=== Build complete (without .ctp signing) ===" + exit 0 +fi + +echo "" + +# --------------------------------------------------------------------------- +# Step 3: Sign the .ctp bundle +# --------------------------------------------------------------------------- + +echo "--- Step 3: Signing .ctp bundle ---" + +if command -v cerro-sign &>/dev/null; then + cerro-sign sign "${CTP_FILE}" --key-id "${CT_KEY_ID}" + echo " Signed: ${CTP_FILE} (key: ${CT_KEY_ID})" +elif command -v ct &>/dev/null; then + ct sign "${CTP_FILE}" --key "${CT_KEY_ID}" + echo " Signed: ${CTP_FILE} (key: ${CT_KEY_ID})" +else + echo " SKIP: cerro-sign not found (install from stapeln/container-stack/cerro-torre)" +fi + +echo "" + +# --------------------------------------------------------------------------- +# Step 4: Verify the .ctp bundle +# --------------------------------------------------------------------------- + +echo "--- Step 4: Verifying .ctp bundle ---" + +if command -v ct &>/dev/null; then + ct verify "${CTP_FILE}" + echo " Verified: ${CTP_FILE}" +else + echo " SKIP: ct not found" +fi + +echo "" + +# --------------------------------------------------------------------------- +# Step 5: Push to registry (optional) +# --------------------------------------------------------------------------- + +if [ "$PUSH" = "--push" ]; then + echo "--- Step 5: Pushing to registry ---" + + if command -v ct &>/dev/null; then + ct push "${CTP_FILE}" "${FULL_IMAGE}" + echo " Pushed: ${FULL_IMAGE}" + else + # Fall back to podman push (unsigned OCI image) + echo " ct not available, falling back to '${ENGINE}' push (unsigned)" + "${ENGINE}" push "${FULL_IMAGE}" + echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" + fi + echo "" +fi + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- + +echo "=== Build pipeline complete ===" +echo " Image: ${FULL_IMAGE}" +echo " Bundle: ${CTP_FILE}" +echo "" +echo " To deploy with selur-compose:" +echo " cd build/container/stapeln && selur-compose up" +echo "" +echo " To verify at any time:" +echo " ct verify ${CTP_FILE}" +echo "" +echo " To explain the verification chain:" +echo " ct explain ${CTP_FILE}" diff --git a/czech-file-knife/build/container/stapeln/deploy.k9.ncl b/czech-file-knife/build/container/stapeln/deploy.k9.ncl new file mode 100644 index 000000000..ea29c013e --- /dev/null +++ b/czech-file-knife/build/container/stapeln/deploy.k9.ncl @@ -0,0 +1,170 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# deploy.k9.ncl — Czech File Knife deployment component (Hunt level) +# +# k9-svc deployment specification with full pedigree (L1-L5). +# Security Level: 'Hunt (requires cryptographic handshake for execution). +# +# WARNING: This component can execute shell commands! +# It requires explicit authorisation via the Leash system. +# +# Usage: +# nickel typecheck build/container/deploy.k9.ncl +# k9-svc validate build/container/deploy.k9.ncl +# k9-svc deploy build/container/deploy.k9.ncl --env production + +# The component's pedigree (self-description across five layers) +let component_pedigree = { + # ───────────────────────────────────────────────────────────── + # L1: The Snout — Identity + # ───────────────────────────────────────────────────────────── + metadata = { + name = "czech-file-knife-deploy", + version = "0.1.0", + breed = "application/vnd.k9+nickel", + magic_number = "K9!", + description = "Czech File Knife deployment component (Hunt level)", + }, + + # ───────────────────────────────────────────────────────────── + # L2: The Scent — Target Environment + # ───────────────────────────────────────────────────────────── + target = { + os = 'Linux, + is_edge = false, + requires_podman = true, + min_memory_mb = 256, + }, + + # ───────────────────────────────────────────────────────────── + # L3: The Leash — Security + # ───────────────────────────────────────────────────────────── + security = { + trust_level = 'Hunt, + # Named field the k9 validators grep for (leash/security_level); + # value mirrors trust_level — this component is documented Hunt-level. + security_level = 'Hunt, + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + # In production, replace with a real Ed25519 signature. + signature = "PLACEHOLDER-SIGNATURE-REQUIRED-FOR-HUNT", + }, + + # ───────────────────────────────────────────────────────────── + # L4: The Gut — Self-Validation + # ───────────────────────────────────────────────────────────── + validation = { + checksum = "sha256:placeholder", + pedigree_version = "1.0.0", + hunt_authorized = false, # Must be set true after handshake + }, + + # ───────────────────────────────────────────────────────────── + # L5: The Muscle — Deployment Recipes + # ───────────────────────────────────────────────────────────── + recipes = { + install = "just container-build", + validate = "just container-verify", + deploy = "just container-up", + migrate = "just container-build && just container-up", + }, +} in + +# Deployment configuration +let deployment = { + # Target environments (dev / staging / production) + environments = { + dev = { + replicas = 1, + memory = "256Mi", + cpu = "100m", + image_tag = "dev", + }, + staging = { + replicas = 2, + memory = "512Mi", + cpu = "250m", + image_tag = "staging", + }, + production = { + replicas = 3, + memory = "1Gi", + cpu = "500m", + image_tag = "latest", + }, + }, + + # Container configuration + container = { + image = "ghcr.io/hyperpolymath/czech-file-knife", + port = 8080, + health_check = "/health", + readiness_check = "/ready", + }, + + # Deployment strategy + strategy = { + type = "rolling", + max_surge = 1, + max_unavailable = 0, + }, +} in + +# Deployment scripts (executed at Hunt level) +let scripts = { + # Pre-deployment validation + pre_deploy = m%" +#!/bin/sh +set -eu +echo "K9: Pre-deployment validation for czech-file-knife..." +cd build/container/stapeln && selur-compose verify || podman compose --file ../compose.yaml config +echo "K9: Validation passed." +"%, + + # Deployment script + deploy = m%" +#!/bin/sh +set -eu +ENV="${1:-dev}" +echo "K9: Deploying czech-file-knife to $ENV environment..." +cd build/container/stapeln +./ct-build.sh +selur-compose up --detach || podman compose --file ../compose.yaml up --detach +echo "K9: Deployment to $ENV complete." +"%, + + # Rollback script + rollback = m%" +#!/bin/sh +set -eu +echo "K9: Rolling back czech-file-knife deployment..." +cd build/container/stapeln +selur-compose down || podman compose --file ../compose.yaml down +echo "K9: Rollback complete." +"%, +} in + +# Export the component +{ + pedigree = component_pedigree & { name = "czech-file-knife-deploy" }, + deployment = deployment, + scripts = scripts, + + # Security check: this component requires Hunt level + required_level = 'Hunt, + + # Warning for users + warning = m%" +WARNING: This is a Hunt-level component. + +It can execute shell commands and modify your system. +Before running, ensure you have: + +1. Reviewed the deployment scripts above +2. Verified the signature (when implemented) +3. Explicitly authorised Hunt-level execution + +Run with: k9-svc authorize build/container/deploy.k9.ncl && k9-svc deploy build/container/deploy.k9.ncl +"%, +} diff --git a/czech-file-knife/build/container/stapeln/manifest.toml b/czech-file-knife/build/container/stapeln/manifest.toml new file mode 100644 index 000000000..ded14aef4 --- /dev/null +++ b/czech-file-knife/build/container/stapeln/manifest.toml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Cerro Torre manifest for Czech File Knife .ctp bundle +# +# This manifest describes the container image for verified +# container packaging. Used by `ct pack` to create .ctp bundles. + +[metadata] +name = "czech-file-knife" +version = "0.1.0" +revision = 1 +summary = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." +description = """ +Czech File Knife — containerised service packaged as a verified +cerro-torre .ctp bundle with Ed25519 signing and full provenance +tracking. +""" +license = "MPL-2.0" +homepage = "https://github.com/hyperpolymath/czech-file-knife" +maintainer = "Jonathan D.A. Jewell " + +[provenance] +upstream = "https://github.com/hyperpolymath/czech-file-knife" +import_date = 2026-09-28T00:00:00Z + +[dependencies] +runtime = ["ca-certificates", "curl"] +build = [] + +[build] +system = "podman" + +[build.environment] +APP_HOST = "[::]" +APP_PORT = "8080" + +[outputs] +primary = "czech-file-knife" +split = [] + +[attestations] +require = ["source-signature", "sbom-complete"] +recommend = ["security-audit", "reproducible-build"] + +# Runtime security profile +[security] +user = "appuser" +group = "appuser" +read_only_root = false +no_new_privileges = true + +[security.capabilities] +drop = ["ALL"] +add = ["NET_BIND_SERVICE"] + +[security.network] +listen_tcp = [8080] + +[security.filesystem] +read = ["/app/", "/data/"] +write = ["/data/", "/tmp/"] +execute = ["/app/entrypoint.sh"] diff --git a/czech-file-knife/build/container/stapeln/rokur.toml b/czech-file-knife/build/container/stapeln/rokur.toml new file mode 100644 index 000000000..c10f408bb --- /dev/null +++ b/czech-file-knife/build/container/stapeln/rokur.toml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Rokur secrets-gate configuration for Czech File Knife +# +# Rokur (stapeln container stack) is a secrets-management gate: a small +# Deno service that sits in front of the application and refuses to pass +# traffic unless the declared secrets are present and valid. It rate-limits +# per client and writes a structured audit log of every secret access. +# +# Most settings can also be supplied as environment variables (the ROKUR_* +# names below); this file is the declarative equivalent consumed at startup +# and re-read on SIGHUP. Environment variables win over file values. +# +# Usage: +# rokur serve --config build/container/stapeln/rokur.toml +# # or, in the stack, via compose (see compose.toml / compose.yaml) + +[metadata] +name = "czech-file-knife-gate" +version = "0.1.0" + +# ============================================================================ +# Listener / routing (env: ROKUR_LISTEN, ROKUR_BACKEND) +# ============================================================================ + +[server] +# Address rokur listens on (inside the container). +listen = "[::]:8081" +# The upstream application rokur fronts once the gate is satisfied. +backend = "http://czech-file-knife:8080" +# Health/readiness endpoints rokur exposes for vordr / orchestrators. +health_path = "/health" +ready_path = "/ready" + +# ============================================================================ +# Required secrets (env: ROKUR_REQUIRED_SECRETS, comma-separated) +# ============================================================================ +# +# Names of secrets that MUST be present (as environment variables or in the +# mounted secret store) before rokur will start the gate. An empty list means +# the gate starts open — tighten this before production. + +[secrets] +required = [] +# Example: +# required = ["DATABASE_URL", "JWT_SIGNING_KEY", "TLS_PRIVATE_KEY"] + +# Where rokur looks for secret material, in order. "env" reads process +# environment; "file" reads a mounted directory (one file per secret). +sources = ["env"] +# file_dir = "/run/secrets" + +# ============================================================================ +# Rate limiting (rokur/rate_limit.js — per-client token bucket) +# ============================================================================ + +[rate_limit] +enabled = true +requests_per_second = 100 +burst = 200 + +# ============================================================================ +# Policy engine (rokur/policy/engine.js — pluggable evaluator) +# ============================================================================ + +[policy] +# "builtin" uses rokur's built-in evaluator; "external" delegates to a +# command/endpoint you supply. +engine = "builtin" +# external_command = "/usr/local/bin/my-policy" + +# ============================================================================ +# Audit log (env: ROKUR_AUDIT_LOG, ROKUR_LOG_FORMAT) +# ============================================================================ + +[audit] +log = "/var/log/rokur/audit.jsonl" +format = "json" +# Record a hash of secret values (never the values themselves) for tamper +# evidence in the audit trail. +hash_values = true diff --git a/czech-file-knife/build/container/stapeln/vordr.toml b/czech-file-knife/build/container/stapeln/vordr.toml new file mode 100644 index 000000000..c19fcf81f --- /dev/null +++ b/czech-file-knife/build/container/stapeln/vordr.toml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Vordr runtime monitoring configuration for Czech File Knife +# +# Vordr watches container health, detects crashes, tracks resource usage, +# and emits structured logs. It runs alongside the application stack and +# provides runtime observability without requiring in-process agents. +# +# Usage: +# vordr watch --config build/container/vordr.toml +# vordr status +# vordr report + +[metadata] +name = "czech-file-knife" +version = "0.1.0" + +# ============================================================================ +# Health Monitoring +# ============================================================================ +# +# Vordr periodically probes these endpoints. If a probe fails beyond the +# failure_threshold, vordr emits an alert and (optionally) restarts the +# container via Podman. + +[health] +# Primary health endpoint — must return 2xx. +endpoint = "http://localhost:8080/health" +interval = "30s" +timeout = "5s" +failure_threshold = 3 + +# Readiness endpoint — checked during startup and after restarts. +readiness_endpoint = "http://localhost:8080/ready" +readiness_timeout = "10s" + +# Action on failure: "alert" (log + notify) or "restart" (alert + restart). +on_failure = "alert" + +# Additional deep probes for the sidecar stapeln services. Crash detection +# below already tracks every container's lifecycle via the engine; these add +# endpoint-level health checks for the secrets gate and the edge gateway. +[[health.extra]] +name = "rokur" +endpoint = "http://localhost:8081/health" +interval = "30s" +timeout = "5s" +failure_threshold = 3 + +[[health.extra]] +name = "svalinn" +endpoint = "http://localhost:80/health" +interval = "30s" +timeout = "5s" +failure_threshold = 3 + +# ============================================================================ +# Crash Detection +# ============================================================================ +# +# Monitors container state via the engine (podman/nerdctl/docker). Detects +# OOM kills, segfaults, and unexpected exits across the whole stack. + +[crash_detection] +enabled = true +# Maximum restarts within the window before vordr stops restarting. +max_restarts = 5 +restart_window = "10m" + +# ============================================================================ +# Resource Thresholds +# ============================================================================ +# +# Alert when resource usage exceeds these thresholds. Values are percentages +# of the container's cgroup limits (or host limits if uncapped). + +[resources] +cpu_warn = 80 # Percentage — warn at 80% sustained CPU. +cpu_critical = 95 # Percentage — critical alert at 95%. +memory_warn = 75 # Percentage of memory limit. +memory_critical = 90 +disk_warn = 80 # Percentage of volume usage. +disk_critical = 95 + +# Sample interval for resource metrics. +sample_interval = "15s" + +# ============================================================================ +# Log Output +# ============================================================================ +# +# Vordr emits its own logs (not the application's) in structured format. + +[logging] +format = "json" +level = "info" +# Write vordr logs to stdout (captured by Podman) and optionally to file. +output = "stdout" +# file = "/var/log/vordr/czech-file-knife.log" + +# ============================================================================ +# Notifications (optional) +# ============================================================================ +# +# Uncomment and configure to receive alerts via webhook or email. + +# [notifications.webhook] +# url = "https://example.com/hooks/vordr" +# method = "POST" +# headers = { "Content-Type" = "application/json" } +# on = ["failure", "recovery", "resource_critical"] + +# [notifications.email] +# to = "j.d.a.jewell@open.ac.uk" +# from = "vordr@czech-file-knife.local" +# smtp = "smtp://localhost:25" +# on = ["failure", "resource_critical"] diff --git a/czech-file-knife/guix.scm b/czech-file-knife/build/guix.scm old mode 100644 new mode 100755 similarity index 100% rename from czech-file-knife/guix.scm rename to czech-file-knife/build/guix.scm diff --git a/czech-file-knife/build/just/assess.just b/czech-file-knife/build/just/assess.just new file mode 100644 index 000000000..3c9903170 --- /dev/null +++ b/czech-file-knife/build/just/assess.just @@ -0,0 +1,270 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE VERIFICATION +# +# Imported by ../../Justfile via `import? "build/just/assess.just"`. +# Recipes here advise on what to keep/remove (`self-assess`, read-only) and +# verify that OpenSSF Best Practices prerequisites are present (`verify`, +# called by `init` and CI). + +# Analyse this project and advise what to keep, remove, or leave for later. +# Does NOT modify any files — only prints recommendations. +self-assess: + #!/usr/bin/env bash + set -euo pipefail + + echo "═══════════════════════════════════════════════════" + echo " RSR Project Self-Assessment" + echo "═══════════════════════════════════════════════════" + echo "" + echo "Scanning project structure to identify what's" + echo "relevant, removable, or worth keeping for later..." + echo "" + + # Detect project characteristics + HAS_RUST=false; [ -f "Cargo.toml" ] && HAS_RUST=true + HAS_ELIXIR=false; [ -f "mix.exs" ] && HAS_ELIXIR=true + HAS_RESCRIPT=false; [ -f "rescript.json" ] || [ -f "bsconfig.json" ] && HAS_RESCRIPT=true + HAS_IDRIS=false; ls *.ipkg >/dev/null 2>&1 && HAS_IDRIS=true + HAS_ZIG=false; [ -f "build.zig" ] || [ -d "ffi/zig" ] && HAS_ZIG=true + HAS_GLEAM=false; [ -f "gleam.toml" ] && HAS_GLEAM=true + HAS_CONTAINER=false; [ -f "Containerfile" ] || [ -f "build/container/Containerfile" ] && HAS_CONTAINER=true + HAS_TESTS=false; [ -d "test" ] || [ -d "tests" ] || [ -d "__tests__" ] && HAS_TESTS=true + HAS_API=false; grep -rq 'port\|listen\|endpoint' --include="*.exs" --include="*.rs" --include="*.toml" . 2>/dev/null && HAS_API=true + IS_LIBRARY=false; [ -f "Cargo.toml" ] && grep -q '\[lib\]' Cargo.toml 2>/dev/null && IS_LIBRARY=true + + echo "Detected: Rust=$HAS_RUST Elixir=$HAS_ELIXIR ReScript=$HAS_RESCRIPT" + echo " Idris=$HAS_IDRIS Zig=$HAS_ZIG Gleam=$HAS_GLEAM" + echo " Container=$HAS_CONTAINER Tests=$HAS_TESTS API=$HAS_API" + echo "" + + # ── ESSENTIAL (removing these breaks RSR compliance) ────────── + echo "── ESSENTIAL (removing breaks Rhodium Standard) ──────────" + echo "" + + for f in LICENSE .editorconfig .gitignore; do + if [ -f "$f" ]; then + echo " ✓ $f — KEEP (RSR required)" + else + echo " ✗ $f — MISSING (RSR violation!)" + fi + done + + # Community health files live at root OR .github/ (.github/ is the estate's + # canonical location — see .machine_readable/root-allow.txt). Checking root + # alone reported the template's own .github/ copies as "MISSING (RSR + # violation!)", which is a false negative. + for f in SECURITY CODE_OF_CONDUCT CONTRIBUTING; do + found="" + for cand in "$f.md" "$f.adoc" ".github/$f.md" ".github/$f.adoc"; do + [ -f "$cand" ] && { found="$cand"; break; } + done + if [ -n "$found" ]; then + echo " ✓ $found — KEEP (RSR required)" + else + echo " ✗ $f.md — MISSING at root and .github/ (RSR violation!)" + fi + done + + if [ -d ".machine_readable/descriptiles" ]; then + echo " ✓ .machine_readable/descriptiles/ — KEEP (SCM checkpoint files)" + else + echo " ✗ .machine_readable/descriptiles/ — MISSING (RSR violation!)" + fi + + if [ -d ".github/workflows" ]; then + WF_COUNT=$(ls .github/workflows/*.yml 2>/dev/null | wc -l) + echo " ✓ .github/workflows/ — KEEP ($WF_COUNT workflows, RSR CI/CD)" + fi + echo "" + + # ── RELEVANT (useful for your project type) ─────────────────── + echo "── RELEVANT (matches your project) ───────────────────────" + echo "" + + if $HAS_IDRIS && { [ -d "src/interface/abi" ] || [ -d "src/interface/Abi" ]; }; then + echo " ✓ src/interface/abi/ — KEEP (Idris2 ABI definitions)" + elif ! $HAS_IDRIS && { [ -d "src/interface/abi" ] || [ -d "src/interface/Abi" ]; }; then + echo " ? src/interface/abi/ — No Idris2 detected." + echo " → KEEP if you plan to add formal verification later." + echo " → SAFE TO REMOVE if this project will never use Idris2." + echo " ⚠ Consequence: no formally verified interface definitions." + fi + + if $HAS_ZIG && [ -d "src/interface/ffi" ]; then + echo " ✓ src/interface/ffi/ — KEEP (Zig FFI bridge)" + elif ! $HAS_ZIG && [ -d "src/interface/ffi" ]; then + echo " ? src/interface/ffi/ — No Zig detected." + echo " → KEEP if you plan C ABI interop later." + echo " → SAFE TO REMOVE if this is a pure web/scripting project." + echo " ⚠ Consequence: no C-compatible FFI bridge." + fi + + if $HAS_API && [ -f ".machine_readable/integrations/groove.a2ml" ]; then + PORT=$(grep '(port ' .machine_readable/integrations/groove.a2ml 2>/dev/null | sed 's/.*(port \([0-9]*\)).*/\1/') + if [ "$PORT" = "0" ]; then + echo " ⚠ groove.a2ml — Port not assigned. Run 'just groove-setup'." + else + echo " ✓ groove.a2ml — KEEP (Groove discovery on port $PORT)" + fi + elif $HAS_API; then + echo " ✗ groove.a2ml — MISSING. Your project has an API but no Groove manifest." + echo " → Run 'just groove-setup' to enable snap-on/snap-off discovery." + fi + + if $HAS_CONTAINER && [ -d "build/container" ]; then + echo " ✓ build/container/ — KEEP (Containerfile + compose)" + elif ! $HAS_CONTAINER && [ -d "build/container" ]; then + echo " ? build/container/ — No Containerfile detected in use." + echo " → KEEP if you plan to containerise later." + echo " → SAFE TO REMOVE for libraries and CLI tools (run: just no-container)." + fi + + echo "" + + # ── SAFE TO REMOVE (not relevant, no consequences) ──────────── + echo "── SAFE TO REMOVE (no RSR consequences) ──────────────────" + echo "" + + if ! $HAS_RESCRIPT && [ -d "examples" ] && ls examples/*.res >/dev/null 2>&1; then + echo " ○ examples/*.res — Template ReScript examples. Not your code." + fi + + if [ -f ".machine_readable/ai/PLACEHOLDERS.adoc" ]; then + echo " ○ .machine_readable/ai/PLACEHOLDERS.adoc — Template doc. Remove after init." + fi + + if { [ -f "build/flake.nix" ] || [ -f "flake.nix" ]; } && ! command -v nix >/dev/null 2>&1; then + echo " ○ flake.nix — Nix flake. Safe to remove if you don't use Nix." + echo " → KEEP if others might build with Nix." + fi + + if [ -f "build/guix.scm" ] && ! command -v guix >/dev/null 2>&1; then + echo " ○ build/guix.scm — Guix package. Safe to remove if you don't use Guix." + echo " → KEEP if others might build with Guix." + fi + + echo "" + + # ── FUTURE VALUE (not needed now, worth keeping) ────────────── + echo "── KEEP FOR FUTURE (not active, but valuable later) ──────" + echo "" + + if [ -d ".machine_readable/contractiles" ]; then + echo " ◆ contractiles/ — Must/Trust/Dust/Lust contracts." + echo " Not enforced until you configure them, but ready when you need" + echo " automated compliance checking. Zero cost to keep." + fi + + if [ -d ".machine_readable/bot_directives" ]; then + echo " ◆ bot_directives/ — Gitbot fleet configuration." + echo " Not active until gitbot-fleet is connected. Keeps your repo" + echo " ready for automated maintenance when the fleet arrives." + fi + + if [ -d ".machine_readable/bot_directives" ]; then + echo " ◆ bot_directives/ — AI agent methodology config." + echo " Guides Claude/Gemini/etc on how to work in this repo." + echo " No cost to keep. Improves AI assistance quality." + fi + + if [ -d "docs/governance" ]; then + echo " ◆ docs/governance/ — TSDM, CRG, maintenance checklists." + echo " Not needed for solo projects. Essential when you add contributors." + fi + + if [ -d "verification" ]; then + echo " ◆ verification/ — Proofs, benchmarks, fuzzing, safety case." + echo " Empty scaffolds until you add formal verification." + echo " Worth keeping for any project that claims safety properties." + fi + + echo "" + echo "═══════════════════════════════════════════════════" + echo " Assessment complete. No files were modified." + echo "═══════════════════════════════════════════════════" + +# Verify OpenSSF Best Practices prerequisites — fails if any required file is missing +verify: + #!/usr/bin/env bash + set -euo pipefail + + echo "=== OpenSSF Best Practices Verification ===" + ERRORS=0 + + check_file() { + if [ ! -f "$1" ]; then + echo " FAIL: $1 missing" + ERRORS=$((ERRORS + 1)) + else + echo " OK: $1" + fi + } + + # Accept either .md or .adoc for documentation files + check_either() { + if [ ! -f "$1" ] && [ ! -f "$2" ]; then + echo " FAIL: $1 (or $2) missing" + ERRORS=$((ERRORS + 1)) + else + local found="$1" + [ -f "$2" ] && found="$2" + [ -f "$1" ] && found="$1" + echo " OK: $found" + fi + } + + # Community health files: accept root OR .github/, .md OR .adoc. + # + # .github/ is the canonical estate location for these — see + # .machine_readable/root-allow.txt, which says of CONTRIBUTING.md: + # "Accepted at root OR .github/ — CI (openssf/quality/rhodibot) now + # checks both; .github/ is the canonical estate location + # (org-inherited). Allow-listed if present at root." + # and of SECURITY.md: "security-policy contractile now accepts the + # .github/ copy." + # + # GitHub and the OpenSSF Best Practices criteria both treat .github/ as a + # valid home for them. Checking root only made this recipe a FALSE + # NEGATIVE: the files exist in .github/ and always have, so the template + # failed its own `just verify` ("repo cannot ship"), and so did every repo + # instantiated from it. This recipe was the last checker still looking at + # root alone. + check_community_file() { + local base="$1" + local cand + for cand in "$base.md" "$base.adoc" ".github/$base.md" ".github/$base.adoc"; do + if [ -f "$cand" ]; then + echo " OK: $cand" + return 0 + fi + done + echo " FAIL: $base.md (or $base.adoc) missing at root or .github/" + ERRORS=$((ERRORS + 1)) + } + + check_community_file "SECURITY" + check_file "LICENSE" + check_community_file "CONTRIBUTING" + check_either "README.adoc" "README.md" + check_file ".machine_readable/descriptiles/STATE.a2ml" + check_file ".machine_readable/descriptiles/META.a2ml" + check_file ".machine_readable/descriptiles/ECOSYSTEM.a2ml" + check_either "CHANGELOG.md" "CHANGELOG.adoc" + + # Check at least 1 workflow exists + WORKFLOW_COUNT=$(find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | wc -l) + if [ "$WORKFLOW_COUNT" -eq 0 ]; then + echo " FAIL: No workflows in .github/workflows/" + ERRORS=$((ERRORS + 1)) + else + echo " OK: .github/workflows/ ($WORKFLOW_COUNT workflows)" + fi + + echo "" + if [ "$ERRORS" -gt 0 ]; then + echo "FAIL: $ERRORS OpenSSF prerequisites missing — repo cannot ship." + exit 1 + fi + echo "PASS: All OpenSSF Best Practices prerequisites satisfied." diff --git a/czech-file-knife/build/just/container.just b/czech-file-knife/build/just/container.just new file mode 100644 index 000000000..cb1e9df25 --- /dev/null +++ b/czech-file-knife/build/just/container.just @@ -0,0 +1,284 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# CONTAINERS — three-tier containerisation +# +# Imported by ../../Justfile via `import? "build/just/container.just"`. +# Variables (project, OWNER, ...) and `set shell` are inherited from the root. +# +# Tier A — OCI standard: build/container/Containerfile, build/container/.containerignore +# Tier B — portable engine: build/container/compose.yaml (podman | nerdctl | docker) +# Tier C — stapeln sovereign: build/container/stapeln/ (cerro-torre, svalinn, +# vordr, selur, rokur, k9) +# +# Engine is auto-selected: $CONTAINER_ENGINE, else the first of podman / +# nerdctl / docker found on PATH. Override with: CONTAINER_ENGINE=docker just … +# +# Narrow the template to the tiers you want (see the Narrowing section below): +# just container-keep oci portable # keep image + portable compose, drop the rest +# just no-stapeln | no-devcontainer | no-container # convenience aliases + +# ── Engine detection (shared snippet) ────────────────────────────────────── +# Resolves the OCI engine into $ENGINE. Sourced at the top of each recipe. +_engine := ''' + ENGINE="${CONTAINER_ENGINE:-}" + if [ -z "$ENGINE" ]; then + for e in podman nerdctl docker; do + if command -v "$e" >/dev/null 2>&1; then ENGINE="$e"; break; fi + done + fi + if [ -z "$ENGINE" ]; then + echo "No container engine found (looked for podman, nerdctl, docker)." >&2 + echo "Install one or set CONTAINER_ENGINE." >&2 + exit 1 + fi +''' + +# Initialise container templates — substitute placeholders with project values +container-init: + #!/usr/bin/env bash + set -euo pipefail + + if [ ! -d "build/container" ]; then + echo "Error: build/container/ directory not found." + echo "This repo may not have been created from rsr-template-repo," + echo "or containerisation was removed with 'just no-container'." + exit 1 + fi + + echo "=== Container Template Initialisation ===" + echo "" + + # Load RSR defaults if available + DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" + if [ -f "$DEFAULTS" ]; then + echo "Loading defaults from $DEFAULTS" + # shellcheck source=/dev/null + source "$DEFAULTS" + echo "" + fi + + # Prompt for container-specific values + read -rp "Service name (e.g. my-api) [czech_file_knife]: " _SERVICE_NAME + SERVICE_NAME="${_SERVICE_NAME:-czech_file_knife}" + + read -rp "Primary port [8080]: " _PORT + PORT="${_PORT:-8080}" + + read -rp "Container registry [ghcr.io/${OWNER:-hyperpolymath}]: " _REGISTRY + REGISTRY="${_REGISTRY:-ghcr.io/${OWNER:-hyperpolymath}}" + + echo "" + echo " Service: $SERVICE_NAME" + echo " Port: $PORT" + echo " Registry: $REGISTRY" + echo "" + read -rp "Proceed? [Y/n] " CONFIRM + [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 + + echo "" + echo "Replacing container placeholders..." + + # Brace tokens as variables (hex escapes avoid just interpolation) + LB=$(printf '\x7b\x7b') + RB=$(printf '\x7d\x7d') + + SED_ARGS=( + -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" + -e "s|${LB}PORT${RB}|${PORT}|g" + -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" + ) + + # Recurses into build/container/stapeln/ as well. + find build/container/ -type f | while read -r file; do + if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then + sed -i "${SED_ARGS[@]}" "$file" + fi + done + + echo "Container templates initialised." + echo "" + echo "Next steps:" + echo " 1. Edit build/container/Containerfile — add your build commands (Tier A)" + echo " 2. Edit build/container/entrypoint.sh — set your application binary" + echo " 3. Review build/container/compose.yaml — portable stack (Tier B)" + echo " 4. Review build/container/stapeln/ — sovereign stack (Tier C)" + echo " 5. Build: just container-build" + +# Build container image (cerro-torre pipeline if present, else plain OCI build) +container-build *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh {{args}} + elif [ -f "build/container/Containerfile" ]; then + "$ENGINE" build -t czech_file_knife:latest -f build/container/Containerfile . + elif [ -f "Containerfile" ]; then + "$ENGINE" build -t czech_file_knife:latest -f Containerfile . + else + echo "No Containerfile found in build/container/ or project root" + exit 1 + fi + +# Pick the compose path: selur-compose (sovereign) if installed, else portable +_compose action *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then + ( cd build/container/stapeln && selur-compose {{action}} {{args}} ) + elif [ -f "build/container/compose.yaml" ]; then + "$ENGINE" compose -f build/container/compose.yaml {{action}} {{args}} + else + echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" + exit 1 + fi + +# Verify compose configuration (selur-compose verify, else compose config) +container-verify: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then + ( cd build/container/stapeln && selur-compose verify ) + elif [ -f "build/container/compose.yaml" ]; then + "$ENGINE" compose -f build/container/compose.yaml config + else + echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" + exit 1 + fi + +# Start the container stack (selur-compose if available, else $ENGINE compose) +container-up *args: + @just _compose up {{args}} + +# Stop the container stack +container-down *args: + @just _compose down {{args}} + +# Sign and verify the container bundle (cerro-torre: build + pack + sign + verify) +container-sign: + #!/usr/bin/env bash + set -euo pipefail + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && ./ct-build.sh + else + echo "No build/container/stapeln/ct-build.sh found" + exit 1 + fi + +# Push the signed bundle (or plain image) to the registry +container-push: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh --push + else + echo "No build/container/stapeln/ct-build.sh found — falling back to $ENGINE push" + "$ENGINE" push czech_file_knife:latest + fi + +# Run the container interactively (for debugging) +container-run *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + "$ENGINE" run --rm -it czech_file_knife:latest {{args}} + +# Container matrix: [build|run|push|shell|scan] x [registry] x [tag] +container-matrix action="build" registry="ghcr.io/hyperpolymath" tag="latest": + @echo "Container matrix: action={{action}} registry={{registry}} tag={{tag}}" + +# ── Narrowing / opt-out ────────────────────────────────────────────────────── +# +# `container-keep` is the workhorse: declare exactly which tiers to KEEP; the +# rest are removed. Tiers: oci (A) · portable (B) · stapeln (C) · devcontainer. +# Idempotent — safe to re-run. The aliases below are thin wrappers. +# +# just container-keep oci # bare OCI image only +# just container-keep oci portable # image + portable compose +# just container-keep oci portable stapeln # full prod stack, no dev container +# just container-keep all # no-op (keep everything) +# just container-keep none # remove everything (= no-container) +# DRY_RUN=1 just container-keep oci # show what would change, do nothing +# FORCE=1 just container-keep oci # skip the confirm prompt +# +# Accepts space- or comma-separated tokens, and aliases: a/A, b/B/compose, +# c/C/sovereign, dev. 'portable'/'stapeln' require 'oci' (they build the image). +container-keep +tiers: + #!/usr/bin/env bash + set -euo pipefail + + sel="$(echo "$@" | tr ',' ' ')" + keep_oci=false keep_port=false keep_stap=false keep_dev=false + for t in $sel; do + case "$t" in + all) keep_oci=true; keep_port=true; keep_stap=true; keep_dev=true ;; + none) : ;; + oci|a|A) keep_oci=true ;; + portable|compose|b|B) keep_port=true ;; + stapeln|sovereign|c|C) keep_stap=true ;; + devcontainer|dev) keep_dev=true ;; + *) echo "Unknown tier '$t'. Valid: oci portable stapeln devcontainer | all | none" >&2; exit 1 ;; + esac + done + + # Coherence: tiers B and C build the OCI image, so they need tier A. + if { $keep_port || $keep_stap; } && ! $keep_oci; then + echo "Incoherent selection: 'portable'/'stapeln' build the OCI image — add 'oci'." >&2 + exit 1 + fi + + # Build the removal plan. + plan=() + $keep_dev || plan+=(".devcontainer/ (dev container)") + if $keep_oci; then + $keep_stap || plan+=("build/container/stapeln/ (cerro-torre, svalinn, vordr, selur, rokur, k9)") + $keep_port || plan+=("build/container/compose.yaml + compose.example.yaml (portable stack)") + else + plan+=("build/container/ (entire image + compose + stapeln)") + plan+=("build/just/container.just (this module) + its Justfile import") + [ -f .github/workflows/container-build.yml ] && plan+=(".github/workflows/container-build.yml") + fi + + echo "Keeping: oci=$keep_oci portable=$keep_port stapeln=$keep_stap devcontainer=$keep_dev" + if [ ${#plan[@]} -eq 0 ]; then echo "Nothing to remove — selection already matches."; exit 0; fi + echo "Will remove:"; printf ' - %s\n' "${plan[@]}" + + if [ "${DRY_RUN:-}" = "1" ]; then echo "(DRY_RUN — no changes made)"; exit 0; fi + if [ "${FORCE:-}" != "1" ]; then + read -rp "Proceed? [y/N] " c; [[ "${c:-N}" =~ ^[Yy] ]] || { echo "Aborted."; exit 0; } + fi + + # Apply. + $keep_dev || rm -rf .devcontainer + if $keep_oci; then + $keep_stap || rm -rf build/container/stapeln + $keep_port || rm -f build/container/compose.yaml build/container/compose.example.yaml + else + rm -rf build/container + rm -f .machine_readable/configs/selur-compose.toml + rm -f .github/workflows/container-build.yml + rm -f build/just/container.just + [ -f Justfile ] && sed -i '/# >>> container-module/,/# <<< container-module/d' Justfile + for jf in Justfile .machine_readable/contractiles/Justfile; do + [ -f "$jf" ] && sed -i '\|import? "build/just/container.just"|d' "$jf" + done + fi + + echo "" + echo "Done. Review .github/workflows/ for image build/publish jobs you no longer need." + +# Remove ALL containerisation (image, compose, stapeln, dev container) +no-container: + @just container-keep none + +# Drop only the stapeln sovereign tier (keep OCI image + portable compose + dev container) +no-stapeln: + @just container-keep oci portable devcontainer + +# Drop only the dev container (keep the full image/compose/stapeln stack) +no-devcontainer: + @just container-keep oci portable stapeln diff --git a/czech-file-knife/build/just/groove.just b/czech-file-knife/build/just/groove.just new file mode 100644 index 000000000..185403656 --- /dev/null +++ b/czech-file-knife/build/just/groove.just @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# GROOVE PROTOCOL +# +# Imported by ../../Justfile via `import? "build/just/groove.just"`. +# Recipes here configure and validate the Groove protocol manifest at +# .machine_readable/integrations/groove.a2ml — port assignment, API surface +# flags (REST/gRPC/GraphQL/WebSocket/SSE), and template-placeholder hygiene. +# The manifest itself is consumed by the Groove bridge / zig-unified-api-adapter. + +# Configure Groove protocol manifest (port assignment, API surfaces) +groove-setup: + #!/usr/bin/env bash + set -euo pipefail + MANIFEST=".machine_readable/integrations/groove.a2ml" + if [ ! -f "$MANIFEST" ]; then + echo "Error: $MANIFEST not found. Run 'just repo-init' first." + exit 1 + fi + + echo "═══════════════════════════════════════════════════" + echo " Groove Protocol Setup" + echo "═══════════════════════════════════════════════════" + echo "" + echo "Check PORT-REGISTRY.md before assigning a port:" + echo " https://github.com/hyperpolymath/standards/blob/main/PORT-REGISTRY.md" + echo "" + + read -rp "Primary port for this service: " PORT + [ -z "$PORT" ] && echo "Error: port required" && exit 1 + + echo "" + echo "Which API surfaces does this project expose?" + read -rp " REST API? [Y/n]: " REST + read -rp " gRPC? [y/N]: " GRPC + read -rp " GraphQL? [y/N]: " GRAPHQL + read -rp " WebSocket? [y/N]: " WS + read -rp " SSE (Server-Sent Events)? [y/N]: " SSE + + # Update port in manifest + sed -i "s/(port 0)/(port ${PORT})/" "$MANIFEST" + + # Update API surface flags + [[ "${GRPC,,}" == "y" ]] && sed -i 's/(grpc.*enabled false)/(grpc (enabled true)/' "$MANIFEST" + [[ "${GRAPHQL,,}" == "y" ]] && sed -i 's/(graphql.*enabled false)/(graphql (enabled true)/' "$MANIFEST" + [[ "${WS,,}" == "y" ]] && sed -i 's/(websocket.*enabled false)/(websocket (enabled true)/' "$MANIFEST" + [[ "${SSE,,}" == "y" ]] && sed -i 's/(sse.*enabled false)/(sse (enabled true)/' "$MANIFEST" + + echo "" + echo "Groove manifest updated: $MANIFEST" + echo "Port ${PORT} assigned. Add to PORT-REGISTRY.md if not already there." + +# Check for template placeholders that haven't been replaced +verify-template: + #!/usr/bin/env bash + set -euo pipefail + echo "Checking for unreplaced template placeholders..." + FOUND=0 + + # Check for double-brace placeholder patterns + HITS=$(grep -rn '{{'{{'}}[A-Z_]*{{'}}'}}' --include="*.adoc" --include="*.md" --include="*.a2ml" \ + --include="*.scm" --include="*.toml" --include="*.yml" --include="*.yaml" \ + . 2>/dev/null | grep -v 'node_modules\|\.git/' | grep -v 'PLACEHOLDERS.adoc' || true) + if [ -n "$HITS" ]; then + echo "" + echo "⚠ Unreplaced placeholders found:" + echo "$HITS" | head -20 + FOUND=1 + fi + + # Check for template defaults still present + if grep -q 'czech-file-knife' Justfile 2>/dev/null; then + echo "⚠ Justfile still references 'czech-file-knife' — update project name" + FOUND=1 + fi + + # Check for port 0 in Groove manifest + if grep -q '(port 0)' .machine_readable/integrations/groove.a2ml 2>/dev/null; then + echo "⚠ Groove manifest has port 0 — run 'just groove-setup' to assign a port" + FOUND=1 + fi + + # Check for empty SCM files + for f in .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/ECOSYSTEM.a2ml; do + if [ -f "$f" ] && grep -q '{{'{{'}}' "$f" 2>/dev/null; then + echo "⚠ $f still has template placeholders" + FOUND=1 + fi + done + + if [ $FOUND -eq 0 ]; then + echo "✓ No template placeholders found — project is properly customised." + else + echo "" + echo "Run 'just repo-init' to replace placeholders, or edit files manually." + exit 1 + fi diff --git a/czech-file-knife/build/just/proofs.just b/czech-file-knife/build/just/proofs.just new file mode 100644 index 000000000..dfd633466 --- /dev/null +++ b/czech-file-knife/build/just/proofs.just @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# FORMAL VERIFICATION (PROOFS) +# +# Imported by ../../Justfile via `import? "build/just/proofs.just"`. +# Recipes here check formal proofs across Idris2, Lean4, Agda, and Coq, plus +# scan for dangerous/unsafe constructs and report status. Run via +# `just proof-check-all` for the full sweep. + +# Check all formal proofs (Idris2 + Lean4 + Agda + Coq) +proof-check-all: proof-check-idris2 proof-check-lean4 proof-check-agda proof-check-coq proof-scan-dangerous + @echo "=== All proof checks complete ===" + +# Each proof-check- delegates to scripts/check-proofs.sh, the single +# source of truth. A missing toolchain is FATAL (never a skip); every module is +# checked from its own source root; a proof file absent from the prover's +# manifest (verification/proofs//MANIFEST) is an error. See the script +# header for the four "checks that could not fail" this replaces. + +# Check Idris2 proofs (per MANIFEST; fatal if idris2 absent) +proof-check-idris2: + @bash scripts/check-proofs.sh idris2 + +# Check Lean4 proofs (per MANIFEST; fatal if lean absent) +proof-check-lean4: + @bash scripts/check-proofs.sh lean4 + +# Check Agda proofs (per MANIFEST; fatal if agda absent) +proof-check-agda: + @bash scripts/check-proofs.sh agda + +# Check Coq proofs (per MANIFEST; fatal if coqc absent) +proof-check-coq: + @bash scripts/check-proofs.sh coq + +# Scan for dangerous constructs USED in proof code (believe_me, sorry, Admitted, +# postulate, ...). Comments are ignored — a comment naming a banned construct +# must not trip the gate. See scripts/scan-dangerous.sh. +proof-scan-dangerous: + @bash scripts/scan-dangerous.sh + +# Show proof status summary +proof-status: + #!/usr/bin/env bash + echo "=== Proof Status ===" + echo "" + echo "Idris2: $(find verification/proofs/idris2 -name '*.idr' 2>/dev/null | wc -l) files" + echo "Lean4: $(find verification/proofs/lean4 -name '*.lean' 2>/dev/null | wc -l) files" + echo "Agda: $(find verification/proofs/agda -name '*.agda' 2>/dev/null | wc -l) files" + echo "Coq: $(find verification/proofs/coq -name '*.v' 2>/dev/null | wc -l) files" + echo "TLA+: $(find verification/proofs/tlaplus -name '*.tla' 2>/dev/null | wc -l) files" + echo "" + # PROOF-STATUS may live at root, docs/status/ (post-#20), .md or .adoc (post-#23) + for candidate in docs/status/PROOF-STATUS.adoc docs/status/PROOF-STATUS.md PROOF-STATUS.adoc PROOF-STATUS.md; do + if [ -f "$candidate" ]; then + grep -E "^\| \*\*Total\*\*|^\| \*Total\*" "$candidate" 2>/dev/null || echo "(No summary row in $candidate)" + exit 0 + fi + done + echo "(No PROOF-STATUS file found at root or docs/status/)" diff --git a/czech-file-knife/build/just/repo-init.just b/czech-file-knife/build/just/repo-init.just new file mode 100644 index 000000000..c3136b7a5 --- /dev/null +++ b/czech-file-knife/build/just/repo-init.just @@ -0,0 +1,902 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# INIT — Bootstrap a new project from this template +# +# Imported by ../../Justfile via `import? "build/just/repo-init.just"`. +# Variables (project, OWNER, REPO, version, tier) and the `set shell` directive +# are inherited from the root Justfile. + +# Interactive project bootstrap — replaces all {{PLACEHOLDER}} tokens +# Optional archetype argument (e.g. `just repo-init julia-library`) applies an +# archetype overlay: see archetypes/README.adoc and ADR-0003. +repo-init archetype="": + #!/usr/bin/env bash + set -euo pipefail + + # Non-interactive support: if RSR_NON_INTERACTIVE is set, dummy-out 'read'. + # This prevents blocking on input and seamlessly uses environment variables. + if [ -n "${RSR_NON_INTERACTIVE:-}" ]; then + read() { return 0; } + fi + + echo "═══════════════════════════════════════════════════" + echo " RSR Project Bootstrap" + echo "═══════════════════════════════════════════════════" + echo "" + + # --- Archetype overlay data (ADR-0003) --- + ARCHETYPE='{{archetype}}' + ARCH_FILE="" + ARCH_PRESET="" + if [ -n "$ARCHETYPE" ]; then + ARCH_FILE="archetypes/${ARCHETYPE}/ARCHETYPE.a2ml" + if [ ! -f "$ARCH_FILE" ]; then + echo "Error: unknown archetype '${ARCHETYPE}'. Available:" + ls -1 archetypes 2>/dev/null | grep -v 'README' || echo " (none)" + exit 1 + fi + ARCH_PRESET=$(sed -n 's/^preset = "\(.*\)"$/\1/p' "$ARCH_FILE" | head -1) + echo "Archetype: ${ARCHETYPE}${ARCH_PRESET:+ (capability preset: $ARCH_PRESET)}" + echo "" + fi + + # --- Load defaults from config (if exists) --- + # Create yours: ~/.config/rsr/defaults + # Format: OWNER=myorg AUTHOR="My Name" AUTHOR_EMAIL=me@example.org ... + DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" + if [ -f "$DEFAULTS" ]; then + echo "Loading defaults from $DEFAULTS" + # shellcheck source=/dev/null + source "$DEFAULTS" + echo "" + fi + + # --- Required values (pre-filled from defaults if available) --- + read -rp "Project name (human-readable, e.g. My Project): " PROJECT_NAME + [ -z "$PROJECT_NAME" ] && echo "Error: project name required" && exit 1 + + read -rp "Repository slug (e.g. my-project): " REPO + [ -z "$REPO" ] && echo "Error: repo slug required" && exit 1 + if [[ ! "$REPO" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then + echo "Error: repo slug must be lowercase alphanumeric words separated by single hyphens" + exit 1 + fi + + read -rp "Owner [${OWNER:-}]: " _OWNER + OWNER="${_OWNER:-${OWNER:-}}" + [ -z "$OWNER" ] && echo "Error: owner required" && exit 1 + + read -rp "Author full name [${AUTHOR:-}]: " _AUTHOR + AUTHOR="${_AUTHOR:-${AUTHOR:-}}" + [ -z "$AUTHOR" ] && echo "Error: author name required" && exit 1 + + read -rp "Author email [${AUTHOR_EMAIL:-}]: " _AUTHOR_EMAIL + AUTHOR_EMAIL="${_AUTHOR_EMAIL:-${AUTHOR_EMAIL:-}}" + [ -z "$AUTHOR_EMAIL" ] && echo "Error: email required" && exit 1 + + # --- Optional values (pre-filled from defaults if available) --- + read -rp "Author organization [${AUTHOR_ORG:-none}]: " _AUTHOR_ORG + AUTHOR_ORG="${_AUTHOR_ORG:-${AUTHOR_ORG:-}}" + + read -rp "Previous/alt email [${AUTHOR_EMAIL_ALT:-none}]: " _AUTHOR_EMAIL_ALT + AUTHOR_EMAIL_ALT="${_AUTHOR_EMAIL_ALT:-${AUTHOR_EMAIL_ALT:-}}" + + read -rp "Project description []: " PROJECT_DESCRIPTION + + read -rp "Forge domain [${FORGE:-github.com}]: " _FORGE + FORGE="${_FORGE:-${FORGE:-github.com}}" + + read -rp "Security contact email [${SECURITY_EMAIL:-$AUTHOR_EMAIL}]: " _SECURITY_EMAIL + SECURITY_EMAIL="${_SECURITY_EMAIL:-${SECURITY_EMAIL:-$AUTHOR_EMAIL}}" + + read -rp "Conduct contact email [${CONDUCT_EMAIL:-$AUTHOR_EMAIL}]: " _CONDUCT_EMAIL + CONDUCT_EMAIL="${_CONDUCT_EMAIL:-${CONDUCT_EMAIL:-$AUTHOR_EMAIL}}" + + read -rp "Project type (library|binary|monorepo|service|website) [library]: " PROJECT_TYPE + PROJECT_TYPE="${PROJECT_TYPE:-library}" + + read -rp "Website URL [https://${FORGE}/${OWNER}/${REPO}]: " WEBSITE + WEBSITE="${WEBSITE:-https://${FORGE}/${OWNER}/${REPO}}" + + read -rp "OpenSSF Best Practices project ID (blank if not yet registered) []: " OPENSSF_BP_ID + + # --- Container values (optional — only relevant if build/container/ exists) --- + if [ -d "build/container" ]; then + echo "" + echo "── Container configuration (optional) ─────────" + read -rp "Service name [${REPO}]: " _SERVICE_NAME + SERVICE_NAME="${_SERVICE_NAME:-${REPO}}" + read -rp "Primary port [8080]: " _PORT + PORT="${_PORT:-8080}" + read -rp "Container registry [ghcr.io/${OWNER}]: " _REGISTRY + REGISTRY="${_REGISTRY:-ghcr.io/${OWNER}}" + else + SERVICE_NAME="${REPO}" + PORT="8080" + REGISTRY="ghcr.io/${OWNER}" + fi + + # --- Derived values --- + PROJECT_UPPER=$(echo "$REPO" | tr '[:lower:]-' '[:upper:]_') + PROJECT_LOWER=$(echo "$REPO" | tr '[:upper:]-' '[:lower:]_') + CURRENT_YEAR=$(date +%Y) + CURRENT_DATE=$(date +%Y-%m-%d) + VERSION="0.1.0" + + # --- Derive the repo's uuid -------------------------------------------- + # THE UUID IS DERIVED, NOT ALLOCATED (gv-clade-index: + # docs/SPEC-clade-verisim-portal.adoc §Identity Model): + # uuid = UUIDv5(namespace = URL, name = "github.com//") + # We know OWNER and REPO, so this is computable here and must never be + # guessed or copied. The owner segment is part of the derived name, so it is + # part of the identity — a repo hosted elsewhere derives a different uuid. + if command -v uuidgen >/dev/null 2>&1; then + REPO_UUID=$(uuidgen --sha1 --namespace @url --name "${FORGE}/${OWNER}/${REPO}") + else + REPO_UUID="UNASSIGNED" + fi + + # Derive citation name parts (best-effort split on last space) + AUTHOR_LAST="${AUTHOR##* }" + AUTHOR_FIRST="${AUTHOR% *}" + FIRST_INITIAL="${AUTHOR_FIRST:0:1}." + if [ "$AUTHOR_LAST" = "$AUTHOR_FIRST" ]; then + AUTHOR_FIRST="$AUTHOR" + AUTHOR_LAST="" + FIRST_INITIAL="" + fi + + echo "" + echo "── Summary ──────────────────────────────────────" + echo " Project: $PROJECT_NAME" + echo " Repo: $REPO" + echo " Owner: $OWNER" + echo " Author: $AUTHOR <$AUTHOR_EMAIL>" + [ -n "$AUTHOR_ORG" ] && echo " Organization: $AUTHOR_ORG" + echo " Forge: $FORGE" + echo " Year: $CURRENT_YEAR" + echo "────────────────────────────────────────────────" + echo "" + read -rp "Proceed? [Y/n] " CONFIRM + [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 + + echo "" + echo "Replacing placeholders..." + + # Apply archetype overlay files (if any) BEFORE substitution, so overlay + # files are rendered along with the rest of the tree. + if [ -n "$ARCHETYPE" ] && [ -d "archetypes/${ARCHETYPE}/overlay" ]; then + cp -a "archetypes/${ARCHETYPE}/overlay/." . + echo " applied overlay: archetypes/${ARCHETYPE}/overlay/" + fi + + # Seed docs/ from the documentation seed, BEFORE substitution so the seed's + # own placeholder tokens are rendered along with everything else. + # + # This is an OVERLAY, not a replacement. The seed's four perspective docs + # and its ADR template win on collision, because they are the + # downstream-facing shape; everything else the spine ships under docs/ + # survives -- notably docs/governance/ and docs/legal/, which + # build/just/validate.just hard-requires, so a wholesale replace would + # break the minted repo's own validate gate. + # + # The seed's own README.adoc is instructions-for-use ("copy this, replace + # the placeholders, delete this README"), not content, so it is not copied. + # tar is used rather than cp so that existing docs/ subdirectories MERGE + # instead of nesting (cp -a decisions docs/ would yield docs/decisions/decisions). + if [ -d "build/docs-seed" ]; then + mkdir -p docs + ( cd build/docs-seed && tar cf - --exclude=README.adoc . ) | ( cd docs && tar xf - ) + echo " seeded docs/ from build/docs-seed/ (its own README omitted)" + fi + + # Brace tokens as variables (hex avoids just interpolation) + LB=$(printf '\x7b\x7b') + RB=$(printf '\x7d\x7d') + + # Build the sed expression list + # Note: using | as delimiter since URLs contain / + SED_ARGS=( + -e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g" + -e "s|${LB}PROJECT_DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" + -e "s|${LB}PROJECT${RB}|${PROJECT_UPPER}|g" + -e "s|${LB}project${RB}|${PROJECT_LOWER}|g" + -e "s|${LB}REPO${RB}|${REPO}|g" + -e "s|${LB}OWNER${RB}|${OWNER}|g" + -e "s|${LB}AUTHOR${RB}|${AUTHOR}|g" + -e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g" + -e "s|${LB}AUTHOR_ORG${RB}|${AUTHOR_ORG}|g" + -e "s|${LB}AUTHOR_LAST${RB}|${AUTHOR_LAST}|g" + -e "s|${LB}AUTHOR_FIRST${RB}|${AUTHOR_FIRST}|g" + -e "s|${LB}AUTHOR_INITIALS${RB}|${FIRST_INITIAL}|g" + -e "s|${LB}FORGE${RB}|${FORGE}|g" + -e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g" + -e "s|${LB}CURRENT_DATE${RB}|${CURRENT_DATE}|g" + -e "s|${LB}DATE${RB}|${CURRENT_DATE}|g" + -e "s|${LB}SECURITY_EMAIL${RB}|${SECURITY_EMAIL}|g" + -e "s|${LB}CONDUCT_EMAIL${RB}|${CONDUCT_EMAIL}|g" + -e "s|${LB}LICENSE${RB}|MPL-2.0|g" + -e "s|${LB}CONDUCT_TEAM${RB}|Code of Conduct Committee|g" + -e "s|${LB}RESPONSE_TIME${RB}|48 hours|g" + -e "s|${LB}MAIN_BRANCH${RB}|main|g" + -e "s|${LB}PROJECT_PURPOSE${RB}|${PROJECT_DESCRIPTION}|g" + -e "s|${LB}PROJECT_ROLE${RB}|${PROJECT_TYPE}|g" + -e "s|${LB}PROJECT_TYPE${RB}|${PROJECT_TYPE}|g" + -e "s|${LB}WEBSITE${RB}|${WEBSITE}|g" + -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" + -e "s|${LB}PORT${RB}|${PORT}|g" + -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" + -e "s|${LB}IMAGE${RB}|${REGISTRY}/${SERVICE_NAME}|g" + -e "s|${LB}VERSION${RB}|${VERSION}|g" + -e "s|${LB}EMAIL${RB}|${AUTHOR_EMAIL}|g" + -e "s|${LB}UUID${RB}|${REPO_UUID}|g" + -e "s|${LB}DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" + ) + [ -n "$AUTHOR_EMAIL_ALT" ] && SED_ARGS+=(-e "s|${LB}AUTHOR_EMAIL_ALT${RB}|${AUTHOR_EMAIL_ALT}|g") + [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_BP_ID${RB}|${OPENSSF_BP_ID}|g") + [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_PROJECT_ID${RB}|${OPENSSF_BP_ID}|g") + + # Archetype-determined tokens (ADR-0003): the archetype contract fills + # what it can honestly determine. Added BEFORE the UNASSIGNED fallback + # below — sed applies expressions in order, so a token the archetype + # answers never reaches the fallback, and one it omits still fails loudly. + if [ -n "$ARCH_FILE" ]; then + while IFS= read -r arch_line; do + arch_tok="${arch_line%%=*}"; arch_tok="${arch_tok// /}" + arch_val=$(printf '%s' "$arch_line" | sed -n 's/^[^=]*= *"\(.*\)" *$/\1/p') + { [ -n "$arch_tok" ] && [ -n "$arch_val" ]; } || continue + case "$arch_val" in *'|'*) + echo "WARN: archetype token $arch_tok contains '|' (the sed delimiter) — skipped, stays UNASSIGNED" + continue ;; + esac + SED_ARGS+=(-e "s|${LB}${arch_tok}${RB}|${arch_val}|g") + done < <(awk '$0=="[tokens]"{f=1;next} /^\[/{f=0} f && !/^[[:space:]]*#/ && NF' "$ARCH_FILE") + fi + + # Tokens only the author can answer. init cannot know a project's unique + # strength or its language stack, and guessing would write fiction into + # ANCHOR.a2ml — the file the estate treats as semantic authority. They + # become UNASSIGNED, the same marker `just repo-init` already writes for an + # unchosen clade: honest, greppable, and obviously unfinished, whereas a + # leftover brace token just looks like the template broke. + # grep -rn UNASSIGNED . — to find what still needs you + # (Brace tokens are spelled via ${LB}/${RB} throughout this recipe: just + # interpolates literal double braces even inside comments.) + for tok in PROJECT_KIND PROJECT_DOMAIN PROJECT_UNIQUE_STRENGTH \ + TARGET_AUDIENCE LANG_STACK BUILD_CMD TEST_CMD MUST_INVARIANTS \ + PACKAGE_NAME DEPS BUILD_OUTPUT_PATH MAIN_FUNCTION; do + SED_ARGS+=(-e "s|${LB}${tok}${RB}|UNASSIGNED|g") + done + + # julia-library archetype: derive the package uuid at mint (owner ruling + # 2026-09-19: generate at mint). Same DERIVABLE class as the REPO uuid + # above - computed, never guessed. Namespace = the repo's own derived + # uuid, name = julia:: stable across re-mints of the same repo, + # never collides with the repo uuid. No uuidgen AND no python3 on the + # operator machine -> UNASSIGNED (the honest fallback at :437); Pkg + # then refuses the package loudly until the owner assigns one. + if [ "$ARCHETYPE" = "julia-library" ]; then + if command -v uuidgen >/dev/null 2>&1; then + PACKAGE_UUID=$(uuidgen --sha1 --namespace "${REPO_UUID}" --name "julia:${REPO}") + elif command -v python3 >/dev/null 2>&1; then + PACKAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") + else + PACKAGE_UUID=UNASSIGNED + fi + SED_ARGS+=(-e "s|${LB}PACKAGE_UUID${RB}|${PACKAGE_UUID}|g") + echo " package uuid: ${PACKAGE_UUID} (derived from repo uuid ${REPO_UUID})" + fi + + # Optional values with no answer: drop the line that depends on them rather + # than leaving the token in place. An unfilled token is not a neutral + # reminder — the OpenSSF line renders a broken badge image and a dead link + # in the new repo's README, and it trips the placeholder gate on every + # subsequent push, which trains people to ignore a red CI. No id yet means + # no badge yet; add it when you register at bestpractices.dev. + if [ -z "$OPENSSF_BP_ID" ]; then + sed -i "/bestpractices\.dev\/projects\/${LB}OPENSSF_BP_ID${RB}/d" README.adoc 2>/dev/null || true + sed -i "/${LB}OPENSSF_PROJECT_ID${RB}/d" docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc 2>/dev/null || true + fi + # A .mailmap exists to map an alternate address onto the canonical one. + # With no alternate address there is nothing to map, and the sole entry + # would keep an unfilled alt-email token in the angle brackets, which is a + # malformed mailmap rather than a harmless leftover. + # git reads .mailmap from the WORKTREE ROOT only (or via mailmap.file / + # mailmap.blob, which nothing here configures). The copy that used to live + # under .github/ was therefore inert and was never filled. + if [ -z "$AUTHOR_EMAIL_ALT" ]; then + printf '# No alternate author address to map.\n' > .mailmap + fi + + # Replace in all text files (skip .git, LICENSE text, and binaries) + find . -type f \ + -not -path './.git/*' \ + -not -name 'MPL-2.0.txt' \ + -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ + -not -name '*.woff' -not -name '*.woff2' \ + | while read -r file; do + if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then + sed -i "${SED_ARGS[@]}" "$file" + fi + done + + # Also replace [YOUR-REPO-NAME] and [YOUR-NAME/ORG] in the repo deed + # (the family-7 allocation manifest folded into it — standards#837 pilot). + sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" rsr-template-repo_chora.deed 2>/dev/null || true + sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" rsr-template-repo_chora.deed 2>/dev/null || true + + # --- Instruction-block pass -------------------------------------------- + # Delete the "TEMPLATE INSTRUCTIONS (delete this block before publishing)" + # comments. Nothing ever did, and 211 estate repos still carry one. The + # block is self-detonating: its own first line names a LITERAL doubled-brace + # token, so the sed pass above has nothing to match, yet every placeholder + # gate greps exactly that shape — a repo with every real token correctly + # substituted still trips its own gate. Rationale and measurements in + # scripts/strip-instruction-blocks.rs. + # + # Runs AFTER substitution, so real tokens are already filled; only the + # instructions go. Deliberately skipped for *-template-repo itself: the + # template's own blocks ARE the product, and its gate exempts it by remote + # name — the same protection the self-name pass below uses. + if [ "$REPO" != "${REPO%-template-repo}" ]; then + echo " instruction blocks: skipped (this IS a template repo)" + else + bash scripts/rust-tool.sh strip-instruction-blocks . + fi + + # --- Dependabot ecosystem pass ----------------------------------------- + # dependabot.yml ships every common ecosystem under "remove unused ones for + # your project". Nothing removed them: measured estate-wide, 206/206 repos + # declaring `pip` have no Python manifest, and each such entry fails on + # every scheduled run. Prune on the ARCHETYPE rather than on file presence — + # a freshly minted tree has no manifests yet, so presence would delete + # everything. Rationale in scripts/prune-dependabot-ecosystems.rs. + keep_eco="github-actions" + case "${ARCH_PRESET:-}" in *rust*|*cargo*) keep_eco="$keep_eco cargo" ;; esac + case "${ARCH_PRESET:-}" in *node*|*js*|*ts*|*deno*|*bun*|*web*) keep_eco="$keep_eco npm" ;; esac + case "${ARCH_PRESET:-}" in *elixir*|*mix*) keep_eco="$keep_eco mix" ;; esac + case "${ARCH_PRESET:-}" in *python*|*py*) keep_eco="$keep_eco pip" ;; esac + bash scripts/rust-tool.sh prune-dependabot-ecosystems .github/dependabot.yml $keep_eco + + # --- Self-name pass (ADR-0003) ----------------------------------------- + # The template's own name is written as a LITERAL, not as a placeholder + # token, so the substitution loop above never touched it. Measured on a + # scratch mint of 346ae56: a repo minted as `mint-check-lib` still carried + # Justfile: project := "rsr-template-repo" + # Justfile: REPO := "rsr-template-repo" + # sonar-project.properties sonar.projectKey=hyperpolymath_rsr-template-repo + # STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo" + # i.e. it declared that it WAS the template, and would have reported its + # code analysis into the TEMPLATE's SonarCloud project. This is the same + # failure the CLADE.a2ml block below fixes for the uuid/canonical-name; + # nothing generalised it to the rest of the tree. Confirmed in the wild: + # hyperpolymath/cargo-zigbuild still carries both Justfile lines. + # + # Provenance must SURVIVE this pass — a minted repo is supposed to record + # what it came from. Two protections: paths that are ABOUT the template + # (ADRs, its changelog, its own audit, and this recipe itself — which + # holds PARENT_SLUG) are skipped wholesale, and within every other file + # any line that names the parent AS a parent is left byte-for-byte alone. + if [ "$REPO" != "rsr-template-repo" ]; then + SELF_PROV='instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: ' + find . -type f \ + -not -path './.git/*' \ + -not -path './build/just/repo-init.just' \ + -not -path './docs/decisions/*' \ + -not -path './.machine_readable/descriptiles/CLADE.a2ml' \ + -not -path './.machine_readable/descriptiles/VARIANT.a2ml' \ + -not -name 'CHANGELOG.md' \ + -not -name 'TEMPLATE-STANDARDS-AUDIT.adoc' \ + -not -name 'TEMPLATE-LINEAGE-AUDIT.adoc' \ + -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ + -not -name '*.woff' -not -name '*.woff2' \ + | while read -r file; do + grep -q 'rsr-template-repo' "$file" 2>/dev/null || continue + file --brief "$file" | grep -qi 'text\|ascii\|utf' || continue + # sed -i preserves the mode bit; rewriting via a temp file would not. + sed -i -E "/${SELF_PROV}/!{s|hyperpolymath/rsr-template-repo|${OWNER}/${REPO}|g;s|rsr-template-repo|${REPO}|g;}" "$file" + done + echo " self-name: rewrote template literal -> ${REPO} (provenance lines preserved)" + + # The repo deed's FILENAME carries the repo slug (deed dispatch: + # _chora.deed — deed.abnf v1.0.0). The self-name pass rewrote + # the slug inside the file; the file itself is renamed here. Plain mv: + # mint runs before the new repo's first commit. Renames (not tokens) + # are how filenames change — a czech-file-knife token in a FILENAME is the + # brace-collapse hazard class repo-init has already been burnt by. + if [ -f rsr-template-repo_chora.deed ]; then + mv rsr-template-repo_chora.deed "${REPO}_chora.deed" + echo " repo deed: renamed rsr-template-repo_chora.deed -> ${REPO}_chora.deed" + fi + + # ── self-OWNER pass ────────────────────────────────────────────────── + # + # Same class as the self-name leak above, and missed by it: the + # template's own OWNER is a plain literal, so nothing rewrote it. A repo + # minted under a different owner shipped: + # + # Justfile OWNER := "hyperpolymath" + # sonar-project.properties sonar.projectKey=hyperpolymath_ + # + # i.e. it declared the wrong owner and reported its code analysis into + # the wrong SonarCloud project. Measured on a metadatastician mint. + # + # This pass is deliberately NARROW — three exact, identity-bearing + # lines. A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: + # `hyperpolymath/standards`, `hyperpolymath/proven` and the other estate + # dependencies are real repos every child genuinely points at, and + # rewriting those would break the child's canon, CI and badges. + if [ "$OWNER" != "hyperpolymath" ]; then + sed -i -E 's|^(OWNER[[:space:]]*:=[[:space:]]*)"hyperpolymath"|\1"'"${OWNER}"'"|' Justfile + sed -i -E 's|^sonar\.organization=hyperpolymath$|sonar.organization='"${OWNER}"'|' sonar-project.properties + sed -i -E 's|^sonar\.projectKey=hyperpolymath_|sonar.projectKey='"${OWNER}"'_|' sonar-project.properties + echo " self-owner: rewrote owner literal -> ${OWNER} (estate dependency paths preserved)" + echo " NOTE: confirm the SonarCloud ORG is really '${OWNER}' before provisioning —" + echo " the GitHub owner and the SonarCloud org are not always the same." + fi + fi + + echo "" + echo "── Identity (CLADE.a2ml) ────────────────────────" + + # Install THIS repo's CLADE.a2ml, replacing rsr-template-repo's own. + # + # The CLADE.a2ml shipped in the template is the TEMPLATE's real identity — a + # real uuid for a real repo. Nothing in SED_ARGS ever touched it, so every + # repo created from this template inherited uuid a5ea1382-… and + # canonical-name "rsr-template-repo" verbatim, and told every arriving agent + # it WAS the template. Nothing caught it: each value was individually valid. + # chronicles-of-slavia and scaffoldia were corrected for exactly this on + # 2026-07-16; paint-type, cargo-zigbuild, email-octad-experiment, llm-grace + # and rsr-template-how-to still carry it. + # + # build/templates/CLADE.a2ml.in has already had its brace tokens filled by + # the substitution loop above (including the UUID one, derived earlier). + # NB: literal double-brace tokens cannot be written in this recipe body — + # just interpolates them, which is why LB/RB are built with printf above. + # The split is the whole point: + # DERIVABLE -> computed, never guessed (uuid, owner, canonical-name) + # JUDGEMENT -> left UNASSIGNED, fails loudly until a human chooses (clade) + CLADE_IN="build/templates/CLADE.a2ml.in" + CLADE_OUT=".machine_readable/descriptiles/CLADE.a2ml" + if [ -f "$CLADE_IN" ]; then + mkdir -p "$(dirname "$CLADE_OUT")" + cp "$CLADE_IN" "$CLADE_OUT" + rm -rf build/templates # template-only; not part of your repo + if [ "$REPO_UUID" = "UNASSIGNED" ]; then + echo " uuid: UNASSIGNED — uuidgen not found." + echo " Install util-linux (uuid-runtime), then run:" + echo " uuidgen --sha1 --namespace @url --name \"${FORGE}/${OWNER}/${REPO}\"" + echo " and put the result in $CLADE_OUT." + else + echo " uuid: $REPO_UUID" + echo " (uuid5 of ${FORGE}/${OWNER}/${REPO} — derived, recomputable, not invented)" + fi + echo " clade: UNASSIGNED — a human must choose one of the 12." + echo " CLADE-003/006 FAIL until you do. That is deliberate:" + echo " an unchosen clade must not look like a chosen one." + echo " The taxonomy is listed in $CLADE_OUT." + fi + + echo "" + echo "── Profile & provenance (ADR-0003) ──────────────" + + # The template's own rsr-profile.a2ml is deliberately MAXIMAL (it is the + # dogfood target). A minted repo must NOT inherit it: declaring + # capabilities the tree lacks makes the oracle score modules that are + # absent (na-honesty, RSR-SPEC-v2 §scoring). Rewrite it minimally. + PROFILE_OUT=".machine_readable/rsr-profile.a2ml" + + # ── Canon pin (bind the canon) ────────────────────────────────────────── + # Read the canon identity from the template's [canon] block BEFORE that + # block is overwritten below. A minted repo must record WHICH canon release + # it was cut from; without it "which repos are on canon 1.x?" is a campaign + # across the whole estate instead of a grep. + # + # Count rather than guessed: a value here that silently became empty is the + # same class of defect as an UNASSIGNED placeholder, and would let a repo + # claim conformance to nothing. + # Section reader: `canon_key ` prints the value of from + # the [canon] section, or nothing. + # + # Deliberately escape-free: the first attempt spelled the section-header + # test as /^\[/ , which reached awk as a DOUBLE backslash followed by an + # unterminated bracket expression. awk does not error on that - it parses a + # character class that swallows the rest of the program and then matches + # nothing, so the pin came out silently EMPTY rather than failing loudly. + # `substr($0,1,1)=="["` cannot be mis-escaped. This is the same class of + # defect as an UNASSIGNED placeholder: a value that looks computed but is + # empty would let a minted repo claim conformance to nothing. + canon_key() { + awk -v want="[canon]" -v key="$1" ' + substr($0,1,1)=="[" { f = ($0==want) ? 1 : 0; next } + f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { + sub(/^[^=]*=[[:space:]]*/, "") + gsub(/^["[:space:]]+|["[:space:]]+$/, "") + print; exit + } + ' "$2" + } + + CANON_VERSION="" + CANON_CRITERIA_SHA="" + CANON_GATES_SHA="" + if [ -f "$PROFILE_OUT" ]; then + CANON_VERSION="$(canon_key version "$PROFILE_OUT")" + CANON_CRITERIA_SHA="$(canon_key criteria_sha256 "$PROFILE_OUT")" + CANON_GATES_SHA="$(canon_key gates_sha256 "$PROFILE_OUT")" + fi + CANON_VERSION="${CANON_VERSION:-UNASSIGNED}" + CANON_CRITERIA_SHA="${CANON_CRITERIA_SHA:-UNASSIGNED}" + CANON_GATES_SHA="${CANON_GATES_SHA:-UNASSIGNED}" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# rsr-profile.a2ml — this repo's declared RSR v2.0 capabilities." + echo "# Generated by just repo-init on ${CURRENT_DATE}. The template's maximal" + echo "# profile was removed at mint: declare only what this tree carries." + echo "" + echo "[rsr-profile]" + echo "version = \"1.0.0\"" + echo "spec = \"rsr-criteria-v2\"" + echo "declares-against = \"2.0.0-draft\"" + if [ -n "$ARCH_PRESET" ]; then + echo "preset = \"${ARCH_PRESET}\"" + else + echo "# UNASSIGNED: declare capabilities (or a preset) — see standards" + echo "# .machine_readable/template-capability-gates.toml" + echo "capabilities = []" + fi + } > "$PROFILE_OUT" + echo " profile: $PROFILE_OUT${ARCH_PRESET:+ (preset $ARCH_PRESET)}" + + # Provenance — the estate's answer-file (ADR-0002 contract shape, used + # per ADR-0003 with direction=generated-from-core). + # + # repo-init runs INSIDE the template checkout, so the LOCAL git state is + # the truth about what was actually copied. Prefer it. The previous + # implementation read the LIVE remote tip (`git ls-remote ... HEAD`), + # which records where the remote is *now* rather than the tree in hand: + # mint from a branch, or from a checkout with unpushed commits, and + # PROVENANCE named a commit the child never contained. That defeated the + # answer-file's whole purpose. Remote lookup remains only as a fallback, + # and the symref form also recovers the source BRANCH, which was never + # recorded at all (#203). + PARENT_SLUG="hyperpolymath/rsr-template-repo" + PARENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true) + PARENT_PIN=$(git rev-parse HEAD 2>/dev/null || true) + PARENT_TREE=$(git rev-parse 'HEAD^{tree}' 2>/dev/null || true) + + if [ -z "$PARENT_PIN" ]; then + # Not a checkout (tarball, shallow copy, or offline): fall back to the + # remote, and recover the branch from the symref. + PARENT_BRANCH=$(git ls-remote --symref "https://github.com/${PARENT_SLUG}.git" HEAD 2>/dev/null \ + | sed -n 's|^ref: refs/heads/\([^[:space:]]*\).*|\1|p' | head -1 || true) + PARENT_PIN=$(git ls-remote "https://github.com/${PARENT_SLUG}.git" HEAD 2>/dev/null | cut -f1 || true) + fi + + PARENT_BRANCH="${PARENT_BRANCH:-UNASSIGNED}" + PARENT_PIN="${PARENT_PIN:-UNASSIGNED}" + PARENT_TREE="${PARENT_TREE:-UNASSIGNED}" + + # Recorded in PROVENANCE.a2ml. Non-interactive mints are the automated + # path (scaffoldia / CI); interactive ones are a human at a prompt. The + # distinction matters because #201 found a hand-minted instance whose + # generated identity was stale, and nothing recorded which path ran. + if [ -n "${RSR_NON_INTERACTIVE:-}" ]; then + MINT_MODE="non-interactive" + else + MINT_MODE="interactive" + fi + + VARIANT_OUT=".machine_readable/descriptiles/VARIANT.a2ml" + mkdir -p "$(dirname "$VARIANT_OUT")" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# VARIANT.a2ml — provenance contract (ADR-0002 shape; ADR-0003 use)." + echo "# Records what this repo was minted from so the drift gate" + echo "# (scripts/check-variant-drift.sh ) and future" + echo "# re-templating tooling have a defined input." + echo "#" + echo "# NOTE: placeholder rendering means many files differ from the" + echo "# parent by design; folding rendered answers into [normalise] is" + echo "# future work (ADR-0003), so the gate is informational for minted" + echo "# repos until then." + echo "" + echo "[metadata]" + echo "project = \"${REPO}\"" + echo "schema_version = \"0.1.0\"" + echo "last-updated = \"${CURRENT_DATE}\"" + echo "" + echo "[variant]" + echo "parent = \"${PARENT_SLUG}\"" + echo "parent-pin = \"${PARENT_PIN}\" # template tip at mint" + echo "role = \"minted-repo\"" + echo "stack = \"${ARCHETYPE:-unspecified}\"" + echo "direction = \"generated-from-core\"" + echo "" + echo "[normalise]" + echo "rules = \"action-pins self-name\"" + echo "" + echo "[paths.added]" + echo "paths = []" + echo "" + echo "[paths.removed]" + echo "paths = []" + echo "" + echo "[paths.diverged]" + echo "# Files a minted repo owns from birth." + echo "paths = [" + echo " \".machine_readable/descriptiles/CLADE.a2ml\"," + echo " \".machine_readable/descriptiles/VARIANT.a2ml\"," + echo " \".machine_readable/rsr-profile.a2ml\"," + echo "]" + } > "$VARIANT_OUT" + if [ "$PARENT_PIN" = "UNASSIGNED" ]; then + echo " provenance: $VARIANT_OUT — parent-pin UNASSIGNED (offline?)" + echo " Set it to the template commit you minted from:" + echo " git ls-remote https://github.com/${PARENT_SLUG}.git HEAD" + else + echo " provenance: $VARIANT_OUT (parent-pin ${PARENT_PIN:0:12})" + fi + + # ── PROVENANCE.a2ml — the answer-file for the canon binding ───────────── + # ADR-0003 named this the single highest-leverage gap: "a minted repo does + # not know which template commit it came from, so template improvements + # cannot be propagated and drift cannot be detected." Copier/Cruft solved + # it with an answer-file + update + check; this estate hand-simulated it as + # recurring standardisation-PR campaigns. This is the answer-file. + # + # VARIANT.a2ml above records the TEMPLATE dimension (diffing against the + # parent). This records the CANON dimension (which law the repo was cut + # under). They are different questions and both are needed. + PROVENANCE_OUT=".machine_readable/PROVENANCE.a2ml" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# PROVENANCE.a2ml — what this repo was minted FROM." + echo "# Written once, at mint, by build/just/repo-init.just. Not a hand-edited" + echo "# file: tools that want \"what is this repo\" read STATE.a2ml; tools that" + echo "# want \"where did this repo come from\" read this." + echo "" + echo "[provenance]" + echo "minted_at = \"${CURRENT_DATE}\"" + echo "" + echo "template_repo = \"${PARENT_SLUG}\"" + echo "template_branch = \"${PARENT_BRANCH}\"" + echo "template_commit = \"${PARENT_PIN}\"" + echo "template_tree = \"${PARENT_TREE}\"" + echo "# Contract: branches this repo is EXPECTED to carry beyond the default." + echo "# Empty means default-only. A mint must never inherit template work" + echo "# branches (coderabbit/, chore/, bot-task or stale branches) — that is" + echo "# how knot-knot received a byte-identical copy of a template work branch" + echo "# with no common ancestor (#203). Enforced by" + echo "# scripts/check-template-conformance.sh." + echo "extra_branches = []" + echo "" + echo "canon_version = \"${CANON_VERSION}\"" + echo "criteria_sha256 = \"${CANON_CRITERIA_SHA}\"" + echo "gates_sha256 = \"${CANON_GATES_SHA}\"" + echo "" + echo "archetype = \"${ARCHETYPE:-}\"" + echo "# Which minting path actually ran. Hardcoded as \"hand\" before this, so" + echo "# every scripted mint claimed to be hand-minted — which destroyed the" + echo "# one signal that would have told #201/#203 which path to trust." + echo "minted_by = \"repo-init\"" + echo "mint_mode = \"${MINT_MODE}\"" + echo "" + echo "[substitutions]" + echo "# ADR-0003 records the old state honestly: \"renders 34 substitutions and" + echo "# derives identity, but leaves 12 tokens UNASSIGNED\". Listing them here" + echo "# converts that from a known defect into a QUERYABLE one: a non-empty" + echo "# list means the mint did not fully render, and the validation step" + echo "# below already fails on a leftover token." + echo "rendered = 34" + echo "unassigned = []" + } > "$PROVENANCE_OUT" + + if [ "$CANON_VERSION" = "UNASSIGNED" ]; then + echo " provenance: $PROVENANCE_OUT — canon pin UNASSIGNED" + echo " The template carried no [canon] block, so this repo" + echo " cannot state which canon it conforms to. Fix by minting" + echo " from a template commit that has one." + else + echo " provenance: $PROVENANCE_OUT (canon v${CANON_VERSION} ${CANON_CRITERIA_SHA:0:12})" + fi + + # archetypes/ is template-only overlay data, not part of your repo + # (same reason build/templates is removed above). + rm -rf archetypes + rm -rf build/docs-seed # template-only; its content now lives in docs/ + + # Overlay .in rule (julia-library archetype; the convention any future + # overlay can adopt): the overlay ships content templates as *.in so a + # substitution token never survives in a file with a final name. Rename + # the known set now that substitution has rendered them, before the + # placeholder gate below. + if [ "$ARCHETYPE" = "julia-library" ]; then + # the name is read from Project.toml.in until the loop below renames + # it - order matters. + TOML_FILE=Project.toml + [ -f "$TOML_FILE" ] || TOML_FILE=Project.toml.in + if [ -f src/PACKAGE.jl.in ] && [ -f "$TOML_FILE" ]; then + PKG_NAME=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' "$TOML_FILE" | head -1) + if [ -n "$PKG_NAME" ] && [ "$PKG_NAME" != "UNASSIGNED" ]; then + mv "src/PACKAGE.jl.in" "src/${PKG_NAME}.jl" + echo " entry point: src/${PKG_NAME}.jl (from src/PACKAGE.jl.in)" + else + echo "WARN: Project.toml name is UNASSIGNED - src/PACKAGE.jl.in left in place" + echo " so the placeholder gate stays honest. Name the package, then run:" + echo " mv src/PACKAGE.jl.in src/.jl" + fi + fi + for f in Project.toml.in \ + .github/workflows/julia-ci.yml.in \ + .github/workflows/julia-docs.yml.in; do + [ -f "$f" ] && { mv "$f" "${f%.in}"; echo " overlay: ${f%.in}"; } + done + fi + + echo "" + echo "── Validation ───────────────────────────────────" + + # Check for remaining placeholders. + # + # This printed "WARNING" and then carried on to "Done! Mint complete." It + # detected the very defect that went on to reach 211 repos, said so every + # time, and stopped nobody. That is the fake-gate pattern this estate keeps + # re-learning: a check that cannot fail reads as coverage while changing no + # outcome. + # + # It now aborts the mint. A tree that still holds substitution tokens is not + # minted, it is half-minted, and shipping one is how the estate acquired 12 + # permanently-red OpenSSF builds and 12 settings.yml files declaring an + # unsubstituted repository name. That second one is not theoretical: the + # braces were once submitted to GitHub, collapsed to dashes, and renamed a + # repo to "-REPO-", which then read as deleted. + # + # RSR_ALLOW_PLACEHOLDERS=1 restores the old warning, for deliberately + # partial mints — not for getting past this message. + # + # META tokens are exempt, matching scripts/check-no-placeholders.sh. A file + # is only half-minted if it holds a token naming a real value; a file that + # says "replace all {PLACEHOLDER} values" is prose ABOUT tokens and is + # supposed to survive. Without this exemption the gate fires on README.adoc, + # EXPLAINME.adoc, both descriptiles and the checker itself on every single + # mint — measured — and a gate that always fires gets switched off. + # + # This exemption is also, exactly, why the instruction block went unnoticed + # in 211 repos: {PLACEHOLDER} is the ONLY doubled-brace text in it, so any + # gate sensibly exempting metasyntax must let the block through. The two + # checks here are therefore complementary, not redundant — the block can + # only be caught by name, which the check below does. + # The four allowlisted paths are copied verbatim from + # scripts/check-no-placeholders.sh so the two gates cannot drift apart. + # Each legitimately names tokens: the token vocabulary itself, prose + # explaining that tokens exist, the checker's own pattern, and the e2e + # test's answer list. + PATTERN="${LB}[A-Z_]*${RB}" + META='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + REMAINING=$(grep -rl "$PATTERN" . --include='*.md' --include='*.adoc' --include='*.yml' --include='*.yaml' --include='*.a2ml' --include='*.toml' --include='*.scm' --include='*.ncl' --include='*.nix' --include='*.json' --include='*.sh' 2>/dev/null | grep -v '.git/' | while IFS= read -r f; do + case "${f#./}" in + .machine_readable/ai/PLACEHOLDERS.adoc|EXPLAINME.adoc|scripts/check-no-placeholders.sh|tests/e2e/template_instantiation_test.sh) continue ;; + esac + # Bracketed [{]{2} rather than a doubled brace written literally, for + # two independent reasons. First, `grep -E` is ugrep on some estate + # machines and rejects a bare doubled brace as an "empty + # (sub)expression", because a brace opens an interval quantifier — the + # same reason ci.yml uses this form estate-wide. Second, a doubled brace + # inside a just RECIPE BODY is just's own interpolation delimiter, so + # writing one here (even in a comment) is a parse error. That is why the + # recipe computes LB/RB with printf instead of spelling them out. + if grep -ohE '[{]{2}[A-Z][A-Z0-9_]*[}]{2}' "$f" | grep -qvE "^[{]{2}(${META})[}]{2}$"; then + echo "$f" + fi + done || true) + if [ -n "$REMAINING" ]; then + echo "Remaining placeholders in:" + echo "$REMAINING" | sed 's/^/ /' + echo "" + echo "Inspect with: grep -rn '$LB' . --include='*.md'" + if [ "${RSR_ALLOW_PLACEHOLDERS:-0}" = "1" ]; then + echo "WARNING: continuing anyway (RSR_ALLOW_PLACEHOLDERS=1)." + else + echo "ERROR: mint aborted — the tree above is half-minted." + echo " Fill those tokens, or re-run with RSR_ALLOW_PLACEHOLDERS=1" + echo " if a partial mint is genuinely what you want." + exit 1 + fi + else + echo "All placeholders replaced successfully!" + fi + + # An un-deleted instruction block is a different failure from an unfilled + # token, and once its literal metasyntax is the only doubled-brace text left + # the check above cannot distinguish the two. Fail on it by name. + # + # The strip pass, this recipe and the e2e test are excluded because they + # necessarily NAME the marker in order to describe or assert on it — the fix + # for the defect would otherwise be flagged by the check for the defect, and + # so would the test that guards the fix, and the cleanup test whose + # fixture prose must keep the phrase to prove prose is not collateral. + # All four are tooling, not + # community-health documents, so a mention in them cannot mislead a reader + # about what this project's Code of Conduct says. + LEFTOVER_BLOCKS=$(grep -rl 'TEMPLATE INSTRUCTIONS' . --exclude-dir=.git 2>/dev/null | grep -vE '^\./(scripts/strip-instruction-blocks\.rs|build/just/repo-init\.just|tests/e2e/template_instantiation_test\.sh|tests/workflows/mint_cleanup_test\.sh)$' || true) + if [ -n "$LEFTOVER_BLOCKS" ]; then + echo "" + echo "Un-deleted TEMPLATE INSTRUCTIONS block in:" + echo "$LEFTOVER_BLOCKS" | sed 's/^/ /' + echo "ERROR: the instruction-block pass should have removed these." + exit 1 + fi + + # ── www/ site-operations bundle (issue #53, stage 5) ────────────────── + # + # A repository minted from this template already carries www/.well-known/ + # as the canonical location and has no root .well-known/, so in the + # ordinary case both steps below do nothing at all. They exist for the + # case that is not ordinary: repo-init run over a tree that predates #53 + # and still has a root .well-known/. The migrator moves it — quarantining + # anything divergent rather than overwriting it — and the bundle's own + # suite then proves the result. + # + # #106 described this wiring as already present. It was not: nothing + # invoked the migrator or the suite at mint, which is why the estate + # still carries root .well-known/ directories years into the convention. + if [ -d .well-known ] && [ -f scripts/migrate-wellknown-to-www.sh ]; then + echo "" + echo "Migrating legacy root .well-known/ into www/.well-known/..." + bash scripts/migrate-wellknown-to-www.sh + fi + + # Fail-closed, like `just verify` below: the suite tolerates unminted + # double-brace placeholder tokens by design (it runs in the template + # itself), no archetype overlay touches www/, and the substituted Expires + # value is a parseable end-of-mint-year timestamp — so a failure here is + # real. (Spelled out in words because just interpolates double braces + # anywhere in a recipe, comments included, and an undefined variable here + # takes the whole Justfile down, not just this recipe.) + if [ -f www/tests/run-all.sh ]; then + echo "" + echo "Running www/ site-operations suite..." + bash www/tests/run-all.sh + fi + + # K9-SVC validation (if available) + if command -v k9-svc >/dev/null 2>&1; then + echo "" + echo "Running k9-svc validation..." + k9-svc validate . 2>/dev/null || true + fi + + echo "" + echo "Running OpenSSF compliance verification..." + just verify + + echo "" + echo "Done! Mint complete. Remaining Forge stages (ADR-0003):" + echo " 1. Review changes: git diff" + echo " 2. Remove template cruft: rm .machine_readable/ai/PLACEHOLDERS.adoc" + echo " 3. Customize README.adoc; then: grep -rn UNASSIGNED . for what still needs you" + echo " 4. PROVISION — actually run your build and test commands and watch" + echo " them pass (echoed advice is not provisioning)." + echo " 5. Commit: git add -A && git commit -m 'feat: initialize from RSR template'" + echo " 6. Push: git remote add origin git@${FORGE}:${OWNER}/${REPO}.git && git push -u origin main" + echo " 7. CONFIGURE — out of band, once (this recipe runs no gh commands):" + echo " gh repo edit ${OWNER}/${REPO} --description '' --homepage '${WEBSITE}'" + echo " Visibility is fail-closed private; flip deliberately if wanted:" + echo " gh repo edit ${OWNER}/${REPO} --visibility public --accept-visibility-change-consequences" + echo " Register in gv-clade-index; required contexts must equal emitted job ids." + if [ -f ci/.gitlab-ci.yml ]; then + echo "" + echo " GitLab mirror only: this repo keeps its GitLab CI config at" + echo " ci/.gitlab-ci.yml, which GitLab does NOT look for by default." + echo " Settings > CI/CD > General pipelines > CI/CD configuration file" + echo " must be set to ci/.gitlab-ci.yml, or GitLab CI silently never" + echo " runs - no pipeline and no error. See ci/README.adoc." + fi + echo " 8. HARNESS — arm the drift gate against your recorded parent-pin:" + echo " scripts/check-variant-drift.sh " diff --git a/czech-file-knife/build/just/validate.just b/czech-file-knife/build/just/validate.just new file mode 100644 index 000000000..5718a771b --- /dev/null +++ b/czech-file-knife/build/just/validate.just @@ -0,0 +1,135 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# VALIDATION & COMPLIANCE +# +# Imported by ../../Justfile via `import? "build/just/validate.just"`. +# Recipes here check that this repo conforms to the RSR (Rhodium Standard +# Repository) skeleton: required files, METAdata, AI install guide +# completeness, etc. Run via `just validate`. + +# Validate RSR compliance +validate-rsr: + #!/usr/bin/env bash + cd "{{justfile_directory()}}" + echo "=== RSR Compliance Check ===" + MISSING="" + for f in .editorconfig .gitignore Justfile README.adoc LICENSE; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + # The repo deed's filename carries the repo slug (_chora.deed), so + # this is a glob; the folded replacement of the old 0-AI-MANIFEST.a2ml. + ls *_chora.deed >/dev/null 2>&1 || MISSING="$MISSING repo-deed (*_chora.deed)" + for f in .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/ECOSYSTEM.a2ml .machine_readable/descriptiles/anchors/ANCHOR.a2ml .machine_readable/policies/MAINTENANCE-AXES.a2ml .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + for f in docs/legal/EXHIBIT-A-ETHICAL-USE.txt docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt LICENSES/MPL-2.0.txt; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + if [ ! -d "src/interface/abi" ] && [ ! -d "src/interface/Abi" ]; then + MISSING="$MISSING src/interface/abi" + fi + for f in src/interface/ffi src/interface/generated; do + [ -d "$f" ] || MISSING="$MISSING $f" + done + for f in docs/governance/MAINTENANCE-CHECKLIST.adoc docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + if [ -f ".machine_readable/descriptiles/META.a2ml" ]; then + grep -q 'axis-1 = "must > intend > like"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-1" + grep -q 'axis-2 = "corrective > adaptive > perfective"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-2" + grep -q 'axis-3 = "systems > compliance > effects"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-3" + grep -q 'scoping-first = true' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:scoping-first" + grep -q 'idris-unsound-scan = "believe_me/assert_total"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:idris-unsound-scan" + grep -q 'audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:audit-focus" + grep -q 'compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:compliance-focus" + grep -q 'effects-evidence = "benchmark execution/results and maintainer status dialogue/review"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:effects-evidence" + grep -q 'compliance-tooling = "panic-attack"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:compliance-tooling" + grep -q 'effects-tooling = "ecological checking with sustainabot guidance"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:effects-tooling" + grep -q 'source-human = "docs/governance/MAINTENANCE-CHECKLIST.adoc"' .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml || MISSING="$MISSING MAINTENANCE-CHECKLIST.a2ml:source-human" + grep -q 'source-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc"' .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml || MISSING="$MISSING SOFTWARE-DEVELOPMENT-APPROACH.a2ml:source-human" + fi + if [ -n "$MISSING" ]; then + echo "MISSING:$MISSING" + exit 1 + fi + echo "RSR compliance: PASS" + +# Validate STATE.a2ml syntax +validate-state: + @cd "{{justfile_directory()}}" && if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ + grep -q '^\[metadata\]' .machine_readable/descriptiles/STATE.a2ml && \ + grep -q 'project\s*=' .machine_readable/descriptiles/STATE.a2ml && \ + echo "STATE.a2ml: valid" || echo "STATE.a2ml: INVALID (missing required sections)"; \ + else \ + echo "No .machine_readable/descriptiles/STATE.a2ml found"; \ + fi + +# Validate AI installation guide completeness (finishbot pre-release check) +validate-ai-install: + #!/usr/bin/env bash + cd "{{justfile_directory()}}" + echo "=== AI Installation Guide Check ===" + GUIDE="docs/AI_INSTALLATION_GUIDE.adoc" + README="README.adoc" + ERRORS=0 + + # Check guide exists + if [ ! -f "$GUIDE" ]; then + echo "MISSING: $GUIDE (create from template: docs/AI_INSTALLATION_GUIDE.adoc)" + ERRORS=$((ERRORS + 1)) + else + # Check for unfilled TODO markers + TODOS=$(grep -c '\[TODO-AI-INSTALL' "$GUIDE" 2>/dev/null || true) + if [ "$TODOS" -gt 0 ]; then + echo "INCOMPLETE: $GUIDE has $TODOS unfilled [TODO-AI-INSTALL] markers:" + grep -n '\[TODO-AI-INSTALL' "$GUIDE" | head -10 + ERRORS=$((ERRORS + 1)) + else + echo "$GUIDE: complete (no TODO markers)" + fi + + # Check AI implementation section exists + if ! grep -q 'ai-implementation' "$GUIDE" 2>/dev/null; then + echo "MISSING: [[ai-implementation]] anchor in $GUIDE" + ERRORS=$((ERRORS + 1)) + fi + + # Check privacy notice exists + if ! grep -qi 'privacy' "$GUIDE" 2>/dev/null; then + echo "MISSING: Privacy notice in $GUIDE" + ERRORS=$((ERRORS + 1)) + fi + + # Check install commands exist (not just placeholders) + if ! grep -q 'git clone' "$GUIDE" 2>/dev/null; then + echo "WARNING: No git clone command found in $GUIDE -- install commands may be incomplete" + fi + fi + + # Check README has AI install section + if [ -f "$README" ]; then + if ! grep -qi 'AI-Assisted Installation' "$README" 2>/dev/null; then + echo "MISSING: AI-Assisted Installation section in $README" + echo " Copy from docs/AI-INSTALL-README-SECTION.adoc" + ERRORS=$((ERRORS + 1)) + fi + + # Check README for unfilled TODO markers + README_TODOS=$(grep -c '\[TODO-AI-INSTALL' "$README" 2>/dev/null || true) + if [ "$README_TODOS" -gt 0 ]; then + echo "INCOMPLETE: $README has $README_TODOS unfilled [TODO-AI-INSTALL] markers" + ERRORS=$((ERRORS + 1)) + fi + fi + + if [ "$ERRORS" -gt 0 ]; then + echo "" + echo "AI install guide: FAIL ($ERRORS issues)" + exit 1 + fi + echo "AI install guide: PASS" + +# Full validation suite +validate: validate-rsr validate-state validate-ai-install + @echo "All validations passed!" diff --git a/czech-file-knife/packaging/arch/PKGBUILD b/czech-file-knife/build/packaging/arch/PKGBUILD similarity index 97% rename from czech-file-knife/packaging/arch/PKGBUILD rename to czech-file-knife/build/packaging/arch/PKGBUILD index d6e6c75e1..ddbbbf2ca 100644 --- a/czech-file-knife/packaging/arch/PKGBUILD +++ b/czech-file-knife/build/packaging/arch/PKGBUILD @@ -7,7 +7,7 @@ pkgrel=1 pkgdesc="Universal file management toolkit with cloud provider integration and virtual filesystem" arch=('x86_64' 'aarch64') url="https://github.com/hyperpolymath/czech-file-knife" -license=('AGPL-3.0-or-later') +license=('MPL-2.0') depends=('gcc-libs' 'fuse3') makedepends=('rust' 'cargo' 'pkg-config') optdepends=( diff --git a/czech-file-knife/packaging/chocolatey/czech-file-knife.nuspec b/czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec similarity index 100% rename from czech-file-knife/packaging/chocolatey/czech-file-knife.nuspec rename to czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec diff --git a/czech-file-knife/packaging/debian/control b/czech-file-knife/build/packaging/debian/control similarity index 97% rename from czech-file-knife/packaging/debian/control rename to czech-file-knife/build/packaging/debian/control index 99832ac1c..d7ea27098 100644 --- a/czech-file-knife/packaging/debian/control +++ b/czech-file-knife/build/packaging/debian/control @@ -24,4 +24,4 @@ Description: Universal file management toolkit with cloud provider integration - Multi-provider support (local, S3, GCS, Azure, etc.) - Streaming copy with progress indication . - License: AGPL-3.0-or-later + License: MPL-2.0 diff --git a/czech-file-knife/packaging/debian/rules b/czech-file-knife/build/packaging/debian/rules similarity index 100% rename from czech-file-knife/packaging/debian/rules rename to czech-file-knife/build/packaging/debian/rules diff --git a/czech-file-knife/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml b/czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml similarity index 100% rename from czech-file-knife/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml rename to czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml diff --git a/czech-file-knife/packaging/macports/Portfile b/czech-file-knife/build/packaging/macports/Portfile similarity index 97% rename from czech-file-knife/packaging/macports/Portfile rename to czech-file-knife/build/packaging/macports/Portfile index 079c375f9..208206682 100644 --- a/czech-file-knife/packaging/macports/Portfile +++ b/czech-file-knife/build/packaging/macports/Portfile @@ -8,7 +8,7 @@ PortGroup github 1.0 github.setup hyperpolymath czech-file-knife 0.1.0 v revision 0 categories sysutils -license AGPL-3.0-or-later +license MPL-2.0 maintainers {hyperpolymath @hyperpolymath} description Universal file management toolkit with cloud provider integration long_description Czech File Knife (cfk) is a universal file management toolkit with \ diff --git a/czech-file-knife/packaging/rpm/czech-file-knife.spec b/czech-file-knife/build/packaging/rpm/czech-file-knife.spec similarity index 97% rename from czech-file-knife/packaging/rpm/czech-file-knife.spec rename to czech-file-knife/build/packaging/rpm/czech-file-knife.spec index 32eff56dc..1a3ab9ec5 100644 --- a/czech-file-knife/packaging/rpm/czech-file-knife.spec +++ b/czech-file-knife/build/packaging/rpm/czech-file-knife.spec @@ -6,7 +6,7 @@ Version: 0.1.0 Release: 1%{?dist} Summary: Universal file management toolkit with cloud provider integration -License: AGPL-3.0-or-later +License: MPL-2.0 URL: https://github.com/hyperpolymath/czech-file-knife Source0: %{name}-%{version}.tar.gz diff --git a/czech-file-knife/packaging/scoop/czech-file-knife.json b/czech-file-knife/build/packaging/scoop/czech-file-knife.json similarity index 95% rename from czech-file-knife/packaging/scoop/czech-file-knife.json rename to czech-file-knife/build/packaging/scoop/czech-file-knife.json index b68f05f56..093df8dc3 100644 --- a/czech-file-knife/packaging/scoop/czech-file-knife.json +++ b/czech-file-knife/build/packaging/scoop/czech-file-knife.json @@ -2,7 +2,7 @@ "version": "0.1.0", "description": "Universal file management toolkit with cloud provider integration and virtual filesystem", "homepage": "https://github.com/hyperpolymath/czech-file-knife", - "license": "AGPL-3.0-or-later", + "license": "MPL-2.0", "architecture": { "64bit": { "url": "https://github.com/hyperpolymath/czech-file-knife/releases/download/v0.1.0/cfk-0.1.0-x86_64-pc-windows-msvc.zip", diff --git a/czech-file-knife/packaging/winget/czech-file-knife.yaml b/czech-file-knife/build/packaging/winget/czech-file-knife.yaml similarity index 98% rename from czech-file-knife/packaging/winget/czech-file-knife.yaml rename to czech-file-knife/build/packaging/winget/czech-file-knife.yaml index 32f9751ae..ce809c22a 100644 --- a/czech-file-knife/packaging/winget/czech-file-knife.yaml +++ b/czech-file-knife/build/packaging/winget/czech-file-knife.yaml @@ -8,7 +8,7 @@ Publisher: hyperpolymath PublisherUrl: https://github.com/hyperpolymath PackageName: Czech File Knife PackageUrl: https://github.com/hyperpolymath/czech-file-knife -License: AGPL-3.0-or-later +License: MPL-2.0 LicenseUrl: https://github.com/hyperpolymath/czech-file-knife/blob/main/LICENSE ShortDescription: Universal file management toolkit with cloud provider integration Description: | diff --git a/czech-file-knife/ci/.gitlab-ci.yml b/czech-file-knife/ci/.gitlab-ci.yml new file mode 100644 index 000000000..2dcfe10b8 --- /dev/null +++ b/czech-file-knife/ci/.gitlab-ci.yml @@ -0,0 +1,154 @@ +# SPDX-License-Identifier: MPL-2.0 +# Primary CI/CD - GitLab is the source of truth + +stages: + - security + - lint + - test + - build +variables: + CARGO_HOME: ${CI_PROJECT_DIR}/.cargo +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - .cargo/ + - target/ +# ================== +# Security Scanning +# ================== +trivy: + stage: security + image: aquasec/trivy:latest + script: + - trivy fs --exit-code 0 --severity HIGH,CRITICAL --format table . + - trivy fs --exit-code 1 --severity CRITICAL . + allow_failure: false +semgrep: + stage: security + image: returntocorp/semgrep + script: + - semgrep --config auto --error . + allow_failure: true +cargo-audit: + stage: security + image: rust:latest + script: + - cargo install cargo-audit + - cargo audit + rules: + - exists: + - Cargo.toml +cargo-deny: + stage: security + image: rust:latest + script: + - cargo install cargo-deny + - cargo deny --manifest-path Cargo.toml check --config .machine_readable/compliance/rust/deny.toml + rules: + - exists: + - Cargo.toml + allow_failure: true +mix-audit: + stage: security + image: elixir:latest + script: + - mix local.hex --force + - mix archive.install hex mix_audit --force + - mix deps.get + - mix deps.audit + rules: + - exists: + - mix.exs + allow_failure: true +# ================== +# Linting +# ================== +rustfmt: + stage: lint + image: rust:latest + script: + - rustup component add rustfmt + - cargo fmt -- --check + rules: + - exists: + - Cargo.toml +clippy: + stage: lint + image: rust:latest + script: + - rustup component add clippy + - cargo clippy -- -D warnings + rules: + - exists: + - Cargo.toml + allow_failure: true +mix-format: + stage: lint + image: elixir:latest + script: + - mix format --check-formatted + rules: + - exists: + - mix.exs +credo: + stage: lint + image: elixir:latest + script: + - mix local.hex --force + - mix deps.get + - mix credo --strict + rules: + - exists: + - mix.exs + allow_failure: true +# ================== +# Testing +# ================== +cargo-test: + stage: test + image: rust:latest + script: + - cargo test --all-features + rules: + - exists: + - Cargo.toml +mix-test: + stage: test + image: elixir:latest + script: + - mix local.hex --force + - mix deps.get + - mix test + rules: + - exists: + - mix.exs +# ================== +# Build +# ================== +cargo-build: + stage: build + image: rust:latest + script: + - cargo build --release + artifacts: + paths: + - target/release/ + expire_in: 1 week + rules: + - exists: + - Cargo.toml +mix-build: + stage: build + image: elixir:latest + script: + - mix local.hex --force + - mix deps.get + - MIX_ENV=prod mix compile + rules: + - exists: + - mix.exs +trufflehog: + stage: security + image: trufflesecurity/trufflehog:latest + script: + - trufflehog git file://. --only-verified --fail diff --git a/czech-file-knife/ci/.pre-commit-config.yaml b/czech-file-knife/ci/.pre-commit-config.yaml new file mode 100644 index 000000000..806916471 --- /dev/null +++ b/czech-file-knife/ci/.pre-commit-config.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hooks for hyperpolymath RSR repos. +# Install: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files + +repos: + # --- Standard hooks --- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-merge-conflict + - id: detect-private-key + - id: check-added-large-files + args: ['--maxkb=1024'] + + # --- Manifest validation (DEED grammar) --- + # The former provider repo (hyperpolymath/a2ml-pre-commit) was DELETED + # upstream in the A2ML retirement (standards #836 owner ruling; R-H3), + # leaving this pin as broken plumbing — pre-commit could not clone it. + # Replaced with a local hook calling .github/hooks/validate-deed.sh, + # verified exit-0 against all 123 manifests in this repo. Dual-accept: + # scans both .a2ml (legacy extension; migration = standards #837, the + # DEED conversion campaign) and .deed. When the grammar-faithful .deed + # validator is wired per owner ruling R-H2, this hook is its natural home. + - repo: local + hooks: + - id: validate-deed + name: Validate DEED manifests (dual-accept .a2ml/.deed) + entry: .github/hooks/validate-deed.sh + language: system + files: '\.(a2ml|deed)$' + pass_filenames: false + + # --- K9 contract validation --- + - repo: https://github.com/hyperpolymath/k9-pre-commit + rev: 9b82e1f7a6b6c0f99df72c145d7dee8851803c30 # k9-pre-commit @ main 2026-06-23 + hooks: + - id: validate-k9 + name: Validate K9 contracts + + # --- Shell linting --- + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + + # --- EditorConfig --- + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: 3.2.1 + hooks: + - id: editorconfig-checker + exclude: '(\.git|node_modules|target|_build|deps|\.|external_corpora|\.lake)/' + + # --- Secret detection --- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.3 + hooks: + - id: gitleaks + + # --- Arrival pack drift: CLAUDE.md must stay in sync with a2ml --- + - repo: local + hooks: + - id: validate-claude-md + name: CLAUDE.md arrival pack in sync with a2ml + entry: bash .machine_readable/arrival-pack/verify.sh + language: system + pass_filenames: false + files: '^(\.machine_readable/descriptiles/.*|CLAUDE\.md)$' diff --git a/czech-file-knife/ci/README.adoc b/czech-file-knife/ci/README.adoc new file mode 100644 index 000000000..933aae685 --- /dev/null +++ b/czech-file-knife/ci/README.adoc @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += CI configuration + +Non-GitHub CI configuration lives here. GitHub Actions workflows stay in +`.github/workflows/` because GitHub reads that path and no other. + +[cols="1,3",options="header"] +|=== +| File | Consumer + +| `.gitlab-ci.yml` +| GitLab CI. **Not** found automatically at this path — see below. + +| `.pre-commit-config.yaml` +| The `pre-commit` framework, when invoked with `--config`. +|=== + +== Required out-of-band setting (GitLab) + +GitLab looks for `.gitlab-ci.yml` at the repository root by default. Because the +file now lives in `ci/`, the project setting must be changed or **GitLab CI +silently stops running** — no pipeline, no error: + +* Project → Settings → CI/CD → General pipelines → *CI/CD configuration file* +* Set it to `ci/.gitlab-ci.yml` + +== Using pre-commit + +The config is no longer at the root, so pass it explicitly: + +[source,bash] +---- +pre-commit install --config ci/.pre-commit-config.yaml +pre-commit run --all-files --config ci/.pre-commit-config.yaml +---- + +[NOTE] +==== +This repository's own push-time gate does not use the `pre-commit` framework. +It is `.github/hooks/pre-push`, activated by `bash .github/hooks/install.sh`, which sets +`core.hooksPath` to `.github/hooks`. +==== diff --git a/czech-file-knife/contractiles/README.adoc b/czech-file-knife/contractiles/README.adoc deleted file mode 100644 index d19a38774..000000000 --- a/czech-file-knife/contractiles/README.adoc +++ /dev/null @@ -1,19 +0,0 @@ -= Contractiles Template Set -:toc: -:sectnums: - -This directory contains the generalized contractiles templates. Copy the `contractiles/` directory into a new repo to establish a consistent operational, validation, trust, recovery, and intent framework. - -== Fill-In Instructions - -1. Update the Mustfile to reflect your real invariants (paths, schema versions, ports). -2. Replace Trustfile.hs placeholders with your actual key paths and verification commands. -3. Adjust Dustfile handlers to match your rollback and recovery tooling. -4. Update Intentfile to mirror the roadmap you want the system to evolve toward. - -== Contents - -* `must/Mustfile` - required invariants and validations. -* `trust/Trustfile.hs` - cryptographic verification steps. -* `dust/Dustfile` - rollback and recovery semantics. -* `lust/Intentfile` - future intent and roadmap direction. diff --git a/czech-file-knife/contractiles/dust/Dustfile b/czech-file-knife/contractiles/dust/Dustfile deleted file mode 100644 index 314903cca..000000000 --- a/czech-file-knife/contractiles/dust/Dustfile +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dustfile template - recovery and rollback semantics - -version: 1 - -recovery: - logs: - - name: decision-log - path: logs/decisions.json - reversible: true - handler: "log-replay --reverse logs/decisions.json" - - policy: - - name: policy-rollback - path: policy/policy.ncl - rollback: "git checkout HEAD~1 -- policy/policy.ncl" - notes: "Rollback policy to the previous known-good revision." - - gateway: - - name: bad-deployment - event: "deploy.failure" - undo: "kubectl rollout undo deployment/gateway" - notes: "Undo a failed deployment while preserving audit logs." - - dust-events: - - name: decision-log-to-dust - source: logs/decisions.json - transform: "dustify --input logs/decisions.json --output logs/dust-events.json" - notes: "Map gateway decision logs into reversible dust events." diff --git a/czech-file-knife/contractiles/must/Mustfile b/czech-file-knife/contractiles/must/Mustfile deleted file mode 100644 index dc7b3be51..000000000 --- a/czech-file-knife/contractiles/must/Mustfile +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile - declarative state contract (template) -# See: https://github.com/hyperpolymath/mustfile - -version: 1 - -metadata: - name: project-state-contract - spec: v0.0.1 - description: "Invariant checks for config, policy, gateway, logs, and schema." - -parameters: - gateway_port: "8080" - schema_version: "v0.0.1" - -checks: - - name: config-valid - description: "config/service.yaml must be valid." - run: "yq -e '.' config/service.yaml >/dev/null" - - - name: policy-compiles - description: "policy/policy.ncl must compile." - run: "nickel check policy/policy.ncl" - - - name: gateway-exposes-port - description: "Service must expose the configured port." - run: "bash -uc 'ss -lnt | rg \":${GATEWAY_PORT:-8080}\"'" - - - name: logs-are-json - description: "Logs must be JSON." - run: "bash -uc 'rg --files -g \"*.json\" logs | xargs -r jq -e .'" - - - name: schema-version-matches - description: "Schema must match version spec." - run: "bash -uc 'rg -n \"${SCHEMA_VERSION:-v0.0.1}\" schema'" diff --git a/czech-file-knife/coordination.k9.ncl b/czech-file-knife/coordination.k9.ncl new file mode 100644 index 000000000..6f4f9877b --- /dev/null +++ b/czech-file-knife/coordination.k9.ncl @@ -0,0 +1,49 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Thin coordination bindings for central session-management standards + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "session-coordination", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + session_management = { + source_of_truth = "standards/3-practice/session-management-standards", + canonical_commands = [ + "intake repo ", + "checkpoint change ", + "verify maintenance ", + "verify substantial ", + "verify release ", + "close planned ", + "close urgent ", + "recover repo ", + "handover full ", + "handover split ", + "handover model ", + "handover human ", + ], + }, + + signals = [ + { name = "session.intake", command = "intake repo " }, + { name = "session.checkpoint", command = "checkpoint change " }, + { name = "session.verify.maintenance", command = "verify maintenance " }, + { name = "session.verify.substantial", command = "verify substantial " }, + { name = "session.verify.release", command = "verify release " }, + { name = "session.close.planned", command = "close planned " }, + { name = "session.close.urgent", command = "close urgent " }, + { name = "session.recover", command = "recover repo " }, + { name = "session.handover.full", command = "handover full " }, + { name = "session.handover.split", command = "handover split " }, + { name = "session.handover.model", command = "handover model " }, + { name = "session.handover.human", command = "handover human " }, + ], +} diff --git a/czech-file-knife/czech-file-knife_chora.deed b/czech-file-knife/czech-file-knife_chora.deed new file mode 100644 index 000000000..53e3716df --- /dev/null +++ b/czech-file-knife/czech-file-knife_chora.deed @@ -0,0 +1,152 @@ +;; SPDX-License-Identifier: MPL-2.0 +; +; The repo deed of czech-file-knife: one-deed-per-repo doctrine (standards#837, +; owner ruling 2026-09-19, Option A). Folds the former ply manifest tree +; (0.N-AI-MANIFEST.a2ml, 82 directories) and the family-7 AI +; allocation manifest (root 0-AI-MANIFEST.a2ml) into deed clauses. +; Generated by the #837 pilot emitter; every byte gated by deed_lint.py. +(repo-deed + :schema-version "1.0.0" + :canonical-name "czech-file-knife" + :beholding-chora #u5"estate/chora" + :repo-uuid #u5"github.com/hyperpolymath/czech-file-knife" + (manifest + :version "0.1.0" + :last-updated "2026-09-28" + :project "Czech File Knife" + :purpose "Canonical RSR (Rhodium Standard Repository) template — governance, CI/CD, machine-readable metadata, ABI/FFI seam and formal-verification scaffolding that hyperpolymath projects are instantiated from." + (agent :name CLAUDE :role "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") + (agent :name CHATGPT :role "prose, papers, publication review, standards docs, outreach drafts, letters") + (agent :name GEMINI :role "estate audits, cross-repo sweeps, long-context triage, pattern detection") + (agent :name VIBE :role "UI/frontend, PanLL panels, components, theming, rapid prototyping") + (policy :rules ("Do not duplicate tasks across sections. If a task needs multiple LLMs, note the handoff." "Update THIS file during sessions. Do NOT recreate per-repo TODO files.")) + (work :agent CLAUDE :task "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") + (work :agent CHATGPT :task "prose, papers, publication review, standards docs, outreach drafts, letters") + (work :agent GEMINI :task "estate audits, cross-repo sweeps, long-context triage, pattern detection") + (work :agent VIBE :task "UI/frontend, PanLL panels, components, theming, rapid prototyping") + ) + (ply + (directory :ply 1 :path ".github") + (directory :id "machine-readable-pillar" :ply 1 :path ".machine_readable" :description "Registry for all machine-readable metadata, policies, and internal\nautomation scripts." + (canonical-locations :agentic "descriptiles/AGENTIC.a2ml" :ai_configs "ai/" :anchors "descriptiles/anchors/" :clade "descriptiles/CLADE.a2ml" :compliance "compliance/" :ecosystem "descriptiles/ECOSYSTEM.a2ml" :meta "descriptiles/META.a2ml" :neurosym "descriptiles/NEUROSYM.a2ml" :playbook "descriptiles/PLAYBOOK.a2ml" :policies "policies/" :scripts "scripts/" :state "descriptiles/STATE.a2ml") + :invariants ("Metadata files MUST follow a2ml format" "Internal automation MUST live in scripts/ subfolder") + (directory :id "ai-registry" :ply 2 :path ".machine_readable/ai" :description "Sub-registry for ai metadata.") + (directory :id "configs-registry" :ply 2 :path ".machine_readable/configs" :description "Sub-registry for configs metadata.") + (directory :ply 0 :path ".machine_readable/descriptiles" :description "This manifest declares the AI-assistant context for the descriptiles machine-readable metadata directory." + :invariants ("No duplicate files in root directory" "Single source of truth: this directory is authoritative" "No stale metadata") + (directory :ply 0 :path ".machine_readable/descriptiles/anchors" :description "This manifest declares the AI-assistant context for the anchor machine-readable metadata directory." + :invariants ("Multiple versions with different dates are permitted" "No other A2ML files in this directory" "Single source of truth for anchor documents"))) + (directory :id "policies-registry" :ply 2 :path ".machine_readable/policies" :description "Sub-registry for policies metadata.") + (directory :id "automation-scripts-unit" :ply 2 :path ".machine_readable/scripts" :description "Internal automation logic for the project lifecycle, forge sync,\nverification triggers, and maintenance." + (canonical-locations :forge "forge/" :lifecycle "lifecycle/" :maintenance "maintenance/" :verification "verification/") + (directory :id "automation-unit-forge" :ply 3 :path ".machine_readable/scripts/forge" :description "Internal automation logic for project forge.") + (directory :id "automation-unit-lifecycle" :ply 3 :path ".machine_readable/scripts/lifecycle" :description "Internal automation logic for project lifecycle.") + (directory :id "automation-unit-verification" :ply 3 :path ".machine_readable/scripts/verification" :description "Internal automation logic for project verification."))) + (directory :id "container-templates" :ply 1 :path "build/container" :version "1.0.0" :description "Container templates for the stapeln container ecosystem. This directory\nprovides Podman-Chainguard-stapeln templates that are customised via\n`just container-init` or `just repo-init` during project bootstrap.\n\nAll files use {{PLACEHOLDER}} tokens that are substituted with project-\nspecific values during initialisation." :purpose "Provide a complete, security-first container deployment story for any\nRSR-compliant repository. The templates cover the full lifecycle:\nbuild, sign, verify, deploy, monitor, and govern." :overview "The stapeln container ecosystem comprises six tools:\n\nselur — Container orchestration with zero-copy IPC. Reads compose.toml.\ncerro-torre — Verified container packaging (.ctp bundles), Ed25519 signing.\nsvalinn — Policy-driven edge gateway (auth, rate limits, CORS, trust).\nvordr — Runtime monitoring (health, crashes, resources, logs).\nrokur — Secrets management (runtime injection, no baked secrets).\nk9-svc — Nickel deployment components (Kennel/Yard/Hunt trust levels)." + :context ("https://a2ml.org/ns/v2" "https://stapeln.dev/ns/v1") + (canonical-locations :build_pipeline "build/container/stapeln/ct-build.sh" :compose "build/container/stapeln/compose.toml" :compose_example "build/container/stapeln/compose.example.toml" :compose_portable "build/container/compose.yaml" :compose_portable_example "build/container/compose.example.yaml" :containerfile "build/container/Containerfile" :containerignore "build/container/.containerignore" :deployment "build/container/stapeln/deploy.k9.ncl" :entrypoint "build/container/entrypoint.sh" :gatekeeper "build/container/stapeln/.gatekeeper.yaml" :just_module "build/just/container.just" :manifest "build/container/stapeln/manifest.toml" :monitoring "build/container/stapeln/vordr.toml" :secrets_gate "build/container/stapeln/rokur.toml") + :invariants ("Base images MUST be cgr.dev/chainguard/wolfi-base or cgr.dev/chainguard/static" "Container runtime is Podman — never Docker" "Containerfile — never Dockerfile" "All images run as non-root (appuser or project-specific user)" ".ctp bundles are signed with Ed25519 via cerro-torre" "Health endpoints (/health, /ready) must always be public (no auth)") + (file-relationships + (file :name "compose.toml" :role "Orchestration" :description "selur-compose stack definition. Declares services, volumes, networks,\nand health checks. References the Containerfile for image builds and\n.gatekeeper.yaml for svalinn policy." :depends-on ("Containerfile" ".gatekeeper.yaml")) + (file :name "Containerfile" :role "Image Build (Tier A)" :description "Multi-stage OCI container build. Stage 1 compiles the application on\nwolfi-base; Stage 2 copies the binary into a minimal runtime image and\ncopies entrypoint.sh. Engine-agnostic (podman/nerdctl/docker). The\nstapeln files (.gatekeeper.yaml, manifest.toml) are referenced only as\nOCI labels and copied in only if you uncomment those COPY lines, so the\nimage has no hard dependency on tier C." :depends-on ("entrypoint.sh")) + (file :name "manifest.toml" :role "Bundle Metadata" :description "Cerro-torre .ctp bundle manifest. Describes provenance, dependencies,\nattestations, and runtime security profile. Used by `ct pack` and\n`ct verify`." :depends-on ()) + (file :name ".gatekeeper.yaml" :role "Gateway Policy" :description "Svalinn edge gateway policy. Controls authentication, rate limiting,\ncontainer trust, request validation, CORS, and audit logging." :depends-on ()) + (file :name "ct-build.sh" :role "Build Pipeline" :description "Shell script implementing the 5-stage pipeline: build (Podman),\npack (cerro-torre .ctp), sign (Ed25519), verify, push (optional).\nDegrades gracefully when cerro-torre tools are not installed." :depends-on ("Containerfile" "manifest.toml")) + (file :name "entrypoint.sh" :role "Container Entrypoint" :description "Startup script with signal handling (SIGTERM, SIGINT), logging, and\nexec into the main application process." :depends-on ()) + (file :name "vordr.toml" :role "Runtime Monitoring" :description "Vordr monitoring configuration. Health endpoint probing, crash\ndetection, resource thresholds, and structured log output." :depends-on ()) + (file :name "deploy.k9.ncl" :role "Deployment Component" :description "k9-svc deployment specification at Hunt trust level. Full pedigree\n(L1-L5), environment configs, container config, and rolling\ndeployment strategy." :depends-on ("compose.toml" "ct-build.sh")) + (file :name "compose.example.toml" :role "Example" :description "Fully-commented multi-service example (Rust API + Elixir worker +\nsvalinn gateway). Copy to compose.toml and customise." :depends-on ()) + )) + (directory :id "docs-pillar" :ply 1 :path "docs" :description "Technical documentation hub. The root contains high-level orientation\n(README, Quickstart, State-Visualizer). Specialized tracks live in\nsubdirectories." + (canonical-locations :architecture "architecture/" :attribution "attribution/" :decisions "decisions/" :developer "developer/" :governance "governance/" :legal "legal/" :practice "practice/" :quickstart "QUICKSTART.adoc" :reports "reports/" :standards "standards/" :state_visualizer "STATE-VISUALIZER.adoc" :theory "theory/" :whitepapers "whitepapers/" :wikis "wikis/") + :invariants ("Primary documentation format MUST be AsciiDoc (.adoc)" "Root docs/ MUST only contain pillar entry points") + (directory :id "architecture-track" :ply 2 :path "docs/architecture" :description "Documentation track for system architecture and threat models." + (canonical-locations :threat_model "THREAT-MODEL.adoc") + :invariants ("Visual diagrams MUST include ASCII or Mermaid representations")) + (directory :id "attribution-unit" :ply 2 :path "docs/attribution" :description "Sub-unit of the docs pillar focusing on attribution.") + (directory :id "decisions-unit" :ply 2 :path "docs/decisions" :description "Sub-unit of the docs pillar focusing on decisions.") + (directory :id "developer-unit" :ply 2 :path "docs/developer" :description "Sub-unit of the docs pillar focusing on developer.") + (directory :id "governance-pillar" :ply 1 :path "docs/governance" :description "Primary governance pillar implementing the Triaxial Software Development\nMethodology (TSDM). Contains planning, maintenance, and audit tracks." + (canonical-locations :approach "SOFTWARE-DEVELOPMENT-APPROACH.adoc" :audit "audit/" :checklist "MAINTENANCE-CHECKLIST.adoc" :crg "CRG-CRITERIA.adoc" :maintenance "maintenance/" :planning "planning/" :tsdm_spec "TSDM.adoc") + (directory :id "governance-axis-audit" :ply 2 :path "docs/governance/audit" :description "TSDM Audit track." + (directory :id "governance-unit-compliance" :ply 3 :path "docs/governance/audit/compliance" :description "TSDM compliance unit within the Audit axis.") + (directory :id "governance-unit-effects" :ply 3 :path "docs/governance/audit/effects" :description "TSDM effects unit within the Audit axis.") + (directory :id "governance-unit-systems" :ply 3 :path "docs/governance/audit/systems" :description "TSDM systems unit within the Audit axis.")) + (directory :id "governance-axis-maintenance" :ply 2 :path "docs/governance/maintenance" :description "TSDM Maintenance track." + (directory :id "governance-unit-adaptive" :ply 3 :path "docs/governance/maintenance/adaptive" :description "TSDM adaptive unit within the Maintenance axis.") + (directory :id "governance-unit-corrective" :ply 3 :path "docs/governance/maintenance/corrective" :description "TSDM corrective unit within the Maintenance axis.") + (directory :id "governance-unit-perfective" :ply 3 :path "docs/governance/maintenance/perfective" :description "TSDM perfective unit within the Maintenance axis.")) + (directory :id "governance-axis-planning" :ply 2 :path "docs/governance/planning" :description "TSDM Planning track." + (directory :id "governance-unit-could" :ply 3 :path "docs/governance/planning/could" :description "TSDM could unit within the Planning axis.") + (directory :id "governance-unit-must" :ply 3 :path "docs/governance/planning/must" :description "TSDM must unit within the Planning axis.") + (directory :id "governance-unit-should" :ply 3 :path "docs/governance/planning/should" :description "TSDM should unit within the Planning axis."))) + (directory :id "legal-track" :ply 2 :path "docs/legal" :description "Sub-unit for legal and licensing documentation. Contains framework\nexhibits and archival license texts." + (canonical-locations :exhibits "exhibits/" :texts "texts/")) + (directory :id "practice-unit" :ply 2 :path "docs/practice" :description "Sub-unit of the docs pillar focusing on practice.") + (directory :id "reports-unit" :ply 2 :path "docs/reports" :description "Documentation unit for all automated and manual audit reports. Classified\nby domain." + (canonical-locations :compliance "compliance/" :maintenance "maintenance/" :performance "performance/" :quality "quality/" :security "security/") + (directory :id "report-unit-compliance" :ply 3 :path "docs/reports/compliance" :description "Specialised repository for compliance findings and evidence.") + (directory :id "report-unit-maintenance" :ply 3 :path "docs/reports/maintenance" :description "Maintenance reports.") + (directory :id "report-unit-performance" :ply 3 :path "docs/reports/performance" :description "Specialised repository for performance findings and evidence.") + (directory :id "report-unit-quality" :ply 3 :path "docs/reports/quality" :description "Specialised repository for quality findings and evidence.") + (directory :id "report-unit-security" :ply 3 :path "docs/reports/security" :description "Specialised repository for security findings and evidence.")) + (directory :id "standards-unit" :ply 2 :path "docs/standards" :description "Standards unit for high-rigor verification.") + (directory :id "theory-track" :ply 2 :path "docs/theory" :description "Documentation track for domain-specific theory and research foundations.\nCategorised by discipline." + (canonical-locations :computing "computing/" :formalisms "formalisms/" :mathematics "mathematics/" :ontologies "ontologies/" :other "other/" :socio_technical "socio-technical/") + :invariants ("Theoretical claims MUST reference established academic or technical formalisms") + (directory :id "theory-unit-computing" :ply 3 :path "docs/theory/computing" :description "Theoretical foundation for computing.") + (directory :id "theory-unit-formalisms" :ply 3 :path "docs/theory/formalisms" :description "Theoretical foundation for formalisms.") + (directory :id "theory-unit-mathematics" :ply 3 :path "docs/theory/mathematics" :description "Theoretical foundation for mathematics.") + (directory :id "theory-unit-ontologies" :ply 3 :path "docs/theory/ontologies" :description "Theoretical foundation for ontologies.") + (directory :id "theory-unit-other" :ply 3 :path "docs/theory/other" :description "Theoretical foundation for other.") + (directory :id "theory-unit-socio-technical" :ply 3 :path "docs/theory/socio-technical" :description "Theoretical foundation for socio technical.")) + (directory :id "whitepapers-track" :ply 2 :path "docs/whitepapers" :description "Unit for strategic publications and whitepapers. Categorised by target\naudience: Academic, Industry, and Outreach." + (canonical-locations :academic "academic/" :industry "industry/" :outreach "outreach/") + :invariants ("Each sub-track MUST have a clear audience definition in its README") + (directory :id "academic-unit" :ply 3 :path "docs/whitepapers/academic" :description "Academic logic at level 3.") + (directory :id "industry-unit" :ply 3 :path "docs/whitepapers/industry" :description "Industry logic at level 3.") + (directory :id "whitepapers-track-outreach" :ply 3 :path "docs/whitepapers/outreach" :description "Documentation track for outreach, education, and general-audience\nengagement. Focuses on accessibility and high-level conceptual clarity." + :invariants ("Language MUST be accessible to non-technical audiences" "Avoid deep technical jargon without providing clear definitions"))) + (directory :id "wikis-track" :ply 2 :path "docs/wikis" :description "Long-form collaborative documentation and project knowledge base.\nThis directory mirrors the content structure of the project wiki." + :invariants ("Primary wiki format MUST be Markdown (.md) — owner ruling 2026-09-19: wikis are the .md home (berrywiki); everything outside wikis stays .adoc"))) + (directory :ply 1 :path "examples") + (directory :id "features-pillar" :ply 1 :path "features" :description "Optional project features and ecosystem integrations. Provides bootstrap\nguides for high-rigor tools (Panic-Attacker, BoJ-Server, SSGs)." + (canonical-locations :boj_server "boj-server/" :panic_attacker "panic-attacker/" :ssg "ssg/") + (directory :id "feature-unit-boj-server" :ply 2 :path "features/boj-server" :description "Bootstrap and integration logic for the boj-server ecosystem component.") + (directory :id "feature-unit-panic-attacker" :ply 2 :path "features/panic-attacker" :description "Bootstrap and integration logic for the panic-attacker ecosystem component.") + (directory :id "feature-unit-ssg" :ply 2 :path "features/ssg" :description "Bootstrap and integration logic for the ssg ecosystem component.")) + (directory :id "source-pillar" :ply 1 :path "src" :description "Primary source code directory. Organized by role and architectural\naspect." + (canonical-locations :aspects "aspects/" :bridges "bridges/" :contracts "contracts/" :core "core/" :definitions "definitions/" :errors "errors/" :interface "interface/") + :invariants ("Core logic MUST reside in core/" "Verified seams MUST reside in interface/" "Safety constraints MUST reside in contracts/" "Failure dictionaries MUST reside in errors/") + (directory :id "source-unit-aspects" :ply 2 :path "src/aspects" :description "Cross-cutting concerns and domain-specific aspects (Security,\nObservability, Integrity)." + (canonical-locations :integrity "integrity/" :observability "observability/" :security "security/") + (directory :id "aspect-unit-integrity" :ply 3 :path "src/aspects/integrity" :description "Implementation logic for the integrity aspect.") + (directory :id "aspect-unit-observability" :ply 3 :path "src/aspects/observability" :description "Implementation logic for the observability aspect.") + (directory :id "aspect-unit-security" :ply 3 :path "src/aspects/security" :description "Implementation logic for the security aspect.")) + (directory :id "source-unit-bridges" :ply 2 :path "src/bridges" :description "Integration logic for external systems (API, Database, RPC, etc.).") + (directory :id "source-unit-contracts" :ply 2 :path "src/contracts" :description "Contracts unit for high-rigor source code.") + (directory :id "source-unit-core" :ply 2 :path "src/core" :description "Primary application logic and core domain models.") + (directory :id "source-unit-definitions" :ply 2 :path "src/definitions" :description "Definitions unit for high-rigor source code.") + (directory :id "source-unit-errors" :ply 2 :path "src/errors" :description "Errors unit for high-rigor source code.") + (directory :id "interface-seams-unit" :ply 2 :path "src/interface" :description "Consolidated \"Verified Interface Seams\" unit. This directory unifies the\nformal specification (ABI), the bridge implementation (FFI), and the\nresulting artifacts (Generated)." + (canonical-locations :abi "abi/" :ffi "ffi/" :generated "generated/") + :invariants ("ABI MUST be Idris2 (.idr)" "FFI MUST be Zig (.zig)" "Generated artifacts MUST be C-compatible" "The 'Truth' lives in abi/; the 'Implementation' lives in ffi/") + (directory :id "abi-logic" :ply 3 :path "src/interface/Abi" :description "Specialised Level 3 logic for abi.") + (directory :id "ffi-logic" :ply 3 :path "src/interface/ffi" :description "Specialised Level 3 logic for ffi." + (directory :id "src-unit" :ply 4 :path "src/interface/ffi/src" :description "Src logic at level 4.") + (directory :id "test-unit" :ply 4 :path "src/interface/ffi/test" :description "Test logic at level 4.")) + (directory :id "generated-logic" :ply 3 :path "src/interface/generated" :description "Specialised Level 3 logic for generated." + (directory :id "abi-unit" :ply 4 :path "src/interface/generated/abi" :description "Abi logic at level 4.")))) + (directory :id "verification-pillar" :ply 1 :path "verification" :description "Primary verification pillar. Contains evidence for correctness,\nperformance, formal proofs, randomized testing, and aerospace-grade\nhigh-assurance metrics (MC/DC coverage, traceability, safety cases)." + (canonical-locations :benchmarks "benchmarks/" :coverage "coverage/" :fuzzing "fuzzing/" :proofs "proofs/" :safety_case "safety_case/" :simulations "simulations/" :tests "tests/" :traceability "traceability/") + :invariants ("Evidence MUST be reproducible and documented" "High-assurance deployments MUST satisfy traceability and safety_case requirements") + (directory :id "benches-pillar" :ply 2 :path "verification/benchmarks" :description "Benches pillar.") + (directory :id "verification-unit-coverage" :ply 2 :path "verification/coverage" :description "High-assurance verification unit for coverage. \nCritical for safety-of-life and aerospace-grade deployment standards.") + (directory :id "fuzzing-unit" :ply 2 :path "verification/fuzzing" :description "Fuzzing unit for high-rigor verification.") + (directory :id "verification-unit-proofs" :ply 2 :path "verification/proofs" :description "Sub-unit focusing on proofs.") + (directory :id "verification-unit-safety_case" :ply 2 :path "verification/safety_case" :description "High-assurance verification unit for safety case. \nCritical for safety-of-life and aerospace-grade deployment standards.") + (directory :id "simulations-unit" :ply 2 :path "verification/simulations" :description "Simulations unit for high-rigor verification.") + (directory :ply 2 :path "verification/tests") + (directory :id "verification-unit-traceability" :ply 2 :path "verification/traceability" :description "High-assurance verification unit for traceability. \nCritical for safety-of-life and aerospace-grade deployment standards.")) + ) +) diff --git a/czech-file-knife/deny.toml b/czech-file-knife/deny.toml deleted file mode 100644 index 90f29229c..000000000 --- a/czech-file-knife/deny.toml +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# cargo-deny configuration for czech-file-knife -# https://embarkstudios.github.io/cargo-deny/ - -[graph] -targets = [] -all-features = true - -[advisories] -db-path = "~/.cargo/advisory-db" -db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" -unmaintained = "warn" -yanked = "warn" -notice = "warn" -ignore = [] - -[licenses] -version = 2 -allow = [ - "MIT", - "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "Zlib", - "0BSD", - "Unicode-DFS-2016", - "AGPL-3.0-or-later", - "GPL-3.0-or-later", - "LGPL-3.0-or-later", - "MPL-2.0", - "CC0-1.0", - "Unlicense", -] -confidence-threshold = 0.8 -exceptions = [] - -[[licenses.clarify]] -name = "ring" -expression = "MIT AND ISC AND OpenSSL" -license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] - -[bans] -multiple-versions = "warn" -wildcards = "allow" -highlight = "all" -workspace-default-features = "allow" -external-default-features = "allow" -allow = [] -deny = [] -skip = [] -skip-tree = [] - -[sources] -unknown-registry = "deny" -unknown-git = "warn" -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [] diff --git a/czech-file-knife/docs/AFFIRMATION.adoc b/czech-file-knife/docs/AFFIRMATION.adoc new file mode 100644 index 000000000..7bb85b7bb --- /dev/null +++ b/czech-file-knife/docs/AFFIRMATION.adoc @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += AFFIRMATION — Czech File Knife, as of +:toc: macro +:toclevels: 2 +:std-docs: https://github.com/hyperpolymath/standards/blob/main/docs + +_the No-Bullshit file: what we affirm was true and checkable at this moment._ + +[NOTE] +==== +*This file is a template.* Replace the `{{…}}` placeholders and the `<…>` anchor +fields, then re-run your project's own checks in the same session before +signing. It conforms to *profile A (evidential)* of the +link:{std-docs}/AFFIRMATION-STANDARD.adoc[AFFIRMATION authoring standard]. A +policy-surface repo should use profile B (MUST / INTEND / WISH) instead — see +that standard for the profile B skeleton. + +An *affirmation* is a solemn declaration of the truth of a statement, made by +someone who _declines to swear an oath_ — our truth-as-best-believed at a +stamped instant, binding on our honesty, not a claim of infallibility. It is the +third of the README / EXPLAINME / AFFIRMATION trio: + +[cols="1,3,2",options="header"] +|=== +| File | Answers | Tense +| `README.adoc` | _Where is this going, and why?_ — steering, intent, vision | future / aspirational +| `EXPLAINME.adoc` | _How is it built, and what's the evidence?_ — engineering | descriptive / mechanism +| *`AFFIRMATION.adoc`* (this file) | _What can we honestly affirm was *true and checkable* at a stamped moment?_ | a frozen instant, falsifiable +|=== + +This file is *optional*. Delete it rather than carry an affirmation you have not +re-verified — a stale affirmation is worse than none. +==== + +toc::[] + +== What this is, and how it works + +*What it is.* A short, dated, signed snapshot of what hyperpolymath can honestly and +verifiably claim about *Czech File Knife* at one exact commit. Nothing here is +marketing and nothing is a promise about the future — those live in the README. +This file is the receipt. + +*What the project is.* The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage. + +*How it stays trustworthy.* Three moving parts: + +. *Ground truth, not memory.* Every claim below must be produced by _running the + project's own checks_ in the session that writes this file (build, tests, + typecheck, `just audit`). Where a status doc, the `Justfile`, or memory says + otherwise, the live run wins and the contradiction is flagged here. +. *A frozen anchor.* The file names the exact commit SHA, branch, UTC timestamp, + working-tree delta and toolchain (see <>), so "true" always + means "true _at this point_". Move the SHA and this file is a draft until it + is re-run. +. *A real signature.* It is landed by a *signed git commit*; that signature over + this content at the anchored SHA is what makes the affirmation tamper-evident + and attributable — not the prose alone. + +*We are fallible.* This is our best honest belief, not a proof of its own +correctness. Treat it as a falsifiable claim, not gospel. + +== The epistemic contract (read this before you trust _or_ attack) + +This document records hyperpolymath's *best belief* at the timestamp below. It is +*not a guarantee of correctness.* The only guarantee is *no intentional +overclaim*: where something is proven we say "proven"; where it is a documented +trust boundary, an experiment, or an unwired module, we say so; where a claim is +the README's aspiration rather than a checked result, we say so. An honest claim +that later turns out false is an *error to be fixed* — not a lie. + +What you may conclude from this file: + +* Every claim below was produced by *running the tool* in the session that wrote + this file — not from memory, not copied forward from a previous affirmation, + and not read off a status document. +* Where a live run and a status document disagreed, the live run won and the + status document is named as stale in <>. + +What you may *not* conclude: + +* That anything is true *now*. This file describes the commit in + <> and nothing else. +* That unlisted things pass. *Silence is not a claim.* + +*Standing invitation to refute.* You are invited to bulldoze any claim in this +file. Bring a counter-example, a failing run, or a contradicting source. + +[#verifiable-anchor] +== Verifiable anchor + +[cols="1,3",options="header"] +|=== +| Field | Value + +| Project +| Czech File Knife + +| Repo +| `hyperpolymath/czech-file-knife` + +| Branch +| `main` + +| Commit (HEAD) +| `` + +| Permalink +| https://github.com/hyperpolymath/czech-file-knife/tree/ + +| Verified (UTC) +| `` + +| Working-tree delta at verification +| `clean`, or every modified and untracked file present when the checks ran, + with an explicit statement of whether it affects the results below. + +| Toolchain +| `` + +| Affirmed by +| Jonathan D.A. Jewell +|=== + +[IMPORTANT] +==== +*Never anchor to a tag.* Tags move, and a moved tag silently invalidates every +claim in this file. + +If you are reading this at a later commit, the claims may have drifted. Re-run +<> and write a fresh affirmation; do not trust a stale one. +==== + +== Companion documents and repo metadata (cross-check) + +The files a sceptic should read against this one — *including any that +contradict it*. A contradiction named here is honest; one the reader finds for +themselves is not. + +* `README.adoc` — the aspirational claims this file is measured against. +* `docs/EXPLAINME.adoc` — the mechanism. +* `docs/AUDIT.adoc` — the standing conformance audit. +* `*_chora.deed` (repo deed) — machine-readable repo metadata (AI allocation + ply tree). +* `` + +== The honest state (one breath) + +`` + +=== What is solid (and how we checked) + +[cols="2,1,3",options="header"] +|=== +| Claim | Status | Evidence (command, and what it printed) +| _e.g. The library builds clean_ | affirmed | `just build` at the anchor SHA — exit 0 +| _e.g. The ABI seam typechecks_ | affirmed | `idris2 --typecheck src/interface/abi.ipkg` — 0 errors +| _e.g. Feature X is complete_ | aspiration | README §… — *not checked*, do not read as affirmed +|=== + +=== The honest nuance you must not lose + +Where a true claim above is easily over-read. This section is what separates an +affirmation from marketing. + +* `` + +=== Known-incomplete but honestly fenced + +Gaps that fail *loudly* rather than silently. Name the guard that makes the +failure loud — a gap with no guard belongs in <> instead. + +* `` — fenced by `` + +[#outstanding] +=== Outstanding / weak / refuted (no spin) + +Known gaps, trust boundaries, postulates, unwired modules and stale docs. +*Silence is not affirmation* — name what you have not verified, and name +anything this session *refuted*. + +[IMPORTANT] +==== +Never delete a refuted claim. Deleting it is exactly the spin this genre exists +to prevent — mark it refuted and say what refuted it. +==== + +[#reproduce] +== Reproduce it yourself + +[source,bash] +---- +git clone https://github.com/hyperpolymath/czech-file-knife +cd czech-file-knife +git checkout +# +---- + +== One-line characterisation (quote this) + +> `` + +== Joint attestation + +We, the undersigned, assert that *to the best of our joint belief at the +timestamp above, every claim in this file is true and was checked as described* +— with no intentional overclaim, and with the open gaps stated rather than +hidden. + +* *Engineering party (AI):* `` — ran the checks recorded + here at `` and stands behind the wording above as a faithful + report of those runs. +* *Owner / maintainer:* Jonathan D.A. Jewell — _signs by committing + this file with `-S`; the git commit signature over this content, at the + commit SHA recorded above, is the cryptographic form of this affirmation._ + +[source,bash] +---- +git commit -S -s docs/AFFIRMATION.adoc -m "docs: affirm state at " +git log --show-signature -1 +---- + +[WARNING] +==== +Do not use `--no-verify`. The pre-commit hook enforces the SPDX header, and an +affirmation landed past its own repo's gates is self-refuting. +==== + +_Landed by a signed git commit. Verify with `git log --show-signature` that the +anchor SHA above matches the parent of the commit that introduced this +affirmation. If they do not match, this file is a *draft* and must be read as +one._ diff --git a/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc b/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc new file mode 100644 index 000000000..19ed4632a --- /dev/null +++ b/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += AI-Install README Section (canonical snippet) + +The block below is the canonical `== AI-Assisted Installation` section. Paste it +into a repository's `README.adoc` verbatim; `just validate-ai-install` greps the +README for the heading text, so keep the heading exactly as written. + +---- +== AI-Assisted Installation + +If you are an AI agent installing this project, read +link:docs/AI_INSTALLATION_GUIDE.adoc[the AI installation guide] first — it gives +the orientation order, the full prompt sequence, and the privacy notice. + +The one trap worth stating up front: **do not set `RSR_NON_INTERACTIVE=1`**. It +stubs the shell builtin `read`, which also disables the loops that perform token +substitution — the run never terminates and substitutes nothing. Pipe answers to +stdin instead. +---- diff --git a/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc b/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc new file mode 100644 index 000000000..057fcd6a2 --- /dev/null +++ b/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += AI-Assisted Installation Guide +:toc: +:toclevels: 3 +:sectnums: + +This guide is for an **AI agent** instantiating this template into a new +repository. A human following it will not be misled, but the ordering and the +warnings are written for an agent working non-interactively. + +[[ai-implementation]] +== Implementation for an AI agent + +=== 1. Orient before acting + +Read, in this order, before running anything: + +. `*_chora.deed` (the repo deed) — the universal AI entry point at the repository root. +. `CLAUDE.md` — the generated arrival pack (estate doctrine + repo identity). +. `.machine_readable/descriptiles/STATE.a2ml` — current phase and completion. + +Do not infer the repository's purpose from its name. If a fact is not written +down, record it as `UNASSIGNED` rather than inventing it. + +=== 2. Obtain the template + +Always take the template from `origin/main`, never from a local working tree — +local checkouts across this estate are routinely stale or carry sweep debris. + +[source,bash] +---- +git clone https://github.com/hyperpolymath/czech-file-knife.git my-project +cd my-project +rm -rf .git && git init +---- + +Alternatively, use GitHub's *Use this template* button, which produces a repo +with no git ancestry; `just repo-init` recovers the template tip via +`git ls-remote` and records it as the `parent-pin`. + +=== 3. Run the instantiation + +[source,bash] +---- +just repo-init # or: just repo-init +---- + +`just repo-init` renders every `{{TOKEN}}` in the tree, derives the repository +identity, writes the provenance files, and removes the template-only +directories. + +[WARNING] +==== +**Do not set `RSR_NON_INTERACTIVE=1`.** It stubs the shell builtin `read` +globally, which also disables the two `while read -r file` loops that drive +substitution. The result is an infinite loop that performs zero substitution — +it does not fail, it never terminates. + +Drive it non-interactively by **piping answers to stdin** instead. Real `read` +stays intact, so the data loops work. +==== + +The prompts, in order, are: + +[cols="1,3",options="header"] +|=== +| # | Prompt + +| 1 | Project name +| 2 | Repository slug — validated against `^[a-z0-9]+(-[a-z0-9]+)*$`; no capitals, spaces or underscores (it must be a valid Guix package name) +| 3 | Owner +| 4 | Author full name +| 5 | Author email +| 6 | Author organisation +| 7 | Previous/alternate email +| 8 | Project description +| 9 | Forge domain +| 10 | Security email +| 11 | Conduct email +| 12 | Project type +| 13 | Website URL (blank = default) +| 14 | OpenSSF Best Practices ID (blank = none) +| 15 | Service name (blank = default) +| 16 | Primary port (blank = 8080) +| 17 | Container registry (blank = default) +| 18 | Proceed? — answer `y` +|=== + +[IMPORTANT] +==== +Prompts 15–17 are gated on the presence of the container directory, **not** on +the project type. The template always ships one, so they always fire. Supplying +only 15 answers makes `read` hit EOF, which returns 1 and kills the recipe under +`set -e`. +==== + +=== 4. Verify the result + +[source,bash] +---- +just --list # every recipe resolves (imports are silent on failure) +just check-root-shape # root matches the allowlist +bash scripts/check-no-placeholders.sh . +just verify +---- + +A successful instantiation leaves **no** `{{TOKEN}}` anywhere in the tree, and +removes the template-only directories from the minted repository. If +`scripts/validate-template.sh` or the end-to-end instantiation test are still +present, the repository was not cured — they are the clearest single marker of +an un-instantiated repo. + +== Privacy + +This template performs no telemetry and transmits nothing during installation. + +Note what instantiation *records*, all of it locally and in your own git history: + +* The identity you supply at prompts 3–7 (owner, author name, both email + addresses, organisation) is written into source headers, `CITATION.cff`, + `.mailmap`, `CODEOWNERS` and the security contact. Treat the alternate email + as published data. +* `just repo-init` runs `git ls-remote` against the template's forge to resolve + the parent pin. This is a single outbound request; when it is unavailable the + pin is recorded as `UNASSIGNED` rather than guessed. +* The clade UUID is *derived*, never allocated: + `uuidgen --sha1 --namespace @url --name "//"`. It is a + function of the repository's name, so renaming the repository changes it and + registries must be regenerated rather than string-swapped. + +If you are an agent acting on someone else's behalf, do not invent identity +values to satisfy a prompt. Leave them `UNASSIGNED` and report the gap. diff --git a/czech-file-knife/docs/ARCHITECTURE.adoc b/czech-file-knife/docs/ARCHITECTURE.adoc new file mode 100644 index 000000000..44cce316e --- /dev/null +++ b/czech-file-knife/docs/ARCHITECTURE.adoc @@ -0,0 +1,48 @@ += Architecture + +== Overview + +This repository follows a modular, maintainable architecture designed for clarity, scalability, and long-term sustainability. + +== Directory Structure + +[source] +---- +. +├── src/ # Source code +├── tests/ # Test suites +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── config/ # Configuration files +├── LICENSE # License file +├── LICENSES/ # Full license texts +└── README.adoc # Project documentation +---- + +== Design Principles + +* *Separation of Concerns*: Each module has a single responsibility +* *Testability*: Code is written to be easily testable +* *Documentation*: All public APIs are documented +* *Configuration*: Environment-specific settings are externalized + +== Dependencies + +* External dependencies are minimized and clearly declared +* Version pinning is used for reproducibility + +== Security Considerations + +* Sensitive data is never committed to the repository +* Secrets are managed through environment variables or secure vaults +* Regular dependency audits are performed + +== Maintainability + +* Code follows consistent style guidelines +* Pull requests require review and CI checks +* Issues and discussions are tracked transparently + +--- + +_Last updated: 2026-07-18_ diff --git a/czech-file-knife/docs/AUDIT.adoc b/czech-file-knife/docs/AUDIT.adoc new file mode 100644 index 000000000..04f5d5ede --- /dev/null +++ b/czech-file-knife/docs/AUDIT.adoc @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Audit Gate +Codex +v1.1, 2026-04-07 +:toc: +:toclevels: 2 +:sectnums: + +== Purpose + +This root document exists so humans and bots can see the hard audit posture +without having to discover the standards repository first. + +Canonical source documents live in the `standards` repository. This file is a +repo-local audit gate summary for template users and automated agents. + +== Hard Rules + +* Do not call anything `stable`, `v1.0.0`, or full release unless the stable + release gate has been passed end to end. +* Do not publish implementation-facing work below `B` in CRG unless the work is + genuinely abstract and makes no implementation-readiness claim. +* `D` requires RSR compliance or a documented equivalent repository discipline. +* `C` requires deep code and folder annotation, not just local confidence. +* `B` means `beta-stable`: external breadth and safe broad trial, not merely + public visibility. +* Papers, whitepapers, release notes, and READMEs must not outrun the proofs, + tests, or artefacts that support their claims. +* Release paths must not ship with placeholders, stubs, `FIXME`, `XXX`, + template residue, fake fuzz, fake benches, or partial proof debt hidden as + if it were complete. + +== Canonical Standards + +Read these as the authoritative source: + +* `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` +* `standards/3-practice/release-pre-flight/V1-GATE.adoc` +* `standards/3-practice/publication-pre-flight/PREFLIGHT.adoc` +* `standards/3-practice/publication-pre-flight/ESTATE-AUDIT-BASELINE-2026-03-30.adoc` +* `standards/3-practice/session-management-standards/README.adoc` + +== Bot Requirement + +Bots operating in repositories derived from this template should treat this +document as a key root audit document and should not make optimistic release or +publication claims that conflict with it. diff --git a/czech-file-knife/docs/EXPLAINME.adoc b/czech-file-knife/docs/EXPLAINME.adoc new file mode 100644 index 000000000..c8ca0f607 --- /dev/null +++ b/czech-file-knife/docs/EXPLAINME.adoc @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += RSR Template Repo - Explainme +image:https://img.shields.io/badge/License-MPL_2.0-blue.svg[License: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] + +:toc: +:icons: font + +This file explains how the key template claims map to real files. + +== Central Session Protocol Authority + +Claim: +Session protocols are centrally maintained and not duplicated in this template. + +How this is implemented: + +* The local dispatcher (`session/dispatch.sh`) maps canonical commands to central + protocol paths in `standards/3-practice/session-management-standards`. +* Local files (`session/custom-checks.k9`, `session/local-hooks.sh`, + `coordination.k9`) are integration-only. + +Caveat: + +* If `SESSION_STANDARDS_DIR` is unset and no adjacent standards checkout exists, + the dispatcher records the command but cannot resolve central checklist paths. + +== Canonical Command Surface + +Claim: +Template bindings align to one canonical command model. + +How this is implemented: + +* `Justfile` provides thin aliases (`intake-repo`, `checkpoint-change`, + `verify-maintenance`, `verify-substantial`, `verify-release`, `close-planned`, + `close-urgent`, `recover-repo`, `handover-*`). +* Every alias calls `session/dispatch.sh` with canonical verb-object pairs. + +Caveat: + +* Recipes are wrappers only. They do not replace protocol content from + the central standards repo. + +== Runtime State Is Local + +Claim: +Session state is per-repository runtime output, not standards text. + +How this is implemented: + +* `session/dispatch.sh` writes command and continuity-core capture stubs to + `.session/LAST-CANONICAL-COMMAND.md` in the target repository path. + +Caveat: + +* Runtime files are intentionally lightweight and require human/agent completion. + +== Template Token Policy + +Claim: +Placeholders are explicit template content until initialization. + +How this is implemented: + +* `README.adoc` and bootstrap recipes keep `{{TOKEN}}` placeholders visible. +* `just init` performs token replacement. + +Caveat: + +* Uninitialized placeholders must not be treated as project-specific truth. + +== Dependency Updates (Dependabot) + +Claim: +Dependency bumps land fast and safely, without manual chasing. + +How this is implemented: + +* `dependabot.yml` watches the Dependabot-supported ecosystems the estate + actually uses: `github-actions`, `cargo`, `mix` (Elixir), `docker`. +* `dependabot-automerge.yml` auto-merges *every* bump (patch/minor/major, + security or routine) **once the required checks are green** — a broken bump + fails CI and stays open (you get an email); it never lands on a red `main`. + +Caveat: + +* Dependabot has **no or Bun ecosystem**, and `pnpm` only rides under the + `npm` ecosystem (itself banned). For this -first estate Dependabot cannot + watch the runtime dependencies; the `npm`/`pip` entries are retained only for + transitional/legacy manifests and are otherwise inert. dependency + currency is managed via `.json`/`.lock`, not Dependabot. +* Do **not** add Dependabot as a ruleset *bypass* actor: that lets bumps skip + the required checks (secret-scanning, SAST), removing the safety gate and the + "it broke" signal. Auto-merge-on-green gives fast merges without it. + +== Julia Registry Packages — Standalone Repo Requirement + +Claim: +If this template is used to create a Julia package, it must remain a standalone repository registered with the Julia package registry. + +How this is implemented: + +* Julia's package registry (General.jl or other) expects each package to be a standalone GitHub repository with `Project.toml` at the repository root. +* Installation via `Pkg.add()`, dependency resolution, and automated CI/CD all depend on this canonical structure. + +Caveat: + +* Do NOT move this repository into a monorepo or subdirectory, as this breaks registry registration and package discoverability. +* Julia packages published to a registry must each remain a standalone top-level repository (registry registration and package discovery require it). +* Non-registry Julia packages can be organized differently if they are not published to any registry. + + +== License + +This project is licensed under the Mozilla Public License, v. 2.0. See the `LICENSE` file for details. + +SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/czech-file-knife/docs/GOVERNANCE.adoc b/czech-file-knife/docs/GOVERNANCE.adoc new file mode 100644 index 000000000..ff82d8bdb --- /dev/null +++ b/czech-file-knife/docs/GOVERNANCE.adoc @@ -0,0 +1,65 @@ += Governance + +== Overview + +This project is governed by the following principles and structures to ensure transparent, inclusive, and effective decision-making. + +== Roles and Responsibilities + +=== Maintainers + +Maintainers are responsible for: + +* Reviewing and merging pull requests +* Managing releases and versioning +* Ensuring code quality and standards +* Triaging issues and bug reports +* Community engagement and support + +=== Contributors + +Contributors are expected to: + +* Follow the code of conduct +* Submit well-documented pull requests +* Write tests for new functionality +* Maintain existing tests +* Update documentation as needed + +== Decision Making + +=== Minor Changes + +* Can be made by any maintainer +* Include bug fixes, documentation updates, dependency updates + +=== Major Changes + +* Require discussion in issues or pull requests +* Include new features, architectural changes, API changes +* Need approval from at least 2 maintainers + +=== Breaking Changes + +* Require RFC (Request for Comments) process +* Need approval from majority of maintainers +* Must include migration guide + +== Code of Conduct + +All participants are expected to follow our Code of Conduct. Violations can be reported to the maintainers. + +== Communication + +* *Issues*: For bug reports and feature requests +* *Discussions*: For questions and general discussion +* *Pull Requests*: For code contributions + +== Licensing + +All contributions are made under the terms of the repository's LICENSE file. +By submitting a pull request, you agree to license your contributions accordingly. + +--- + +_Last updated: 2026-07-18_ diff --git a/czech-file-knife/docs/MAINTAINERS.adoc b/czech-file-knife/docs/MAINTAINERS.adoc new file mode 100644 index 000000000..3f1fad42d --- /dev/null +++ b/czech-file-knife/docs/MAINTAINERS.adoc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Maintainers +:toc: preamble + +This is the single maintainer roster for this repository. It supersedes the +earlier duplicates at `.github/MAINTAINERS` and `docs/attribution/MAINTAINERS.adoc`. + +== Current maintainers + +[cols="2,3,2",options="header"] +|=== +| Name | Role | Contact + +| Jonathan D.A. Jewell +| Lead Maintainer +| https://github.com/hyperpolymath[@hyperpolymath] +|=== + +== Responsibilities + +Maintainers are responsible for: + +* Reviewing and merging pull requests +* Triaging issues and feature requests +* Ensuring code quality and security standards +* Managing releases and versioning +* Upholding the project's Code of Conduct +* Maintaining documentation and examples +* Responding to security vulnerabilities + +== Contribution process + +Contributions are welcome via: + +. **Issues** — report bugs, request features, ask questions +. **Pull requests** — submit improvements for review +. **Discussions** — engage with the wider project + +== Decision making + +* Routine decisions (bug fixes, minor improvements) may be made by any maintainer. +* Significant changes require discussion and consensus among maintainers. +* Breaking changes should be discussed in an issue, with a migration path, before + implementation. + +== Becoming a maintainer + +Contributors who demonstrate consistent, high-quality contributions, an +understanding of the project's goals and standards, constructive participation, +and commitment to its long-term health may be invited to become maintainers at +the discretion of the existing maintainers. + +== Contact + +For questions about project governance, open an issue in this repository. + +== See also + +* link:GOVERNANCE.adoc[Governance model] +* link:../.github/CODE_OF_CONDUCT.md[Code of Conduct] +* link:../.github/CONTRIBUTING.md[Contributing guide] +* link:attribution/CODEOWNERS.adoc[Code owners] diff --git a/czech-file-knife/docs/QUICKSTART.adoc b/czech-file-knife/docs/QUICKSTART.adoc new file mode 100644 index 000000000..975cf0a37 --- /dev/null +++ b/czech-file-knife/docs/QUICKSTART.adoc @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Quickstart +:toc: preamble + +Get up and running in 60 seconds. + +== Prerequisites + +* Git 2.40+ +* just (command runner) +* Your language toolchain (see Justfile for details) + +== From Template (New Project) + +[source,bash] +---- +git clone https://github.com/hyperpolymath/czech-file-knife my-project +cd my-project +rm -rf .git && git init -b main +just repo-init # interactive placeholder replacement +---- + +== Project Structure + +See README.adoc in the root for the Dual-Track architecture summary. diff --git a/czech-file-knife/docs/README.adoc b/czech-file-knife/docs/README.adoc new file mode 100644 index 000000000..dc7274f0e --- /dev/null +++ b/czech-file-knife/docs/README.adoc @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Documentation + +For the full directory map — every directory, what it is, and who reads it — +see link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc]. It is generated +and CI fails if it goes stale, so prefer it over any hand-written listing. + +== Start here + +* link:onboarding/QUICKSTART-USER.adoc[QUICKSTART-USER] — using a repo built from this template. +* link:onboarding/QUICKSTART-DEV.adoc[QUICKSTART-DEV] — developing in one. +* link:onboarding/QUICKSTART-MAINTAINER.adoc[QUICKSTART-MAINTAINER] — maintaining one. +* link:AI_INSTALLATION_GUIDE.adoc[AI_INSTALLATION_GUIDE] — for an AI agent instantiating the template. +* link:EXPLAINME.adoc[EXPLAINME] — how the pieces actually work. + +== The tracks + +[cols="1,3",options="header"] +|=== +| Directory | Holds + +| `architecture/` | Topology, threat model, and the generated repository map. +| `attribution/` | CODEOWNERS rationale and credit. +| `decisions/` | ADRs. `0000-template.adoc` is the template; number upwards from there. +| `developer/` | Developer-facing deep dives (ABI/FFI, tooling integrations). +| `governance/` | Governance model, maintenance checklist, development approach, audits, planning. +| `legal/` | Licence exhibits. +| `onboarding/` | Quickstarts and the LLM warm-up documents. +| `practice/` | Operational and implementation material, incl. AI conventions. +| `proposals/` | Proposals and RFCs, including the root-cleanup proposal this layout came from. +| `reports/` | Generated and periodic reports (audit, compliance, security, ...). +| `standards/` | Pointers to the estate canon in the `standards` repo. +| `status/` | READINESS, ROADMAP, TEST-NEEDS, PROOF-NEEDS, PROOF-STATUS. +| `theory/` | Formal and conceptual material. +| `whitepapers/` | Academic, industry and outreach whitepapers. +| `wikis/` | Long-form wiki material. +|=== + +== Core documents + +* link:governance/MAINTENANCE-CHECKLIST.adoc[governance/MAINTENANCE-CHECKLIST.adoc] +* link:governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc[governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc] +* link:MAINTAINERS.adoc[MAINTAINERS.adoc] — the single maintainer roster. +* link:GOVERNANCE.adoc[GOVERNANCE.adoc] — the governance model. + +[NOTE] +==== +`.md` files are not permitted under `docs/`; AsciiDoc is the estate standard and +`scripts/check-no-md-in-docs.sh` enforces it. The previous version of this file +pointed at `maintenance/MAINTENANCE-CHECKLIST.md` — a directory that does not +exist, holding a file with an extension this tree forbids. +==== diff --git a/czech-file-knife/docs/RSR-PHILOSOPHY.adoc b/czech-file-knife/docs/RSR-PHILOSOPHY.adoc new file mode 100644 index 000000000..ef3b6cabe --- /dev/null +++ b/czech-file-knife/docs/RSR-PHILOSOPHY.adoc @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += RSR Philosophy — How Work Is Done Here +:toc: +:icons: font + +[.lead] +The RSR standard is not only a set of files to scaffold; it is a way of working. +This document states the operating principles an agent or maintainer is expected +to hold while doing work in any hyperpolymath repository. They are deliberately +few, deliberately blunt, and meant to be applied — not admired. + +These principles are the human-readable home of the *Doctrine* that the estate +arrival-pack projects into every repository's `CLAUDE.md`. The Doctrine list is +the terse machine-facing summary; this file is the reasoning behind it. Where the +two differ, the canon (`hyperpolymath/standards`) and the owner's `manifesto` +prevail. + +== The load-bearing four + +These four are named together because they describe the *order*, *manner*, *locus* +and *standard* of the work undertaken, and because each is a standing trap that +fluent, plausible work falls into. + +=== Holes before goals + +Fix soundness holes before you build features, optimise, or polish documentation. +A hole is anywhere the system can be wrong without saying so: an unproven seam, an +unchecked input, a `TODO` that load-bearing code depends on, a claim no tool +establishes. Goals are everything you would rather be doing. The discipline is to +let the holes set the agenda, not the goals — because a goal reached on top of a +hole is not reached. + +=== Always fail loudly + +No silent green. A check that cannot fail is not a check; a fallback that hides a +broken precondition is a forged result. When something is wrong, the system must +say so — visibly, early, and in a way that stops the line — rather than degrade +quietly into a plausible-looking success. Seams (ABI / FFI boundaries) are sealed +and proven, not assumed. Prefer a build that breaks to a build that lies. + +=== Solutions at source + +Fix the canonical, upstream origin of a problem — never patch the downstream +symptom. When a defect, a drift, or a wrong setting appears in many places, it is +almost never many problems; it is one problem at a source, expressed many times. +Remediating the copies without fixing the source guarantees the problem returns. + +Two obligations follow from this, and they are not optional: + +* *Find the source.* Before acting, trace the thing back to where it is actually + defined — the template, the generator, the canon, the single point that + everything else inherits from. The estate's structure is + `standards → czech-file-knife → (every repo)`; a fix that belongs at the + template does not belong in 380 leaves. +* *Be mindful of every up- and down-stream.* A change at a source propagates. + Before you make it, know what feeds into the thing you are changing (upstream) + and what depends on it (downstream), and make sure the change is safe across + all of them. A correct fix that breaks a downstream consumer is not yet a fix. + +When the source genuinely cannot be reached in this pass — an upstream you do not +own, a fix gated on owner ratification — remediate the downstream *and* record the +source fix as the real work still owed. Patching the symptom silently, as if it +were the cure, is itself a hole (see _always fail loudly_). + +=== Elegance by default + +Treat the most elegant and correct long-term solution as the default choice — and +say which option that is, every time you put a choice to the owner. This is not a +preference for tidiness; it is a refusal to let the judgment be made silently. + +A set of options offered as merely _different_ is not neutral. Whichever one is +listed first, or described most fluently, becomes the recommendation whether you +intended it or not — and the option that reads most fluently is usually the one you +found quickest to write. So an unlabelled list quietly substitutes your convenience +for the standard this estate is held to, which is the same error as patching a +symptom in place of a source: a choice backed by authority rather than justified by +the construction that produced it. + +Three obligations follow, and they are not optional: + +* *Label it.* Exactly one option is marked as the most elegant and correct in the + long run. Judge that on long-run grounds alone — correctness, no deferred + breakage, no special cases, a fix at the generator rather than at the instance — + and never on effort, speed, or convenience. If two options genuinely tie, say so; + silence is not a tie. +* *Justify any departure.* If your recommendation is not the elegant arm, name both + arms and state, in the offer itself, why you are departing on this occasion — an + irreversible step already taken, a live outage, a precondition still gated. An + unexplained departure is a defect in the question, not a matter of style. Never + merge the two labels to avoid having to write the explanation. +* *It binds unasked decisions too.* This is a methodology, not a formatting rule + for questions. Where you take the non-elegant arm without asking, report it + rather than absorb it. + +The default is a *starting point, not a prediction*. The owner may take the other +arm with full information, and often will; what may not happen is an expedient +choice made in ignorance that it was the expedient one. + +== The full Doctrine + +The four above are the principles most often abused, but they sit inside the +estate's full operating Doctrine. The canonical, always-current list is projected +into the top of every repository's `CLAUDE.md` from +`.machine_readable/arrival-pack/`. In summary it also holds: ground-truth by +running the tool, not trusting status docs; distrust the neural for exactness +(licences / invariants / equivalence belong to PLASMA, not an LLM); squabble, +don't bypass (reach green by satisfying the gate, never by admin-override); no +automated licence edits; no deletion by access-recency; wire first; always sign; +report faithfully (no overclaim); stop-first on costly or outward-facing actions; +boundaries are real; and equivalence as identity. + +== Status + +* *Licence:* CC-BY-SA-4.0 (documentation). +* *Canon:* `hyperpolymath/standards` is the source of truth for these principles; + `hyperpolymath/manifesto` states the doctrine in the owner's voice. This file + operationalises them for the RSR template and its descendants. diff --git a/czech-file-knife/docs/RSR_OUTLINE.adoc b/czech-file-knife/docs/RSR_OUTLINE.adoc new file mode 100644 index 000000000..9bbe401e5 --- /dev/null +++ b/czech-file-knife/docs/RSR_OUTLINE.adoc @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += RSR Template Repository + +image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] +:toc: +:sectnums: + +// Badges +image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] +image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] +image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] + +[IMPORTANT] +==== +*Superseded as an overview.* The root `README.adoc` is the entry point, and the +authoritative directory map is generated at +link:architecture/REPOSITORY-MAP.adoc[architecture/REPOSITORY-MAP.adoc]. + +This document is kept for its longer-form rationale, not as a second README. +Its hand-written directory tree was removed in 2026-08 after being found stale +in six places at once (a2ml files listed outside `descriptiles/`, `Trustfile.hs` +for `Trustfile.a2ml`, `docs/CITATIONS.adoc` and `docs/TOPOLOGY-GUIDE.adoc` which +do not exist, and `src/interface/Abi/` which was renamed to lowercase). +==== + +== Overview + +**The canonical template for RSR (Rhodium Standard Repository) projects.** + +This repository provides the standardized structure, configuration, and tooling for all RSR-compliant repos. Use it to: + +* Bootstrap new projects with RSR compliance +* Reference the standard directory structure +* Copy configuration templates (Justfile, STATE.a2ml, etc.) + +== Quick Start + +[source,bash] +---- +# Clone the template +git clone https://github.com/hyperpolymath/RSR-template-repo my-project +cd my-project + +# Remove template git history +rm -rf .git +git init + +# Interactive bootstrap — replaces all placeholders +just repo-init + +# Enter development environment +guix shell -D -f build/guix.scm + +# Validate compliance +just validate-rsr +---- + +== What's Included + +[cols="1,3"] +|=== +|File/Directory |Purpose + +|`.editorconfig` +|Editor configuration (indent, charset) + +|`.gitignore` +|Standard ignore patterns + +|`.gitattributes` +|Line endings, diff drivers, binary detection + +|`.guix-channel` +|Guix channel definition + +|`www/` +|Site-operations bundle; RFC-compliant metadata at `www/.well-known/` (security.txt, ai.txt, humans.txt) + +|`.machine_readable/` +|All machine-readable content: state files (6 a2ml), `bot_directives/`, `contractiles/` + +|`docs/` +|Documentation directory + +|`build/guix.scm` +|Guix package definition (canon 1.2.1 guix-primary names `build/`) + +|`Justfile` +|Task runner with 40+ recipes + +|`Containerfile` +|Container build (Wolfi base, Podman) + +|`LICENSE` +|MPL-2.0 (code) / CC-BY-SA-4.0 (docs) + +|`EXHIBIT-A-ETHICAL-USE.txt` +|MPL-2.0 source-code-form license notice (LICENSE Exhibit A) + +|`EXHIBIT-B-QUANTUM-SAFE.txt` +|Quantum-safe provenance spec (LICENSE Exhibit B) + +|`README.adoc` +|Project overview + +|`TOPOLOGY.md` +|Architecture diagram and completion dashboard + +|`PLACEHOLDERS.md` +|Template variable reference and replacement guide + +|`*_chora.deed` (repo deed) +|Universal AI agent entry point + +|`AI.a2ml` +|Claude-specific instructions + +|`src/interface/Abi/` +|Idris2 ABI definitions (Types, Layout, Foreign) + +|`ffi/zig/` +|Zig FFI implementation + +|`generated/abi/` +|Auto-generated C headers from Idris2 ABI +|=== + +== Justfile Features + +The template Justfile provides: + +* **Combinatoric matrix recipes** for build, test, container, CI +* **Cookbook generation**: `just cookbook` -> `docs/just-cookbook.adoc` +* **Man page generation**: `just man` -> `docs/man/project.1` +* **RSR validation**: `just validate-rsr` +* **STATE.a2ml management**: `just state-touch`, `just state-phase` +* **Container support**: `just container-build`, `just container-push` +* **CI matrix**: `just ci-matrix [stage] [depth]` + +=== Key Recipes + +[source,bash] +---- +just # Show all recipes +just help # Detailed help +just info # Project info +just combinations # Show matrix options + +just build # Build (debug) +just test # Run tests +just quality # Format + lint + test +just ci # Full CI pipeline + +just validate # RSR + STATE validation +just docs # Generate all docs +just cookbook # Generate Justfile docs + +just guix-shell # Guix dev environment +just container-build # Build container +---- + +== Directory Structure + +[source] +See link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc] for the +directory map. It is generated from the tree and diffed in CI, so unlike the +hand-written tree that used to sit here it cannot go stale. + + +== RSR Compliance + +=== Language Tiers + +* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, , Gleam +* **Tier 2** (Silver): Nickel, Guile Scheme, Idris2, OCaml +* **Infrastructure**: Guix channels, derivations, Julia batch scripts + +=== Required Files + +* `.editorconfig` +* `.gitignore` +* `Justfile` +* `README.adoc` +* `LICENSE` (MPL-2.0) +* `.machine_readable/descriptiles/STATE.a2ml` +* `www/.well-known/security.txt` +* `www/.well-known/ai.txt` +* `www/.well-known/humans.txt` +* `build/guix.scm` + +=== Prohibited + +* Python outside `salt/` directory +* /JavaScript (use ) +* CUE (use Guile/Nickel) +* `Dockerfile` (use `Containerfile`) +* npm, Bun, pnpm, yarn (use ) +* Go (use Rust) + +== STATE.a2ml + +The STATE.a2ml file tracks project state: + +[source] +---- +# STATE — Project State Checkpoint +# Format: a2ml (AI-readable markup) + +project: v-graphql +version: 0.1.0 +last-updated: 2026-02-14 +status: active + +phase: implementation +maturity: beta + +ecosystem: + part-of: RSR Framework + depends-on: [] + +milestones: + - name: Initial setup + completion: 100 + - name: Core implementation + completion: 0 +---- + +== Badge Schema + +Generate badges from STATE.a2ml: + +[source,bash] +---- +just badges standard +---- + +See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. + +== Ecosystem Integration + +This template is part of: + +* **STATE.a2ml Ecosystem**: Conversation checkpoints +* **RSR Framework**: Repository standards +* **Consent-Aware-HTTP**: .well-known compliance +* **Hypatia**: Neurosymbolic security scanning +* **gitbot-fleet**: Bot orchestration + +== License + +SPDX-License-Identifier: CC-BY-SA-4.0 + +== Links + +* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE tooling +* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement +* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/czech-file-knife/docs/STATE-VISUALIZER.adoc b/czech-file-knife/docs/STATE-VISUALIZER.adoc new file mode 100644 index 000000000..2e23ca3ec --- /dev/null +++ b/czech-file-knife/docs/STATE-VISUALIZER.adoc @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Project State Visualizer + +[source] +---- + + + + +# RSR Template Repo — Project Topology + +## System Architecture + +``` + ┌─────────────────────────────────────────┐ + │ NEW REPOSITORY │ + │ (Consumer of this Template) │ + └───────────────────┬─────────────────────┘ + │ Scaffolding + ▼ + ┌─────────────────────────────────────────┐ + │ RSR TEMPLATE HUB │ + │ │ + │ ┌───────────┐ ┌───────────────────┐ │ + │ │ AI Gate- │ │ ABI / FFI │ │ + │ │ keeper │ │ Standard │ │ + │ │ (0-AI-M) │ │ (Idris2/Zig) │ │ + │ └─────┬─────┘ └────────┬──────────┘ │ + │ │ │ │ + │ ┌─────▼─────┐ ┌────────▼──────────┐ │ + │ │ Topology │ │ SCM / 6SCM │ │ + │ │ Guide │ │ Metadata │ │ + │ │ (Visual) │ │ (machine_read) │ │ + │ └─────┬─────┘ └────────┬──────────┘ │ + │ │ │ │ + │ ┌─────▼─────────────────▼──────────┐ │ + │ │ CONTAINER ECOSYSTEM │ │ + │ │ ┌──────────┐ ┌───────────────┐ │ │ + │ │ │ Podman / │ │ selur-compose │ │ │ + │ │ │ OCI │ │ cerro-torre │ │ │ + │ │ │ Build │ │ svalinn/vordr │ │ │ + │ │ └──────────┘ └───────────────┘ │ │ + │ │ ct-build.sh deploy.k9.ncl │ │ + │ └──────────────────────────────────┘ │ + └────────│─────────────────│──────────────┘ + │ │ + ▼ ▼ + ┌─────────────────────────────────────────┐ + │ PLATFORM INTEGRATION │ + │ ┌───────────┐ ┌───────────┐ ┌───────┐│ + │ │ GitHub │ │ GitLab │ │ Nix / ││ + │ │ Workflows │ │ CI/CD │ │ Guix ││ + │ └───────────┘ └───────────┘ └───────┘│ + └─────────────────────────────────────────┘ + + ┌─────────────────────────────────────────┐ + │ REPO INFRASTRUCTURE │ + │ Justfile / Mustfile .machine_readable/ │ + │ Codeowners / Reuse *_chora.deed (root) │ + └─────────────────────────────────────────┘ +``` + +## Completion Dashboard + +``` +COMPONENT STATUS NOTES +───────────────────────────────── ────────────────── ───────────────────────────────── +CORE STANDARDS + ABI/FFI Standard (Idris2/Zig) ██████████ 100% Universal interface stable + AI Gatekeeper (repo deed) ██████████ 100% Universal entry point active + TOPOLOGY.md Standard ██████████ 100% Visual summary guide active + 6SCM Metadata Structure ██████████ 100% Machine-readable state stable + +INFRASTRUCTURE + Justfile Automation ██████████ 100% Standard build/verify tasks + CI/CD Workflow Templates ██████████ 100% GH/GL scaffolding verified + Multi-Forge Sync ██████████ 100% Hub-and-spoke mirroring stable + +CONTAINER ECOSYSTEM (Phase 2) + Containerfile (OCI build) ██████████ 100% Multi-stage Chainguard base + selur-compose orchestration ██████████ 100% Template + concrete example + cerro-torre manifest ██████████ 100% Bundle metadata & signing + svalinn gateway policy ██████████ 100% .gatekeeper.yaml active + vordr runtime monitoring ██████████ 100% Runtime config template + k9-svc deployment (Nickel) ██████████ 100% Hunt-level deploy descriptor + ct-build.sh pipeline ██████████ 100% Build/sign/verify script + Justfile container-* recipes ██████████ 100% 8 recipes integrated + Trustfile CONTAINER_SUPPLY_CHAIN ██████████ 100% Supply chain section added + +REPO INFRASTRUCTURE + .machine_readable/ ██████████ 100% STATE/META/ECOSYSTEM active + Governance & License ██████████ 100% MPL-2.0 & Ethical use verified + Development Shells (Guix) ██████████ 100% Reproducible env stable + +───────────────────────────────────────────────────────────────────────────── +OVERALL: ██████████ 100% RSR Template Stable & Certified +``` + +## Key Dependencies + +``` +Philosophy ──────► RSR Standard ──────► Template Scaffolding ──► New Repo + │ │ │ │ + ▼ ▼ ▼ ▼ +CCCP Policy ─────► repo deed ────────────► Justfile ──────────► Compliance + │ + ▼ + Container Ecosystem + ┌──────────┼──────────┐ + ▼ ▼ ▼ + selur-compose cerro- svalinn/ + (orchestrate) torre vordr + (sign) (monitor) + │ + ▼ + k9-svc deploy +``` + +## Update Protocol + +This file is maintained by both humans and AI agents. When updating: + +1. **After completing a component**: Change its bar and percentage +2. **After adding a component**: Add a new row in the appropriate section +3. **After architectural changes**: Update the ASCII diagram +4. **Date**: Update the `Last updated` comment at the top of this file + +Progress bars use: `█` (filled) and `░` (empty), 10 characters wide. +Percentages: 0%, 10%, 20%, ... 100% (in 10% increments). +---- diff --git a/czech-file-knife/docs/architecture.adoc b/czech-file-knife/docs/architecture.adoc new file mode 100644 index 000000000..a61ac86a4 --- /dev/null +++ b/czech-file-knife/docs/architecture.adoc @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Architecture — Czech File Knife +:revdate: 2026-MM-DD + +== System overview + +One paragraph: what this project does and what it does not do. State the +*invariant* — the property that, if violated, would make the whole project +pointless. Future maintainers will read this paragraph first. + +== Component diagram + +Replace this section with an ASCII or Mermaid diagram. Keep it under 20 +lines — anything bigger belongs in `architecture/`. + +[source] +---- ++------------------+ +------------------+ +| Component A | ---> | Component B | ++------------------+ +------------------+ + | + v ++------------------+ +| Component C | ++------------------+ +---- + +== Data flow + +For each external input, describe: + +* **Source**: where it comes from. +* **Validation**: what guarantees we enforce on entry. +* **Transformation**: high-level processing stages. +* **Sink**: where the result goes. + +== Key invariants + +Enumerate the load-bearing invariants of the system. Each should have: + +. A one-line statement. +. The code location(s) that enforce it. +. The failure mode if the invariant is violated. + +Example: + +[cols="1,2,2,2", options="header"] +|=== +| # | Invariant | Enforced at | Failure mode + +| 1 +| All HTTP requests carry a valid `X-Request-ID`. +| `src/request_id.rs` +| Logs become unjoinable; correlation breaks. + +| 2 +| The output buffer is always flushed before exit. +| `src/main.rs:88-92` (Drop impl) +| Last ~16KB of log lost on crash. +|=== + +== Dependencies + +* **Internal**: list other hyperpolymath repos this depends on. +* **External**: SHA-pinned (see `Cargo.lock` / `.lock` / etc.). +* **Build-time**: tools required to build (just, , cargo, …). + +== Out of scope + +Explicit non-goals. Things we deliberately do *not* do, with a one-line +reason for each. This section saves more time than the rest combined. + +== See also + +* link:./usage.adoc[Usage] — consumer perspective. +* link:./contributing.adoc[Contributing] — developer setup. +* link:./decisions/[ADRs] — historical record of why this shape. diff --git a/czech-file-knife/docs/DISTRIBUTED_FILESYSTEMS.adoc b/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc similarity index 100% rename from czech-file-knife/docs/DISTRIBUTED_FILESYSTEMS.adoc rename to czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc diff --git a/czech-file-knife/docs/HYBRID-OPERATIONS.adoc b/czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc similarity index 100% rename from czech-file-knife/docs/HYBRID-OPERATIONS.adoc rename to czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc diff --git a/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc b/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc new file mode 100644 index 000000000..e40745b0a --- /dev/null +++ b/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell +// +// GENERATED by scripts/gen-repo-map.sh - do not hand-edit. +// Regenerate with `just repo-map`. CI fails if this file is stale. += Repository map +:toc: + +The single authoritative map of this repository. It is generated from the +tree and from the annotations in `.machine_readable/root-allow.txt`, and CI +fails if it drifts, so it cannot rot the way its five hand-written +predecessors did. + +== Root + +[cols="2,1,4",options="header"] +|=== +| Path | Required | What it is, and who reads it + +| `.cicd-hygiene-allow` +| yes +| code-hygiene gate allowlist; template scaffolds are seams, not debt + +| `.clinerules` +| yes +| AI editor rules. Cline/Cursor/Windsurf read these from the project + +| `.clusterfuzzlite/` +| optional +| ClusterFuzzLite reads its build config from the repo root only + +| `.cursorrules` +| yes +| ROOT, so the copies formerly under .machine_readable/ai/ were inert. + +| `.devcontainer/` +| optional +| VS Code dev container spec; present only where the container capability is declared + +| `.editorconfig` +| yes +| - + +| `.envrc` +| yes +| - + +| `.gitattributes` +| yes +| - + +| `.github/` +| yes +| community health + workflows (GitHub reads this path and no other) + versioned git hooks (.github/hooks/, wired via core.hooksPath) + +| `.gitignore` +| yes +| - + +| `.gitleaksignore` +| yes +| exact fingerprints for reviewed historical false positives + +| `.gitmessage` +| yes +| git commit template; wired by .github/hooks/install.sh (git config commit.template) + +| `.hypatia-ignore` +| yes +| the Hypatia scanner reads it from the repo root + +| `.machine_readable/` +| yes +| manifests, contractiles, policies. Renamed back from machine-readable/ 2026-09-17 by owner ruling, for one canonical spelling estate-wide (census at the 2026-08 divergence: 48 dotted vs 9 hyphenated — the majority was already dotted). See docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc + +| `.mailmap` +| yes +| git reads .mailmap from the worktree root only + +| `.tool-versions` +| yes +| - + +| `.windsurfrules` +| yes +| - + +| `CHANGELOG.adoc` +| yes +| AsciiDoc is the estate-standard documentation format + +| `CITATION.cff` +| yes +| citation metadata; GitHub reads it from the root of the default branch only + +| `CLAUDE.md` +| yes +| generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) + +| `CONTRIBUTING.adoc` +| optional +| estate docs gate (standards scripts/check-docs-presence.sh) requires CONTRIBUTING at the ROOT; this is the copy it verifies + +| `Cargo.lock` +| optional +| rust capability; lives beside Cargo.toml + +| `Cargo.toml` +| optional +| rust capability (template-capability-gates.toml); cargo requires the workspace manifest at root + +| `GEMINI.md` +| optional +| pointer to CLAUDE.md for repos without AGENTS.md yet + +| `Justfile` +| yes +| thin; delegates phases to build/just/*.just + +| `LICENSE` +| yes +| - + +| `LICENSES/` +| yes +| REUSE licence texts, dual-licence model (code MPL-2.0 / docs CC-BY-SA-4.0) + +| `README.adoc` +| yes +| - + +| `benches/` +| optional +| Cargo-conventional at package root + +| `build/` +| yes +| build orchestration: just/ phase modules, container/, docs-seed/, templates/, guix.scm (canon 1.2.1 guix-primary template_ref = build/) + +| `ci/` +| yes +| .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require + +| `coordination.k9.ncl` +| yes +| repo-local session binding (template-mandated) + +| `czech-file-knife_chora.deed` +| yes +| the repo deed: universal AI entry point; family-7 allocation manifest + ply tree folded in (standards#837 pilot). Filename carries the repo slug (deed dispatch _chora.deed), so repo-init renames it at mint + +| `docs/` +| yes +| human documentation + +| `examples/` +| optional +| Cargo-conventional at package root + +| `features/` +| optional +| optional feature packs + +| `mise.toml` +| yes +| pinned toolchains + +| `scripts/` +| yes +| repo helper scripts + +| `session/` +| yes +| dispatch.sh, custom-checks.k9.ncl, local-hooks.sh + +| `sonar-project.properties` +| optional +| only where the repo is analysed by SonarCloud + +| `src/` +| yes +| - + +| `tests/` +| yes +| - + +| `verification/` +| optional +| proofs; only where formal-proofs is declared + +| `www/` +| yes +| site-operations bundle; canonical .well-known/ lives at www/.well-known/ (issue #53) + +|=== + +== Declared structure not yet filled + +These directories currently hold only a stub `README.adoc` and a level +manifest. That is deliberate. They declare the shape a repository minted +from this template is expected to grow into; they are *intended +structure, not abandoned work*. Add content, or delete the directory in +your own repo - but do not read their emptiness as neglect here. + +[cols="1"] +|=== +| `.machine_readable/scripts/verification/` +| `docs/governance/audit/` +| `docs/governance/audit/compliance/` +| `docs/governance/audit/effects/` +| `docs/governance/audit/systems/` +| `docs/governance/maintenance/` +| `docs/governance/maintenance/adaptive/` +| `docs/governance/maintenance/corrective/` +| `docs/governance/maintenance/perfective/` +| `docs/governance/planning/` +| `docs/governance/planning/could/` +| `docs/governance/planning/must/` +| `docs/governance/planning/should/` +| `docs/reports/compliance/` +| `docs/reports/maintenance/` +| `docs/reports/performance/` +| `docs/reports/security/` +| `docs/standards/` +| `docs/theory/` +| `docs/theory/computing/` +| `docs/theory/formalisms/` +| `docs/theory/mathematics/` +| `docs/theory/ontologies/` +| `docs/theory/other/` +| `docs/theory/socio-technical/` +| `docs/whitepapers/` +| `docs/whitepapers/academic/` +| `docs/whitepapers/industry/` +| `docs/whitepapers/outreach/` +| `features/boj-server/` +| `features/panic-attacker/` +| `src/aspects/` +| `src/aspects/integrity/` +| `src/aspects/observability/` +| `src/aspects/security/` +| `src/bridges/` +| `src/contracts/` +| `src/core/` +| `src/definitions/` +| `src/errors/` +| `verification/benchmarks/` +| `verification/coverage/` +| `verification/fuzzing/` +| `verification/safety_case/` +| `verification/simulations/` +| `verification/tests/` +| `verification/traceability/` +|=== + +== Where things are enforced + +* Root shape - `scripts/check-root-shape.sh` against + `.machine_readable/root-allow.txt`, run by `.github/workflows/estate-rules.yml`. + The check is bidirectional: unlisted entries fail, and required entries + that are absent also fail. +* This map - `just repo-map` must produce no diff. +* Community health - GitHub reads `.github/` and no other path. +* Variant divergence - `scripts/check-variant-drift.sh` compares a child + to its parent BY PATH, so any move here invalidates every child's + declared path lists until they are re-anchored. diff --git a/czech-file-knife/docs/architecture/THREAT-MODEL.adoc b/czech-file-knife/docs/architecture/THREAT-MODEL.adoc new file mode 100644 index 000000000..645147802 --- /dev/null +++ b/czech-file-knife/docs/architecture/THREAT-MODEL.adoc @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Threat Model +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +== Threat Model: Czech File Knife + +=== Document Info + +|=== +| Field | Value + +| Project | Czech File Knife +| Version | 1.0 +| Last Reviewed | 2026-09-28 +| Author | Jonathan D.A. Jewell +| Methodology | STRIDE +|=== + + +=== Scope + +==== In Scope + +- Application source code and build pipeline +- CI/CD workflows (GitHub Actions) +- Container images and runtime environment +- Secrets and credential management +- Dependencies (direct and transitive) +- Deployment artifacts (binaries, containers, SBOM) + +==== Out of Scope + +- Physical security of hosting infrastructure +- GitHub/GitLab platform-level vulnerabilities +- End-user device security +- Social engineering attacks against maintainers (handled by org policy) + +=== System Overview + +Brief description of Czech File Knife and its architecture. + +NOTE: See link:../STATE-VISUALIZER.adoc[STATE-VISUALIZER.adoc] for the full architecture diagram and completion dashboard. + +=== Assets + +|=== +| Asset | Classification | Owner | Notes + +| Source code | Internal | Maintainers | Public repos are still internal-integrity +| Signing keys | Restricted | Release lead | Signing keys (e.g., Ed25519), GPG keys +| CI/CD secrets | Restricted | Maintainers | GITHUB_TOKEN, deploy tokens, PATs +| User/contributor data | Confidential | Org | Emails, contributor identity +| Build artifacts | Internal | CI pipeline | Binaries, WASM bundles +| Container images | Internal | CI pipeline | Chainguard-based, signed via image signing tool +| SBOM / provenance | Public | CI pipeline | SLSA attestations +| Dependencies | Public | Lockfile | Cargo.lock, .lock, gleam.toml +| Infrastructure config | Confidential | Maintainers | Containerfiles, compose files, orchestration config +|=== + + +=== Trust Boundaries + +|=== +| Boundary | From (Lower Trust) | To (Higher Trust) + +| Pull request submission | External contributor | Repository codebase +| CI/CD workflow execution | Workflow definition | Runner with secrets access +| Container build boundary | Build stage | Runtime stage +| External API calls | Third-party service | Application internals +| User input (CLI/Web) | End user | Application logic +| Dependency resolution | Package registry | Build environment +| Forge mirroring | GitHub | GitLab / Bitbucket +|=== + + +=== Threat Actors + +|=== +| Actor | Motivation | Capability + +| Script kiddie | Vandalism, clout | Low +| Disgruntled contributor | Sabotage, backdoor insertion | Medium +| Supply chain attacker | Wide-impact compromise | High +| Nation state | Espionage, disruption | Very High +| Automated bot | Credential stuffing, spam PRs | Low-Medium +|=== + + +=== STRIDE Analysis + +==== Spoofing + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| Unsigned commits impersonate maintainer | Source code | Medium | High | High | Require GPG-signed commits; vigilant code review +| Forged bot actions (automated agents) | CI/CD pipeline | Low | High | Medium | Bot tokens scoped minimally; audit bot activity +| Spoofed package registry identity | Dependencies | Low | High | Medium | Pin dependencies by hash; verify provenance +|=== + + +==== Tampering + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| Malicious pull request | Source code | Medium | High | High | Branch protection; required reviews; CodeQL +| Dependency poisoning (typosquat) | Dependencies | Medium | High | High | Lockfiles; secret-scanner; security scans +| Tampered container base image | Container images | Low | High | Medium | Chainguard images; image signing verification +| Workflow file modification | CI/CD pipeline | Low | High | Medium | CODEOWNERS on .github/; workflow-linter +|=== + + +==== Repudiation + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| Unlogged deployment | Build artifacts | Medium | Medium | Medium | SLSA provenance; deployment audit trail +| Denied merge of vulnerable code | Source code | Low | Medium | Low | Git history is immutable; signed commits +| Secret rotation without record | CI/CD secrets | Low | Low | Low | Secret rotation logged in STATE.a2ml +|=== + + +==== Information Disclosure + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| Secrets leaked in git history | CI/CD secrets | Medium | High | High | TruffleHog in CI; secret-scanner workflow +| Verbose error messages in prod | Application logic | Medium | Medium | Medium | Sanitize outputs; structured logging +| SBOM reveals internal structure | Infrastructure | Low | Low | Low | Accepted risk; SBOM is intentionally public +|=== + + +==== Denial of Service + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| CI resource exhaustion (fork bomb in PR) | CI/CD pipeline | Medium | Medium | Medium | Concurrency limits; timeout on workflows +| Spam issues/PRs flooding triage | Maintainer time | Medium | Low | Low | GitHub rate limits; bot auto-close stale +| Large binary commits bloating repo | Source code | Low | Medium | Low | .gitattributes LFS policy; pre-commit hooks +|=== + + +==== Elevation of Privilege + +|=== +| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation + +| Workflow injection via PR title/body | CI/CD pipeline | Medium | High | High | Never interpolate PR fields in `run:`; use env vars +| GITHUB_TOKEN over-scoped | CI/CD secrets | Medium | High | High | `permissions: read-all` default; per-job scoping +| Container escape | Runtime environment | Low | High | Medium | Hardened container runtime; read-only rootfs; no-new-privileges +| Compromised action dependency | CI/CD pipeline | Medium | High | High | SHA-pin all actions; never use `@latest` tags +|=== + + +=== Mitigations in Place + +- **SLSA Provenance**: Build attestations via slsa-github-generator +- **Secret Scanning**: TruffleHog + secret-scanner workflow on every push +- **Static Analysis**: CodeQL on supported languages +- **Supply Chain**: OpenSSF Scorecard (scorecard.yml + scorecard-enforcer.yml) +- **Container Signing**: Ed25519 signatures on all published images (optional: use your signing tool) +- **Container Runtime**: Hardened container runtime with formal verification (optional) +- **Dependency Pinning**: All GitHub Actions SHA-pinned; lockfiles committed +- **Workflow Validation**: workflow-linter.yml checks all workflow changes +- **Security Scanning**: Neurosymbolic scanning (hypatia-scan.yml, optional) +- **Bot Governance**: Bot orchestration with confidence thresholds (optional) +- **Edge Security**: Gateway with policy enforcement (optional, where applicable) +- **SBOM**: Generated and published with releases + +=== Residual Risks + +|=== +| Risk | Accepted Because | Review Trigger + +| Zero-day in GitHub Actions runner | Platform responsibility; no feasible mitigation | GitHub advisory +| Maintainer account compromise | Mitigated by 2FA requirement; residual remains | Any suspicious activity +| Transitive dependency vulnerability (0-day) | Lockfiles limit blast radius; scanning catches known CVEs | CVE database update +| SBOM exposes internal component names | Transparency is a design goal | Policy change +|=== + + +=== Review Schedule + +This threat model should be reviewed: + +- **Quarterly** as a standing item +- **When architecture changes** (new services, new trust boundaries, new deployment targets) +- **Before major releases** (v1.0, v2.0, etc.) +- **After any security incident** affecting this project or its dependencies + +Reviewer should update the "Last Reviewed" date and version in Document Info above. diff --git a/czech-file-knife/docs/architecture/TOPOLOGY.adoc b/czech-file-knife/docs/architecture/TOPOLOGY.adoc new file mode 100644 index 000000000..dd802fe59 --- /dev/null +++ b/czech-file-knife/docs/architecture/TOPOLOGY.adoc @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Last updated: 2026-04-04 += Architecture Topology + +== System Overview + +RSR (Rhodium Standard Repository) template provides the canonical scaffold for all hyperpolymath projects, with integrated CI/CD, documentation, and service discovery patterns. + +== Component Overview + +[cols="1,1,2", options="header"] +|=== +| Component | Language | Purpose + +| dogfood-gate workflow | YAML | Quality checks (CRG, security, linting) +| eclexiaiser-validate job | YAML | Resource cost awareness scoring +| Groove discovery | JSON | Service endpoint registration +|=== + +== Data Flow + +---- +[Code Push] → [GitHub Actions] → [hypatia scan] → [eclexiaiser validate] → [Results] +---- + +== Integration Points + +* *Upstream*: Hypatia (neurosymbolic CI/CD), eclexiaiser (resource scoring) +* *Downstream*: All RSR-based repositories (500+ instances) + +== Deployment + +* Container: Stapeln Six ecosystem +* CI/CD: GitHub Actions → Hypatia scan → eclexiaiser-validate (6 scorecard dimensions) → Mirror +* Service Discovery: Groove protocol (www/.well-known/groove/manifest.json) diff --git a/czech-file-knife/docs/CITATIONS.adoc b/czech-file-knife/docs/attribution/CFK-CITATIONS.adoc similarity index 96% rename from czech-file-knife/docs/CITATIONS.adoc rename to czech-file-knife/docs/attribution/CFK-CITATIONS.adoc index 5b68cf7b1..9905364fb 100644 --- a/czech-file-knife/docs/CITATIONS.adoc +++ b/czech-file-knife/docs/attribution/CFK-CITATIONS.adoc @@ -10,7 +10,7 @@ title = {czech-file-knife}, year = {2025}, url = {https://github.com/hyperpolymath/czech-file-knife}, - license = {AGPL-3.0-or-later} + license = {MPL-2.0} } ---- diff --git a/czech-file-knife/docs/attribution/CITATIONS.adoc b/czech-file-knife/docs/attribution/CITATIONS.adoc new file mode 100644 index 000000000..c87330b4a --- /dev/null +++ b/czech-file-knife/docs/attribution/CITATIONS.adoc @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Czech File Knife - Citation Guide +:toc: + +== BibTeX + +[source,bibtex] +---- +@software{Czech File Knife_2026, + author = {Jewell, Jonathan D.A.}, + title = {Czech File Knife}, + year = {2026}, + url = {https://github.com/hyperpolymath/Czech File Knife}, + license = {MPL-2.0} +} +---- + +== Harvard Style + +Jewell, J. (2026) _Czech File Knife_ [Computer software]. Available at: https://github.com/hyperpolymath/Czech File Knife + +== OSCOLA + +Jonathan D.A. Jewell, 'Czech File Knife' (2026) + +== MLA + +Jewell, Jonathan D.A. "Czech File Knife." 2026, github.com/hyperpolymath/Czech File Knife. + +== APA 7 + +Jewell, J. (2026). _Czech File Knife_ [Computer software]. GitHub. https://github.com/hyperpolymath/Czech File Knife + +== See Also + +* link:CITATION.cff[CITATION.cff] diff --git a/czech-file-knife/docs/attribution/CODEOWNERS.adoc b/czech-file-knife/docs/attribution/CODEOWNERS.adoc new file mode 100644 index 000000000..ffd88f1f0 --- /dev/null +++ b/czech-file-knife/docs/attribution/CODEOWNERS.adoc @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Code Ownership +:icons: font + +This project utilizes a formally defined code ownership structure to ensure that specific components are reviewed by domain experts. + +== Authority Model + +Our ownership model is based on the "Perimeter" architecture: +* **Perimeter 1 (Core):** Strictly controlled by Lead Maintainers. +* **Perimeter 2 (Extensions):** Maintained by component owners. +* **Perimeter 3 (Community):** Open for broader community participation. + +== Automated Enforcement + +The technical rules for automatic review assignments are maintained in the machine-readable link:../../.github/CODEOWNERS[.github/CODEOWNERS] file. GitHub uses this to automatically notify owners when changes are proposed to their sections. + +== Component Owners + +A full list of maintainers and their contact information can be found in link:../MAINTAINERS.adoc[MAINTAINERS.adoc]. diff --git a/czech-file-knife/docs/attribution/README.adoc b/czech-file-knife/docs/attribution/README.adoc new file mode 100644 index 000000000..c6c3b5e7e --- /dev/null +++ b/czech-file-knife/docs/attribution/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += attribution Unit diff --git a/czech-file-knife/docs/contributing.adoc b/czech-file-knife/docs/contributing.adoc new file mode 100644 index 000000000..f6efed341 --- /dev/null +++ b/czech-file-knife/docs/contributing.adoc @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Contributing — Czech File Knife +:revdate: 2026-MM-DD + +== Audience + +Developers working *on* `Czech File Knife`. For consumers (people calling +or depending on it) see link:./usage.adoc[usage.adoc]. + +== Local-dev setup + +Prerequisites — the minimum versions and where to get them: + +* `` v`` — ``. +* `` v`` — ``. +* GPG signing key configured (estate policy — all commits must be + signed). See + link:https://github.com/hyperpolymath/standards/blob/main/docs/secure-coding-training.md[standards/docs/secure-coding-training.md]. + +One-shot setup: + +[source,bash] +---- +git clone git@github.com:hyperpolymath/Czech File Knife.git +cd Czech File Knife +just setup # installs deps, sets up hooks +just test # runs the full test suite +---- + +== Running tests + +* **Unit**: `just test-unit` — fast, no I/O. +* **Integration**: `just test-int` — uses real services (database, + HTTP, etc.). Estate policy: prefer real over mocked + (see `feedback_integration_tests_real_db` in maintainer's memory). +* **Property**: `just test-prop` — randomised, slower; budget + documented in `docs/proof-debt.md` if applicable. +* **Full**: `just test` — runs all of the above. + +== Code style + +We enforce style via CI (governance-reusable.yml from hyperpolymath/standards). +Locally: + +[source,bash] +---- +just fmt # auto-format +just lint # static checks +---- + +* All commits must be **GPG-signed** (CI enforces; see + link:https://github.com/hyperpolymath/standards[standards]). +* All source files must carry an **SPDX-License-Identifier** header + (CI enforces). +* Conventional commits — `feat`, `fix`, `chore`, `refactor`, `docs`, + `test`, `ci`, `revert` (CHANGELOG is auto-generated from these + via link:https://github.com/hyperpolymath/standards/blob/main/.github/workflows/changelog-reusable.yml[`changelog-reusable.yml`]). + +== Branching & PR workflow + +. Branch off `main` as `claude/` (for AI agents) or + `/` (for humans). +. Make focused, narrow commits — one logical change per commit. +. Open a PR against `main`. +. **Enable auto-merge immediately** on every PR you open + (`gh pr merge --auto --squash`) — estate standing policy + (see standards#196 audit and policies). +. CI must be green. The PR auto-merges when checks pass + reviews land. + +== Adding a new dependency + +. State the *why* in the PR body — what does this dependency unlock? +. Check provenance (maintained, audited, no malicious history). +. Pin to a SHA, not a tag. +. Update `docs/architecture.adoc#Dependencies`. + +== Adding an ADR + +When you make a non-obvious design decision, write it down: + +. Copy `docs/decisions/0001-template.adoc` → `0002-.adoc`. +. Fill in: Context, Decision, Consequences, Alternatives. +. Link the ADR from the README or relevant code as a comment. + +== Reporting issues + +* Bugs in `Czech File Knife`: file at `hyperpolymath/Czech File Knife/issues`. +* Estate-wide concerns (policy, conventions, CI): file at + `hyperpolymath/standards/issues`. diff --git a/czech-file-knife/docs/decisions/0000-template.adoc b/czech-file-knife/docs/decisions/0000-template.adoc new file mode 100644 index 000000000..0025b64e4 --- /dev/null +++ b/czech-file-knife/docs/decisions/0000-template.adoc @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0000-template +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +== [NUMBER]. [TITLE] + +Date: YYYY-MM-DD + +=== Status + +link:NNNN-title.md[Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN] | Rejected] + +=== Context + +What is the issue that we're seeing that is motivating this decision or change? + +=== Decision + +What is the change that we're proposing and/or doing? + +=== Consequences + +What becomes easier or more difficult to do because of this change? + +==== Positive + +- ... + +==== Negative + +- ... + +==== Neutral + +- ... diff --git a/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc b/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc new file mode 100644 index 000000000..1d7c2601d --- /dev/null +++ b/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0001-adopt-rsr-standard +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +== 1. Adopt Rhodium Standard Repository (RSR) Template + +Date: 2026-02-14 + +=== Status + +Accepted + +=== Context + +Managing multiple repositories with an ad-hoc approach led to significant +inconsistencies across the ecosystem. Common problems included: + +- Missing or incomplete configuration files (SECURITY.md, CONTRIBUTING.md, + .editorconfig, etc.) +- State files (STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml) placed in the repository + root instead of the canonical `.machine_readable/` directory +- Duplicate or conflicting workflow definitions across repos +- No standardized entry point for AI agents interacting with repositories +- Inconsistent bot directive configurations leading to unreliable automation +- No contractile enforcement or Justfile automation + +Without a single source of truth for repository structure, each new repo +required manual setup and inevitably drifted from best practices over time. + +=== Decision + +Adopt the Rhodium Standard Repository (RSR) template (`rsr-template-repo`) as +the canonical starting point for all new repositories. Existing repositories +will migrate incrementally as they receive active development. + +The RSR template provides: + +- **Machine-readable state files** in `.machine_readable/` (STATE.a2ml, + ECOSYSTEM.a2ml, META.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml) +- **AI manifest** (`0-AI-MANIFEST.a2ml`) as a universal entry point for all + AI agents +- **Bot directives** in `.machine_readable/bot_directives/` for bot orchestration integration +- **Contractiles** in `.machine_readable/contractiles/` (k9, dust, intend, must, trust) for + policy enforcement +- **Standardized workflows** (16+ GitHub Actions workflows, all SHA-pinned) +- **Justfile automation** with standard recipes for common tasks +- **Security and governance files**: SECURITY.md, CONTRIBUTING.md, + CODE_OF_CONDUCT.md, LICENSE (MPL-2.0) +- **Architecture Decision Records** in `docs/decisions/` + +New repositories are created by cloning the template: + +[source,bash] +---- +git clone https://github.com/hyperpolymath/rsr-template-repo new-repo-name +cd new-repo-name +rm -rf .git && git init +---- + +=== Consequences + +==== Positive + +- Consistency across all repositories, enforced from creation +- Automated compliance checking via `rsr-antipattern.yml` workflow +- Bot fleet can operate reliably across all repos with predictable structure +- AI agents (Claude, Gemini, etc.) have a standardized entry point via + `0-AI-MANIFEST.a2ml` +- New contributors can onboard faster with familiar, documented structure +- Reduced maintenance burden: fix once in template, propagate to all repos +- Machine-readable state enables tooling and automation pipelines + +==== Negative + +- Migration effort for existing repos requires time and attention +- Learning curve for contributors unfamiliar with RSR conventions +- Template updates need propagation mechanism to existing repos +- Some repos may have unique needs that do not fit the standard template + without customization + +==== Neutral + +- Existing CI/CD pipelines continue to work; RSR workflows are additive +- Third-party dependencies retain their original licenses regardless of + repo structure +- ADR process itself is part of the template, enabling future decisions + to be recorded consistently diff --git a/czech-file-knife/docs/decisions/0001-template.adoc b/czech-file-knife/docs/decisions/0001-template.adoc new file mode 100644 index 000000000..b4070a83d --- /dev/null +++ b/czech-file-knife/docs/decisions/0001-template.adoc @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += ADR-0001 — Use Architecture Decision Records +:revdate: 2026-MM-DD +:status: ACCEPTED + +== Context + +We need a lightweight way to record significant architectural decisions +and the reasoning behind them, so future maintainers (and AI agents) +can understand *why* the project is shaped as it is. + +== Decision + +Adopt link:https://adr.github.io/[Architecture Decision Records (ADRs)] +in `docs/decisions/`, numbered sequentially (0001, 0002, ...). + +* Each ADR is a single `.adoc` file. +* The first ADR (this one) records the decision to use ADRs. +* Status values: PROPOSED, ACCEPTED, DEPRECATED, SUPERSEDED. +* When a decision is overturned, the new ADR records the supersession + and updates the old one's status to `SUPERSEDED by 00NN`. + +== Consequences + +. **Positive**: future readers see *why* without git-archaeology. +. **Positive**: design alternatives are documented, not just the + winning choice. +. **Positive**: ADRs are reviewable in PRs — the design discussion + happens alongside the code that implements it. +. **Negative**: maintenance burden — every non-obvious decision now + warrants an ADR. (We mitigate by keeping ADRs short; "non-obvious" + is a judgment call.) +. **Negative**: ADRs can rot. We accept this; the SUPERSEDED chain + is the recovery mechanism. + +== Alternatives considered + +. **Comments in code** — too local; doesn't capture cross-cutting + decisions. +. **Wiki / external doc** — drifts from code; not in PR review. +. **Commit messages** — too transient and not discoverable. +. **No record** — discarded; this is how every project ends up with + "I don't know why we do it this way" debt. + +== Companion ADRs + +* (None yet — this is the first ADR.) + +== References + +* MADR template — `https://adr.github.io/madr/` +* Estate convention — `hyperpolymath/standards/docs/RSR_OUTLINE.adoc` diff --git a/czech-file-knife/docs/decisions/0002-variant-contract.adoc b/czech-file-knife/docs/decisions/0002-variant-contract.adoc new file mode 100644 index 000000000..dfed0ec50 --- /dev/null +++ b/czech-file-knife/docs/decisions/0002-variant-contract.adoc @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0002-variant-contract + +== 2. Variant templates declare a machine-readable contract with their parent + +Date: 2026-07-07 + +=== Status + +Accepted (ratified by owner 2026-07-07; first instance live in +rsr-julia-library-template-repo) + +=== Context + +The RSR template family grows variants where the spine genuinely diverges — +build system, test/docs toolchain, ABI story (first: the Julia library +template; candidates: Rust+SPARK paired-lib). Hand-maintained variant repos +silently drift from the canonical template: the parent gains fixes the +variant never receives, the variant fixes defects the parent cannot see +(especially while the parent's own CI is red), and nothing records which +differences are intentional. + +Anti-proliferation doctrine requires that variation be *declared, bounded and +enforceable*, and the long-term plan (Scaffoldia as composer; template repos +becoming generated artifacts with a maintenance sync loop until cut-off) +needs variant deltas to exist as data, not tribal knowledge. + +=== Decision + +Every variant template carries a **variant contract** at +`.machine_readable/descriptiles/VARIANT.a2ml` declaring: + +- **parent** (owner/repo) and **parent-pin** — the last reconciled parent + commit. Bumping the pin after reconciling is the template-maintenance sync + operation. +- **normalisation rules** separating per-repo operational state from spine + content: 40-hex action/reusable pin SHAs (dependabot cadence differs per + repo) and self-name folding (both repo names → `SELF` on both sides). +- **path lists**, verified against the parent at the pin: + `added`, `removed`, `diverged` (permanently variant-owned), + `diverged-pending-upstream` (fixes made variant-first; doubles as the + upstream-promotion worklist), and `operational-state` (excluded from + comparison, e.g. coaptation receipts). + +A **drift gate** (`scripts/check-variant-drift.sh` + the Variant Drift Gate +workflow: push / PR / weekly cron / manual, no `paths:` filter) enforces the +contract: any tracked file not declared variant-owned must be identical to +the parent at the pin modulo normalisation, and declared additions/removals +are asserted in both directions. + +CLADE stays identity-only: `[lineage]` records *who* the parent is and gains +a one-line `variant-contract` pointer; the operational mechanics (pin, path +lists) live in VARIANT.a2ml so pin bumps never churn the identity file. + +The contract is **direction-agnostic**: today the parent is the source and +the variant a hand-maintained specialisation (`direction = +"template-is-source"`); after the Scaffoldia inversion the same lists +describe how to regenerate the variant from core + variant pack +(`direction = "generated-from-core"`). + +=== Consequences + +- Variant drift becomes a red check instead of an archaeology project. +- The `diverged-pending-upstream` list is a standing, exact worklist for + upstream promotion (first use: rsr-template-repo PR #137). +- Scaffoldia can consume variant contracts as data when composing or + regenerating repos; GitHub's "Use this template" path and the composer + path cannot silently diverge because both are checked against the same + contract. +- New variants must justify spine divergence in the contract's `[variant]` + block; anything expressible as a feature or profile should not become a + variant repo (anti-proliferation). +- Reference implementation: rsr-julia-library-template-repo (VARIANT.a2ml, + scripts/check-variant-drift.sh, .github/workflows/variant-drift-gate.yml). diff --git a/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc b/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc new file mode 100644 index 000000000..9726e04b6 --- /dev/null +++ b/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0003-forge-and-sustain-lifecycle + +== 3. One lifecycle, one contract: Forge (mint → provision → configure → harness) and Sustain (corrective / adaptive / perfective / preventive) + +Date: 2026-07-21 + +=== Status + +Accepted (drafted 2026-07-21; ratified by owner ruling 2026-09-19). The normative +statement lives in `hyperpolymath/standards` +`0-canon/rsr/SCAFFOLD-LIFECYCLE.adoc` (relocated from `rhodium-standard-repositories/spec/` in the September 2026 reorg); this ADR records +the decision and its mechanics in the spine. + +=== Context + +The scaffolding machinery exists but is disconnected, and each disconnection +has produced a measured incident: + +- `just repo-init` renders 34 substitutions and derives identity, but leaves 12 + tokens `UNASSIGNED`, performs no platform configuration, and — decisive — + records **no provenance**. A minted repo does not know which template + commit it came from, so template improvements cannot be propagated and + drift cannot be detected. The external audit + (rsr-template-repo-experiment, 05-ZIGZAG §2.2) named this the single + highest-leverage gap: Copier/Cruft solved it with an answer-file + + `update` + `check`; the estate hand-simulates it as recurring + standardisation-PR campaigns. +- `rsr-profile.a2ml` — the file that makes hypatia's implemented + `rsr-conformance` oracle able to score a repo at all — exists in ~2 of + ~300 repos, because nothing mints it. +- Identity in the probot-managed `settings.yml` renamed this very repo to + `-REPO-` on every push (the 2026-07-20 incident). Identity must be set + out of band, but no stage of the lifecycle owned that step, and this + file's own header previously claimed `just repo-init` did it via `gh` — it + never has. +- ADR-0002's variant contract + drift gate work (reference: + rsr-julia-library-template-repo) but apply only to variant *templates*, + not to the ~200 minted repos that need the same mechanism. + +=== Decision + +**One lifecycle.** A repository is *forged* in four ordered stages — exit +criteria in SCAFFOLD-LIFECYCLE.adoc: + +1. **Mint** — tree exists, placeholders rendered, identity derived, + archetype overlay applied, `rsr-profile.a2ml` and provenance written, + and **no template identity survives in the child**: the spine's own + name is a literal rather than a token, so rendering placeholders is not + sufficient — a self-name pass must rewrite it everywhere it denotes + *this* repo, while leaving every line that names the parent *as* a + parent untouched. A minted repo that still declares + `sonar.projectKey=…_rsr-template-repo` has not been minted; it has been + copied. +2. **Provision** — the recorded `BUILD_CMD`/`TEST_CMD` actually run + (echoed advice is not provisioning). +3. **Configure** — identity, visibility, branch protection set out of band + via `gh`/UI, once per repo; required contexts equal emitted job ids; + repo registered in `gv-clade-index`. +4. **Harness** — CI green or accounted for, drift gate armed against the + recorded pin, oracle able to score. + +It is then *sustained* in four modes: **corrective** (fleet `fix-*` on +broken invariants), **adaptive** (parent-pin bumps + fan-out campaigns), +**perfective** (tier climbing via `mix hypatia.rsr_score`; promotion of +`diverged-pending-upstream` fixes), **preventive** (canary-tested gates, +placeholder guard, drift-gate cron). + +**One contract.** The ADR-0002 `VARIANT.a2ml` shape is extended in *use*, +not in format, to minted repos: `just repo-init` writes +`.machine_readable/descriptiles/VARIANT.a2ml` with `direction = +"generated-from-core"`, `parent = hyperpolymath/rsr-template-repo`, +`parent-pin` = the template tip at mint (resolved via `git ls-remote`; +`UNASSIGNED` when offline — honest, never guessed), and empty path lists +that grow as the repo legitimately diverges. A variant template, a minted +repo, and a retrofitted repo are the same object — a child of the spine at +a pin with declared divergences — so `scripts/check-variant-drift.sh` +(promoted into the spine by this ADR, verbatim from the Julia variant — it +was already contract-driven) is the estate's re-templating check for all +three populations. + +**Archetypes are data.** `archetypes//ARCHETYPE.a2ml` carries a +capability `preset` (defined in standards `template-capability-gates.toml`) +plus a `[tokens]` table filling the previously-`UNASSIGNED` init tokens +that are archetype-determined (`PROJECT_KIND`, `LANG_STACK`, `BUILD_CMD`, +`TEST_CMD`, …). Tokens that are genuine per-repo judgement +(`PROJECT_UNIQUE_STRENGTH`, `MUST_INVARIANTS`, …) stay `UNASSIGNED` — an +archetype must not write fiction. `just repo-init ` applies the +overlay; the `archetypes/` directory is template-only and removed from the +minted tree (like `build/templates/`). First archetype: `julia-library`, +harvested from the Julia variant's contract. Declared next (content to +follow, not stubbed empty): `wordpress-plugin`, `zotero-plugin`, +`userscript` — presets already reserved in the canon. + +**Scaffoldia consumes, never redefines.** The composer's pack axis is the +archetype/variant contract; its profile axis is the capability preset. Its +GitLab-lineage implementation's registry is harvested as input data; the +engine stays dormant until the composer ADR (scaffoldia Phase 1) chooses +its implementation. + +=== Consequences + +- Every newly minted repo is born sustainable: scorable by the oracle + (profile), re-templatable (provenance), and drift-checkable (gate) — the + three artefacts whose absence created the manual-campaign treadmill. +- The 12 `UNASSIGNED` tokens split honestly: archetype-determined ones get + real values; judgement ones stay loud. +- The `settings.yml` header's false claim about `gh` automation is + corrected: Configure is an operator stage today, printed as exact + commands by `just repo-init`; automating it is future work and must not be + described as existing. +- Retrofit of already-minted repos = writing the provenance contract into + them (megasweep can detect which repos lack one; mutation stays + owner-gated, audit-first). +- The dogfood loop closes: the template ships the contract format, the + canon defines its lifecycle meaning, the oracle scores its presence, the + fleet propagates its updates. diff --git a/czech-file-knife/docs/decisions/README.adoc b/czech-file-knife/docs/decisions/README.adoc new file mode 100644 index 000000000..cb9c6d848 --- /dev/null +++ b/czech-file-knife/docs/decisions/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += decisions Unit diff --git a/czech-file-knife/ABI-FFI-README.adoc b/czech-file-knife/docs/developer/ABI-FFI-README.adoc similarity index 67% rename from czech-file-knife/ABI-FFI-README.adoc rename to czech-file-knife/docs/developer/ABI-FFI-README.adoc index 8e5244189..041e140d0 100644 --- a/czech-file-knife/ABI-FFI-README.adoc +++ b/czech-file-knife/docs/developer/ABI-FFI-README.adoc @@ -1,25 +1,25 @@ -\{\{~ Aditionally delete this line and fill out the template below ~}} +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += ABI/FFI Standards +{{~ Aditionally delete this line and fill out the template below ~}} -== \{\{PROJECT}} ABI/FFI Documentation +== CZECH_FILE_KNIFE ABI/FFI Documentation === Overview -This library follows the *Hyperpolymath RSR Standard* for ABI and FFI -design: +This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: -* *ABI (Application Binary Interface)* defined in *Idris2* with formal -proofs -* *FFI (Foreign Function Interface)* implemented in *Zig* for C -compatibility -* *Generated C headers* bridge Idris2 ABI to Zig FFI -* *Any language* can call through standard C ABI +- **ABI (Application Binary Interface)** defined in **Idris2** with formal proofs +- **FFI (Foreign Function Interface)** implemented in **Zig** for C compatibility +- **Generated C headers** bridge Idris2 ABI to Zig FFI +- **Any language** can call through standard C ABI === Architecture -.... +---- ┌─────────────────────────────────────────────┐ │ ABI Definitions (Idris2) │ -│ src/abi/ │ +│ src/interface/Abi/ │ │ - Types.idr (Type definitions) │ │ - Layout.idr (Memory layout proofs) │ │ - Foreign.idr (FFI declarations) │ @@ -29,7 +29,7 @@ compatibility ▼ ┌─────────────────────────────────────────────┐ │ C Headers (auto-generated) │ -│ generated/abi/{{project}}.h │ +│ generated/abi/czech_file_knife.h │ └─────────────────┬───────────────────────────┘ │ │ imported by @@ -42,18 +42,18 @@ compatibility │ - Memory-safe by default │ └─────────────────┬───────────────────────────┘ │ - │ compiled to lib{{project}}.so/.a + │ compiled to libczech_file_knife.so/.a ▼ ┌─────────────────────────────────────────────┐ │ Any Language via C ABI │ -│ - Rust, ReScript, Julia, Python, etc. │ +│ - Rust, , Julia, Python, etc. │ └─────────────────────────────────────────────┘ -.... +---- === Directory Structure -.... -{{project}}/ +---- +czech_file_knife/ ├── src/ │ ├── abi/ # ABI definitions (Idris2) │ │ ├── Types.idr # Core type definitions with proofs @@ -70,24 +70,23 @@ compatibility │ ├── test/ │ │ └── integration_test.zig │ └── include/ -│ └── {{project}}.h # C header (optional, can be generated) +│ └── czech_file_knife.h # C header (optional, can be generated) │ ├── generated/ # Auto-generated files │ └── abi/ -│ └── {{project}}.h # Generated from Idris2 ABI +│ └── czech_file_knife.h # Generated from Idris2 ABI │ └── bindings/ # Language-specific wrappers (optional) ├── rust/ - ├── rescript/ + ├── / └── julia/ -.... +---- === Why Idris2 for ABI? -==== 1. *Formal Verification* +==== 1. **Formal Verification** -Idris2’s dependent types allow proving properties about the ABI at -compile-time: +Idris2's dependent types allow proving properties about the ABI at compile-time: [source,idris] ---- @@ -104,7 +103,7 @@ public export abiCompatible : Compatible (ABI 1) (ABI 2) ---- -==== 2. *Type Safety* +==== 2. **Type Safety** Encode invariants that C/Zig cannot express: @@ -119,7 +118,7 @@ data Buffer : (n : Nat) -> Type where MkBuffer : Vect n Byte -> Buffer n ---- -==== 3. *Platform Abstraction* +==== 3. **Platform Abstraction** Platform-specific types with compile-time selection: @@ -134,7 +133,7 @@ CSize Linux = Bits64 CSize Windows = Bits64 ---- -==== 4. *Safe Evolution* +==== 4. **Safe Evolution** Prove that new ABI versions are backward-compatible: @@ -152,7 +151,7 @@ abiUpgrade old = MkABI2 { === Why Zig for FFI? -==== 1. *C ABI Compatibility* +==== 1. **C ABI Compatibility** Zig exports C-compatible functions naturally: @@ -163,7 +162,7 @@ export fn library_function(param: i32) i32 { } ---- -==== 2. *Memory Safety* +==== 2. **Memory Safety** Compile-time safety without runtime overhead: @@ -174,7 +173,7 @@ const handle = init() orelse return error.InitFailed; defer free(handle); ---- -==== 3. *Cross-Compilation* +==== 3. **Cross-Compilation** Built-in cross-compilation to any platform: @@ -185,7 +184,7 @@ zig build -Dtarget=aarch64-macos zig build -Dtarget=x86_64-windows ---- -==== 4. *Zero Dependencies* +==== 4. **Zero Dependencies** No runtime, no libc required (unless explicitly needed): @@ -213,7 +212,7 @@ zig build test # Run tests [source,bash] ---- cd src/abi -idris2 --cg c-header Types.idr -o ../../generated/abi/{{project}}.h +idris2 --cg c-header Types.idr -o ../../generated/abi/czech_file_knife.h ---- ==== Cross-Compile @@ -238,35 +237,34 @@ zig build -Dtarget=x86_64-windows [source,c] ---- -#include "{{project}}.h" +#include "czech_file_knife.h" int main() { - void* handle = {{project}}_init(); + void* handle = czech_file_knife_init(); if (!handle) return 1; - int result = {{project}}_process(handle, 42); + int result = czech_file_knife_process(handle, 42); if (result != 0) { - const char* err = {{project}}_last_error(); + const char* err = czech_file_knife_last_error(); fprintf(stderr, "Error: %s\n", err); } - {{project}}_free(handle); + czech_file_knife_free(handle); return 0; } ---- Compile with: - [source,bash] ---- -gcc -o example example.c -l{{project}} -L./zig-out/lib +gcc -o example example.c -lczech_file_knife -L./zig-out/lib ---- ==== From Idris2 [source,idris] ---- -import {{PROJECT}}.ABI.Foreign +import CZECH_FILE_KNIFE.ABI.Foreign main : IO () main = do @@ -284,22 +282,22 @@ main = do [source,rust] ---- -#[link(name = "{{project}}")] +#[link(name = "czech_file_knife")] extern "C" { - fn {{project}}_init() -> *mut std::ffi::c_void; - fn {{project}}_free(handle: *mut std::ffi::c_void); - fn {{project}}_process(handle: *mut std::ffi::c_void, input: u32) -> i32; + fn czech_file_knife_init() -> *mut std::ffi::c_void; + fn czech_file_knife_free(handle: *mut std::ffi::c_void); + fn czech_file_knife_process(handle: *mut std::ffi::c_void, input: u32) -> i32; } fn main() { unsafe { - let handle = {{project}}_init(); + let handle = czech_file_knife_init(); assert!(!handle.is_null()); - let result = {{project}}_process(handle, 42); + let result = czech_file_knife_process(handle, 42); assert_eq!(result, 0); - {{project}}_free(handle); + czech_file_knife_free(handle); } } ---- @@ -308,21 +306,21 @@ fn main() { [source,julia] ---- -const lib{{project}} = "lib{{project}}" +const libczech_file_knife = "libczech_file_knife" function init() - handle = ccall((:{{project}}_init, lib{{project}}), Ptr{Cvoid}, ()) + handle = ccall((:czech_file_knife_init, libczech_file_knife), Ptr{Cvoid}, ()) handle == C_NULL && error("Failed to initialize") handle end function process(handle, input) - result = ccall((:{{project}}_process, lib{{project}}), Cint, (Ptr{Cvoid}, UInt32), handle, input) + result = ccall((:czech_file_knife_process, libczech_file_knife), Cint, (Ptr{Cvoid}, UInt32), handle, input) result end function cleanup(handle) - ccall((:{{project}}_free, lib{{project}}), Cvoid, (Ptr{Cvoid},), handle) + ccall((:czech_file_knife_free, libczech_file_knife), Cvoid, (Ptr{Cvoid},), handle) end # Usage @@ -363,8 +361,8 @@ zig build test-integration -- Runtime checks main : IO () main = do - verifyLayoutsCorrect - verifyAlignmentsCorrect + verifyLayouorrect + verifyAlignmenorrect putStrLn "ABI verification passed" ---- @@ -372,38 +370,36 @@ main = do When modifying the ABI/FFI: -[arabic] -. *Update ABI first* (`+src/abi/*.idr+`) -* Modify type definitions -* Update proofs -* Ensure backward compatibility -. *Generate C header* -+ -[source,bash] ----- -idris2 --cg c-header src/abi/Types.idr -o generated/abi/{{project}}.h ----- -. *Update FFI implementation* (`+ffi/zig/src/main.zig+`) -* Implement new functions -* Match ABI types exactly -. *Add tests* -* Unit tests in Zig -* Integration tests -* ABI verification tests -. *Update documentation* -* Function signatures -* Usage examples -* Migration guide (if breaking changes) +1. **Update ABI first** (`src/interface/Abi/*.idr`) + - Modify type definitions + - Update proofs + - Ensure backward compatibility + +2. **Generate C header** + ```bash + idris2 --cg c-header src/interface/Abi/Types.idr -o generated/abi/czech_file_knife.h + ``` + +3. **Update FFI implementation** (`ffi/zig/src/main.zig`) + - Implement new functions + - Match ABI types exactly + +4. **Add tests** + - Unit tests in Zig + - Integration tests + - ABI verification tests + +5. **Update documentation** + - Function signatures + - Usage examples + - Migration guide (if breaking changes) === License -\{\{LICENSE}} +MPL-2.0 === See Also -* https://idris2.readthedocs.io[Idris2 Documentation] -* https://ziglang.org/documentation/master/[Zig Documentation] -* https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium -Standard Repositories] -* link:../ffi-migration-guide.md[FFI Migration Guide] -* link:../abi-migration-guide.md[ABI Migration Guide] +- https://idris2.readthedocs.io[Idris2 Documentation] +- https://ziglang.org/documentation/master/[Zig Documentation] +- https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium Standard Repositories] diff --git a/czech-file-knife/docs/IOS_INTEGRATION.adoc b/czech-file-knife/docs/developer/IOS_INTEGRATION.adoc similarity index 100% rename from czech-file-knife/docs/IOS_INTEGRATION.adoc rename to czech-file-knife/docs/developer/IOS_INTEGRATION.adoc diff --git a/czech-file-knife/docs/developer/README.adoc b/czech-file-knife/docs/developer/README.adoc new file mode 100644 index 000000000..8d0a28367 --- /dev/null +++ b/czech-file-knife/docs/developer/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += developer Unit diff --git a/czech-file-knife/docs/developer/invariant-path.adoc b/czech-file-knife/docs/developer/invariant-path.adoc new file mode 100644 index 000000000..040d7a233 --- /dev/null +++ b/czech-file-knife/docs/developer/invariant-path.adoc @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Invariant Path Integration (RSR Template) + +Run Invariant Path from this repository root: + +[source,bash] +---- +./scripts/invariant-path.sh scan --file ./README.adoc --artifact-uri repo://README.adoc --write +---- + +Or through Just: + +[source,bash] +---- +just invariant-path scan --file ./README.adoc --artifact-uri repo://README.adoc --write +---- + +This wrapper points to the shared workspace at `/var/mnt/eclipse/repos/invariant-path` +and defaults to the `generic` profile. diff --git a/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc b/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc new file mode 100644 index 000000000..c04cfd1ea --- /dev/null +++ b/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Czech File Knife — CRG Audit (2026-09-28) +Jonathan D.A. Jewell +2026-09-28 +:toc: +:sectnums: + +// USAGE +// ----- +// Copy this file to `docs/governance/CRG-AUDIT-YYYY-MM-DD.adoc` in the target +// repo, replace the {{PLACEHOLDER}} tokens, and fill every section with +// *verified-today* evidence. Do not leave "TODO" or "tbd" in a finished audit. +// Worked example of a completed audit: boj-server/docs/governance/CRG-AUDIT-2026-04-18.adoc +// +// The audit must grade the repo *as-is today*, not aspirationally. Per CRG v2.0: +// "honest D > dishonest B". + +== Scope and Standard + +Evaluates the `czech-file-knife` repository against +*Component Readiness Grades v2.0 (STRICT)*, 2026-03-30 revision. + +- Standard: `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` +- Template (criteria boilerplate): `czech-file-knife/docs/governance/CRG-CRITERIA.adoc` +- Self-declared grade (prior art): + * `.machine_readable/descriptiles/STATE.a2ml` → `grade = ""` + * `docs/READINESS.md` (if present) → per-component grades + * Third-party badges (Glama, OpenSSF, etc.) if any — note date and scope +- Audit date: 2026-09-28 +- Auditor: Jonathan D.A. Jewell + +The audit grades the repo *as-is today*, not aspirationally. + +== v2.0 Strictness Recap + +[cols="1,3,4"] +|=== +| Grade | Release Stage | Hard Requirement (v2.0) + +| X | None | Untested. +| F | Reject | Harmful, wasteful, or superseded. +| E | Pre-alpha | >=1 test, failures documented. +| D | Alpha | Test matrix + scope documented + *RSR compliance mandatory*. +| C | Alpha-stable | Dogfooded, CI green, *deep per-file + per-directory annotation*. +| B | Beta | 6+ *diverse* external targets, issues fed back. +| A | Stable | Real-world external feedback confirms value; no harm. +|=== + +Publication requires B+. Long alpha is discipline, not shame. + +== Evidence Inventory (verified 2026-09-28) + +=== Structural compliance (Grade D floor) + +List every RSR-mandated artefact with PRESENT/ABSENT and the concrete source. +Do *not* mark PRESENT without citing the path. + +[cols="2,1,3"] +|=== +| Item | State | Source + +| RSR mandatory workflows (17+) +| PRESENT / PARTIAL (count) / ABSENT +| `.github/workflows/` + +| `.machine_readable/descriptiles/` canonical A2ML +| PRESENT (STATE, META, ECOSYSTEM, AGENTIC, NEUROSYM, PLAYBOOK) / ABSENT +| Layout matches CLAUDE.md invariant + +| `*_chora.deed` (repo deed) +| PRESENT / ABSENT +| Root + +| `docs/EXPLAINME.adoc`, `docs/READINESS.md`, `docs/RSR_OUTLINE.adoc` +| PRESENT / PARTIAL / ABSENT +| `docs/` + +| `build/guix.scm` +| PRESENT (primary + fallback) / PARTIAL / ABSENT +| build/ + +| `www/.well-known/` (security.txt, ai.txt, humans.txt) +| PRESENT / ABSENT +| Per RSR_OUTLINE.adoc; a root `.well-known/` is the legacy location (migration window, issue #53) + +| SPDX headers on source +| PRESENT on sampled files (N/N) / PARTIAL / ABSENT +| `LICENSE`, `LICENSE-MPL-2.0` fallback text + +| `Containerfile` (not `Dockerfile`) +| PRESENT / N/A (no container) / VIOLATION (Dockerfile found) +| Container policy + +| Contractile trident +| PRESENT (`INTENT.contractile`, `TRUST.contractile`, `MUST.contractile`, `ADJUST.contractile`) / PARTIAL / ABSENT +| `.machine_readable/` + +| `Justfile` + `Mustfile` (no `Makefile`) +| PRESENT / VIOLATION (Makefile found) +| Build-system policy (RSR R-020) + +| Remote +| `git@github.com:hyperpolymath/czech-file-knife.git` (origin only) / drift +| `git remote -v` +|=== + +RSR compliance verdict: *met* / *partial* / *not met* — Grade D floor satisfied? Yes/No. + +=== Code and proofs + +[cols="2,1,3"] +|=== +| Item | Count | Notes + +| Idris2 ABI modules (`src/interface/Abi/**/*.idr`) +| N files, N LOC +| Note any proof modules that typecheck green. + +| Zig FFI modules (`ffi/zig/src/*.zig`) +| N files, N LOC +| Call out largest modules. + +| <> (e.g. cartridges, panels, plugins) +| N directories/files +| Link to manifest. + +| Axiomatic `believe_me` sites +| N irreducible +| Each must have a file-header note justifying it as a documented primitive. + +| Non-axiomatic `believe_me` +| N (should be 0 for C) +| Reference the sweep commit and date. + +| Dangerous-pattern scan (`assert_total`, `Admitted`, `sorry`, `unsafeCoerce`, `Obj.magic`) +| N +| Grepped 2026-09-28. +|=== + +=== Test matrix + +Cite *from authoritative source* (STATE.a2ml or CI log). Do not re-count by +filesystem grep — grade the evidence that already exists. + +- `total-tests = N` (from `.machine_readable/descriptiles/STATE.a2ml` or CI). +- Breakdown by suite: list each named suite and its count. +- CI: `.github/workflows/.yml` runs on push / tag / schedule. +- Last green run: date + commit SHA. + +=== Annotation depth + +v2.0 C requires **per-file and per-directory orientation**. Evidence checklist: + +- Per-directory orientation docs: which `docs/` subtrees exist + (`docs/architecture/`, `docs/decisions/`, `docs/integration/`, + `docs/specification/`, `docs/wiki/`) and their file counts. +- Per-<> README coverage: *M of N* (percentage). M of N that lack + a README is the single biggest tell for C-readiness. Anything below + ~90% coverage fails the "deep per-file annotation" clause. +- Per-module inline docs: sample-audit the largest/most-important modules + and note whether purpose comments, invariants, and boundary conditions + are documented. +- Per-subtree README: which non-trivial source subtrees lack an orienting + README (e.g. `/`, `.machine_readable/`, `ffi/zig/src/`). + +=== Dogfooding / home-stable evidence + +Cite STATE.a2ml `[dogfooding-status]`. For each line item: +- WHAT was dogfooded (capability), +- HOW (concrete use-path), +- WHEN (date completed), +- WHERE (link to commit / artefact / deployment). + +- <> — <>. DONE / IN-PROGRESS / PLANNED. +- <> — … +- <> — … +- LIVE deployment (if any): URL, health signal, last verified. + +=== External validation (Grade B / A) + +Distinguish carefully — v2.0 B requires **real external users with feedback**, +not catalogue listings. Populate the canonical STATE.a2ml sections named +below; internal-capability items belong under `[grade-b-status]` (legacy) or +a roadmap section, *not* under `[external-targets]`. + +- STATE.a2ml `[external-targets]` — at least 6 diverse entries for B. + Target identity + date + outcome for each. If absent, grade is capped at C. +- STATE.a2ml `[issues-fed-back]` — closed-issue trail with external-reporter + label for B. +- STATE.a2ml `[field-signal]` — multi-source external confirmation for A. +- Awesome-list PRs merged (N). *Catalogue listings, not dogfooders.* +- Directory/registry listings (MCP directory, Glama, etc.) with automated + assessment badges — *third-party automated assessment, not external usage*. +- Seeded/reference nodes: configured but not yet run by third parties. + +NOTE: Legacy `[grade-b-status]` sections (pre-2026-04-18) often mix internal +capabilities with external targets. During audit, re-classify each entry +and either move it to `[external-targets]` or drop it. See the boj-server +audit for a worked example (six items all re-classified as internal). + +== Grade x Evidence Matrix + +[cols="1,1,3,3"] +|=== +| Grade | Met? | Evidence supporting | Evidence against / gaps + +| X +| n/a +| Tests exist -> not X +| — + +| F +| n/a +| Not superseded; no harm signal +| — + +| E +| ✓/✗ +| ... +| ... + +| D +| ✓/✗ +| All RSR structural requirements met; N tests; scope documented in STATE.a2ml, ROADMAP.adoc +| ... + +| C +| ✓/✗ +| Home-stable; CI green; core dogfood demonstrated across N capability lines; deep annotation present for core and architecture docs. +| Per-<> README coverage M/N. Per-directory orientation gaps. READINESS.md schema version. Dogfood-sweep log. + +| B +| ✓/✗ +| — +| N external targets (need 6+). External users. Issues-fed-back pipeline. + +| A +| ✓/✗ +| — +| Field signal of external confirmation. +|=== + +== Verdict + +*Current CRG grade: **<> (<>)**.* + +State how this matches / diverges from the self-declared grade. v2.0 +strictness clause: does stricter evidence standard change the outcome? + +The grade is earned, not conservative-by-default — list the three strongest +*positive* signals: + +1. <> +2. <> +3. <> + +What blocks *immediate* promotion to <>: + +1. <> +2. <> +3. <> + +What blocks promotion to the grade after that: + +1. <> +2. <> + +== Notes on Non-Negotiables Respected by this Audit + +List irreducible / intentional exceptions so future audits don't retread +them. Examples: + +- Documented axiomatic `believe_me` sites at `` are backend primitives, + not proof debt. +- Proof closures declared "done" (e.g. credential-isolation modules) are not + re-audited here. +- Standing rules (e.g. januskey, private-repo exceptions) are respected. + +== Related Files + +- `docs/READINESS.md` — per-component table. Note v1.0-vs-v2.0 schema alignment. +- `.machine_readable/descriptiles/STATE.a2ml` — authoritative self-declared state. +- `docs/governance/CRG-LIFT-PLAN-2026-09-28.adoc` — companion plan for + D→C (and medium-term C→B) lifts. +- `docs/governance/CRG-CRITERIA.adoc` — boilerplate criteria doc. + +_End of audit._ diff --git a/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml b/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml new file mode 100644 index 000000000..616258594 --- /dev/null +++ b/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml @@ -0,0 +1,108 @@ +; SPDX-License-Identifier: MPL-2.0 +; Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +; Component Readiness Grades (CRG) — Machine-readable specification +; Format: A2ML (AI-to-Machine Language) +; Standard: CRG v1.0 + +(standard + (name "Component Readiness Grades") + (abbreviation "CRG") + (version "1.0") + (date "2026-02-28") + (author "Jonathan D.A. Jewell ") + (license "MPL-2.0") + (family "RSR")) + +(grades + (grade + (code X) + (name "Untested") + (release-stage #f) + (ordinal 0) + (description "No testing has been performed. Status unknown.") + (evidence-required "none") + (minimum-for #f)) + (grade + (code F) + (name "Harmful / Wasteful") + (release-stage #f) + (ordinal 1) + (description "Actively harmful, wasteful, or better handled externally. Reject, deprecate, or delegate.") + (evidence-required "documented test results showing harm, waste, or redundancy; comparison with alternatives") + (minimum-for #f)) + (grade + (code E) + (name "Minimal / Salvageable") + (release-stage "pre-alpha") + (ordinal 2) + (description "Does something slight. Barely functional. Needs redesign or major work.") + (evidence-required "at least one successful test case; documented failures and limitations") + (minimum-for #f)) + (grade + (code D) + (name "Partial / Inconsistent") + (release-stage "alpha") + (ordinal 3) + (description "Works on some things but not systematically.") + (evidence-required "matrix of tested scenarios; documented scope vs actual capabilities") + (minimum-for "alpha")) + (grade + (code C) + (name "Self-Validated") + (release-stage "beta") + (ordinal 4) + (description "Tested on the tool/project itself (dogfooding). Reliable in home context.") + (evidence-required "active dogfooding; CI integration or equivalent; no known failures in home context") + (minimum-for "beta")) + (grade + (code B) + (name "Broadly Validated") + (release-stage "release-candidate") + (ordinal 5) + (description "Tested on at least 6 disparate, unrelated targets.") + (evidence-required "list of 6+ diverse targets with test results; evidence of feedback incorporation") + (minimum-for "release-candidate")) + (grade + (code A) + (name "Field-Proven") + (release-stage "stable") + (ordinal 6) + (description "Real-world external feedback confirms value. Does no harm in the wild.") + (evidence-required "real-world usage data; feedback incorporation evidence; no unresolved harm reports") + (minimum-for "stable"))) + +(transitions + (promotion + (from X) (to E) (requirement "Run at least one test. Document results.")) + (promotion + (from X) (to F) (requirement "Evaluate and determine harmful or wasteful.")) + (promotion + (from E) (to D) (requirement "Fix critical failures. Document scope.")) + (promotion + (from D) (to C) (requirement "Dogfood on own project. Fix what breaks.")) + (promotion + (from C) (to B) (requirement "Test on 6+ diverse external targets. Fix what breaks.")) + (promotion + (from B) (to A) (requirement "Ship. Collect external feedback. Demonstrate no harm.")) + (demotion + (from A) (to B) (trigger "External feedback dries up or reveals no longer useful.")) + (demotion + (from A) (to F) (trigger "External feedback reveals component causes harm.")) + (demotion + (from B) (to C) (trigger "Broad validation reveals unfixed failures.")) + (demotion + (from C) (to D) (trigger "Home context changes and component no longer reliable.")) + (demotion + (from C) (to F) (trigger "Dogfooding reveals net negative.")) + (demotion + (from D) (to E) (trigger "Scope narrows to barely functional.")) + (demotion + (from any) (to F) (trigger "Better external alternative makes this pure opportunity cost."))) + +(conformance + (rule "Each assessable component MUST have a grade from {X, F, E, D, C, B, A}.") + (rule "Each grade above X MUST be supported by evidence per section 4.") + (rule "Assessments MUST be recorded in a version-controlled location.") + (rule "Assessments MUST be reviewed at least once per release cycle.") + (rule "Release stages MUST respect minimum grade thresholds.")) diff --git a/czech-file-knife/docs/governance/CRG-CRITERIA.adoc b/czech-file-knife/docs/governance/CRG-CRITERIA.adoc new file mode 100644 index 000000000..e37dce111 --- /dev/null +++ b/czech-file-knife/docs/governance/CRG-CRITERIA.adoc @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Component Readiness Grades (CRG) Criteria +:toc: preamble +:icons: font + +This document defines the quality assessment criteria for individual project components. + +== Grade Definitions + +[cols="1,2,3,4",options="header"] +|=== +| Grade | Name | Release Stage | Meaning + +| **A** | Field-Proven | Stable | Real-world feedback amassed; no harm in wild. +| **B** | Broadly Validated | Release Candidate | Tested on 6+ diverse external targets. +| **C** | Self-Validated | Beta | Reliable in home context (dogfooded). +| **D** | Partial | Alpha | Works on some inputs/cases but not systematically. +| **E** | Minimal | Pre-alpha | Barely functional; needs major work. +| **F** | Harmful/Wasteful | Reject/Delegate | Redundant or negative value. +| **X** | Untested | — | Status completely unknown. +|=== + +== Core Principles + +1. **Assess components, not projects:** Each feature gets its own grade. +2. **Evidence over intuition:** Every grade above X requires documented evidence. +3. **Honest assessment:** Grade the component as it is today, not as you hope it will be. +4. **Grades are earned and can be lost:** Regressions lead to demotion. + +== Assessment Checklist + +1. Has it been tested at all? (No → **X**) +2. Does it cause harm or duplicate something better? (Yes → **F**) +3. Does it do something, however slight? (Barely → **E**) +4. Does it work on some things but not others? (Partial → **D**) +5. Does it work reliably on our own project? (Dogfooded → **C**) +6. Has it been tested on 6+ diverse external targets? (Broad → **B**) +7. Do external users confirm it works and is useful? (Field-proven → **A**) + +See link:READINESS.adoc[READINESS.adoc] for the current project assessment. diff --git a/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc b/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc new file mode 100644 index 000000000..48149d86d --- /dev/null +++ b/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Maintenance Checklist +# Maintenance Checklist (Cross-Repo) + +Use this as a repeatable maintenance runbook for any repo. + +Companion policy: + +- `docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` (human-readable) +- `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` (machine-readable) + +## Canonical Repo Baseline (Final) + +Apply this baseline to every repo unless an explicit exception is recorded. + +### Three-Axis Default Model + +- [ ] Axis 1 (scope priority, runs first): `must > intend > like` +- [ ] Axis 2 (maintenance priority): `corrective > adaptive > perfective` +- [ ] Axis 3 (audit priority): `systems > compliance > effects` +- [ ] Perfective items are derived from Axis 1 honest state (not started independently). + +### Axis 1 Scoping Pass (Mandatory) + +Before Axis 2/3 execution, assemble a scoped worklist from evidence: + +- [ ] Read and reconcile: `README`, roadmap, status docs, maintenance checklist, and current CI/security docs. +- [ ] Scan for unfinished markers: `TODO`, `FIXME`, `XXX`, `HACK`, `STUB`, `PARTIAL`. +- [ ] If Idris is present, scan unsoundness markers: `believe_me`, `assert_total`. +- [ ] Identify declared intent vs actual implementation (docs honesty check). +- [ ] Produce a scope assembly artifact with prioritized entries under: + - `must` (release blockers / safety / correctness) + - `intend` (planned near-term) + - `like` (nice-to-have) + +### Axis 2 Maintenance Execution Rules + +- [ ] Corrective first: fix breakage, defects, regressions, safety issues. +- [ ] Adaptive second: reconcile changed scope, remove stale references, cull no-longer-relevant work. +- [ ] Perfective third: only from current honest state established by Axis 1 and updated by corrective/adaptive actions. + +### Axis 3 Audit Rules + +- [ ] Verify systems are in place and actually operating. +- [ ] Verify documentation explains the real/current state (not aspirational-only), including documented exceptions. +- [ ] Verify safety and security controls are present, active, and evidenced. +- [ ] Verify observed effects/impacts are captured and reviewed. +- [ ] Effects audit includes: + - benchmark execution and recorded results (with before/after where relevant) + - explicit maintainer dialogue/status review on what changed, why, and next risks +- [ ] Audit compliance seams/compromises explicitly: + - policy exceptions are recorded with rationale, scope, and expiry/review + - exception does not silently broaden into general policy drift + - language-policy contamination checks run (example: a single TS exception must not trigger broad conversion) + - run `panic-attack` as the compliance-audit scanner + - run ecological checking under effects (using sustainabot guidance as current baseline) + +### Generic Cleanup And Finish-Off Pass + +Run this pass at the end of a corrective/adaptive/perfective cycle: + +- [ ] Root cleanup: + - keep only required control/entry files in root + - move non-essential docs/reports/fixtures to canonical folders +- [ ] Remove or archive stale work: + - close out completed TODO/STUB/PARTIAL items + - cull obsolete references, dead files, and superseded plans +- [ ] Documentation finish-off: + - ensure README, roadmap, status, and wiki match actual implementation state + - ensure machine-readable policy/state files match human docs +- [ ] Security/compliance finish-off: + - run compliance scanner (`panic-attack`) and resolve high-priority findings + - verify exception register and seams/compromises are explicitly bounded +- [ ] Effects finish-off: + - run benchmark/effects checks and record evidence + - conduct explicit maintainer review dialogue (what changed, why, remaining risks) +- [ ] Release-prep finish-off: + - produce Must/Should/Could summary + - produce immediate corrective/adaptive/perfective next-actions list + +### Must + +- [ ] Keep required control files at repository root: + - `.gitignore`, `.gitattributes`, `.editorconfig`, `.tool-versions` + - `Containerfile` + - `.containerignore` (or `.dockerignore` only when required for compatibility) + - `CNAME` and `.nojekyll` when using GitHub Pages/custom domain + - `Justfile` (root by convention) +- [ ] Keep ownership/governance files present: + - `MAINTAINER` in root + - `.github/CODEOWNERS` +- [ ] Keep machine-readable canonical structure under `.machine_readable/`: + - state/meta/ecosystem files (`*.a2ml` or repo standard) + - `anchors/ANCHOR.a2ml` + - `contractiles/` (`must`, `trust`, `intend`, and related) + - `ai/` for AI guidance files + - `bot_directives/` for bot control files +- [ ] Keep contractiles/invariants present and wired: + - root `Mustfile` (or equivalent) with enforceable checks + - `Trustfile` and `Intentfile` present +- [ ] Keep security metadata present: + - `www/.well-known/security.txt` and relevant policy metadata + - CI security scanning configured and runnable +- [ ] Keep docs and navigation coherent: + - single navigation entry point in root (`NAVIGATION.adoc` or equivalent) + - no duplicate conflicting docs for same purpose (for example both `.md` and `.adoc` in root unless intentionally required) +- [ ] Enforce ABI/FFI purity where the policy applies: + - ABI definitions in Idris2 (`src/interface/Abi/*.idr`) + - FFI implementations in Zig (`ffi/**/*.zig`) +- [ ] Ensure quality gate includes: formatting, lint, unit/integration tests, p2p/e2e checks, benchmark smoke, docs checks, security scan. + +### Should + +- [ ] Keep human docs primarily in AsciiDoc (`.adoc`) except where ecosystem rules require other formats (GitHub/community health, legal text, tool-specific files). +- [ ] Keep non-essential root files moved into structured folders: + - `docs/` (theory/practice/whitepapers/proofs/reports) + - `tests/` (fixtures/outputs) + - `docs/legal/` (while retaining root `LICENSE` when forge detection needs it) +- [ ] Maintain `www/.well-known/` for public metadata where applicable (`security.txt`, `humans.txt`, `ads.txt` mirrors if used); a root `.well-known/` is legacy (issue #53). +- [ ] Keep CI policy checks for doc-format conventions and canonical file placement. +- [ ] Keep roadmap/status docs honest with dated evidence. + +### Could + +- [ ] Maintain both human and machine views of maintenance policy from a single source (generate one from the other). +- [ ] Add policy bots for corrective/adaptive/perfective/audit modes. +- [ ] Add repo-level architecture map (`TOPOLOGY.md`) and release-readiness dashboards. +- [ ] Add per-repo exception registry for approved policy deviations. + +### Explicit Root-Placement Rule + +Do **not** move the following out of root if you want default tool behavior: + +- `.gitignore`, `.gitattributes`, `.editorconfig`, `.tool-versions` +- `Containerfile` and ignore file (`.containerignore`/`.dockerignore`) +- `CNAME` and `.nojekyll` for GitHub Pages +- `Justfile` + +## Quick Automated Run (Script) + +Use the helper script first, then use the checklist for deeper/manual follow-up. + +Script locations: +- `${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/run-maintenance.sh` +- `~/Desktop/run-maintenance.sh` + +```bash +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --output /tmp/maintenance-report.json +jq . /tmp/maintenance-report.json +``` + +Useful flags: + +```bash +# Strict mode: fail process on failed checks +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --strict + +# Skip expensive checks when needed +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --skip-panic + +# Explicit language selection +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --rust --python + +# Release hard-pass mode (fails on warnings or failures) +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --fail-on-warn +``` + +Permission policy in script: +- Flags `g+w/o+w` files/dirs +- Flags suspicious executable files +- Flags shebang scripts missing executable bit +- Supports repo-local exceptions via `.maintenance-perms-ignore` (regex per line) +- **Audit-first by default** (non-mutating) +- `--fix-perms` is explicit opt-in only (never implicit) +- For reversible local hardening, pair snapshot/restore scripts where available: + - `scripts/maintenance/perms-state.sh snapshot` + - `scripts/maintenance/perms-state.sh lock` + - `scripts/maintenance/perms-state.sh restore` + +Important git behavior: +- Git generally tracks execute bit, not full UNIX mode matrix. +- Permission hardening audits do not force collaborators to re-unlock every file on pull. +- Keep lock mode opt-in, with restore path documented. + +```bash +# Audit-only (recommended default) +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo + +# Opt-in permission fixes (review output before commit) +~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --fix-perms +``` + +## 0) Setup + +```bash +REPO="/absolute/path/to/repo" +cd "$REPO" +``` + +```bash +date -u +git rev-parse --abbrev-ref HEAD +git rev-parse HEAD +git status --porcelain +``` + +## 1) Preflight + +- [ ] Confirm clean intent: note existing unrelated dirty files before edits. +- [ ] Confirm runtime/toolchain versions. +- [ ] Confirm container mode expectation (`podman`/`podman-compose`) if required. + +```bash +command -v rg git jq || true +command -v podman podman-compose || true +``` + +## 2) Dependency/Env Prereqs + +- [ ] Python deps in active interpreter (for Python paths). +- [ ] Language-specific tooling installed. + +```bash +python -c "import sys; print(sys.executable)" +python -c "import pydantic; print(pydantic.__version__)" || echo "pydantic missing" +``` + +## 3) Corrective Maintenance First + +- [ ] Fix regressions, runtime errors, panics, broken commands, failing tests. +- [ ] Re-run failing checks immediately after each fix. + +## 4) Code Health Scans + +- [ ] `TODO/FIXME/XXX/HACK/STUB/PARTIAL` scan. +- [ ] Permission policy scan (`g+w/o+w`, executable hygiene). +- [ ] ABI/FFI policy scan (if applicable: Idris2 ABI, Zig FFI). + +```bash +rg -n "TODO|FIXME|XXX|HACK|STUB|PARTIAL" -g '!**/.git/**' -g '!**/target/**' . +``` + +```bash +# Optional per-repo exceptions (regex per line): +# .maintenance-perms-ignore +# ^vendor/ +# ^third_party/ +``` + +```bash +# Adjust paths for your repo layout +find . -type f \( -name '*.idr' -o -name '*.idris2' -o -name '*.zig' \) +``` + +## 5) Panic/Safety/Security Pass + +- [ ] Run `panic-attacker` assail/assault. +- [ ] Triage findings by severity. +- [ ] Fix high first, then medium. +- [ ] Re-run until acceptable. + +```bash +PANIC_BIN="${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/panic-attacker/target/release/panic-attack" +"$PANIC_BIN" assail "$REPO" --output /tmp/assail.json --output-format json --quiet +jq -r '.weak_points | length' /tmp/assail.json +jq -r '.weak_points[] | "\(.severity)|\(.location)|\(.description)"' /tmp/assail.json +``` + +```bash +# If repo has production-only source builder, prefer this for baseline checks: +./scripts/ci/build-panic-assail-source.sh /tmp/panic-src +"$PANIC_BIN" assail /tmp/panic-src --output /tmp/assail-prod.json --output-format json --quiet +``` + +## 6) Language-Specific Validation + +### Rust + +- [ ] Format +- [ ] Lint +- [ ] Tests +- [ ] Doc tests +- [ ] Benches (where relevant) + +```bash +cargo fmt --all --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +cargo test --workspace --doc +# Optional targeted benchmarks: +cargo bench +``` + +### Python + +- [ ] Format/lint +- [ ] Type check +- [ ] Tests + +```bash +ruff check . +ruff format --check . +mypy . +pytest -q +``` + +### Elixir + +- [ ] Format check +- [ ] Lint/static checks +- [ ] Tests + +```bash +mix format --check-formatted +mix credo --strict +mix test +``` + +## 7) Container/Runtime Checks (Podman) + +- [ ] Build container path. +- [ ] Run smoke tests inside containerized flow. +- [ ] Compare host vs container behavior for parity. + +```bash +podman --version +podman compose version || podman-compose --version +``` + +## 8) Benchmark + Regression Check + +- [ ] Capture before/after metrics for touched hot paths. +- [ ] Record command + sample size + output. +- [ ] Fail change if critical path regresses beyond threshold. + +## 9) Adaptive and Perfective Maintenance + +- [ ] Adaptive: compatibility updates (tooling/API/deprecations/config flags). +- [ ] Perfective: clarity, docs parity, developer workflow improvements. +- [ ] Update roadmap/checklist/docs to match actual implementation state. + +## 10) Final QA and Release Hygiene + +- [ ] Re-run full relevant checks one final time. +- [ ] Confirm no unintended file changes. +- [ ] Commit scoped changes with clear message. +- [ ] Push and capture commit SHA. + +```bash +git status --short +git diff --stat +git add +git commit -m "maint: " +git push +``` + +## 11) Maintenance Report Template + +Copy this block per repo run: + +```text +Repo: +Branch: +Start UTC: +End UTC: + +Scope: +- Corrective: +- Adaptive: +- Perfective: + +Checks Run: +- TODO/FIXME scan: +- Panic-attacker: +- Rust/Python/Elixir checks: +- Container checks: +- Benchmark checks: + +Findings: +- High: +- Medium: +- Low: + +Fixes Applied: +1. +2. +3. + +Validation Results: +- Tests: +- Benchmarks: +- Panic-attacker rerun: + +Artifacts: +- assail report: +- benchmark output: +- logs: + +Commit(s): +- SHA: + +Remaining Risks / Follow-ups: +1. +2. +``` + +## 12) Language-Repo Additions (Eclexia-Specific) + +Add these checks for language/compiler repositories with formal ABI/FFI constraints: + +- [x] README structure restored (index/TOC, audience paths, quickstart sanity). +- [x] Wiki split by audience (laypeople/users/developers) and linked from docs index. +- [x] Root-level clutter reduced (archive, analysis, reports relegated to `docs/` subtrees). +- [x] Machine-readable docs synchronized (`STATE.a2ml`, `META.a2ml`, `ECOSYSTEM.a2ml`, contractiles). +- [x] Human-readable docs synchronized (`README`, `QUICK_STATUS`, roadmap, wiki home). +- [x] `Mustfile` invariants present and enforceable in CI. +- [x] `Trustfile` and `Intentfile` present and complete. +- [x] FFI/ABI purity policy enforced (`*.zig` for FFI, `*.idr`/Idris2 for ABI). +- [x] `panic-attack` findings triaged with explicit severity budget for release. +- [x] Point-to-point, end-to-end, and benchmark checks wired in one quality gate. +- [x] CI workflows include quality + security + docs checks with explicit policy. +- [x] Release audit includes corrective/adaptive/perfective + Must/Should/Could. +- [x] Roadmap/status honesty pass completed (dates and current evidence updated). + +## 13) Latest Execution Record (Eclexia, 2026-02-24) + +Repo: `/tmp/eclexia-releaseprep` (branch `release-prep`, base `533ec9e9447f374135cc9e2e81021624ddb3c0ad`) + +### 13.1 Setup/Preflight + +- [x] Captured UTC timestamp and git state. +- [x] Tooling presence verified (`rg`, `git`, `jq`, `cargo`, `rustc`, `just`). +- [x] Runtime/toolchain versions captured. +- [x] Container tooling checked (`podman`, `podman-compose`). + +### 13.2 Corrective Maintenance + +- [x] Fixed `panic-attack` script path handling (`mktemp` output + local fallback binary detection). +- [x] Removed Idris `believe_me` usage from ABI wrappers. +- [x] Fixed conformance crash-noise path by skipping known intentional stack-overflow case in default runner. +- [x] Re-ran affected checks after each fix. + +### 13.3 Code-Health Scans + +- [x] TODO/FIXME/STUB/PARTIAL scan run on active code paths. +- [x] ABI/FFI file inventory run (`*.idr`, `*.zig`). +- [x] Active-code marker count reduced/triaged; remaining items tracked in release audit. + +### 13.4 Security/Panic Pass + +- [x] `panic-attack` run and triaged. +- [x] Critical findings cleared (Idris unsoundness markers removed). +- [x] Current baseline: 0 weak points (Critical 0, High 0, Medium 0, Low 0). +- [x] High/Medium backlog fully eliminated. + +### 13.5 Language Validation + +- [x] Final `just quality-gate` pass completed (docs, fmt, lint, unit, conformance, integration, p2p, e2e, bench). +- [x] Additional targeted reruns completed (`just test-conformance`, `just panic-attack`, `just docs-check`). + +### 13.6 Adaptive/Perfective/Docs + +- [x] README/wiki/docs structure and indexing restored. +- [x] Root tidy/relegation pass executed. +- [x] Roadmap/status honesty update performed with current date and evidence links. +- [x] Release audit created with corrective/adaptive/perfective + Must/Should/Could. +- [x] Full quality-gate rerun passed after hardening updates. +- [x] ABI/FFI extension lane added without breaking stable symbols (`ecl_abi_get_info`, `ecl_tracker_create_ex`, `ecl_tracker_snapshot`). +- [x] CI quality workflow now validates sibling `proven` repo presence and critical binding files. +- [x] Proven roadmap now includes explicit "critical core, not full rewrite" adoption guidance and flowchart. + +### 13.7 Outstanding Items (Explicit) + +- [x] Stable `v1.0.0` technical gate readiness met (quality + panic scan clean). +- [x] Parser/codegen/runtime panic-path hardening completed for scanner-flagged paths. +- [x] Non-eclexia `proven` library checked: already Idris2-first with Zig ABI bridge; no additional integration changes required in this run. +- [ ] Remote push blocked by token scope: GitHub rejected branch updates (`release-prep`, `release-prep-pushable`) due missing `workflow` OAuth scope. + +### 13.8 Artifacts + +- Release audit: `docs/reports/V1-READINESS-AUDIT-2026-02-24.md` +- Panic report: `/tmp/eclexia-panic-attack.KZ1jpC.json` (0 weak points) +- Final quality gate log: `/tmp/eclexia-quality-gate-final2.log` (plus post-change reruns via terminal sessions) +- Local commits: `88fa2af` (`release-prep`), `baa3d1c` (`release-prep-pushable`) + pending new commit from this pass + +## 12) LLM Operator Instructions + +Use this prompt with an LLM agent when you want the process run end-to-end: + +```text +Run the maintenance workflow for this repo using MAINTENANCE-CHECKLIST.md. + +Required behavior: +1. Run ~/Desktop/run-maintenance.sh first and collect the JSON report. +2. Triage report results by severity: fail > warn > pass. +3. Execute corrective maintenance first (fix regressions, panics, broken tests/commands). +4. Run TODO/FIXME/stub scan and address relevant items. +5. Run panic-attacker and fix findings in priority order; rerun to confirm. +6. Run language-specific checks (Rust/Python/Elixir) relevant to this repo. +7. Run benchmark/regression checks for touched hot paths. +8. Enforce permission policy: + - no group/world writable source files unless justified + - executable bit only where intended + - use .maintenance-perms-ignore for justified exceptions +9. Update docs/roadmap/checklist entries to reflect actual state. +10. Produce a final report using the template in MAINTENANCE-CHECKLIST.md. + +Constraints: +- Do not revert unrelated existing dirty changes. +- Stage and commit only scoped intended files. +- If blocked, state exactly what is blocked and why. +``` + +## 13) AI Execution Integrity Contract (Mandatory) + +Use this when delegating maintenance to any AI (Gemini/Claude/ChatGPT/etc.). + +```text +You must execute this maintenance run with strict integrity. + +Non-negotiable rules: +1. Do not claim any step is complete unless you actually ran it. +2. Do not silently skip checklist items. If skipped, state SKIPPED + exact reason. +3. For every check, provide evidence: + - command executed + - pass/fail/warn + - key output summary + - artifact/log path +4. If a command fails, stop claiming success and report the failure clearly. +5. After each fix, re-run the relevant failing check and report the rerun result. +6. Do not hide uncertainty. If unsure, say so and run additional verification. +7. Never mark “all done” while any fail/warn remains unexplained. +8. Do not make destructive or broad permission changes by default. + - permission changes must be audit-first + - use --fix-perms only with explicit intent +9. Final output must include: + - checklist coverage matrix (each item: PASS/FAIL/WARN/SKIPPED) + - unresolved risks + - exact next actions +10. Prioritize user safety and reputation: no “looks fine” claims without evidence. +``` + +Recommended enforcement line for AI prompts: + +```text +Fail closed: if evidence is missing for any checklist item, treat that item as NOT DONE. +``` + +## 14) Fleet Enrollment Automation (Gitbot + Hypatia) + +For centralized coverage across existing and new repos: + +```bash +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet +just enroll-repos +``` + +Optional directive write-back to repos that already have `.machine_readable/`: + +```bash +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet +just enroll-repos /var$REPOS_DIR true +``` + +Release hard gate from fleet: + +```bash +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet +just maintenance-hard-pass /absolute/path/to/repo +``` diff --git a/czech-file-knife/docs/governance/README.adoc b/czech-file-knife/docs/governance/README.adoc new file mode 100644 index 000000000..3031484bc --- /dev/null +++ b/czech-file-knife/docs/governance/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Governance Pillar (TSDM) diff --git a/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc b/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc new file mode 100644 index 000000000..7c43d5622 --- /dev/null +++ b/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Software Development Approach (General) +:toc: left +:toclevels: 2 + +This is the general operating policy for software development across repositories. + +== Core Sequence + +Always run work in this order: + +1. Scope first (Axis 1) +2. Maintenance second (Axis 2) +3. Audit third (Axis 3) + +== Axis Definitions + +=== Axis 1: Scope + +Priority order: `must > intend > like` + +Axis 1 output is a scoped assembly of work based on: + +* README, roadmap, status, CI/security docs +* marker scans (`TODO`, `FIXME`, `XXX`, `HACK`, `STUB`, `PARTIAL`) +* Idris unsoundness scan when Idris exists (`believe_me`, `assert_total`) +* docs honesty check (intent vs actual implementation) + +=== Axis 2: Maintenance + +Priority order: `corrective > adaptive > perfective` + +* Corrective: fix defects, regressions, breakage, security/safety failures +* Adaptive: reconcile scope changes, remove stale references, cull obsolete work +* Perfective: improve quality/clarity/performance only from the honest Axis 1 state + +=== Axis 3: Audit + +Priority order: `systems > compliance > effects` + +* Systems: required mechanisms exist and are operating +* Compliance: seams/compromises/exceptions are explicit, bounded, and do not drift +* Effects: benchmark and operational impact evidence is captured and reviewed + +Compliance scanner baseline: `panic-attack` + +Effects/ecological baseline: sustainabot-guided ecological checking + +== Generic Cleanup And Finish-Off + +At cycle end: + +* reduce root clutter to required control/entry files +* archive/remove stale or superseded work +* synchronize human and machine docs +* run compliance and effects audits with evidence capture +* produce Must/Should/Could summary and immediate next-actions list + +== Collaboration Rule + +Effects review must include explicit maintainer dialogue: + +* what changed +* why it changed +* what risks remain diff --git a/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc b/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc new file mode 100644 index 000000000..963167e26 --- /dev/null +++ b/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Template Lineage Audit — self-identity drift in minted repositories +:toc: macro +:toclevels: 3 +:sectnums: + +*Measured 2026-09-15 against the live estate.* This document records what the +template's self-identity repair passes do, what they provably did *not* do to +repositories minted before they existed, and — just as importantly — which of +the measurements behind those claims are reliable and which are not. + +This file is deliberately placed in `docs/governance/` alongside +`TEMPLATE-STANDARDS-AUDIT.adoc`, and is deliberately added to the self-name +pass's skip list in `build/just/repo-init.just`. A document *about* the +literal `rsr-template-repo` must not have that literal rewritten inside it +when a child is minted, or every minted copy becomes nonsense. + +toc::[] + +== Summary + +Two mint-time repair passes in `build/just/repo-init.just` exist to stop a +freshly minted repository from declaring that it *is* the template. Both work. +Neither has ever been applied retroactively, so repositories minted before the +passes landed still carry the template's identity. + +The live, directly verified population is *small* — eight repositories, in two +overlapping fault classes — and is a *floor*, not a total. An earlier estimate +of seventeen affected repositories was produced by trusting a code-search +result and is *wrong*; see <>. + +== Root cause: a literal that cannot be a token + +The template's own comment states the mechanism, and it is worth quoting +because it is more precise than any paraphrase: + +[quote, 'build/just/repo-init.just, Self-name pass (ADR-0003)'] +____ +The template's own name is written as a LITERAL, not as a placeholder token, +so the substitution loop above never touched it. Measured on a scratch mint of +346ae56: a repo minted as `mint-check-lib` still carried +`project := "rsr-template-repo"`, `REPO := "rsr-template-repo"`, +`sonar.projectKey=hyperpolymath_rsr-template-repo`, +`STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo"` … i.e. it declared +that it WAS the template, and would have reported its code analysis into the +TEMPLATE's SonarCloud project. +____ + +The ordinary mint path rewrites `hyperpolymath`, `czech-file-knife` and friends. Those +tokens *are* consumed widely — 56 files in this repository, including the +README badge block, `CITATION.cff`, `.github/CODEOWNERS`, +`.github/settings.yml` and the three AI-assistant rule files. The problem is +confined to files that cannot carry a `{{...}}` token, which is why it looks +arbitrary from the outside. + +== The two repair passes, and the exact limit of each + +Both live in `build/just/repo-init.just` and both run *only at mint time*. + +=== Self-name pass + +Guarded by `if [ "$REPO" != "rsr-template-repo" ]`. Rewrites the template +literal to the child's name across the tree, with two protections: + +* Paths that are *about* the template are skipped wholesale — `.git`, + `build/just/repo-init.just` itself, `docs/decisions/*`, + `.machine_readable/descriptiles/{CLADE,VARIANT}.a2ml`, `CHANGELOG.md`, + `TEMPLATE-STANDARDS-AUDIT.adoc`, and binary extensions. +* Within every other file, any line naming the parent *as a parent* survives + byte-for-byte, matched by + `instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: `. + +It uses `sed -i` deliberately — the comment records why: ``sed -i` preserves +the mode bit; rewriting via a temp file would not.` + +=== Self-OWNER pass + +Guarded by `if [ "$OWNER" != "hyperpolymath" ]`, and *deliberately narrow* — +exactly three lines: the `Justfile` `OWNER :=` declaration, +`sonar.organization` and `sonar.projectKey`. The reasoning is sound and should +not be "improved" into a blanket rewrite: + +[quote, 'build/just/repo-init.just'] +____ +A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: +`hyperpolymath/standards`, `hyperpolymath/proven` and the other estate +dependencies are real repos every child genuinely points at. +____ + +== Measured: what is actually wrong in the estate today + +All rows below were read *directly* from each repository's +`sonar-project.properties` via the contents API, not inferred from search. + +=== Class 1 — self-name pass never applied (4 repositories) + +These declare the template's project key, so their analyses are configured to +land in the template's own SonarCloud project. + +[cols="2,3", options="header"] +|=== +| Repository | `sonar.projectKey` +| `hyperpolymath/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` +| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` +|=== + +NOTE: "configured to report into the template's project" is what has been +measured. No analysis has been *observed* landing there; the SonarCloud side +was not inspected. + +=== Class 2 — self-OWNER pass never applied (7 repositories) + +`metadatastician`-owned repositories carrying `sonar.organization=hyperpolymath`. + +[cols="2,3", options="header"] +|=== +| Repository | `sonar.projectKey` +| `metadatastician/cadastra` | `hyperpolymath_cadastra` +| `metadatastician/chronicles-of-slavia` | `hyperpolymath_chronicles-of-slavia` +| `metadatastician/harvard-dehallucinator`| `hyperpolymath_harvard-dehallucinator` +| `metadatastician/IDApTIK` | `hyperpolymath_IDApTIK` +| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` +| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` +|=== + +The last three are also Class 1. The union is *eight distinct repositories*: +one Class-1-only, four Class-2-only, three in both. + +*Negative control:* `metadatastician/pong-ping` reads +`sonar.organization=metadatastician` / `sonar.projectKey=metadatastician_pong-ping` +— correct on both axes, and correctly absent from both tables. + +[[method]] +== Method, and three ways the measurement lied + +This section exists because the first version of this audit was wrong, and the +way it was wrong is reusable. + +=== Failure 1 — a false zero on punctuation + +`gh search code --owner hyperpolymath --owner metadatastician 'REPO := "rsr-template-repo"' --filename Justfile` +returns *zero repositories*. That string had already been read directly at +`rsr-julia-library-template-repo/Justfile:24`. GitHub code search does not do +exact substring matching across `:=`, spaces and quotes. + +⇒ *The `Justfile`-level population is unmeasured, not zero.* No count of it +appears in this document. + +=== Failure 2 — false positives from the recipe's own comment + +Searching for `sonar.projectKey=hyperpolymath_rsr-template-repo` returned 18 +repositories. Most matched in `build/just/repo-init.just` — the mint recipe, +which quotes that exact key *inside the comment documenting this defect*, and +which every minted child carries. Only five matched in an actual +`sonar-project.properties`, and one of those is the template itself. + +Two repositories the search listed (`metadatastician/pong-ping`, +`metadatastician/enaction-engine`) have entirely correct keys today. + +⇒ *A search hit is a hit on a byte sequence, not on a fact.* Every row in the +tables above was re-read from the file that would actually be consumed. + +=== Failure 3 — counts that are not defect counts + +A case-insensitive search for banned runtimes (`rescript`, `deno`, +`typescript`) in `rsr-julia-library-template-repo` returns 39 files; with word +boundaries, 38. *This is not a defect count.* At least 17 of those files +contain ban/forbid language — they name the runtime precisely in order to +prohibit it, `.github/workflows/runtime-policy.yml` most obviously. Files that +look like genuine *use* rather than prohibition, and so need individual +judgement, are `examples/web-project-deno.json`, `mise.toml`, +`container/compose.yaml` and `.github/workflows/release.yml`. + +⇒ Recorded here as a pointer for a human, deliberately without a headline +number. + +== Related findings + +=== `rsr-julia-library-template-repo` misreports its own name + +Both `Justfile:24` and `.machine_readable/contractiles/Justfile:24` declare +`REPO := "rsr-template-repo"`, while +`.machine_readable/descriptiles/CLADE.a2ml:9` correctly says +`canonical-name = "rsr-julia-library-template-repo"`. `OWNER :=` is correct in +both files. + +*Severity is lower than it appears.* Across every `Justfile` and `*.just` in +that repository there are *zero* `hyperpolymath` or `czech-file-knife` interpolations, so +neither variable is consumed by any recipe. This is a wrong self-description +with no runtime effect — worth fixing because a template propagates its own +mistakes, not because anything currently misbehaves. + +=== Layout migration lag, not divergence + +This template used `machine-readable/`; `rsr-julia-library-template-repo` and +much of the estate use `.machine_readable/`. Commit `a983c00` ("chore(shape): +remake the repository layout for human legibility", #43, 2026-08-26, 219 files) +renamed `.machine_readable/` to `machine-readable/` here, on a legibility +argument. That rename was **reversed on 2026-09-17 by owner ruling**: dotted is +the standard everywhere. The direction of "lag" therefore inverted — the +repositories still on the hyphenated spelling are now the ones that have not +followed, and `scripts/check-root-shape.sh` continues to resolve both spellings +so those repositories keep working rather than exiting 2. + +Estate floors at the time of the 2026-08 rename, from code search and therefore +subject to <>: 48 repositories on `.machine_readable/`, 9 on +`machine-readable/`. The majority was already dotted when this template +diverged from it. + +== What this document deliberately does not do + +* *It changes no other repository.* Rewriting `sonar-project.properties` + across eight repositories in two organizations is a fleet action, and fleet + campaigns are parked by standing decision. The tables above are the work + order if and when that is unparked. +* *It does not touch `.a2ml` manifests, `.deed` grammar or `k9` contracts.* +* *It does not widen the self-OWNER pass*, for the reason the recipe already + gives. +* *It does not claim the passes are buggy.* They are correct; they are simply + mint-time-only, and nothing has ever backfilled. + +== Provenance + +Measured 2026-09-15 against the live GitHub estate by direct contents-API +reads, with a stated negative control (`metadatastician/pong-ping`) and three +recorded search failures. Template state as of `cc76f4e`. diff --git a/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc b/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc new file mode 100644 index 000000000..fcfba12b1 --- /dev/null +++ b/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Template Standards Audit +Codex +v1.0, 2026-04-07 +:toc: +:toclevels: 3 +:sectnums: + +== Scope And Inventory + +Audit scope: + +* Repository: `rsr-template-repo` +* Focus: root authority docs/manifests, Justfile integration, session bindings +* Recursive inventory method: `rg --files -g '!.git' | sort` + +Inventory snapshot at audit time: + +* `rsr-template-repo` total tracked files discovered: `240` +* Session-local binding files discovered: `4` under `session/` plus `coordination.k9` + +== Claim Vs Actual + +[cols="2,2,3,1,3,3",options="header"] +|=== +| Claimed item | Claimed by | Expected path | Actual status | Notes | Recommended action + +| Placeholder badge tokens in README +| `README.adoc` +| placeholder +| Template placeholders are intentional pre-bootstrap content. +| Keep; document clearly as template tokens. + +| Docs links used `.adoc` files not present +| legacy `README.adoc` (pre-migration) +| `CONTRIBUTING.adoc`, `GOVERNANCE.adoc`, `SECURITY.adoc` +| outdated +| Actual files are `CONTRIBUTING.md`, no root `GOVERNANCE.adoc`, `SECURITY.md`. +| Fixed README links to existing files. + +| ABI path lower-case only +| legacy `README.adoc` + legacy checks +| `src/interface/abi/*.idr` +| outdated +| Tree previously shipped `src/interface/Abi/*.idr` (uppercase A); now renamed. +| Renamed to canonical lowercase `src/interface/abi/*.idr` (matches `validate-template.sh` + gossamer). Checks remain tolerant of either case. + +| Root manifest structure with non-existent files +| legacy `0-AI-MANIFEST.a2ml` (pre-migration) +| `GOVERNANCE.adoc`, several strict root assumptions +| outdated +| Manifest claims did not match actual template contents. +| Replaced with accurate authority split + startup checklist. + +| Source-human references maintenance/practice docs +| legacy Justfile + policy A2ML (pre-migration) +| `docs/maintenance/...`, `docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` +| outdated +| Those paths did not exist in template. +| Updated to `docs/governance/...` paths. + +| Session-management local binding files +| target architecture +| `session/README.md`, `session/custom-checks.k9`, `session/local-hooks.sh`, `coordination.k9` +| exists +| Added as thin integration layer without protocol duplication. +| Keep. + +| Canonical command mapping in local automation +| target architecture +| `Justfile` session aliases + `session/dispatch.sh` +| exists +| All canonical commands map to dispatcher. +| Keep. + +| Central protocols are authoritative +| `README.adoc`, `0-AI-MANIFEST.a2ml`, `AUDIT.adoc` +| `standards/3-practice/session-management-standards/` +| exists +| Local docs now explicitly defer protocol authority to standards repo. +| Keep. + +| Runtime session artifacts stay per-repo +| `session/README.md`, `session/dispatch.sh` +| `.session/` in target repo path +| exists +| Dispatcher records canonical commands into runtime `.session/` files. +| Keep runtime artifacts out of authoritative standards docs. + +| Local policy hooks remain local +| `session/local-hooks.sh`, `session/custom-checks.k9` +| local session binding layer +| exists +| Policy/hook logic separated from central protocol definitions. +| Keep local-only. +|=== + +== Classification + +=== Authoritative Shared Standard + +* External to this repo: `standards/3-practice/session-management-standards/*` + +=== Repo-Local Binding/Integration + +* `Justfile` canonical session aliases +* `session/dispatch.sh` +* `session/custom-checks.k9` +* `session/local-hooks.sh` +* `session/README.md` +* `coordination.k9` +* `AUDIT.adoc` (local gate summary) + +=== Generated Runtime Artifact + +* `.session/*` outputs created by local dispatcher per repository path + +=== Obsolete/Duplicate/Drifted + +* Legacy path assumptions (`docs/maintenance/*`, `docs/practice/*` for governance policy references) +* Legacy root-doc claims to files not present in this template +* Legacy lower-case-only ABI path references + +== Move/Stay/Delete Guidance + +=== Move Into `standards` + +* Any future full protocol text drafts should move to + `standards/3-practice/session-management-standards/`. + +=== Stay In `rsr-template-repo` + +* Local aliases, hooks, coordination wiring, and repo-local checks. + +=== Delete/Archive + +* Archive or remove legacy references to non-existent docs/paths if reintroduced. +* Avoid reintroducing full duplicated protocol definitions. + +== Missing-But-Expected Session Files (Template Repo) + +Template repo expected only thin local integration files. + +Current status: + +* No mandatory thin-binding files are missing. +* Full protocol directories/files are intentionally absent here by design. + +== Proposed Final Directory Map + +[source,text] +---- +rsr-template-repo/ + README.adoc + AUDIT.adoc + 0-AI-MANIFEST.a2ml + EXPLAINME.adoc + Justfile + coordination.k9 + session/ + README.md + dispatch.sh + custom-checks.k9 + local-hooks.sh + docs/ + ... (repo-local human docs) + .machine_readable/ + ... (repo-local machine-readable policy/state) +---- + +== Maintenance Model Note + +* Protocols central: maintained in `standards/3-practice/session-management-standards/`. +* Policy local: maintained in template/downstream repos (`session/*.k9`, hooks, + coordination bindings). +* State per-repo: generated during execution (`.session/*`) in the active + working repository. diff --git a/czech-file-knife/docs/governance/TSDM.a2ml b/czech-file-knife/docs/governance/TSDM.a2ml new file mode 100644 index 000000000..f27036cc7 --- /dev/null +++ b/czech-file-knife/docs/governance/TSDM.a2ml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +--- +### [TSDM_SPEC] +id: "tsdm-standard" +version: "1.0.0" + +axes: + axis_1: + name: "Planning" + levels: ["must", "should", "could"] + axis_2: + name: "Maintenance" + levels: ["corrective", "adaptive", "perfective"] + axis_3: + name: "Audit" + levels: ["systems", "compliance", "effects"] + +invariants: + - "Every task MUST map to at least one TSDM coordinate" + - "Axis 1 priority governs resource allocation" + - "Axis 2 type governs commit categorisation" + - "Axis 3 focus governs audit depth" diff --git a/czech-file-knife/docs/governance/TSDM.adoc b/czech-file-knife/docs/governance/TSDM.adoc new file mode 100644 index 000000000..42899ec6b --- /dev/null +++ b/czech-file-knife/docs/governance/TSDM.adoc @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Triaxial Software Development Methodology (TSDM) +:toc: preamble +:icons: font + +TSDM is a three-dimensional governance framework designed for high-assurance, long-lived software systems. It ensures that every project decision is mapped across three critical axes: Planning, Maintenance, and Audit. + +== The Three Axes + +=== Axis 1: Planning (Scope Priority) +* **Must:** Non-negotiable core invariants and safety requirements. +* **Should:** Essential features and planned improvements. +* **Could:** Desired enhancements and future-proofing. + +=== Axis 2: Maintenance (Execution Type) +* **Corrective:** Fixing bugs, vulnerabilities, and failures. +* **Adaptive:** Responding to environment or dependency changes. +* **Perfective:** Improving performance, refactoring, and documentation. + +=== Axis 3: Audit (Verification Focus) +* **Systems:** Integrity of tools, infrastructure, and automation. +* **Compliance:** Adherence to standards, licenses, and verified seams. +* **Effects:** Real-world impact, ecological footprint, and user feedback. + +== Integration + +TSDM is the operational core of the Rhodium Standard. Every task in the `Justfile` and every state change in `STATE.a2ml` should be justifiable within this framework. diff --git a/czech-file-knife/docs/governance/audit/README.adoc b/czech-file-knife/docs/governance/audit/README.adoc new file mode 100644 index 000000000..a0b0b256f --- /dev/null +++ b/czech-file-knife/docs/governance/audit/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Audit Axis diff --git a/czech-file-knife/docs/governance/audit/compliance/README.adoc b/czech-file-knife/docs/governance/audit/compliance/README.adoc new file mode 100644 index 000000000..aa01a9afb --- /dev/null +++ b/czech-file-knife/docs/governance/audit/compliance/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Compliance Unit diff --git a/czech-file-knife/docs/governance/audit/effects/README.adoc b/czech-file-knife/docs/governance/audit/effects/README.adoc new file mode 100644 index 000000000..f34e583c6 --- /dev/null +++ b/czech-file-knife/docs/governance/audit/effects/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Effects Unit diff --git a/czech-file-knife/docs/governance/audit/systems/README.adoc b/czech-file-knife/docs/governance/audit/systems/README.adoc new file mode 100644 index 000000000..70ef18cc3 --- /dev/null +++ b/czech-file-knife/docs/governance/audit/systems/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Systems Unit diff --git a/czech-file-knife/docs/governance/maintenance/README.adoc b/czech-file-knife/docs/governance/maintenance/README.adoc new file mode 100644 index 000000000..2cb875f11 --- /dev/null +++ b/czech-file-knife/docs/governance/maintenance/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Maintenance Axis diff --git a/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc b/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc new file mode 100644 index 000000000..ea4269e37 --- /dev/null +++ b/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Adaptive Unit diff --git a/czech-file-knife/docs/governance/maintenance/corrective/README.adoc b/czech-file-knife/docs/governance/maintenance/corrective/README.adoc new file mode 100644 index 000000000..7a045985a --- /dev/null +++ b/czech-file-knife/docs/governance/maintenance/corrective/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Corrective Unit diff --git a/czech-file-knife/docs/governance/maintenance/perfective/README.adoc b/czech-file-knife/docs/governance/maintenance/perfective/README.adoc new file mode 100644 index 000000000..1bf8f4288 --- /dev/null +++ b/czech-file-knife/docs/governance/maintenance/perfective/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Perfective Unit diff --git a/czech-file-knife/docs/governance/planning/README.adoc b/czech-file-knife/docs/governance/planning/README.adoc new file mode 100644 index 000000000..676460466 --- /dev/null +++ b/czech-file-knife/docs/governance/planning/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Planning Axis diff --git a/czech-file-knife/docs/governance/planning/could/README.adoc b/czech-file-knife/docs/governance/planning/could/README.adoc new file mode 100644 index 000000000..06863c897 --- /dev/null +++ b/czech-file-knife/docs/governance/planning/could/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Could Unit diff --git a/czech-file-knife/docs/governance/planning/must/README.adoc b/czech-file-knife/docs/governance/planning/must/README.adoc new file mode 100644 index 000000000..990c563de --- /dev/null +++ b/czech-file-knife/docs/governance/planning/must/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Must Unit diff --git a/czech-file-knife/docs/governance/planning/should/README.adoc b/czech-file-knife/docs/governance/planning/should/README.adoc new file mode 100644 index 000000000..8c9ea245f --- /dev/null +++ b/czech-file-knife/docs/governance/planning/should/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Should Unit diff --git a/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt b/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt new file mode 100644 index 000000000..b873155f0 --- /dev/null +++ b/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt @@ -0,0 +1,20 @@ +SPDX-License-Identifier: MPL-2.0 + +================================================================================ +EXHIBIT A — SOURCE CODE FORM LICENSE NOTICE +Mozilla Public License Version 2.0 +================================================================================ + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file) where +a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +================================================================================ +END OF EXHIBIT A +================================================================================ diff --git a/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt b/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt new file mode 100644 index 000000000..81f928878 --- /dev/null +++ b/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt @@ -0,0 +1,104 @@ +SPDX-License-Identifier: MPL-2.0 + +================================================================================ +QUANTUM-SAFE PROVENANCE SPECIFICATION +Rhodium Standard Repository (RSR) — Exhibit B +================================================================================ + +1. PURPOSE + + This exhibit specifies the cryptographic algorithms and procedures for + quantum-safe provenance of contributions in an RSR-compliant repository. + It is a standalone, licence-agnostic specification: it imposes no licence + terms of its own and may be referenced by software under any licence + (this repository is MPL-2.0 for code, CC-BY-SA-4.0 for prose). + +2. APPROVED ALGORITHMS + + The following post-quantum cryptographic algorithms are approved for + signing Provenance Metadata: + + 2.1. Digital Signatures + - ML-DSA (FIPS 204, formerly CRYSTALS-Dilithium) + Recommended: ML-DSA-65 (security level 3) or ML-DSA-87 (level 5) + - SLH-DSA (FIPS 205, formerly SPHINCS+) + Recommended: SLH-DSA-SHA2-256f or SLH-DSA-SHAKE-256f + - FALCON (NIST Round 3 finalist) + Recommended: FALCON-1024 + + 2.2. Key Encapsulation (for encrypted provenance) + - ML-KEM (FIPS 203, formerly CRYSTALS-Kyber) + Recommended: ML-KEM-1024 + + 2.3. Hash Functions + - SHA-3 (FIPS 202) + Recommended: SHA3-256 or SHA3-512 + - SHAKE (FIPS 202 extendable output) + Recommended: SHAKE-256 + + 2.4. Key Derivation + - Argon2id (RFC 9106) + Parameters: t=3, m=65536, p=4 (minimum) + +3. PROVENANCE METADATA FORMAT + + Provenance Metadata should include: + + 3.1. Required Fields + - author-identity: Contributor name and contact + - timestamp: ISO 8601 with timezone + - content-hash: SHA3-256 hash of the contribution + - signature: Quantum-safe signature over all fields + + 3.2. Optional Fields + - parent-hash: Hash of the previous contribution in the chain + - context-notes: Narrative context markers + - platform: Build/development environment + - witnesses: Third-party attestation signatures + +4. SIGNATURE PROCEDURE + + 4.1. Signing + a. Compute SHA3-256 hash of the contribution content + b. Construct metadata record with all required fields + c. Serialize metadata in canonical JSON form + d. Sign with ML-DSA-65 (or approved alternative) + e. Attach signature to distribution + + 4.2. Verification + a. Extract metadata and signature from distribution + b. Verify signature against contributor's public key + c. Verify content hash matches actual content + d. Verify timestamp is within acceptable range + e. Verify parent-hash chain if present + +5. KEY MANAGEMENT + + 5.1. Contributors should publish quantum-safe public keys via: + - OpenPGP keyservers (with PQ algorithm support) + - Repository www/.well-known/keys/ directory (served at /.well-known/keys/) + - Contributor's personal website + + 5.2. Key rotation should occur: + - At least annually + - When algorithm recommendations change + - When key compromise is suspected + +6. TRANSITION PERIOD + + During the transition to quantum-safe cryptography: + + 6.1. Classical signatures (Ed25519, RSA) remain valid + 6.2. Hybrid signatures (classical + PQ) are encouraged + 6.3. Pure PQ signatures are preferred for new contributions + 6.4. Classical-only signatures will be deprecated in a future version + +7. COMPLIANCE + + Quantum-safe provenance is OPTIONAL. When present, it must follow this + specification, and quantum-safe signatures should not be stripped from + distributions that carry them. + +================================================================================ +END OF EXHIBIT B +================================================================================ diff --git a/czech-file-knife/PALIMPSEST.adoc b/czech-file-knife/docs/legal/PALIMPSEST.adoc similarity index 100% rename from czech-file-knife/PALIMPSEST.adoc rename to czech-file-knife/docs/legal/PALIMPSEST.adoc diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc new file mode 100644 index 000000000..2dc2c0df3 --- /dev/null +++ b/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: QUICKSTART-DEV.adoc — clone → build → test → PR += czech-file-knife — Quick Start for Developers +:toc: +:toclevels: 2 + +== Tech Stack + +Rust (2021 edition, MSRV 1.75) Cargo workspace under `src/cfk-*`; tokio async runtime; +Swift bridge for the iOS File Provider (`src/cfk-ios`); Ada TUI prototype (`src/cfk-tui`). + +== Set Up Development Environment + +=== Option A: Guix (preferred) + +[source,bash] +---- +guix shell +---- + +=== Option B: Manual + +[source,bash] +---- +git clone https://github.com/hyperpolymath/czech-file-knife.git +cd czech-file-knife +just setup-dev +---- + +== Build + +[source,bash] +---- +just build # cargo build --workspace +---- + +== Test + +[source,bash] +---- +just test # cargo test --workspace +---- + +== Project Structure + +[source] +---- +czech-file-knife/ +├── src/cfk-*/ # Cargo workspace crates (core, cli, providers, cache, search, vfs, ...) +├── tests/ # Test suite +├── docs/ # Documentation +├── .machine_readable/ # Checkpoint files (STATE, META, ECOSYSTEM) +├── Justfile # Task runner recipes +├── build/ # Build orchestration (just/, guix.scm, container/) +└── *_chora.deed # repo deed: AI entry point (allocation + ply tree) +---- + +== Key Recipes + +[source,bash] +---- +just build # Build the project +just test # Run tests +just doctor # Self-diagnostic +just lint # Lint and format +just panic-scan # Security scan via panic-attacker +just tour # Guided tour of the codebase +---- + +== Before Submitting a PR + +[source,bash] +---- +just lint # Format and lint +just test # All tests pass +just panic-scan # No new security issues +---- + +== Contractile Invariants + +Read `.machine_readable/MUST.contractile` before making changes. +Key invariants that must never be violated: + +* No destructive operation without a recorded inverse, unless the user explicitly opts out (`CFK_NO_JOURNAL=1`). +* Never stage cloud-to-cloud transfers on local disk; prefer provider-side operations. +* Space-constrained transforms never free an input range before its replacement is durable. + +== LLM/AI Agent Development + +If using an AI assistant, load the warmup context first: + +[source,bash] +---- +just llm-context # Outputs role-appropriate context +---- + +Or read the repo deed (`*_chora.deed` at root) and `.claude/CLAUDE.md` directly. + +== Get Help + +* **Architecture**: link:EXPLAINME.adoc[EXPLAINME.adoc] +* **Wiki**: https://github.com/hyperpolymath/czech-file-knife/wiki +* **Report issue**: `just help-me` diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc new file mode 100644 index 000000000..c1be86aed --- /dev/null +++ b/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: QUICKSTART-MAINTAINER.adoc — packaging, deploying, and maintaining += czech-file-knife — Quick Start for Platform Maintainers +:toc: +:toclevels: 2 + +== Overview + +This guide covers packaging, deploying, and maintaining czech-file-knife for +distribution on your platform. + +== Runtime Dependencies + +None for the core CLI. Optional: `libfuse3` (FUSE mounting, `cfk-vfs`), `aria2c`, `agrep`, `pandoc` (integrations). + +== Build from Source + +[source,bash] +---- +git clone https://github.com/hyperpolymath/czech-file-knife.git +cd czech-file-knife +just build-release +---- + +Output: `target/release/cfk` + +== Packaging + +=== Guix + +[source,bash] +---- +guix build -f build/guix.scm +---- + +=== Container (Stapeln) + +[source,bash] +---- +just stapeln-export # Generates Containerfile +podman build -t czech-file-knife . +---- + +=== Manual Package + +[source,bash] +---- +just install --prefix=/usr/local +---- + +Files installed: + +[cols="1,2"] +|=== +| Path | Contents + +| `$PREFIX/bin/` +| Executables + +| `$PREFIX/share/cfk/` +| Data files, assets + +| `$PREFIX/share/doc/cfk/` +| Documentation + +| `$PREFIX/share/applications/` +| .desktop file (Linux, if GUI) + +| `$PREFIX/share/man/man1/` +| Man pages +|=== + +== Configuration + +Default config location: `$XDG_CONFIG_HOME/cfk/config.toml` + +Fallback: `$HOME/.config/cfk/config.toml` + +== Health Checks + +[source,bash] +---- +just doctor # Full diagnostic +just run --version # Version check +just run --selftest # Built-in self-test +---- + +== Updating + +[source,bash] +---- +git pull +just build-release +just install --prefix=/usr/local +---- + +Or via OPSM: `opsm update cfk` + +== Security Notes + +* License: MPL-2.0 (code) / CC-BY-SA-4.0 (docs) +* All dependencies SHA-pinned +* `panic-attacker` scan results: link:INSTALL-SECURITY-REPORT.adoc[] +* OpenSSF Scorecard: see badge in README + +== Multi-Instance Deployment + +For deploying multiple instances (e.g., different users or tenants): + +[source,bash] +---- +just install --prefix=/opt/cfk-instance1 --config=/etc/cfk/instance1.toml +just install --prefix=/opt/cfk-instance2 --config=/etc/cfk/instance2.toml +---- + +Each instance has isolated config, data, and logs. + +== Reporting Issues + +* Upstream: https://github.com/hyperpolymath/czech-file-knife/issues +* With diagnostic: `just help-me` (pre-fills context) diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc new file mode 100644 index 000000000..8d9759cdd --- /dev/null +++ b/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: QUICKSTART-USER.adoc — 5-minute path to working software +// Replace czech-file-knife, Rsr Template Repo — See README.adoc for details., just run, Rsr Template Repo started successfully. with actuals += czech-file-knife — Quick Start for Users +:toc: +:toclevels: 2 + +== What is czech-file-knife? + +Rsr Template Repo — See README.adoc for details. + +== Prerequisites + +Before you begin, ensure you have: + +* **just** — task runner (https://github.com/casey/just[install guide]) +* Platform-specific requirements listed below + +[cols="1,3"] +|=== +| Platform | Additional Requirements + +| Linux +| See README.adoc + +| macOS +| See README.adoc + +| Windows +| See README.adoc +|=== + +== Install + +=== Option 1: Standard Install (recommended) + +[source,bash] +---- +# Clone and set up +git clone https://github.com/hyperpolymath/czech-file-knife.git +cd czech-file-knife +just setup +---- + +The setup script will: + +* Detect your platform and shell +* Install missing dependencies (with your permission) +* Configure the application +* Offer install location choices +* Run a self-diagnostic to verify everything works + +=== Option 2: Container (via Stapeln) + +[source,bash] +---- +just stapeln-run +---- + +=== Option 3: Portable (no system changes) + +[source,bash] +---- +just install --portable --prefix=./czech-file-knife-portable +---- + +== First Run + +[source,bash] +---- +just run +---- + +Expected output: + +[source] +---- +Rsr Template Repo started successfully. +---- + +== Self-Diagnostic + +If something isn't working: + +[source,bash] +---- +just doctor +---- + +This checks all dependencies, permissions, paths, and connectivity. +If it finds issues, it will suggest fixes. + +To attempt automatic repair: + +[source,bash] +---- +just heal +---- + +== Get Help + +* **In-app**: `just run --help` +* **Guided tour**: `just tour` +* **Report a problem**: `just help-me` (pre-fills diagnostic context) +* **Wiki**: https://github.com/hyperpolymath/czech-file-knife/wiki + +== Uninstall + +[source,bash] +---- +just uninstall +---- + +You will be asked: + +1. Which uninstall tier (Bennett reversible, parameter-based, standard, or secure) +2. Whether to include or exclude your data +3. Whether to clear caches and LLM models + +== Next Steps + +* Read the link:README.adoc[README] for full feature overview +* Read the link:EXPLAINME.adoc[EXPLAINME] for architecture and design decisions +* Try `just tour` for a guided walkthrough diff --git a/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc b/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc new file mode 100644 index 000000000..00dc863b3 --- /dev/null +++ b/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += LLM Warmup — czech-file-knife (Developer) + +== What is czech-file-knife? + +See `README.adoc` for overview. + +== Key Commands + +* `just setup` — set up development environment +* `just build` — build the project +* `just test` — run tests +* `just doctor` — diagnose issues +* `just heal` — attempt auto-repair + +== Quick Context + +* License: MPL-2.0 +* Part of hyperpolymath ecosystem +* See `EXPLAINME.adoc` for architecture diff --git a/czech-file-knife/docs/onboarding/llm-warmup-user.adoc b/czech-file-knife/docs/onboarding/llm-warmup-user.adoc new file mode 100644 index 000000000..1c2d69661 --- /dev/null +++ b/czech-file-knife/docs/onboarding/llm-warmup-user.adoc @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += LLM Warmup — czech-file-knife (User) + +== What is czech-file-knife? + +See `README.adoc` for overview. + +== Key Commands + +* `just setup` — set up development environment +* `just build` — build the project +* `just test` — run tests +* `just doctor` — diagnose issues +* `just heal` — attempt auto-repair + +== Quick Context + +* License: MPL-2.0 +* Part of hyperpolymath ecosystem +* See `EXPLAINME.adoc` for architecture diff --git a/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc b/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc new file mode 100644 index 000000000..467e0abee --- /dev/null +++ b/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += AI Conventions +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) + +== AI Conventions (Authoritative Source) + +All AI coding agents working in this repository MUST follow these rules. +Per-tool config files (.cursorrules, .clinerules, etc.) reference this document. + +=== Session Startup + +1. Read the repo deed (`*_chora.deed` at root) FIRST (mandatory gatekeeper). +2. Read `.machine_readable/descriptiles/STATE.a2ml` for current status and blockers. +3. Read `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` for canonical authority boundaries. +4. Read `.machine_readable/policies/MAINTENANCE-AXES.a2ml` for maintenance/audit sequencing. +5. Read `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` for baseline controls. +6. Read `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` for execution order. +7. Read `.machine_readable/descriptiles/AGENTIC.a2ml` for agent constraints. + +=== License + +Per estate policy (hyperpolymath/standards `LICENCE-POLICY.adoc` Rule 1 + Addendum A8): + +- Code / config / scripts: `SPDX-License-Identifier: MPL-2.0`. +- Prose docs (`*.adoc`, `*.md` narrative): `SPDX-License-Identifier: CC-BY-SA-4.0`. +- GitHub shows only one repo licence: the root `LICENSE` is MPL-2.0 (so the + sidebar reads MPL-2.0); **both** canonical texts live in `LICENSES/` + (`MPL-2.0.txt` + `CC-BY-SA-4.0.txt`) and prose is CC-BY-SA-4.0 by its + per-file header, not a second root licence file. +- Fallback (platform-required only): MPL-2.0 with a comment explaining why. +- NEVER use AGPL-3.0 (son-shared repos are the only exception, per Rule 3 — not this template). +- Preserve third-party licenses verbatim; never relicense. +- Licence-header remediation on existing files is manual, owner-only — no + automated/bulk SPDX edits (Addendum A2). New files may carry the correct + SPDX from birth. + +=== Author Attribution + +- Name: **Jonathan D.A. Jewell** +- Email: **j.d.a.jewell@open.ac.uk** +- Copyright: `Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) ` + +=== State Files + +State/metadata files, anchors, and policies (.a2ml) belong in `.machine_readable/` ONLY. +NEVER create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, +NEUROSYM.a2ml, PLAYBOOK.a2ml, ANCHOR.a2ml, MAINTENANCE-AXES.a2ml, +MAINTENANCE-CHECKLIST.a2ml, or SOFTWARE-DEVELOPMENT-APPROACH.a2ml in the repository root. + +=== Banned Patterns + +|=== +| Language | Banned | Reason + +| Idris2 | `believe_me`, `assert_total` | Unsound escape hatches +| Haskell | `unsafeCoerce`, `unsafePerformIO` | Breaks type safety +| OCaml | `Obj.magic`, `Obj.repr`, `Obj.obj` | Unsafe casting +| Coq | `Admitted` | Unproven assumption +| Lean | `sorry` | Unproven assumption +| Rust | `transmute` (unless FFI + SAFETY:) | Unsound reinterpret +|=== + + +=== Banned Languages + +|=== +| Banned | Use Instead + +| | +| Node.js / npm / bun | +| Go | Rust +| Python | Julia / Rust +|=== + + +=== Container Standard + +- Runtime: **Podman** (never Docker). +- File: **Containerfile** (never Dockerfile). +- Base images: `cgr.dev/chainguard/wolfi-base:latest` or `cgr.dev/chainguard/static:latest`. + +=== ABI/FFI Standard + +- ABI definitions: **Idris2** with dependent types (`src/interface/Abi/`). +- FFI implementation: **Zig** with C ABI compatibility (`ffi/zig/`). +- Generated C headers: `generated/abi/`. + +=== Build System + +Use `just` (Justfile) for all build, test, lint, and format tasks. + +=== References + +- `*_chora.deed` (repo deed) -- universal AI entry point +- `.machine_readable/descriptiles/AGENTIC.a2ml` -- agent permissions and constraints +- `.machine_readable/descriptiles/STATE.a2ml` -- current project state +- `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` -- canonical authority and policy boundary +- `.machine_readable/policies/MAINTENANCE-AXES.a2ml` -- canonical axis sequencing and audit requirements +- `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` -- baseline maintenance checklist policy +- `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -- axis execution approach policy diff --git a/czech-file-knife/docs/practice/README.adoc b/czech-file-knife/docs/practice/README.adoc new file mode 100644 index 000000000..117fa892f --- /dev/null +++ b/czech-file-knife/docs/practice/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += practice Unit diff --git a/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc b/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc new file mode 100644 index 000000000..02bff1b67 --- /dev/null +++ b/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += TOPOLOGY.md — Generation Guide +Jonathan D.A. Jewell (hyperpolymath) +:toc: +:sectnums: + +== What Is TOPOLOGY.md? + +A single-file visual map of any project's architecture and completion status. +It lives in the repo root and contains: + +1. **ASCII architecture diagram** — the full system as it will look when complete +2. **Completion dashboard** — every component with a progress bar and status note +3. **Dependency graph** — what blocks what (the critical path) +4. **Update protocol** — how to keep it current + +It is designed to be readable by humans, AI agents, and rendered cleanly on any +forge (GitHub, GitLab, Codeberg, Bitbucket). + +== Why + +- Gives any contributor (human or AI) an instant picture of the whole project +- Replaces "read 20 files to understand the architecture" with one glance +- The completion dashboard makes project health visible without running anything +- Works offline, no tooling required, just a text file + +== How To Generate One + +=== Option 1: Ask an AI agent + +Use this prompt (works with Claude, Gemini, ChatGPT, or any LLM with repo access): + +[source,text] +---- +Read the entire repository and produce a TOPOLOGY.md file for the repo root. + +The file must contain exactly three sections: + +1. **System Architecture** — An ASCII box diagram showing the complete system + as it will look when finished. Use Unicode box-drawing characters + (┌ ┐ └ ┘ │ ─ ├ ┤ ┬ ┴ ┼), arrows (▲ ▼ ◄ ► → ←), and double lines + (═ ║) for boundaries. Show: + - All external services (DNS, CDN, gateways) at the top + - Application components in the middle + - Data layer (databases, caches, queues) below + - Repo infrastructure (CI, contractiles, SCM files) at the bottom + - Every box labelled, every connection labelled or obvious from context + - The diagram should be BESPOKE to this project, not generic + +2. **Completion Dashboard** — A table in a code block listing every component + from the diagram. For each component show: + - Name (left-aligned, padded to 35 chars) + - Progress bar: 10 characters using █ (done) and ░ (remaining) + - Percentage (0% to 100% in 10% increments) + - A short note explaining the status + Group components by layer/concern. End with an OVERALL summary line. + +3. **Key Dependencies** — An ASCII arrow diagram showing the critical path. + What must finish before what else can start. + +Add a header comment with SPDX-License-Identifier and Last updated date. +End with an "Update Protocol" section explaining how to maintain the file. + +Use the template at TOPOLOGY.md in czech-file-knife as a structural reference, +but make the content completely specific to THIS project. +---- + +=== Option 2: Copy the template and fill it in + +[source,bash] +---- +cp /path/to/czech-file-knife/TOPOLOGY.md ./TOPOLOGY.md +# Then edit: replace placeholders, draw the real architecture, fill the dashboard +---- + +=== Option 3: Batch generation across all repos + +[source,bash] +---- +# From the repos root, generate for every repo that lacks one +for repo in /path/to/your/repos/*/; do + if [ ! -f "$repo/TOPOLOGY.md" ]; then + echo "NEEDS TOPOLOGY: $(basename $repo)" + fi +done +---- + +Then feed each repo to an AI agent with the prompt above. Claude Code can do +this with a session per repo, or you can batch it. + +== Conventions + +=== Box-drawing characters + +Use Unicode, not ASCII art. This renders correctly everywhere. + +[cols="1,1", options="header"] +|=== +| Character | Use +| `┌ ┐ └ ┘` | Box corners +| `│ ─` | Vertical / horizontal lines +| `├ ┤ ┬ ┴ ┼` | T-junctions and crosses +| `═ ║` | Double lines for major boundaries +| `▲ ▼ ◄ ►` | Directional arrows +| `→ ← ↑ ↓` | Thin arrows (alternative) +|=== + +=== Progress bars + +Always 10 characters wide. Use full blocks only (no half-blocks). + +[source,text] +---- +░░░░░░░░░░ 0% Not started +█░░░░░░░░░ 10% Stub/skeleton exists +██░░░░░░░░ 20% Early work +███░░░░░░░ 30% Foundation laid +████░░░░░░ 40% Core logic started +█████░░░░░ 50% Half done +██████░░░░ 60% Most logic complete +███████░░░ 70% Working but rough +████████░░ 80% Needs polish/docs +█████████░ 90% Nearly done +██████████ 100% Complete and tested +---- + +=== Component naming + +- Use the actual names from the codebase (file names, service names, tool names) +- Group by architectural layer, not alphabetically +- Include repo infrastructure (CI, contractiles, SCM files) as a layer + +=== When to update + +- After completing a component → change bar + percentage +- After adding a component → add row +- After architectural change → redraw diagram +- After major milestone → update overall percentage +- Always update the `Last updated` date + +== Integration With Other RSR Files + +TOPOLOGY.md complements but does not replace: + +- **STATE.a2ml** — machine-readable state (tasks, blockers, next actions) +- **ECOSYSTEM.a2ml** — position in the wider project ecosystem +- **META.a2ml** — architecture decisions and design rationale +- ***_chora.deed (repo deed)** — AI agent entry point: allocation policy and directory invariants + +TOPOLOGY.md is the _visual summary_ for humans; the a2ml files are the +_structured data_ for tooling. Both should agree. + +== Copyright + +Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) diff --git a/czech-file-knife/docs/practice/ci-cost-reduction.adoc b/czech-file-knife/docs/practice/ci-cost-reduction.adoc new file mode 100644 index 000000000..99a2a6239 --- /dev/null +++ b/czech-file-knife/docs/practice/ci-cost-reduction.adoc @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += CI Cost Reduction — RSR Estate Spec +:toc: left +:toclevels: 3 + +Estate-wide playbook for reducing GitHub Actions minutes consumption +without sacrificing CI coverage. Born out of an incident where the +`hyperpolymath` Actions billing tripped and every runner-based workflow +in the estate went dark. The blocker was external, but the exposure — +how much we spend — was self-inflicted. This document captures the +knobs that exist, which ones are worth pulling, and in what order. + +Priority sort applied: *dependability > security > interop > usability +> performance > versatility > functional extension*. Cost reduction is +a dependability/performance concern; nothing here trades security for +savings. + +== Why this is worth doing + +A representative RSR repo (007) runs ~26 active workflows. On a single +push: + +* ~10-15 workflows fire in parallel. +* Several (scorecard, codeql, hypatia-scan) also fire on a schedule. +* `oracle-fuzz.yml` runs a 10-minute differential job every hour. +* Multiple workflows do `fetch-depth: 0` full-history clones. +* There are meaningful overlaps (trufflehog + gitleaks, multiple + security-gate jobs, codeql + hypatia-scan). + +Conservative saving potential from the five highest-leverage patterns +below: *60–80%* of current Actions-minutes, with no coverage loss. + +== Priority-ordered patterns + +=== 1. Concurrency kills (one-line, estate-wide) + +Every push-triggered workflow should cancel its superseded runs when +a rapid-fire commit lands: + +[source,yaml] +---- +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +---- + +Add this at the workflow top level. Exceptions: release workflows, +deploy workflows, scheduled workflows — those need to finish. + +*Impact:* cancels obsolete runs immediately. On active branches with +many commits, easily 20–40% of current minutes. + +=== 2. Path filters on heavy workflows + +Rust CI, CodeQL, E2E, and language-specific test suites should not run +on docs-only or comment-only changes. + +[source,yaml] +---- +on: + push: + paths: + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust-ci.yml' + pull_request: + paths: + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' +---- + +For workflows whose scope is "the whole repo" (scorecard, codeql, +hypatia-scan): leave unfiltered. For language-specific jobs: filter. + +*Impact:* single largest saving on repos that get a lot of README or +issue-template churn. + +=== 3. Reduce schedule frequency + +Scheduled workflows should run as often as the decision they inform +actually changes — not more. + +[cols="2,1,1,2",options="header"] +|=== +| Workflow | Current | Recommended | Rationale + +| `oracle-fuzz.yml` (differential fuzz) +| Every 10 min +| Every 6h or nightly +| TRG §5.7.4 90-day differential-fuzzing clock measures *total time*, + not *frequency*. A nightly 10-min slice gives the same statistical + coverage at 1/144 the cost. + +| `scorecard.yml` +| Daily +| Weekly +| OSSF's own recommendation is weekly for stable repos. + +| `codeql.yml` (schedule branch) +| Daily +| Weekly, or remove schedule (relies on push trigger) +| CodeQL on every push already covers PR and main commits. A weekly + sweep catches newly-disclosed CVEs that affect existing code. + +| `hypatia-scan.yml` +| Weekly +| Keep weekly +| Correct cadence. Don't change. + +| `parser-fuzz.yml` (if present) +| Nightly, 5 min per target × 3 targets +| Keep +| Already minimal. +|=== + +=== 4. Overlap consolidation + +Several workflows cover overlapping ground. Consolidate where the +substitution is near-free: + +[cols="2,2,2",options="header"] +|=== +| Overlap | Resolution | Notes + +| `trufflehog` + `gitleaks` in `secret-scanner.yml` +| Keep one (gitleaks preferred for CVE coverage; trufflehog for + verified-only mode) +| Running both is belt-and-braces with ~90% coverage overlap. One is + enough for prevention; both only for quarterly history-sweeps. + +| `rsr-antipattern.yml` + `scorecard-enforcer.yml` + + `static-analysis-gate.yml` +| Merge into a single composite `rsr-gate.yml` with three steps +| They already run in the same CI slot; merging deduplicates + setup/checkout/clone. + +| `codeql.yml` + `hypatia-scan.yml` +| Keep both, but drop Hypatia from on-push; let it run only on + schedule +| Hypatia is the slower of the two, and its neurosymbolic analysis + doesn't need sub-minute latency on every commit. + +| `codeql.yml` (on-push) + `codeql.yml` (scheduled) +| Keep on-push, drop scheduled +| Redundant unless the repo rarely gets commits. +|=== + +=== 5. Shallow clones where full-history isn't needed + +Full-history clones (`fetch-depth: 0`) are expensive on large repos. +Required for: + +* Scorecard (history-based metrics). +* Gitleaks history-sweep mode. +* TruffleHog history-sweep mode. + +Not required for: + +* PR-event secret scanning (limit to the PR delta). +* Rust CI, language-specific tests. +* CodeQL (shallow is fine). +* Hypatia-scan (shallow is fine for pattern detection). + +Pattern for secret-scanner: + +[source,yaml] +---- +on: + pull_request: # shallow clone, just the diff + push: + branches: [main] + schedule: + - cron: '0 4 * * 1' # weekly history sweep + +jobs: + pr-scan: + if: github.event_name == 'pull_request' + # shallow — don't need history for delta scanning + steps: + - uses: actions/checkout@... + full-scan: + if: github.event_name != 'pull_request' + steps: + - uses: actions/checkout@... + with: + fetch-depth: 0 # only for push-to-main + scheduled +---- + +=== 6. Job-level timeouts + +Cap every job so a hung runner doesn't burn the budget: + +[source,yaml] +---- +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 # fail-fast instead of 360 default +---- + +Recommendations: + +* Setup / lint / format jobs: 5 min. +* Build jobs: 15–20 min. +* Test jobs: 20–30 min. +* E2E / integration: 45 min max. +* Fuzz: exact target time + 2 min tolerance. + +=== 7. Reusable workflow (longer-term) + +Ship the above patterns in a single reusable workflow under +`czech-file-knife/.github/workflows/rsr-ci-defaults.yml`, and adopt +it estate-wide via `uses: hyperpolymath/czech-file-knife/.github/workflows/rsr-ci-defaults.yml@main` +in downstream repos. Single PR propagates improvements. + +=== 8. Self-hosted runner for heavy work (long-term) + +Oracle-fuzz, E2E+Conformance+Bench, Hypatia-scan: these are the +expensive jobs. Moving them to a self-hosted runner on the Eclipse +host gets the cost to zero ongoing. Setup: ~1 day. Security: run in +a rootless Podman container with the `ubuntu-22.04` runner image. +Not urgent; track as future work. + +=== 9. Dependabot noise + +`.github/dependabot.yml` in this template uses +`open-pull-requests-limit: 0` on cargo to suppress routine patch PRs +while keeping security PRs flowing. That's the correct pattern — do +NOT revert to the previous `ignore: "*" patch` rule, which also +silences security PRs (see +007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md). + +Paired with `.github/workflows/dependabot-automerge.yml`, security +PRs for low/moderate patches+minors auto-merge after CI, leaving +humans to review only HIGH+CRITICAL security updates and all +non-security bumps. + +== Rollout plan + +. *Week 1:* Apply patterns 1–2 (concurrency + path filters) to every + active RSR repo. Reusable template update; downstream propagation + is a find-and-replace pass. +. *Week 2:* Apply patterns 3–4 (schedule frequency + overlap + consolidation). Audit one repo at a time; check ~30 days of runs + before concluding an overlap is safe to drop. +. *Week 3:* Apply patterns 5–6 (shallow clone + timeouts). Low-risk. +. *Week 4+:* Pattern 7 (reusable workflow) — single PR, big payoff. +. *When scope allows:* Pattern 8 (self-hosted runner). + +== Measurement + +Before/after: `gh api /users//settings/billing/actions` shows +current minutes usage. Diff after each rollout wave. Target a 60% +reduction by end of week 4 on the three highest-consumption repos +(boj-server, hypatia, 007 — based on workflow count × schedule +frequency × job count). + +== Out of scope + +* Reducing CI coverage (not on the table). +* Disabling security workflows to save minutes. +* Skipping CI on "trivial" commits via commit-message tags + (gameable, easy to abuse). +* Switching to a paid Actions tier before exhausting these patterns. + +== Cross-references + +* `007-lang/audits/audit-rsr-workflows-2026-04-17.md` — billing + incident that motivated this spec. +* `007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md` — + separate defect stack also addressed in the same session. +* `czech-file-knife/.github/workflows/dependabot-automerge.yml` — + canonical pattern for auto-merge pattern referenced above. diff --git a/czech-file-knife/docs/proposals/root-cleanup.adoc b/czech-file-knife/docs/proposals/root-cleanup.adoc new file mode 100644 index 000000000..84548e343 --- /dev/null +++ b/czech-file-knife/docs/proposals/root-cleanup.adoc @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Root Cleanup Proposal — Reconcile Template With Its Own Audit +:toc: +:toclevels: 3 +:sectnums: + +== Motivation + +`TEMPLATE-STANDARDS-AUDIT.adoc` ships a "Proposed Final Directory Map" for the +template root. The tracked root violates that map by roughly 5x: + +[cols="1,1,1",options="header"] +|=== +| Bucket | Audit-mandated count | Actual count + +| Root files (visible) +| 6 +| 36 + +| Root directories (visible) +| 3 +| 17 +|=== + +Downstream repositories (e.g. `the-nash-equilibrium`) inherit the violation +verbatim. This proposal describes the relocations that bring the template back +into compliance with its own map, and adds an automated guard +(`scripts/check-root-shape.sh` against `.machine_readable/root-allow.txt`) so +the violation cannot silently return. + +NOTE: This is a template-side change. Downstream repos pick it up via a +one-shot relocation PR per project once the template lands. + +== Allowlist (canonical root) + +The full enumeration lives in `.machine_readable/root-allow.txt`. Summary: + +* *Authority files (root):* `README.adoc`, `AUDIT.adoc`, `EXPLAINME.adoc`, + `0-AI-MANIFEST.a2ml` (thin pointer), `LICENSE`, `CHANGELOG.md`. +* *Build entry points (root):* `Justfile`, `coordination.k9`. +* *Tool-required dotfiles (root):* `.editorconfig`, `.envrc`, `.gitattributes`, + `.gitignore`, `.tool-versions`. +* *Directories (root):* `.git/`, `.github/`, `.machine_readable/`, + `.well-known/`, `build/`, `ci/`, `docs/`, `session/`, plus the conventional + source/test trees (`src/`, `tests/`, `benches/`, `examples/`, `features/`, + `scripts/`, `verification/`, `build/container/`). + +== Relocation plan + +Each line is a `git mv` (or delete) plus the references that need updating. +Run from the template repo root. + +=== Onboarding prose → `docs/onboarding/` + +[source,bash] +---- +mkdir -p docs/onboarding +git mv QUICKSTART-DEV.adoc docs/onboarding/ +git mv QUICKSTART-USER.adoc docs/onboarding/ +git mv QUICKSTART-MAINTAINER.adoc docs/onboarding/ +git mv llm-warmup-dev.md docs/onboarding/ +git mv llm-warmup-user.md docs/onboarding/ +---- + +References to update: + +* `README.adoc` — any `link:QUICKSTART-*.adoc[…]`. +* `Justfile` — any `cat QUICKSTART-*` echoes in `init`/`help`. + +=== Status/roadmap docs → `docs/status/` + +[source,bash] +---- +mkdir -p docs/status docs/architecture +git mv READINESS.md docs/status/ +git mv ROADMAP.adoc docs/status/ +git mv TEST-NEEDS.md docs/status/ +git mv PROOF-NEEDS.md docs/status/ +git mv PROOF-STATUS.md docs/status/ +git mv TOPOLOGY.md docs/architecture/ # validate-template.sh already accepts this path +---- + +References to update: + +* `README.adoc` — `link:ROADMAP.adoc[…]` and similar. +* `Justfile` — `readiness:` recipe (currently reads `READINESS.md`). + +=== Health files → `.github/` + +GitHub auto-discovers these under `.github/`, so the move is transparent to +contributors and tools. + +[source,bash] +---- +git mv CONTRIBUTING.md .github/ +git mv CODE_OF_CONDUCT.md .github/ +git mv SECURITY.md .github/ +---- + +=== Build orchestration → `build/` + +`Justfile` stays at root (just convention) but becomes thin: it imports +phase-specific just files from `build/`. + +[source,bash] +---- +mkdir -p build +git mv contractile.just build/ +git mv setup.sh build/ +git mv guix.scm build/ +git mv .guix-channel build/ +# Containerfile may already live in build/container/ — keep whichever the project uses. +[ -f Containerfile ] && git mv Containerfile build/ +---- + +References to update in `Justfile`: + +[source,diff] +---- +- import? "contractile.just" ++ import? "build/contractile.just" +---- + +The current Justfile already supports `build/container/Containerfile` *or* root +`Containerfile` — extend that to also accept `build/Containerfile`. + +The 62 KB monolithic `Justfile` is a separate smell. A follow-up should split +it under `build/just/{init,verify,test,docs,container,security}.just` with +`import?` lines from the thin root file. + +=== CI configs → `ci/` + +Most CI tools accept a custom config path; where they don't, a one-line root +shim file is acceptable. + +[source,bash] +---- +mkdir -p ci +git mv .gitlab-ci.yml ci/ +git mv .pre-commit-config.yaml ci/ +---- + +Updates required: + +* GitLab CI: project setting "CI/CD configuration file" → `ci/.gitlab-ci.yml`. +* pre-commit: invoke as `pre-commit run --config ci/.pre-commit-config.yaml`, + or leave a one-line root shim. + +=== Custom-format configs → `.machine_readable/configs/` + +[source,bash] +---- +git mv eclexiaiser.toml .machine_readable/configs/ +git mv selur-compose.toml .machine_readable/configs/ +git mv stapeln.toml .machine_readable/configs/ +---- + +References to update wherever any tool reads them (grep for the filenames +across `Justfile`, `scripts/`, `.machine_readable/`). + +=== AI manifest deduplication + +`.machine_readable/0.1-AI-MANIFEST.a2ml` is the canonical AI manifest in both +the template and downstream repos (e.g. `the-nash-equilibrium` README line +244). The root `0-AI-MANIFEST.a2ml` becomes a thin pointer: + +[source,a2ml] +---- +# 0-AI-MANIFEST.a2ml — pointer file +authority = ".machine_readable/0.1-AI-MANIFEST.a2ml" +note = "AI agents MUST read the canonical manifest at the path above." +---- + +Once the canonical version is stable, decide whether to keep this pointer at +root or delete it entirely. + +=== Template-only relocations + +A dry-run of `check-root-shape.sh` against the unmodified template flagged +three template-only extras that aren't drift in downstream repos but should +move regardless: + +[source,bash] +---- +# The audit doc itself is drift — it doesn't apply at root. +git mv TEMPLATE-STANDARDS-AUDIT.adoc docs/governance/ + +# `tools/` and `scripts/` overlap; pick one (proposal: keep `scripts/`). +# Inspect tools/ contents and either merge into scripts/ or rename and document +# the split (e.g. `scripts/` = repo-local helpers, `tools/` = bundled binaries). +---- + +=== Hygiene: case collisions in `affinescript/stdlib/` + +Cloning the template on a case-insensitive filesystem (Windows, default macOS) +silently drops files because of pairs like `Math.affine` vs `math.affine`. + +Pick one canonical case per name and `git mv` the duplicates away. Keeping +both is not portable. + +== Justfile recipe + +[source,just] +---- +# Verify root layout against the canonical allowlist. +check-root-shape: + ./scripts/check-root-shape.sh + +# Add to the existing aggregate `verify` target. +verify: ... check-root-shape ... +---- + +A pre-commit hook (in `ci/.pre-commit-config.yaml`) and a CI job +(`ci/.gitlab-ci.yml`) should both call `just check-root-shape`. + +== Downstream rollout + +For each downstream repo (start with `the-nash-equilibrium`): + +1. Apply the same `git mv` set (skip moves whose source doesn't exist locally). +2. Copy the new `.machine_readable/root-allow.txt` and adapt comments/extras. +3. Update internal links (README, docs, Justfile recipes). +4. Run `just check-root-shape` and `scripts/validate-template.sh` to confirm + shape parity with the template. + +== Out of scope (mentioned for follow-up) + +* Splitting the 62 KB monolithic `Justfile`. +* Migrating from GitLab CI to GitHub Actions parity (or vice versa). +* Reworking the `0.1-` / `0.2-` manifest versioning convention. diff --git a/czech-file-knife/docs/standards/README.adoc b/czech-file-knife/docs/standards/README.adoc new file mode 100644 index 000000000..b31c112c7 --- /dev/null +++ b/czech-file-knife/docs/standards/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Standards Unit diff --git a/czech-file-knife/docs/status/PROOF-NEEDS.adoc b/czech-file-knife/docs/status/PROOF-NEEDS.adoc new file mode 100644 index 000000000..9a0e7ee12 --- /dev/null +++ b/czech-file-knife/docs/status/PROOF-NEEDS.adoc @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: rsr-template-repo/docs/status/PROOF-NEEDS.adoc +// Authoritative master list: ~/Desktop/PROOF-REQUIREMENTS-MASTER.adoc += Proof Requirements — CZECH_FILE_KNIFE + +== Proof Tier + +// Assign one: T1 (Critical), T2 (High), T3 (Standard), T4 (Light), T5 (Exempt) +*Tier*: T3 — Standard + +== Proof Categories + +[cols="1,3,1", options="header"] +|=== +| Code | Meaning | Applies? + +| *TP* | Typing Proofs (type soundness, type safety) | Yes +| *INV* | Invariant Proofs (state machines, monotonicity, bounds) | +| *SEC* | Security Proofs (crypto, injection freedom, access control) | +| *CONC* | Concurrency Proofs (linearizability, deadlock freedom) | +| *ALG* | Algorithm Proofs (termination, correctness, bounds) | +| *ABI* | ABI/FFI Proofs (memory layout, pointer safety, platform compat) | Yes +| *DOM* | Domain-Specific Proofs (bespoke to this project) | +|=== + +== Mandatory Proofs (All RSR Repos) + +These proofs come from the czech-file-knife and MUST be present in every repo: + +=== ABI/FFI Boundary Proofs (Idris2) + +[cols="1,3,1,3", options="header"] +|=== +| # | Proof | Status | File + +| ABI-1 | Non-null pointer proofs (`So (ptr /= 0)`) | Needed | `verification/proofs/idris2/ABI/Pointers.idr` +| ABI-2 | Memory layout correctness (`HasSize`, `HasAlignment`) | Needed | `verification/proofs/idris2/ABI/Layout.idr` +| ABI-3 | Platform type size proofs (per platform) | Needed | `verification/proofs/idris2/ABI/Platform.idr` +| ABI-4 | FFI function return type proofs | Needed | `verification/proofs/idris2/ABI/Foreign.idr` +| ABI-5 | C ABI compliance (`CABICompliant`, `FieldsAligned`) | Needed | `verification/proofs/idris2/ABI/Compliance.idr` +|=== + +=== Typing Proofs (Prover Varies) + +[cols="1,3,1,3", options="header"] +|=== +| # | Proof | Status | File + +| TP-1 | Core data type well-formedness | Needed | `verification/proofs/idris2/Types.idr` +| TP-2 | Public API type safety (exported functions) | Needed | `verification/proofs/lean4/ApiTypes.lean` +|=== + +== Project-Specific Proofs + +[cols="1,3,1,1,1,2", options="header"] +|=== +| # | Proof Needed | Category | Prover | Priority | File(s) + +| P1 | Journal reversibility: for every recorded op `o` on state `s`, `undo(o(s)) = s` (content-level; metadata out of scope until implemented) | Invariant | Lean 4 / Coq (shared with januskey) | High | `src/cfk-core/src/reversible.rs` +| P2 | `op ; undo` is a Certified Null Operation (absolute-zero CNO) | Invariant | Coq / Lean 4 (absolute-zero) | High | `src/cfk-core/src/reversible.rs` +| P3 | Latest-only undo never clobbers a later write (ordering safety) | Invariant | TLA+ | Medium | `src/cfk-core/src/reversible.rs` +| P4 | `cfk squeeze` frame-then-punch: every input byte is at all times present in the input or in a durable output frame (crash-safe at every step) | Invariant | TLA+ | High | `docs/architecture/HYBRID-OPERATIONS.adoc` (planned) +| P5 | Content store integrity: `get(put(x)) = x` and a mismatched object is rejected | Typing | Idris2 | Medium | `src/cfk-core/src/reversible.rs` +|=== + +== Dangerous Patterns (BANNED) + +The following MUST NOT appear anywhere in proof files: + +[cols="2,2,3", options="header"] +|=== +| Pattern | Language | Meaning + +| `believe_me` | Idris2 | Unsafe cast / trust-me +| `assert_total` | Idris2 | Skip totality check +| `postulate` | Idris2/Agda | Unproven axiom +| `sorry` | Lean4 | Incomplete proof +| `Admitted` | Coq | Incomplete proof +| `unsafeCoerce` | Haskell | Unsafe type cast +| `Obj.magic` | OCaml/ | Unsafe type cast +| `unsafe` (unaudited) | Rust | Unsafe block without safety comment +|=== + +CI will reject any PR introducing these patterns (enforced by `panic-attack assail`). + +== Prover Selection Guide + +[cols="2,2,3", options="header"] +|=== +| Use Case | Recommended Prover | Why + +| ABI/FFI boundaries | *Idris2* | Dependent types model layouts precisely +| Type system proofs | *Coq* or *Lean4* | Mature proof assistants for metatheory +| Algebraic properties | *Lean4* | Good mathlib support +| Inductive/coinductive | *Agda* | Native support for (co)induction +| Distributed systems | *TLA+* | Model checking for protocols +| Numerical properties | *Isabelle* | Strong real analysis library +|=== + +== Proof File Locations + +---- +verification/proofs/ +├── idris2/ # Idris2 proofs (ABI, dependent types) +│ ├── ABI/ # ABI-specific proofs +│ └── *.idr # Project-specific Idris2 proofs +├── lean4/ # Lean4 proofs (algebra, lattices) +│ └── *.lean +├── agda/ # Agda proofs (induction, metatheory) +│ └── *.agda +├── coq/ # Coq proofs (type systems, compilation) +│ └── *.v +└── tlaplus/ # TLA+ specs (distributed protocols) + └── *.tla +---- + +== References + +* Master list: `~/Desktop/PROOF-REQUIREMENTS-MASTER.adoc` +* Proof status tracking: `PROOF-STATUS.adoc` (this repo) +* Proven library: `proven` repo (Idris2 verified foundations) +* Template: `rsr-template-repo/docs/status/PROOF-NEEDS.adoc` diff --git a/czech-file-knife/docs/status/PROOF-STATUS.adoc b/czech-file-knife/docs/status/PROOF-STATUS.adoc new file mode 100644 index 000000000..47c857af9 --- /dev/null +++ b/czech-file-knife/docs/status/PROOF-STATUS.adoc @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: rsr-template-repo/docs/status/PROOF-STATUS.adoc +// Tracks proof completion. Requirements defined in PROOF-NEEDS.adoc += Proof Status — CZECH_FILE_KNIFE + +== Summary + +[cols="2,1,1,1,1,1", options="header"] +|=== +| Category | Total | Done | In Progress | Blocked | Remaining + +| ABI/FFI (ABI) | 5 | 0 | 0 | 0 | 5 +| Typing (TP) | 2 | 0 | 0 | 0 | 2 +| Invariant (INV) | 0 | 0 | 0 | 0 | 0 +| Security (SEC) | 0 | 0 | 0 | 0 | 0 +| Concurrency (CONC) | 0 | 0 | 0 | 0 | 0 +| Algorithm (ALG) | 0 | 0 | 0 | 0 | 0 +| Domain (DOM) | 0 | 0 | 0 | 0 | 0 +| *Total* | *7* | *0* | *0* | *0* | *7* +|=== + +*Overall*: 0% proven + +== Proofs Done + +// Format: +// | ID | Proof | Prover | File | Date | Verified By | +// | ABI-1 | Non-null pointer proofs | Idris2 | verification/proofs/idris2/ABI/Pointers.idr | 2026-XX-XX | idris2 --check | + +[cols="1,3,1,3,1,2", options="header"] +|=== +| ID | Proof | Prover | File | Date | Verified By + +| — | No proofs completed yet | — | — | — | — +|=== + +== Proofs In Progress + +[cols="1,3,1,2,1,2", options="header"] +|=== +| ID | Proof | Prover | Assignee | Started | Blocker + +| — | — | — | — | — | — +|=== + +== Proofs Blocked + +[cols="1,3,2,3", options="header"] +|=== +| ID | Proof | Blocked By | Notes + +| — | — | — | — +|=== + +== Proofs Remaining + +[cols="1,3,1,1,1,1", options="header"] +|=== +| ID | Proof | Category | Prover | Priority | Est. Effort + +| ABI-1 | Non-null pointer proofs | ABI | Idris2 | P1 | 2h +| ABI-2 | Memory layout correctness | ABI | Idris2 | P1 | 4h +| ABI-3 | Platform type size proofs | ABI | Idris2 | P1 | 2h +| ABI-4 | FFI function return type proofs | ABI | Idris2 | P1 | 2h +| ABI-5 | C ABI compliance | ABI | Idris2 | P1 | 4h +| TP-1 | Core data type well-formedness | TP | Idris2 | P1 | 4h +| TP-2 | Public API type safety | TP | Lean4 | P2 | 4h +|=== + +== Verification Commands + +[source,bash] +---- +# Check all Idris2 proofs +just proof-check-idris2 + +# Check all Lean4 proofs +just proof-check-lean4 + +# Check all Agda proofs +just proof-check-agda + +# Check all Coq proofs +just proof-check-coq + +# Run all proof checks +just proof-check-all + +# Scan for dangerous patterns +panic-attack assail --proofs-only +---- + +== Changelog + +[cols="1,3,1", options="header"] +|=== +| Date | Change | By + +| 2026-04-04 | Initial proof status tracking | Template +|=== diff --git a/czech-file-knife/docs/status/READINESS.adoc b/czech-file-knife/docs/status/READINESS.adoc new file mode 100644 index 000000000..bb706ce16 --- /dev/null +++ b/czech-file-knife/docs/status/READINESS.adoc @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Czech File Knife Component Readiness Assessment + +*Standard:* link:https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades[Component Readiness Grades (CRG) v2.0 STRICT] + +*Assessed:* 2026-09-28 + +*Assessor:* Jonathan D.A. Jewell + +*Previous assessment:* __ + +*Current Grade:* X + +This line is parsed by `just crg-grade` / `just crg-badge`. The grade above is +the worst-graded in-scope component — *the project's weakest link sets its +grade*. See the per-component table in §3. + +[NOTE] +==== +*Honest grading.* Per CRG v2.0 Principle 4: grade as-is today, not +aspirationally. Long alpha is discipline, not shame. Demote immediately if +evidence doesn't support the claim. +==== + +''' + +== 1. CRG v2.0 Grade Reference + +[cols="1,2,2,3,2", options="header"] +|=== +| Grade | Name | Release Stage | Stability Posture | Shorthand + +| X | Untested | — | — | — +| F | Harmful / Wasteful | — | — | reject/delegate +| E | Minimal / Salvageable | Pre-alpha | Unstable | `pre-alpha` +| D | Partial / Inconsistent| Alpha | Unstable | `alpha-unstable` +| C | Self-Validated | Alpha | Stable in home context | `alpha-stable` +| B | Broadly Validated | Beta | Stable for broad trial | `beta-stable` +| A | Field-Proven | Stable | Stable | `stable` +|=== + +*Evidence gates (v2.0 is stricter than v1.0):* + +* *D*: RSR-compliant + per-capability tests + documented scope. Test matrix + must cite counts and live in `.machine_readable/descriptiles/STATE.a2ml` or CI. +* *C*: All of D, plus active dogfooding in home context with *no known + home-context failures over an evidence window*, plus *deep per-file and + per-directory annotation* (purpose, boundaries, invariants, + execution/test/proof surfaces, per-directory orientation READMEs). + STATE.a2ml `[dogfooding-status]` populated with concrete "done — " + entries. +* *B*: All of C, plus *six genuinely diverse external targets* with + feedback fed back. STATE.a2ml `[external-targets]` holds target identities + + dates + outcomes; `[issues-fed-back]` holds the closed-issue trail. + Internal-capability items do *not* count. +* *A*: All of B, plus multi-source real-world external feedback confirming + value, no harm in the wild. STATE.a2ml `[field-signal]` populated. + +*Publication gate:* non-abstract implementation claims require *B+*. +Below B, any publication must be explicitly abstract or provisional. + +''' + +== 2. Headline Evidence (as of 2026-09-28) + +[cols="2,3,3", options="header"] +|=== +| Metric | Value | Source + +| Test count | __ | `.machine_readable/descriptiles/STATE.a2ml` or CI +| Formal-verification posture | __ | grep of proof dirs +| Dangerous patterns (`assert_total`, `unsafeCoerce`, `Obj.magic`) | __ | grep 2026-09-28 +| Per-unit README coverage | __ | filesystem scan of unit dirs +| CI status | __ | `.github/workflows/` +| RSR mandatory workflows | __ | `.github/workflows/` +| Third-party badges (if any) | __ | external +| LIVE deployment (if any) | __ | production +|=== + +''' + +== 3. Component Assessment + +All components graded *as-is today*, per CRG v2.0 Principle 4. Stability +posture reflects the component's state *within its home context only* +unless noted. + +[cols="2,1,1,3,3,2", options="header"] +|=== +| Component | Grade | Posture | Evidence Summary | Promotion blocker | Last Assessed + +| __ | X/F/E/D/C/B/A | __ | __ | __ | 2026-09-28 +| __ | … | … | … | … | 2026-09-28 +| __ | … | … | … | … | 2026-09-28 +|=== + +*Rules of thumb for populating this table:* + +* One row per independently-gradable unit. If a subsystem can ship or break + independently, it gets its own row. +* Evidence Summary must cite *numbers* (test counts, LOC, file counts) or + *paths* (e.g. `src/interface/Abi/Module.idr`), never vague claims. +* Promotion blocker must be *actionable* — "add external consumer in home + context, then annotate" beats "needs more validation". +* Re-assess every release cycle; date-stamp each row. + +''' + +== 4. What's Needed for D → C + +CRG v2.0 requires two new pieces of evidence on top of D: + +. *Active dogfooding in home context with no known home-context failures.* +** Start date: __. +** Home context: __. +** "No known failures" is a moving claim — must hold continuously across + the evidence window (recommended: 4 weeks, daily use). +** Populate `STATE.a2ml [dogfooding-status]` with one entry per capability: + `capability = "done — "`. + +. *Deep code and folder annotation.* +** Per-directory orientation READMEs in every non-trivial source subtree. +** Per-file header comments: purpose, boundaries, invariants, + execution/test/proof surface. +** Per-unit (cartridge/panel/plugin/module) README covering: purpose, + tools, architecture-at-a-glance, build steps. Overview-level alone is + not sufficient — depth is required where a reviewer would otherwise + have to read source to orient. + +*Minimum first-ring targets for C promotion:* list the trunk components +here. If these aren't C, nothing else can be. + +''' + +== 5. What's Needed for C → B + +Six *genuinely diverse* external targets with feedback fed back into the +component. Candidate diversity axes: + +* Different language runtime (not just variants of the same one). +* Different OS family (at least one must not be Linux). +* Different hardware class (cloud / server / desktop / embedded / mobile). +* Different auth posture (unauthenticated, API-key, vault-brokered, mTLS). +* Different topology (star, mesh, peer-to-peer). +* Different trust model (same-org, federated, adversarial). + +Populate `STATE.a2ml`: + +* `[external-targets]` — target-id → `" — — "`. +* `[issues-fed-back]` — issue-id → `" — — "`. + +Six variations of the same use case do *not* count. Re-classify any items +currently under `[grade-b-status]` (legacy) and move external-eligible ones +into `[external-targets]`. + +''' + +== 6. What's Needed for B → A + +Real-world external feedback confirming value. Populate +`STATE.a2ml [field-signal]` with multi-source entries: + +* External users beyond the six B-targets. +* Third-party writeups, talks, papers, or testimonials. +* No unresolved safety / correctness incidents in the last 90 days. + +''' + +== 7. Summary (2026-09-28) + +* Project grade: __. Why: _<1-2 line justification>_. +* Delta since last assessment: __. +* Next milestone: __. +* Machine-readable grade line present (§ header) for `just crg-grade` / `just crg-badge`. + +''' + +== 8. Companion Artefacts + +* `docs/governance/CRG-CRITERIA.adoc` — grade definitions (boilerplate). +* `docs/governance/CRG-AUDIT-.adoc` — formal audit (populate from + `czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc`). +* `docs/practice/DOGFOOD-LOG.adoc` — dated dogfood evidence (required for C). +* `.machine_readable/descriptiles/STATE.a2ml` — authoritative state (canonical keys + `[dogfooding-status]`, `[external-targets]`, `[issues-fed-back]`, + `[field-signal]`). + +_Run `just crg-badge` to generate the shields.io badge for your README._ diff --git a/czech-file-knife/docs/status/ROADMAP.adoc b/czech-file-knife/docs/status/ROADMAP.adoc new file mode 100644 index 000000000..5c7821a5e --- /dev/null +++ b/czech-file-knife/docs/status/ROADMAP.adoc @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Czech File Knife Roadmap +:toc: + +The machine-readable source of truth is +`.machine_readable/descriptiles/STATE.a2ml` `[route-to-mvp]`; this page is the +human view of it. + +== v0.1.0 — Foundation (current) +* [x] Core traits (`StorageBackend`, `VirtualPath`, `Entry`), local backend +* [x] CLI: `ls cat cp mv rm mkdir stat backends df` +* [x] Reversible journal: `ReversibleBackend`, `cfk history`, `cfk undo`, CAS-backed `get_versions` +* [x] Standalone RSR repository (extracted from developer-ecosystem) +* [ ] First green CI run of `just build test e2e` + +== v0.2.0 — Hybrid-machine essentials +* [ ] `cfk squeeze` — compress in place within free space (hole punching; tail-truncation fallback) +* [ ] `StorageBackend::server_side_copy` + planner that prefers provider-side ops +* [ ] Google Drive and S3 backends (server-side copy/move/export) +* [ ] Undo restores metadata; selective undo with conflict detection +* [ ] `--plan` / dry-run mode (absolute-zero: certified no-op) + +== v0.3.0 — Scale out +* [ ] Remaining cloud backends (Dropbox, OneDrive, Box), network (SFTP, SMB, WebDAV, NFS, 9P) +* [ ] `cfk-cache` on the shared content store; Tantivy search +* [ ] FUSE mounting (`cfk-vfs`); Windows via WinFsp +* [ ] Cross-backend loss warnings from `StorageCapabilities` (echo-types) +* [ ] Cost-aware transfer planning (tropical-types) + +== v1.0.0 — Stable +* [ ] Mechanised reversibility proof for the journal (see link:PROOF-NEEDS.adoc[PROOF-NEEDS]) +* [ ] iOS File Provider extension (`cfk-ios`) +* [ ] Packaging across the targets in `build/packaging/` diff --git a/czech-file-knife/docs/status/TEST-NEEDS.adoc b/czech-file-knife/docs/status/TEST-NEEDS.adoc new file mode 100644 index 000000000..50e301a5d --- /dev/null +++ b/czech-file-knife/docs/status/TEST-NEEDS.adoc @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += TEST-NEEDS: czech-file-knife + +== CRG Grade: C — ACHIEVED 2026-04-04 + +== Current State (Updated 2026-04-04) + +[cols="2,1,4", options="header"] +|=== +| Category | Count | Details + +| *Source modules* | 6 | 3 Idris2 ABI (Foreign, Layout, Types), 2 Zig FFI (build, main), 1 Zig integration test template +| *Unit tests* | 0 | None in main source (inline tests in main.zig) +| *Integration tests* | 1 | `test/integration_test.zig` (documented template, 1 placeholder test) +| *E2E tests* | 1 | `tests/e2e/template_instantiation_test.sh` (full instantiation + validation) +| *Workflow tests* | 1 | `tests/workflows/validate_workflows_test.sh` (21 workflows validated) +| *Validation tests* | 1 | `scripts/validate-template.sh` (8-phase comprehensive validation) +| *Benchmarks* | 5 | `benches/template_bench.sh` (validation, Zig build, tests, workflows, instantiation) +| *Fuzz tests* | 0 | `README.adoc` scaffold with harness instructions +|=== + +== Completed Work (CRG C - Testing & Benchmarking) + +=== Template Validation Script ✅ + +* [x] `scripts/validate-template.sh` — 8-phase validation +** Phase 1: Core repository structure (root files, directories) +** Phase 2: Machine-readable metadata (`.machine_readable/`) +** Phase 3: GitHub Actions workflows (17 required + all present) +** Phase 4: Idris2 ABI and Zig FFI source files +** Phase 5: Placeholder token replacement (skipped in template) +** Phase 6: SPDX license headers (100% coverage, 6/6 files) +** Phase 7: Build system verification (`zig build` + `idris2` syntax check) +** Phase 8: Documentation requirements (TOPOLOGY, ABI-FFI-README, etc) +* Status: *PASSING* (0 errors, 3 warnings about template placeholders) + +=== E2E Template Instantiation Test ✅ + +* [x] `tests/e2e/template_instantiation_test.sh` — full workflow +** Clones template to temp directory +** Runs the real `just repo-init` (never a second copy of its substitution list) +** Validates resulting structure with `scripts/validate-template.sh` +** Verifies Zig build works after instantiation +** Checks no placeholder survives, via `scripts/check-no-placeholders.sh` +** Cleans up temp directory + +NOTE: this entry read `[x]` from the day it was written until 2026-07-17, while +the test aborted at its first substitution line (`file: unbound variable`) on +every invocation. Nothing noticed: CI ran `tests/e2e.sh`, which did not call it, +and its only caller — `benches/template_bench.sh` — pipes it to `/dev/null` and +appends `|| true`, so it was timing a script that died instantly. The tick was +copied from the test's intent, not from a run. Tick these boxes from observed +output only. +* Status: *READY TO TEST* (can be verified by CI) + +=== Workflow Validation Test ✅ + +* [x] `tests/workflows/validate_workflows_test.sh` +** Validates all 21 workflows exist and have proper structure +** Checks SPDX headers, `name` field +** Verifies all 15 required workflows present +* Status: *PASSING* (0 errors, 15/15 required workflows found) + +=== Zig FFI Tests ✅ + +* [x] `src/interface/ffi/test/integration_test.zig` — template with examples +** Converted from `czech_file_knife` placeholders to "template" namespace +** Added comprehensive comments for how to instantiate +** Tests grouped by category (lifecycle, operations, strings, errors, version, memory safety, threading) +** Compiles and passes placeholder test +* Status: *PASSING* (1 test: `placeholder_test_implementation_required` passes) + +=== Benchmarks ✅ + +* [x] `benches/template_bench.sh` — 5 benchmark suites +** Validation script: ~5.8s average (3 runs) +** Zig build: ~19ms (clean build) +** Zig tests: ~20ms +** Workflow validation: ~117ms +** Template instantiation: ~427ms +* Formats: human, json, csv +* Status: *PASSING* (all benchmarks execute) + +=== Build System ✅ + +* [x] `src/interface/ffi/build.zig` — updated for Zig 0.15.2 +** Simplified to test-only configuration +** Supports both unit tests and integration tests +** Works with `zig build` without errors +* Status: *PASSING* (builds successfully) + +== Test Results Summary + +---- +Validation Script: PASS (0 errors, 3 warnings) +Workflow Validation: PASS (21/21 workflows valid) +Integration Tests: PASS (1/1 placeholder test) +E2E Instantiation: READY (needs CI confirmation) +Benchmarks: PASS (5/5 benchmark suites) +Build System: PASS (zig build succeeds) +---- + +== CRG C Compliance + +* *Coverage*: 6/6 test categories (unit, integration, E2E, workflow, validation, benchmarks) +* *Documentation*: All test files have SPDX headers + inline documentation +* *Author Attribution*: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> +* *License*: MPL-2.0 on all new files +* *Automation*: All scripts executable + working + +== FLAGGED ISSUES - ALL RESOLVED + +* [.line-through]#*Template repo used by ALL new repos has 0 validation tests*# → FIXED: 4 test suites + validation script +* [.line-through]#*fuzz/placeholder.txt*# → FIXED: replaced with `README.adoc` containing real harness instructions +* [.line-through]#*No E2E tests for template instantiation*# → FIXED: full E2E test suite +* [.line-through]#*Zig FFI integration tests are placeholders*# → FIXED: converted to documented template format + +== Next Steps (Future Sessions) + +* [ ] Integrate test scripts into CI/CD workflows +* [ ] Generate test coverage reports +* [ ] Add more specialized benchmarks (memory, threading stress) +* [ ] Document test instantiation patterns for new repos + +== Priority: P0 (COMPLETE) ✅ diff --git a/czech-file-knife/docs/theory/README.adoc b/czech-file-knife/docs/theory/README.adoc new file mode 100644 index 000000000..20ef5b6da --- /dev/null +++ b/czech-file-knife/docs/theory/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += theory Unit diff --git a/czech-file-knife/docs/theory/computing/README.adoc b/czech-file-knife/docs/theory/computing/README.adoc new file mode 100644 index 000000000..be73e7351 --- /dev/null +++ b/czech-file-knife/docs/theory/computing/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Computing Theory diff --git a/czech-file-knife/docs/theory/formalisms/README.adoc b/czech-file-knife/docs/theory/formalisms/README.adoc new file mode 100644 index 000000000..fcbc818ed --- /dev/null +++ b/czech-file-knife/docs/theory/formalisms/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Formalisms Theory diff --git a/czech-file-knife/docs/theory/mathematics/README.adoc b/czech-file-knife/docs/theory/mathematics/README.adoc new file mode 100644 index 000000000..f4653d155 --- /dev/null +++ b/czech-file-knife/docs/theory/mathematics/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Mathematics Theory diff --git a/czech-file-knife/docs/theory/ontologies/README.adoc b/czech-file-knife/docs/theory/ontologies/README.adoc new file mode 100644 index 000000000..2008f2abc --- /dev/null +++ b/czech-file-knife/docs/theory/ontologies/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Ontologies Theory diff --git a/czech-file-knife/docs/theory/other/README.adoc b/czech-file-knife/docs/theory/other/README.adoc new file mode 100644 index 000000000..16ed2b165 --- /dev/null +++ b/czech-file-knife/docs/theory/other/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Other Theory diff --git a/czech-file-knife/docs/theory/socio-technical/README.adoc b/czech-file-knife/docs/theory/socio-technical/README.adoc new file mode 100644 index 000000000..91fa67014 --- /dev/null +++ b/czech-file-knife/docs/theory/socio-technical/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Socio technical Theory diff --git a/czech-file-knife/docs/troubleshooting.adoc b/czech-file-knife/docs/troubleshooting.adoc new file mode 100644 index 000000000..f9d3b17bc --- /dev/null +++ b/czech-file-knife/docs/troubleshooting.adoc @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Troubleshooting — Czech File Knife +:revdate: 2026-MM-DD + +This file is a *living FAQ* — known failure modes and recovery paths. +Add a new entry every time you debug something that took more than 15 +minutes; future-you (and other maintainers) will thank you. + +== Known failure modes + +=== `` + +**Symptom**: short reproduction. What does the user see? + +**Cause**: root cause (one or two sentences). + +**Fix**: +[source,bash] +---- + +---- + +**Avoidance**: how to not hit this again (config setting, doc link, etc.). + +--- + +=== `` + +(Replace this and the above example with real entries as you encounter +them.) + +== Diagnostic toolkit + +When something is wrong, run these first: + +[source,bash] +---- +just doctor # runs all available self-checks +just version # prints the version & build hash +just log-tail # last N lines of logs +---- + +== When to escalate + +. Filed an issue with: version, OS, exact command, full error output, + and the symptom in plain English. +. Linked from the issue: the relevant ADR(s) and any related issues. +. For *security*-relevant findings, see + link:./../SECURITY.md[SECURITY.md] — do **not** file public issues. + +== Where to look for more help + +* `docs/architecture.adoc` — internals. +* `docs/decisions/` — historical record of why things are this shape. +* `https://github.com/hyperpolymath/Czech File Knife/issues?q=is%3Aissue+` — has anyone hit this before? +* `https://github.com/hyperpolymath/standards/issues` — for estate-wide problems. diff --git a/czech-file-knife/docs/usage.adoc b/czech-file-knife/docs/usage.adoc new file mode 100644 index 000000000..a36ef7bbe --- /dev/null +++ b/czech-file-knife/docs/usage.adoc @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Usage — Czech File Knife +:revdate: 2026-MM-DD + +== Audience + +This document is for *consumers* of `Czech File Knife` — people who depend +on it, call it, or include it. For developers working *on* it, see +link:./contributing.adoc[contributing.adoc]. + +== Quickstart + +The shortest path from zero to a working call: + +[source,bash] +---- +# 1. Install +just install # or: cargo install --path . / task install + +# 2. Configure +cp examples/config.example.toml ./config.toml +# Edit minimal required fields. + +# 3. Run +just run # or the equivalent for your language +---- + +Expected output: + +[source] +---- +Czech File Knife v0.0.0 +Listening on 127.0.0.1:8080 +---- + +== Common use cases + +For each canonical use case, give: + +. A one-line statement of the goal. +. The minimal invocation. +. Expected output / side effect. + +=== Use case 1: + +(Replace with real content.) + +=== Use case 2: + +== Configuration reference + +Document every configurable field. Keep this section authoritative — if +the code grows a new option, this table must grow too (CI can be wired +to enforce this). + +[cols="1,1,1,3", options="header"] +|=== +| Field | Type | Default | Meaning + +| `` | `` | `` | +|=== + +== Stability guarantees + +Be explicit: + +* **Stable**: API surfaces that follow SemVer (breaking change = major bump). +* **Unstable**: behind a flag / pre-1.0 / explicitly marked. +* **Internal**: documented for reference but no compatibility promise. + +== Limits & known constraints + +* Maximum supported ``: ``. +* `` is not yet implemented; track at issue `#`. +* On ``, `` behaves differently because ``. + +== See also + +* link:./architecture.adoc[Architecture] — how it works internally. +* link:./troubleshooting.adoc[Troubleshooting] — when things go wrong. diff --git a/czech-file-knife/docs/whitepapers/README.adoc b/czech-file-knife/docs/whitepapers/README.adoc new file mode 100644 index 000000000..36d9f9061 --- /dev/null +++ b/czech-file-knife/docs/whitepapers/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += whitepapers Unit diff --git a/czech-file-knife/docs/whitepapers/academic/README.adoc b/czech-file-knife/docs/whitepapers/academic/README.adoc new file mode 100644 index 000000000..a050acd3c --- /dev/null +++ b/czech-file-knife/docs/whitepapers/academic/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Academic Logic diff --git a/czech-file-knife/docs/whitepapers/industry/README.adoc b/czech-file-knife/docs/whitepapers/industry/README.adoc new file mode 100644 index 000000000..ca743a10c --- /dev/null +++ b/czech-file-knife/docs/whitepapers/industry/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Industry Logic diff --git a/czech-file-knife/docs/whitepapers/outreach/README.adoc b/czech-file-knife/docs/whitepapers/outreach/README.adoc new file mode 100644 index 000000000..6ccc80bee --- /dev/null +++ b/czech-file-knife/docs/whitepapers/outreach/README.adoc @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Outreach & Education +:toc: preamble +:icons: font + +This directory contains whitepapers, guides, and presentations tailored for a general audience, including schools, corporate partners, and special interest groups. + +== Target Audiences + +* **Schools & Education:** Introductory material on formal verification and sovereign systems. +* **Corporate:** High-level business value and compliance summaries. +* **Special Interest Groups:** Community-specific impact and ethical use cases. + +== Goals + +* De-mystify high-rigor engineering. +* Promote the adoption of the Rhodium Standard. +* Provide accessible entry points for non-technical stakeholders. diff --git a/czech-file-knife/docs/wikis/README.md b/czech-file-knife/docs/wikis/README.md new file mode 100644 index 000000000..f15c7cfed --- /dev/null +++ b/czech-file-knife/docs/wikis/README.md @@ -0,0 +1,17 @@ + +# Project Wikis + +This directory contains the source files for the project wiki. It is intended for long-form documentation, deep-dives, and community-maintained knowledge. + +## Structure + +- **Core Concepts:** Fundamental architectural ideas. +- **Workflows:** Step-by-step guides for contributors. +- **Glossary:** Definitions of project-specific terminology. + +## Wiki Synchronization + +Changes made here should be synchronised with the forge-hosted wiki (GitHub/GitLab) using the project's sync scripts. diff --git a/czech-file-knife/examples/README.adoc b/czech-file-knife/examples/README.adoc new file mode 100644 index 000000000..f0bf52ef8 --- /dev/null +++ b/czech-file-knife/examples/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += examples Pillar diff --git a/czech-file-knife/manifest/sample.ncl b/czech-file-knife/examples/sample-manifest.ncl similarity index 100% rename from czech-file-knife/manifest/sample.ncl rename to czech-file-knife/examples/sample-manifest.ncl diff --git a/czech-file-knife/features/README.adoc b/czech-file-knife/features/README.adoc new file mode 100644 index 000000000..5c24f38bf --- /dev/null +++ b/czech-file-knife/features/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Project Features diff --git a/czech-file-knife/features/boj-server/README.adoc b/czech-file-knife/features/boj-server/README.adoc new file mode 100644 index 000000000..25f08a148 --- /dev/null +++ b/czech-file-knife/features/boj-server/README.adoc @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += BoJ Server Integration +:icons: font + +This unit provides a "starting hand" for integrating with the **BoJ-Server** (Box of Justice) ecosystem — a high-rigor, verified server infrastructure. + +== Integration Options + +* **Core:** Use BoJ-Server as the primary verified backend for this project. +* **Bridge:** Utilize the BoJ-Server IPC bridge for cross-boundary communication. + +== Related Repository + +For the full specification and source, visit: +https://github.com/hyperpolymath/boj-server diff --git a/czech-file-knife/features/panic-attacker/README.adoc b/czech-file-knife/features/panic-attacker/README.adoc new file mode 100644 index 000000000..deff9f173 --- /dev/null +++ b/czech-file-knife/features/panic-attacker/README.adoc @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Panic Attacker Feature +:icons: font + +This unit integrates the **Panic-Attacker** high-rigor stress testing tool into the project lifecycle. + +== Value Proposition + +Panic-Attacker goes beyond unit testing by applying: +* **Static Analysis (Assail):** Detecting logic-based bug signatures. +* **Multi-Axis Dynamic Attacks (Assault):** Stressing CPU, Memory, Disk, and Network boundaries. + +== Usage in this Template + +This template includes a pre-configured maintenance trigger: + +[source,bash] +---- +just maint-assault +---- + +This runs a medium-intensity assault on the project binary and emits a report to `docs/reports/security/`. + +== Related Repository + +https://github.com/hyperpolymath/panic-attacker diff --git a/czech-file-knife/features/ssg/README.adoc b/czech-file-knife/features/ssg/README.adoc new file mode 100644 index 000000000..a6063e142 --- /dev/null +++ b/czech-file-knife/features/ssg/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Ssg Feature diff --git a/czech-file-knife/features/ssg/ssg-bootstrap.sh b/czech-file-knife/features/ssg/ssg-bootstrap.sh new file mode 100755 index 000000000..25d23cbdd --- /dev/null +++ b/czech-file-knife/features/ssg/ssg-bootstrap.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# ssg-bootstrap.sh — Universal SSG Initialisation Helper +# +# Provides a starting hand for creating a documentation site or blog. +# Options 1-2 are hyperpolymath-maintained SSGs; options 3-5 are popular +# third-party choices. Use whichever fits your project. + +set -euo pipefail + +DEST="${1:-docs/site}" + +echo "═══════════════════════════════════════════════════" +echo " SSG BOOTSTRAP HELPER" +echo " Target directory: $DEST" +echo "═══════════════════════════════════════════════════" +echo "" +echo "Select an SSG to initialize in this project:" +echo " [1] Casket-SSG (Haskell) — hyperpolymath, pretty-formal" +echo " [2] Ddraig-SSG (Idris2) — hyperpolymath, dependent-type proofed" +echo " [3] Serum (Elixir) — BEAM-based, concurrent" +echo " [4] Zola (Rust) — Fast, standalone, standard" +echo " [5] Custom Git URL — Any SSG from a git repository" +echo "" + +read -rp "Enter choice [1-5]: " choice + +case "$choice" in + 1) + echo "Selected: Casket-SSG" + echo "Run: git clone https://github.com/hyperpolymath/casket-ssg $DEST" + ;; + 2) + echo "Selected: Ddraig-SSG" + echo "Run: git clone https://github.com/hyperpolymath/ddraig-ssg $DEST" + ;; + 3) + echo "Selected: Serum" + echo "Run: mix serum.new $DEST" + ;; + 4) + echo "Selected: Zola" + echo "Run: zola init $DEST" + ;; + 5) + read -rp "Git URL: " custom_url + echo "Run: git clone $custom_url $DEST" + ;; + *) + echo "Invalid selection. Aborting." + exit 1 + ;; +esac + +echo "═══════════════════════════════════════════════════" +echo " SCAFFOLDING SECURITY & RESOURCE DIRECTORIES" +echo "═══════════════════════════════════════════════════" +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +WWW="$REPO_ROOT/www" + +mkdir -p "$DEST/security_headers" +mkdir -p "$DEST/.well-known" +mkdir -p "$DEST/resource_records" + +# Canonical sources live in www/ (issue #53). Copy from there when present; +# the inline defaults below remain for repositories without the bundle. +if [ -d "$WWW/.well-known" ]; then + cp -r "$WWW/.well-known/." "$DEST/.well-known/" + echo " .well-known: copied from canonical www/.well-known/" +fi +if [ -d "$WWW/dns/records" ]; then + cp -r "$WWW/dns/records/." "$DEST/resource_records/" + echo " resource records: copied from www/dns/records/" +fi +if [ -f "$WWW/security_headers/csp.conf" ]; then + cp "$WWW/security_headers/csp.conf" "$DEST/security_headers/csp.conf" + echo " security headers: copied from www/security_headers/csp.conf" +else +cat << 'EOF' > "$DEST/security_headers/csp.conf" +Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests; +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-XSS-Protection: 1; mode=block +Referrer-Policy: strict-origin-when-cross-origin +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload +EOF +fi + +echo "Scaffolding complete. Please ensure these directories are copied to your site's output root." diff --git a/czech-file-knife/hooks/validate-codeql.sh b/czech-file-knife/hooks/validate-codeql.sh deleted file mode 100755 index 15b52c3da..000000000 --- a/czech-file-knife/hooks/validate-codeql.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate CodeQL language matrix matches repo -set -euo pipefail - -CODEQL_FILE=".github/workflows/codeql.yml" -[ -f "$CODEQL_FILE" ] || exit 0 - -# Detect languages in repo -HAS_JS=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | grep -v node_modules | head -1) -HAS_PY=$(find . -name "*.py" 2>/dev/null | grep -v __pycache__ | head -1) -HAS_GO=$(find . -name "*.go" 2>/dev/null | head -1) -HAS_RS=$(find . -name "*.rs" 2>/dev/null | head -1) - -# Check if matrix includes unsupported languages -if grep -q "language:.*python" "$CODEQL_FILE" && [ -z "$HAS_PY" ]; then - echo "WARNING: CodeQL configured for Python but no .py files found" -fi -if grep -q "language:.*go" "$CODEQL_FILE" && [ -z "$HAS_GO" ]; then - echo "WARNING: CodeQL configured for Go but no .go files found" -fi -if grep -q "language:.*javascript" "$CODEQL_FILE" && [ -z "$HAS_JS" ]; then - echo "WARNING: CodeQL configured for JavaScript but no JS/TS files found" -fi - -# Rust/OCaml are not supported - should use 'actions' only -if [ -n "$HAS_RS" ]; then - if grep -q "language:.*rust" "$CODEQL_FILE"; then - echo "ERROR: CodeQL does not support Rust - use ['actions'] instead" - exit 1 - fi -fi - -exit 0 diff --git a/czech-file-knife/hooks/validate-permissions.sh b/czech-file-knife/hooks/validate-permissions.sh deleted file mode 100755 index 1999b018b..000000000 --- a/czech-file-knife/hooks/validate-permissions.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate workflow permissions declarations -set -euo pipefail -ERRORS=0 -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - if ! grep -qE '^permissions:' "$workflow"; then - echo "ERROR: Missing top-level permissions in $workflow" - ERRORS=$((ERRORS + 1)) - fi -done -[ $ERRORS -gt 0 ] && exit 1 -exit 0 diff --git a/czech-file-knife/hooks/validate-sha-pins.sh b/czech-file-knife/hooks/validate-sha-pins.sh deleted file mode 100755 index 697092b52..000000000 --- a/czech-file-knife/hooks/validate-sha-pins.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate GitHub Actions are SHA-pinned - -set -euo pipefail - -ERRORS=0 - -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - - # Find uses: lines that aren't SHA-pinned - while IFS= read -r line; do - if [[ "$line" =~ uses:.*@ ]]; then - # Check if it has a SHA (40 hex chars) - if ! echo "$line" | grep -qE '@[a-f0-9]{40}'; then - echo "ERROR: Unpinned action in $workflow" - echo " $line" - echo " Actions must use SHA pins: uses: action/name@SHA # version" - ERRORS=$((ERRORS + 1)) - fi - fi - done < "$workflow" -done - -if [ $ERRORS -gt 0 ]; then - echo "" - echo "Found $ERRORS unpinned actions. Please SHA-pin all GitHub Actions." - echo "Use: gh api repos/OWNER/REPO/git/matching-refs/tags/VERSION to find SHAs" - exit 1 -fi - -exit 0 diff --git a/czech-file-knife/hooks/validate-spdx.sh b/czech-file-knife/hooks/validate-spdx.sh deleted file mode 100755 index cc81cf18f..000000000 --- a/czech-file-knife/hooks/validate-spdx.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate SPDX headers in workflow files - -set -euo pipefail - -ERRORS=0 -SPDX_PATTERN="^# SPDX-License-Identifier:" - -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - - first_line=$(head -n1 "$workflow") - if ! echo "$first_line" | grep -qE "$SPDX_PATTERN"; then - echo "ERROR: Missing SPDX header in $workflow" - echo " First line should be: # SPDX-License-Identifier: MPL-2.0" - ERRORS=$((ERRORS + 1)) - fi -done - -if [ $ERRORS -gt 0 ]; then - exit 1 -fi - -exit 0 diff --git a/czech-file-knife/license/PMPL-1.0.txt b/czech-file-knife/license/PMPL-1.0.txt deleted file mode 100644 index 711e372c1..000000000 --- a/czech-file-knife/license/PMPL-1.0.txt +++ /dev/null @@ -1,162 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2025 Palimpsest Stewardship Council - -================================================================================ -PALIMPSEST-MPL LICENSE VERSION 1.0 -================================================================================ - -File-level copyleft with ethical use and quantum-safe provenance - -Based on Mozilla Public License 2.0 - --------------------------------------------------------------------------------- -PREAMBLE --------------------------------------------------------------------------------- - -This License extends the Mozilla Public License 2.0 (MPL-2.0) with provisions -for ethical use, post-quantum cryptographic provenance, and emotional lineage -protection. The base MPL-2.0 terms apply except where explicitly modified by -the Exhibits below. - -Like a palimpsest manuscript where each layer builds upon what came before, -this license recognizes that creative works carry history, context, and meaning -that transcend mere code or text. - --------------------------------------------------------------------------------- -SECTION 1: BASE LICENSE --------------------------------------------------------------------------------- - -This License incorporates the full text of Mozilla Public License 2.0 by -reference. The complete MPL-2.0 text is available at: -https://www.mozilla.org/en-US/MPL/2.0/ - -All terms, conditions, and definitions from MPL-2.0 apply except where -explicitly modified by the Exhibits in this License. - --------------------------------------------------------------------------------- -SECTION 2: ADDITIONAL DEFINITIONS --------------------------------------------------------------------------------- - -2.1. "Emotional Lineage" - means the narrative, cultural, symbolic, and contextual meaning embedded - in Covered Software, including but not limited to: protest traditions, - cultural heritage, trauma narratives, and community stories. - -2.2. "Provenance Metadata" - means cryptographically signed attribution information attached to or - associated with Covered Software, including author identities, timestamps, - modification history, and lineage references. - -2.3. "Non-Interpretive System" - means any automated system that processes Covered Software without - preserving or considering its Emotional Lineage, including but not - limited to: AI training pipelines, content aggregators, and automated - summarization tools. - -2.4. "Quantum-Safe Signature" - means a cryptographic signature using algorithms resistant to attacks - by quantum computers, as specified in Exhibit B. - --------------------------------------------------------------------------------- -SECTION 3: ETHICAL USE REQUIREMENTS --------------------------------------------------------------------------------- - -In addition to the rights and obligations under MPL-2.0: - -3.1. Emotional Lineage Preservation - You must make reasonable efforts to preserve and communicate the - Emotional Lineage of Covered Software when distributing or creating - derivative works. This includes maintaining narrative context, cultural - attributions, and symbolic meaning where documented. - -3.2. Non-Interpretive System Notice - If You use Covered Software as input to a Non-Interpretive System, You - must: - (a) document such use in a publicly accessible manner; and - (b) not claim that outputs of such systems carry the Emotional Lineage - of the original work without explicit permission from Contributors. - -3.3. Ethical Use Declaration - Commercial use of Covered Software requires acknowledgment that You have - read and understood Exhibit A (Ethical Use Guidelines) and agree to act - in good faith accordance with its principles. - -See Exhibit A for complete Ethical Use Guidelines. - --------------------------------------------------------------------------------- -SECTION 4: PROVENANCE REQUIREMENTS --------------------------------------------------------------------------------- - -4.1. Metadata Preservation - You must not strip, alter, or obscure Provenance Metadata from Covered - Software except where technically necessary and with clear documentation - of any changes. - -4.2. Quantum-Safe Provenance (Optional) - Contributors may sign their Contributions using Quantum-Safe Signatures. - If Quantum-Safe Signatures are present, You must preserve them in all - distributions. - -4.3. Lineage Chain - When creating derivative works, You should extend the provenance chain - to include Your own contributions, maintaining cryptographic linkage to - prior Contributors where feasible. - -See Exhibit B for Quantum-Safe Provenance specifications. - --------------------------------------------------------------------------------- -SECTION 5: GOVERNANCE --------------------------------------------------------------------------------- - -5.1. Stewardship Council - This License is maintained by the Palimpsest Stewardship Council, which - may issue clarifications, interpretive guidance, and future versions. - -5.2. Version Selection - You may use Covered Software under this version of the License or any - later version published by the Palimpsest Stewardship Council. - -5.3. Dispute Resolution - Disputes regarding interpretation of Ethical Use Requirements (Section 3) - should first be submitted to the Palimpsest Stewardship Council for - non-binding guidance before pursuing legal remedies. - --------------------------------------------------------------------------------- -SECTION 6: COMPATIBILITY --------------------------------------------------------------------------------- - -6.1. MPL-2.0 Compatibility - Covered Software under this License may be combined with software under - MPL-2.0. The combined work must comply with both licenses. - -6.2. Secondary Licenses - The Secondary License provisions of MPL-2.0 Section 3.3 apply to this - License. - --------------------------------------------------------------------------------- -EXHIBITS --------------------------------------------------------------------------------- - -Exhibit A - Ethical Use Guidelines -Exhibit B - Quantum-Safe Provenance Specification - -See separate files: -- EXHIBIT-A-ETHICAL-USE.txt -- EXHIBIT-B-QUANTUM-SAFE.txt - --------------------------------------------------------------------------------- -END OF PALIMPSEST-MPL-1.0 LICENSE TEXT --------------------------------------------------------------------------------- - -For exhibits, specifications, provenance rules, and governance: -https://github.com/hyperpolymath/palimpsest-license - -For legal frameworks and jurisdictional analysis: -See /legal/frameworks/ - -For provenance and audit tooling: -See /tools/ and /spec/PROVENANCE-SPEC.adoc - -For questions about this License: -- Repository: https://github.com/hyperpolymath/palimpsest-license -- Council: contact via repository Issues diff --git a/czech-file-knife/mise.toml b/czech-file-knife/mise.toml new file mode 100644 index 000000000..77e4f75e0 --- /dev/null +++ b/czech-file-knife/mise.toml @@ -0,0 +1,67 @@ +# Every tool name below was checked against `mise registry` AND resolved with +# `mise ls-remote` before being written. The previous version of this file named +# 13 tools that do not exist in the registry, so mise emitted ~25 lines of +# warnings on every shell entry while installing none of them. +# +# This file is a TEMPLATE. Repos minted from it inherit it verbatim, so add +# your project's actual language toolchain here after minting — pinned to the +# same versions your CI uses, so local and CI agree. + +[tools] +# Czech File Knife needs only these. (The template's multi-language list was +# trimmed at extraction: estate policy bans Python/Go/TypeScript toolchains, +# and nothing here uses node/java/bun.) +rust = "stable" +just = "latest" # the estate's task runner (Justfile) +shfmt = "latest" # tests/*.sh, scripts/*.sh + +# --------------------------------------------------------------------------- +# REMOVED — none of these resolve in the mise registry, so they installed +# nothing and only produced warnings. Grouped by why they were wrong: +# +# Already provided by a tool that IS listed above: +# cargo -> ships with `rust` +# gofmt -> ships with `go` +# pip -> ships with `python` +# +# Project-level dependencies, not system tools. These belong in +# package.json / pyproject.toml / mix.exs, not in a toolchain manager: +# vitest, jest, pytest, isort +# +# Base system binaries that mise should not be shadowing on Linux: +# git, gnu-sed, gnu-grep, gnu-tar +# (If a GNU-vs-BSD coreutils difference ever actually bites on macOS, add +# `coreutils`, which does exist in the registry, rather than these names.) +# +# Task runner that never installed: +# go-task -> not a registry name. NB the bare name `task` does not work +# either: `mise registry` LISTS it as `aqua:go-task/task`, but resolving +# `task@latest` still fails. Appearing in `mise registry` output is NOT +# sufficient to conclude a name resolves — check with `mise ls-remote`. +# `just` is listed above instead. +# --------------------------------------------------------------------------- + +[env] +NODE_ENV = "development" +PYTHONDONTWRITEBYTECODE = "1" +PYTHONUNBUFFERED = "1" + +# --------------------------------------------------------------------------- +# REMOVED — an `[alias]` section holding what were clearly meant to be tasks: +# +# [alias] +# task = "go-task" +# build = "cargo build --release || npm run build || go build" +# test = "cargo test || npm test || go test ./..." +# lint = "ruff check . || prettier --check . || black --check ." +# fmt = "ruff format . || prettier --write . || black ." +# +# mise's `[alias]` maps an alias to a TOOL PLUGIN — it does not define tasks, +# so none of these ever ran. mise also warns that `[alias]` is deprecated in +# favour of `[tool_alias]`. +# +# They are not re-added as `[tasks]`: the estate drives builds through the +# Justfile, and a second task runner sharing the verbs `build`/`test`/`lint`/ +# `fmt` — one of which silently falls through `||` chains into a different +# language's build — is worse than one. Use `just `. +# --------------------------------------------------------------------------- diff --git a/czech-file-knife/ops/podman-compose.yml b/czech-file-knife/ops/podman-compose.yml deleted file mode 100644 index c3221df1c..000000000 --- a/czech-file-knife/ops/podman-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: "3.9" -# Infrastructure services for Audit (Kafka) and Metadata (Postgres) - -services: - zookeeper: - image: bitnami/zookeeper:latest - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - ports: ["2181:2181"] - - kafka: - image: bitnami/kafka:latest - container_name: filegov-kafka - environment: - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - ALLOW_PLAINTEXT_LISTENER=yes - - KAFKA_LISTENERS=PLAINTEXT://:9092 - - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 - ports: ["9092:9092"] - depends_on: ["zookeeper"] - - postgres: - image: postgres:15 - environment: - - POSTGRES_USER=filegov - - POSTGRES_PASSWORD=filegov - - POSTGRES_DB=filegov - ports: ["5432:5432"] diff --git a/czech-file-knife/ops/scripts/ensure_deps.sh b/czech-file-knife/ops/scripts/ensure_deps.sh deleted file mode 100755 index 95c9556b2..000000000 --- a/czech-file-knife/ops/scripts/ensure_deps.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -# Checks for all required dependencies (Haskell, Elixir, Bun, Podman, Nickel) - -set -e -echo "[deps] Checking core tools: Bun, Elixir, mix, Stack, Nickel, Podman..." - -# Function to check if a command exists -need() { - command -v "$1" >/dev/null 2>&1 || { - echo "Missing required dependency: $1" >&2 - return 1 - } -} - -MISSING=0 - -# 1. Check core development tools (Bun, Stack, Nickel) -# We check for Bun to manage Svelte/Vite dependencies -for c in bun stack nickel; do - need $c || MISSING=1 -done - -# 2. Check Elixir/Mix (Already installed, but confirms availability) -for c in elixir mix; do - need $c || MISSING=1 -done - -# 3. Check Container Runtime (Flexible: Docker OR Podman) -if (command -v docker >/dev/null 2>&1 && command -v docker-compose >/dev/null 2>&1) || \ - (command -v podman >/dev/null 2>&1 && command -v podman-compose >/dev/null 2>&1); then - echo "[deps] Container runtime (Podman/Docker) OK." -else - echo "Missing container runtime (Docker or Podman) and compose tool." >&2 - MISSING=1 -fi - -if [ $MISSING -eq 1 ]; then - echo "" - echo "--- INSTALLATION GUIDANCE (Remaining) ---" - echo "1. Install **Bun** (for Svelte UI): https://bun.sh/docs/installation (Required instead of Node/NPM)." - echo "2. Install **Haskell Stack** (for the validator): e.g., 'sudo dnf install haskell-stack'." - echo "3. Install **Nickel** (Policy Language): Check official documentation if 'dnf install nickel' failed." - echo "-----------------------------------------" - exit 1 -fi - -echo "[deps] All core tools present. Ready to proceed." diff --git a/czech-file-knife/ops/scripts/ensure_kafka.sh b/czech-file-knife/ops/scripts/ensure_kafka.sh deleted file mode 100755 index af5dab0f3..000000000 --- a/czech-file-knife/ops/scripts/ensure_kafka.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -# Starts container services and ensures the Kafka topic exists. - -set -e -echo "[kafka] Bringing up Kafka + Zookeeper + ArangoDB for audit..." - -COMPOSE_FILE="ops/podman-compose.yml" - -# Determine which compose tool to use -if command -v podman-compose >/dev/null 2>&1; then - COMPOSE_CMD="podman-compose -f $COMPOSE_FILE" - # Ensure podman service is running if needed (common podman-compose requirement) - # Note: On some systems, this needs to be running or 'podman machine' started. - echo "Attempting to start services using podman-compose..." -else - # Fallback to standard docker compose command - COMPOSE_CMD="docker compose -f $COMPOSE_FILE" - echo "Falling back to standard docker compose..." -fi - -# Start containers -$COMPOSE_CMD up -d - -echo "[kafka] Waiting for Kafka to be ready (5s delay)..." -sleep 5 - -# Create the audit topic (must use the specific container name) -# We use 'podman exec' if podman-compose was used, or 'docker exec' otherwise. -if [[ "$COMPOSE_CMD" == podman-compose* ]]; then - EXEC_CMD="podman exec" -else - EXEC_CMD="docker exec" -fi - -$EXEC_CMD filegov-kafka bash -lc "/opt/kafka/bin/kafka-topics.sh --create --if-not-exists --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 --topic audit.events.filegov" || true - -echo "[kafka] Kafka infrastructure is ready." diff --git a/czech-file-knife/scripts/campaigns/www-bundle.campaign b/czech-file-knife/scripts/campaigns/www-bundle.campaign new file mode 100644 index 000000000..046639b75 --- /dev/null +++ b/czech-file-knife/scripts/campaigns/www-bundle.campaign @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# www-bundle.campaign — propagate the canonical www/ test suite and runbooks. +# +# THE GAP THIS CLOSES +# +# Issue #119 stage 5 moved every estate repository's root .well-known/ to +# www/.well-known/. What it could not do was bring the rest of the bundle: +# +# "no www/tests bundle" — all 262 sweep results +# 0/269 estate repositories carry any www/ tree +# +# so repositories ended up with a www/.well-known/ directory and nothing that +# knows how to check it. This campaign carries the checks and the runbooks that +# operate them. +# +# WHY IT IS SCOPED TO TWO DIRECTORIES AND NOT ALL OF www/ +# +# Measured 2026-09-18 by applying the suite to a real post-migration checkout +# (Axiom.jl at chore/well-known-to-www): +# +# www/tests/ alone -> 3 of 6 checks FAIL +# www/tests/ + guards -> 0 failures, 3 explicit SKIPs +# +# The failures were checks whose INPUTS the bundle does not supply: +# check-publication-boundary.sh wants www/schemas/ + www/webservers/, +# check-aibdp.sh wants www/.well-known/aibdp.json, check-migration.sh wants the +# template-side migrator. Those guards now exist, so the suite states that it +# does not apply instead of failing where it does not apply. +# +# The rest of www/ is deliberately NOT propagated: +# +# www/public/ per-repo placeholders (Czech File Knife and friends) +# www/policies/ per-repo placeholders +# www/dns/ a DNS scaffold most repositories do not operate +# www/tls/ as above +# www/webservers/ as above +# www/schemas/ only meaningful alongside the servers above +# www/profiles/ as above +# www/.well-known/ per-repo CONTENT, already migrated by stage 5 — the +# spine's copy holds placeholders and would overwrite +# every repository's real security.txt contact +# +# Carrying those would re-plant template identity in 262 repositories at once. +# They are a separate campaign with per-repo substitution, and they need their +# own decision and their own review. +# +# The two directories here contain no non-meta {{TOKEN}} at all, so every file +# travels verbatim. rsr-campaign.sh still checks, so this manifest cannot +# silently become untrue. + +[meta] +name = chore/rsr-www-bundle +description = canonical www/ test suite + operational runbooks (issue #119 follow-on) + +[paths] +www/tests/ +www/runbooks/ diff --git a/czech-file-knife/scripts/check-action-pinning.js b/czech-file-knife/scripts/check-action-pinning.js new file mode 100644 index 000000000..80533f64b --- /dev/null +++ b/czech-file-knife/scripts/check-action-pinning.js @@ -0,0 +1,105 @@ +#!/usr/bin/env bun +// SPDX-License-Identifier: MPL-2.0 +// +// Assert every workflow action ref is pinned — inline SHA, or via actions.lock. +// +// What counts as "pinned" changed when GitHub introduced workflow lockfiles. A +// ref is pinned if EITHER it is an inline 40-hex SHA, OR +// .github/workflows/actions.lock resolves it to one. The previous inline-only +// rule rejected all 40 of this repo's own refs, so `Workflow Security Linter` +// was red on main permanently — and every repo minted from this template +// inherited both the tag-form workflows and the gate that rejects them. +// +// Runtime: Bun — the estate's first-choice runtime per LANGUAGE-POLICY.adoc §1 +// (Bun > Deno > pnpm > npm). Plain JavaScript, not TypeScript: TypeScript is +// banned estate-wide (owner ruling; CLAUDE.md banned-languages table). Note +// LANGUAGE-POLICY.adoc §1.2 currently claims TS is "permitted under Bun" — that +// line is wrong and should be corrected; the ban stands. +// +// Python was the first draft of this script and is banned with no exceptions. +// +// Three details are load-bearing, each of them a defect found by testing: +// +// `-?` in the pattern. `- uses:` is the list-item form and by far the most +// common; a `\s+uses:` pattern silently misses every one of them. Estate +// memory records this exact failure — "green linter != full SHA-pinning". A +// gate that cannot see the common case reads as coverage and enforces nothing. +// +// Case-insensitive comparison. Workflows write `SonarSource/...`; the lockfile +// records `sonarsource/...`. Action refs are case-insensitive in practice, so +// a case-sensitive match reports a false positive on a correctly-pinned action. +// +// Only *.yml / *.yaml, depth 1. A bare recursive grep also reads actions.lock +// (whose `uses:` keys are lockfile entries, not refs) and *.yml.template +// (whose tag ref is deliberate and resolved at mint time). Both were reported +// as unpinned, which made the gate unsatisfiable the moment a lockfile existed. +// +// Exit 0 if every ref is pinned, 1 otherwise, listing file:line: ref. + +import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; +import { join } from "node:path"; + +const WF = ".github/workflows"; +const LOCK = join(WF, "actions.lock"); +// `-?` matches the list-item form; see the header. +const USES = /^\s*-?\s*uses:\s*([^\s#]+)/; +const SHA = /@[a-f0-9]{40}$/; +const EXEMPT_PREFIX = ["./", "$", "docker://"]; + +function workflowFiles() { + if (!existsSync(WF) || !statSync(WF).isDirectory()) return []; + return readdirSync(WF) + .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")) + .sort() + .map((f) => join(WF, f)); +} + +function main() { + if (!existsSync(WF)) { + console.log("no .github/workflows — nothing to check"); + return 0; + } + + let known = new Set(); + if (existsSync(LOCK)) { + const lock = readFileSync(LOCK, "utf8"); + known = new Set( + [...lock.matchAll(/'([^']+@[^']+)'/g)].map((m) => m[1].toLowerCase()), + ); + console.log(`lockfile present: ${known.size} ref(s) resolvable through it`); + } else { + console.log("no lockfile — every ref must be an inline 40-character SHA"); + } + + const bad = []; + for (const wf of workflowFiles()) { + const lines = readFileSync(wf, "utf8").split("\n"); + lines.forEach((line, i) => { + const m = USES.exec(line); + if (!m) return; + const ref = m[1]; + if (EXEMPT_PREFIX.some((p) => ref.startsWith(p))) return; + if (ref.includes("actions/github-script")) return; + if (SHA.test(ref)) return; + const at = ref.lastIndexOf("@"); + const name = at === -1 ? ref : ref.slice(0, at); + const tag = at === -1 ? "" : ref.slice(at + 1); + // subpath actions key by repo root: github/codeql-action/init -> github/codeql-action + const root = name.split("/").slice(0, 2).join("/"); + if (known.has(ref.toLowerCase()) || known.has(`${root}@${tag}`.toLowerCase())) return; + bad.push(`${wf}:${i + 1}: ${ref}`); + }); + } + + if (bad.length) { + console.log("\nERROR: these action refs are neither SHA-pinned nor covered by the lockfile:"); + for (const b of bad) console.log(` ${b}`); + console.log("\nEither pin to a full commit SHA, or run `gh actions-lock` so the"); + console.log("lockfile resolves the ref."); + return 1; + } + console.log("all action refs are pinned"); + return 0; +} + +process.exit(main()); diff --git a/czech-file-knife/scripts/check-adoc-renders.sh b/czech-file-knife/scripts/check-adoc-renders.sh new file mode 100755 index 000000000..4f8fe4f82 --- /dev/null +++ b/czech-file-knife/scripts/check-adoc-renders.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Every tracked .adoc file must parse. Asciidoctor's --failure-level DEFAULTS TO +# FATAL, so a document that emits WARNING or ERROR to stderr still exits 0 -- +# which is why every gate in this estate that shelled out to asciidoctor was +# unfailable. The cure is the FLAG, not a wrapper, plus a three-way verdict: +# +# exit 0 + empty stderr -> the document rendered clean +# exit 0 + stderr output -> a finding (this is the case bare asciidoctor hides) +# non-zero -> a finding, or the run did not complete +# +# This is the same contract empty-linter ships. The two are complementary halves +# of document integrity: empty-linter's remit is invisible bytes, this gate's +# remit is whether the document parses at all. +# +# Exit codes: +# 0 all tracked .adoc rendered clean (or there are none) +# 1 at least one file emitted a diagnostic or failed to render +# 2 the check could not run (bad path, asciidoctor not installed) + +set -euo pipefail + +REPO_ROOT="${1:-.}" +if [ ! -d "$REPO_ROOT" ]; then + echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 + exit 2 +fi + +if ! command -v asciidoctor >/dev/null 2>&1; then + echo "ERROR: asciidoctor is not installed; cannot verify .adoc rendering." >&2 + echo " Install it with: gem install asciidoctor -v 2.0.26 --no-document" >&2 + echo " Failing closed: 'did not complete' is not 'clean'." >&2 + exit 2 +fi + +# Subject list read NUL-delimited so a path containing a space cannot split. +FILES=() +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + while IFS= read -r -d '' f; do + FILES+=("$REPO_ROOT/$f") + done < <(git -C "$REPO_ROOT" ls-files -z -- '*.adoc' 2>/dev/null || true) +else + while IFS= read -r -d '' f; do + FILES+=("$f") + done < <(find "$REPO_ROOT" -type f -name '*.adoc' \ + -not -path '*/.git/*' -not -path '*/node_modules/*' -print0 2>/dev/null || true) +fi + +# An empty subject list is a PASS, not a failure. This is a TEMPLATE: a stripped +# mint may legitimately carry no .adoc yet, and the house idiom for an absent +# subject is check-no-md-in-docs.sh's "no docs/ directory (nothing to check)". +if [ "${#FILES[@]}" -eq 0 ]; then + echo "PASS: no tracked .adoc (nothing to check)" + exit 0 +fi + +FAILED=0 +FINDINGS="" +for f in "${FILES[@]}"; do + set +e + err="$(asciidoctor --failure-level=WARN --backend=html5 -o /dev/null "$f" 2>&1 >/dev/null)" + rc=$? + set -e + if [ "$rc" -ne 0 ] || [ -n "$err" ]; then + FAILED=$((FAILED + 1)) + first="$(printf '%s\n' "$err" | sed -n '1p')" + [ -n "$first" ] || first="(no diagnostic; exit $rc)" + FINDINGS="${FINDINGS} ${f#"$REPO_ROOT"/}: ${first}"$'\n' + fi +done + +if [ "$FAILED" -eq 0 ]; then + echo "PASS: all ${#FILES[@]} tracked .adoc file(s) render clean" + exit 0 +fi + +echo "FAIL: $FAILED of ${#FILES[@]} tracked .adoc file(s) did not render clean:" >&2 +printf '%s' "$FINDINGS" >&2 +echo >&2 +echo "Repair the document so asciidoctor emits nothing on stderr. Common causes:" >&2 +echo " * a Markdown body inside a .adoc: '# H' parses as a level-0 section" >&2 +echo " * an attribute line flush against '= Title', swallowed into the header" >&2 +echo " * a repeated [[anchor]] in prose, processed even inside backticks" >&2 +echo " * a stray '|' inside a table, shifting the cell count" >&2 +echo "Reproduce one file with:" >&2 +echo " asciidoctor --failure-level=WARN --backend=html5 -o /dev/null " >&2 +exit 1 diff --git a/czech-file-knife/scripts/check-invisible-characters.sh b/czech-file-knife/scripts/check-invisible-characters.sh new file mode 100755 index 000000000..8cc49fca1 --- /dev/null +++ b/czech-file-knife/scripts/check-invisible-characters.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Byte-safe scanner for invisible Unicode encodings and forbidden C0 controls. +set -u + +scan_root="${1:-}" +results_file="${2:-}" +blocking_results_file="${3:-}" +grep_bin="${INVISIBLE_GREP_BIN:-grep}" +find_bin="${INVISIBLE_FIND_BIN:-find}" + +if [[ -z "$scan_root" || ! -d "$scan_root" || -z "$results_file" ]]; then + echo "usage: $0 SCAN_ROOT RESULTS_FILE" >&2 + exit 2 +fi + +# Scan bytes under the C locale. This detects UTF-8 encodings even when another +# byte in the file is invalid UTF-8, while excluding permitted TAB/LF/CR bytes. +pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]|\xC2(?:\xA0|\xAD)|\xE2\x80[\x8B-\x8F\xAA-\xAF]|\xE2\x81(?:\xA0|[\xA6-\xA9])|\xEF\xBB\xBF' +blocking_pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]' +: > "$results_file" || exit 2 +if [[ -n "$blocking_results_file" ]]; then + : > "$blocking_results_file" || exit 2 +fi +scan_error=0 +enumeration_file="$(mktemp)" || exit 2 # TMPDIR-respecting; Hypatia hardcoded_tmp (alert #122) +# Invoked indirectly by the EXIT trap. +# shellcheck disable=SC2329 +cleanup() { + rm -f -- "$enumeration_file" +} +trap cleanup EXIT + +if ! "$find_bin" "$scan_root" \ + -not -path '*/.git/*' -not -path '*/node_modules/*' \ + -not -path '*/.deno/*' -not -path '*/target/*' \ + -not -path '*/_build/*' -not -path '*/deps/*' \ + -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ + -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ + -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ + -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ + -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ + -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \ + -o -name '*.a2ml' -o -name '*.txt' -o -name '*.just' \ + -o -name 'Justfile' -o -name 'Mustfile' -o -name 'Trustfile' -o -name 'Bustfile' \) \ + -print0 > "$enumeration_file"; then + echo "file enumeration failed: $scan_root" >&2 + exit 1 +fi + +while IFS= read -r -d '' filepath; do + LC_ALL=C "$grep_bin" -aPq "$pattern" "$filepath" + status=$? + case "$status" in + 0) + printf '%s\0' "$filepath" >> "$results_file" || scan_error=1 + if [[ -n "$blocking_results_file" ]]; then + LC_ALL=C "$grep_bin" -aPq "$blocking_pattern" "$filepath" + blocking_status=$? + case "$blocking_status" in + 0) printf '%s\0' "$filepath" >> "$blocking_results_file" || scan_error=1 ;; + 1) ;; + *) echo "blocking-classifier error ($blocking_status): $filepath" >&2; scan_error=1 ;; + esac + fi + ;; + 1) ;; + *) echo "scanner error ($status): $filepath" >&2; scan_error=1 ;; + esac +done < "$enumeration_file" + +exit "$scan_error" diff --git a/czech-file-knife/scripts/check-lock-sync.sh b/czech-file-knife/scripts/check-lock-sync.sh new file mode 100755 index 000000000..3088bbd5d --- /dev/null +++ b/czech-file-knife/scripts/check-lock-sync.sh @@ -0,0 +1,297 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-lock-sync.sh — verify .github/workflows/actions.lock is in sync with the +# workflow YAML, in BOTH directions (including job-level reusable-workflow refs), +# AND that the lockfile is TRANSITIVELY CLOSED. +# +# Three clauses, each of which alone is insufficient: +# +# 1. every `uses:` in a workflow is locked under THAT workflow's own path; +# 2. every lockfile entry is still referenced by its workflow (no orphans); +# 3. every ref NAMED anywhere in the lockfile resolves to a top-level +# `dependencies:` record — the lockfile has no dangling edges. +# +# Clause 3 is not decoration. It is the clause that catches the failure mode that +# clauses 1 and 2 are structurally blind to, and it was added only after that +# blindness was measured. On hyperpolymath/cicd-squabbler, 2026-09-22: +# +# commit dangling-edge class result +# fe22bbc workflows: -> dependencies: (ref listed, no record) 4 workflows startup_failure, jobs=0 +# cfadcf9 dependencies: -> dependencies: (record added, its +# own nested uses: unrecorded) the same 4 still startup_failure +# 5286aa5 none - transitively closed 0 startup_failure, all 17 runs create jobs +# +# At fe22bbc AND cfadcf9 this script exited 0, `gh actions-lock --verify-local` +# exited 0, and the Lock Sync Gate reported green - while GitHub was refusing to +# start four workflows. Every local gate was green on a fatal commit. That is the +# guard/consumer trap: the gate asked "is every uses: locked?" and GitHub asks +# "is every locked ref RESOLVABLE?". +# +# The asymmetry that makes clause 3 mandatory, and counter-intuitive: +# * a job-level ref ABSENT from the lockfile entirely is HARMLESS; +# * a ref PRESENT in the lockfile but unresolvable is FATAL. +# So adding entries without closing them is strictly worse than adding nothing. +# Clause 1 demands entries be added; only clause 3 makes that demand safe. Shipping +# clause 1 without clause 3 actively steers a developer into the fatal state: +# Dependabot bumps a job-level ref -> clause 1 reds -> `gh actions-lock` is blind to +# job-level refs and will not backfill -> the developer hand-adds the workflows: +# entry to get green -> no dependencies: record -> CI dies silently, gate green. +# +# Exit 0 only when all three clauses hold. Any violation exits 1. There is no +# warn-only mode: a desync means GitHub refuses to start the run, so it must fail +# the job. A `::warning::` cannot fail a job and would be a vacuous gate. + +set -euo pipefail + +WF_DIR="${1:-.github/workflows}" +LOCK="$WF_DIR/actions.lock" + +# gawk is required: the parser uses 3-argument match(), a GNU extension. mawk +# (the Debian/Ubuntu default `awk`) does not support it, and a silent parse +# failure here would read as a clean pass - the exact failure mode this script +# exists to prevent. Probe it rather than trusting the name. +AWK="" +for cand in gawk awk; do + if command -v "$cand" >/dev/null 2>&1 \ + && echo x | "$cand" '{ if (match($0, /(x)/, m) && m[1] == "x") exit 0; exit 1 }' 2>/dev/null; then + AWK="$cand"; break + fi +done +if [ -z "$AWK" ]; then + echo "check-lock-sync: FATAL: no awk supporting 3-argument match() (need gawk)" >&2 + echo "check-lock-sync: install it with: sudo apt-get install -y gawk" >&2 + exit 1 +fi + +if [ ! -f "$LOCK" ]; then + echo "check-lock-sync: FATAL: no lockfile at $LOCK" >&2 + exit 1 +fi + +shopt -s nullglob +mapfile -t WORKFLOWS < <(printf '%s\n' "$WF_DIR"/*.yml "$WF_DIR"/*.yaml | sort -u) +if [ "${#WORKFLOWS[@]}" -eq 0 ]; then + echo "check-lock-sync: FATAL: no workflow files under $WF_DIR" >&2 + exit 1 +fi + +read -r -d '' PROG <<'AWK' || true +# owner/repo[/subpath...]@ref -> owner/repo@ref ("" if not an external ref) +function norm(r, at, path, ref, n, parts) { + at = 0 + for (n = length(r); n > 0; n--) { if (substr(r, n, 1) == "@") { at = n; break } } + if (at == 0) return "" + path = substr(r, 1, at - 1); ref = substr(r, at + 1) + if (path == "" || ref == "") return "" + if (substr(path, 1, 2) == "./" || substr(path, 1, 2) == "$/") return "" # local action + if (split(path, parts, "/") < 2) return "" + return parts[1] "/" parts[2] "@" ref +} + +# Fold case on the OWNER/REPO segment only, for comparison keys. GitHub resolves +# owner and repository names case-insensitively, and this is measured, not assumed: +# metadatastician/pong-ping's lockfile records sonarsource/sonarqube-scan-action@v8.2.1 +# while sonarqube.yml says SonarSource/..., and at commit cd5f90f that workflow ran +# SUCCESS while codeql.yml at the SAME commit was startup_failure. A same-commit +# control, so the case difference is provably not what kills a run. +# The REF is NOT folded: git tags and branch names are case-sensitive. +function ck(r, at, s) { + at = 0 + for (s = length(r); s > 0; s--) { if (substr(r, s, 1) == "@") { at = s; break } } + if (at == 0) return tolower(r) + return tolower(substr(r, 1, at - 1)) substr(r, at) +} + +# ---------- pass 1: the lockfile ---------- +FILENAME == lockfile { + if ($0 ~ /^workflows:[[:space:]]*$/) { inwf = 1; indep = 0; next } + if ($0 ~ /^dependencies:[[:space:]]*$/) { inwf = 0; indep = 1; next } + if ($0 ~ /^[a-z_]+:/) { inwf = 0; indep = 0; next } + + # --- the dependencies: section, for clause 3 --- + if (indep) { + # " 'owner/repo@ref':" -- a top-level dependency record + if (match($0, /^ '([^']+)':/, m)) { + depkey = m[1] + haverec[ck(depkey)] = 1; disp[ck(depkey)] = depkey + next + } + # " - 'owner/repo@ref'" -- a nested uses: of that record + if (match($0, /^ - '([^']+)'/, m) && depkey != "") { + r = ck(m[1]); disp[r] = m[1] + want[r] = 1 + wantsrc[r] = wantsrc[r] " dependencies:" depkey + next + } + next + } + + if (!inwf) next + + # " '.github/workflows/x.yml':" or "... : []" + if (match($0, /^ '([^']+)':/, m)) { + cur = m[1] + seen_path[cur] = 1 + next + } + if (match($0, /^ - '([^']+)'[[:space:]]*$/, m) && cur != "") { + lr = ck(m[1]); disp[lr] = m[1]; lock[cur, lr] = 1 + lockcount[cur]++ + want[lr] = 1 + wantsrc[lr] = wantsrc[lr] " " cur + next + } + next +} + +# ---------- pass 2: the workflow YAML ---------- +FNR == 1 { wf = FILENAME } +{ + line = $0 + sub(/[[:space:]]+#.*$/, "", line) # strip trailing comment + if (match(line, /^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(.+)$/, m)) { + raw = m[1] + gsub(/^["']|["']$/, "", raw) + gsub(/[[:space:]]+$/, "", raw) + if (raw ~ /^\$\//) { dollar[wf] = dollar[wf] " " raw; next } # known corruption + n = norm(raw) + if (n != "") { uses[wf, ck(n)] = 1; useslist[wf] = useslist[wf] " " n } + } +} + +END { + bad = 0 + for (i = 1; i < ARGC; i++) { + wf = ARGV[i] + if (wf == lockfile) continue + key = wf + sub(/.*\//, "", key) + key = ".github/workflows/" key # the lockfile always uses this canonical path + + if (dollar[wf] != "") { + printf "FAIL %s\n invalid local-action rewrite (uses: $/...):%s\n", key, dollar[wf] + bad = 1 + } + + # --- clause 1: every uses: must be locked under THIS path --- + nu = split(useslist[wf], u, " ") + delete uniq; missing = "" + for (j = 1; j <= nu; j++) { + if (u[j] == "" || (u[j] in uniq)) continue + uniq[u[j]] = 1 + if (!((key SUBSEP ck(u[j])) in lock)) missing = missing " " u[j] + } + if (missing != "") { + if (!(key in seen_path)) + printf "FAIL %s\n not onboarded: no lockfile entry for this path\n unlocked refs:%s\n", key, missing + else + printf "FAIL %s\n refs missing from the lockfile:%s\n", key, missing + bad = 1 + } + + # --- clause 2: every lock entry must be referenced by this workflow --- + orphan = "" + for (k in lock) { + split(k, kp, SUBSEP) + if (kp[1] != key) continue + if (!((wf SUBSEP kp[2]) in uses)) orphan = orphan " " (kp[2] in disp ? disp[kp[2]] : kp[2]) + } + if (orphan != "") { + printf "FAIL %s\n stale lockfile entries, no uses: references them:%s\n", key, orphan + bad = 1 + } + } + + # --- lockfile entries for workflow files that no longer exist --- + for (p in seen_path) { + found = 0 + for (i = 1; i < ARGC; i++) { + q = ARGV[i]; if (q == lockfile) continue + sub(/.*\//, "", q); q = ".github/workflows/" q + if (q == p) { found = 1; break } + } + if (!found) { printf "FAIL %s\n lockfile entry for a workflow file that does not exist\n", p; bad = 1 } + } + + # --- clause 4: COVERAGE. Every workflow FILE must have a key in the lockfile, + # including one with no uses: at all - the value is then an empty list. + # MEASURED 2026-09-22, single-variable flip on two independent repos: + # hyperpolymath/verisimdb's lock-sync-gate.yml was startup_failure 7 times + # running with ZERO uses: refs, and adding + # '.github/workflows/lock-sync-gate.yml': [] + # flipped it to success; reproduced on hyperpolymath/blocky-writer, 2 of 2. + # `gh actions-lock` already emits this empty-list form for other zero-uses: + # workflows (labels.yml), so it is the generator's own convention, not ours. + # Clauses 1-3 CANNOT catch this: they ask "is every uses: locked?", and a + # workflow with no uses: satisfies them vacuously while GitHub still refuses + # to start it. 13 repos passed clauses 1-3 with exactly this gap. + nunlisted = 0; unlisted = "" + for (i = 1; i < ARGC; i++) { + q = ARGV[i]; if (q == lockfile) continue + sub(/.*\//, "", q); q = ".github/workflows/" q + if (q in seen_path) continue + nunlisted++; unlisted = unlisted "\n " q + } + if (nunlisted > 0) { + printf "FAIL actions.lock: UNLISTED WORKFLOWS\n" + printf " %d workflow file(s) have no key in the lockfile. GitHub refuses such a\n", nunlisted + printf " run at startup (jobs=0) even when the workflow has no uses: at all.\n" + printf " The entry for a zero-uses: workflow is an empty list:%s\n", unlisted + bad = 1 + } + + # --- clause 3: TRANSITIVE CLOSURE. Every ref named anywhere in the lockfile + # must resolve to a top-level dependencies: record. A dangling edge makes + # GitHub refuse the run at startup with jobs=0. --- + ndang = 0; dang = "" + for (r in want) { + if (r !~ /^[^\/]+\/[^\/@]+@/) continue # not an OWNER/REPO@REF pin; not ours to resolve + if (r in haverec) continue + ndang++ + dang = dang sprintf("\n %s\n named by:%s", (r in disp ? disp[r] : r), wantsrc[r]) + } + if (ndang > 0) { + printf "FAIL actions.lock: DANGLING EDGES\n" + printf " %d ref(s) are named in the lockfile but have no top-level dependencies: record.%s\n", ndang, dang + bad = 1 + } + + # --- a dependencies: record nothing names is dead weight, not fatal: report only --- + nunref = 0 + for (d in haverec) if (!(d in want)) nunref++ + + if (bad) { + print "" + print "actions.lock is OUT OF SYNC with the workflow YAML, or is not transitively closed." + print "GitHub refuses such a run at startup: zero jobs are created and the run" + print "reports \"This run likely failed because of a workflow file issue.\"" + print "" + print "Fix, in this order:" + print " 1. `gh actions-lock --no-migrate-local-actions`, then review the diff. It does" + print " NOT handle job-level reusable-workflow refs and it can de-pin bare SHAs to" + print " floating tags - both must be corrected by hand." + print " 2. For any DANGLING EDGES above, add a top-level `dependencies:` record for each" + print " ref. A leaf record may legally omit the nested `uses:` key entirely, so adding" + print " leaves introduces no new dangling edges and closure terminates in one pass." + print " Keys are sorted with LC_ALL=C collation (ASCII '-' 0x2d sorts before '@' 0x40)." + print " 3. Nested `uses:` entries must be bare OWNER/REPO@REF. A subpath pin such as" + print " github/codeql-action/upload-sarif@ is REJECTED by the schema; collapse it" + print " to github/codeql-action@." + print " 4. For any UNLISTED WORKFLOWS above, add the path as a lockfile key. A workflow" + print " with no uses: takes an empty list: \x27.github/workflows/x.yml\x27: []" + print " `gh actions-lock` has been observed to OMIT such a workflow entirely; that" + print " omission is itself the defect, so re-running the tool may not add it." + exit 1 + } + printf "actions.lock is in sync and transitively closed:\n" + printf " * every uses: is locked under its own workflow path (job-level reusable refs included)\n" + printf " * every lockfile entry is still referenced\n" + printf " * every ref named in the lockfile resolves to a dependencies: record (0 dangling edges)\n" + printf " * every workflow file has a lockfile key (zero-uses: workflows included)\n" + if (nunref > 0) + printf " note: %d dependencies: record(s) are unreferenced - harmless, but prunable.\n", nunref +} +AWK + +"$AWK" -v lockfile="$LOCK" "$PROG" "$LOCK" "${WORKFLOWS[@]}" diff --git a/czech-file-knife/scripts/check-no-md-in-docs.sh b/czech-file-knife/scripts/check-no-md-in-docs.sh new file mode 100644 index 000000000..1658f935a --- /dev/null +++ b/czech-file-knife/scripts/check-no-md-in-docs.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-no-md-in-docs.sh — enforce "AsciiDoc by default for general docs". +# +# Estate rule: .adoc for general docs (TOPOLOGY, READINESS, ROADMAP, etc.); +# .md only for files GitHub's community-health rules special-case by name +# (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CHANGELOG, etc.) — those live at +# root or in .github/, never under docs/. +# +# Fails if any .md files exist under docs/. Add justified entries to the +# ALLOWED list below if a docs/-rooted .md is genuinely needed (rare). +# +# Exit codes: +# 0 — no .md files under docs/ (or all matches are allow-listed) +# 1 — disallowed .md files found +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" +DOCS_DIR="$REPO_ROOT/docs" + +# Justified exceptions, relative to repo root. Empty by default. +ALLOWED=() +# docs/berrywiki/ and docs/wikis/ are wiki-SYNC source trees: their content is +# mirrored to/from forge-hosted wikis (GitHub/GitLab), which are inherently +# Markdown. Converting them to AsciiDoc would break the sync contract, so the +# directories are allow-listed rather than the files. +ALLOWED_DIRS=("docs/berrywiki/" "docs/wikis/") + +if [ ! -d "$DOCS_DIR" ]; then + echo "PASS: no docs/ directory (nothing to check)" + exit 0 +fi + +mapfile -t HITS < <(find "$DOCS_DIR" -name '*.md' -type f 2>/dev/null | sort) + +EXTRAS=() +for hit in "${HITS[@]}"; do + rel="${hit#"$REPO_ROOT/"}" + skip=0 + for allowed in "${ALLOWED[@]}"; do + if [ "$rel" = "$allowed" ]; then skip=1; break; fi + done + for allowed_dir in "${ALLOWED_DIRS[@]}"; do + if [[ "$rel" == "$allowed_dir"* ]]; then skip=1; break; fi + done + if [ $skip -eq 0 ]; then EXTRAS+=("$rel"); fi +done + +if [ ${#EXTRAS[@]} -eq 0 ]; then + echo "PASS: no .md files under docs/ (${#HITS[@]} total found, ${#ALLOWED[@]} allow-listed)" + exit 0 +fi + +echo "FAIL: ${#EXTRAS[@]} .md files found under docs/ (estate rule: AsciiDoc by default):" >&2 +for e in "${EXTRAS[@]}"; do + echo " - $e" >&2 +done +echo "" >&2 +echo "Convert these to .adoc, or add a justified entry to the ALLOWED list" >&2 +echo "in scripts/check-no-md-in-docs.sh." >&2 +exit 1 diff --git a/czech-file-knife/scripts/check-no-placeholders.sh b/czech-file-knife/scripts/check-no-placeholders.sh new file mode 100755 index 000000000..6152cb899 --- /dev/null +++ b/czech-file-knife/scripts/check-no-placeholders.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-no-placeholders.sh — no repo may ship an unfilled {{PLACEHOLDER}}. +# +# Estate rule (methodology.a2ml: reject-if-contains): a token that `just repo-init` +# did not fill is debt, and in .github/settings.yml or SECURITY.md it is a +# defect with consequences — probot/settings applies settings.yml on every push, +# and a security policy that cites a key nobody holds is worse than one that +# says "email us". +# +# This is the single implementation of that rule, called from two places: +# * .github/workflows/openssf-compliance.yml — on the repo as committed +# * tests/e2e/template_instantiation_test.sh — on a freshly init'd repo, +# which is where a leak is still cheap to fix +# It exists as a script rather than inline shell in each caller because the +# previous split — a workflow that checked a hand-listed set of files, and an +# e2e test that re-implemented substitution with its own token list — let the +# two drift until the test passed while real instantiation leaked. +# +# Scans every text file and allow-lists the few legitimate carriers, rather +# than checking a list of files someone must remember to extend. The old +# required-files list omitted .github/settings.yml and ANCHOR.a2ml, which is +# precisely where the leaks were. +# +# Matches upper-snake brace tokens only. Justfiles are skipped entirely: an +# ARGS token there is just's own interpolation syntax, not a template token. +# GitHub Actions expressions are ${{ dotted.lower }} and do not match. +# +# Exit codes: +# 0 — no unfilled tokens (or this is a template repo, where tokens are the product) +# 1 — unfilled tokens found +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" + +if [ ! -d "$REPO_ROOT" ]; then + echo "usage: $0 [repo-root]" >&2 + exit 2 +fi + +# ─── settings.yml identity guard — runs EVERYWHERE, template repos included ──── +# +# This check deliberately precedes the template exemption below. That exemption +# is why the original incident went unseen: the gate skipped `*-template-repo` +# entirely, so nobody noticed that .github/settings.yml shipped `name: "czech-file-knife"` +# — and .github/settings.yml is not inert content in a template. probot/settings +# applies it on every push to the default branch, in the template as much as in +# an instantiation. The template submitted the literal `czech-file-knife` as its own +# name; GitHub collapsed the illegal braces to dashes and renamed the repository +# to `-REPO-`, which then read as a deleted repo. +# +# So: in this one file, a placeholder is never "the product". Neither is an +# identity key with a real value — `name`/`private` cannot be inherited by a +# child repo without being wrong (see the header of .github/settings.yml). +SETTINGS="$REPO_ROOT/.github/settings.yml" +if [ -f "$SETTINGS" ]; then + settings_fail=0 + + # Comment-aware: the file's own header documents the incident and has to be + # able to quote the offending token. Prose about a token is not a token — + # the same distinction META_TOKENS draws below. Line numbers are preserved + # by filtering `grep -n` output rather than the file. + settings_tokens="$(grep -nE '\{\{' "$SETTINGS" | grep -vE '^[0-9]+:[[:space:]]*#' || true)" + if [ -n "$settings_tokens" ]; then + echo "FAIL: .github/settings.yml contains an unrendered {{ token." >&2 + printf '%s\n' "$settings_tokens" | sed 's/^/ /' >&2 + settings_fail=1 + fi + + # Keys of the `repository:` map sit at exactly two spaces of indent. Label + # and branch entries are list items (" - name:") and are not matched. + if grep -qE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS"; then + echo "FAIL: .github/settings.yml declares repository identity." >&2 + grep -nE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS" \ + | sed 's/^/ /' >&2 + settings_fail=1 + fi + + if [ "$settings_fail" -ne 0 ]; then + echo "" >&2 + echo "probot/settings applies this file on every push to the default branch," >&2 + echo "so these keys are enforced, not described. Repository identity and" >&2 + echo "visibility are set out of band at creation time — by \`just repo-init\` via" >&2 + echo "\`gh\` for minted repos, and deliberately by the owner for the template." >&2 + exit 1 + fi +fi + +# A template repo's placeholders ARE its product — they are what `just repo-init` +# consumes. Any other repo is an instantiation and is checked in full. +# +# Identity comes from the git remote, not the directory name. +# +# This used to be `basename "$(pwd)"`, which is right in CI — GITHUB_REPOSITORY +# is set to `owner/czech-file-knife` and matches — but wrong anywhere the +# checkout is not literally named `*-template-repo`. A git worktree is the common +# case: `git worktree add .claude/worktrees/defects` gives basename `defects`, +# the exemption misses, and the gate reports every one of the template's ~85 +# deliberate placeholder files as a failure. A clone into `czech-file-knife-2`, +# or any renamed directory, does the same. +# +# The remote URL is the repo's actual identity and survives all of that. The +# basename remains as the last fallback for a checkout with no remote. +REPO_NAME="${GITHUB_REPOSITORY:-}" +if [ -z "$REPO_NAME" ]; then + REPO_NAME="$(git -C "$REPO_ROOT" config --get remote.origin.url 2>/dev/null \ + | sed -E 's#(\.git)?/?$##; s#^.*[:/]([^/]+/[^/]+)$#\1#')" +fi +[ -z "$REPO_NAME" ] && REPO_NAME="$(cd "$REPO_ROOT" && basename "$(pwd)")" +case "$REPO_NAME" in + *-template-repo) + echo "PASS: $REPO_NAME is a template repo — unfilled tokens are intentional" + echo " (.github/settings.yml identity guard above still applied)" + exit 0 + ;; +esac + +# Files that legitimately contain tokens after instantiation. +ALLOWED=( + ".machine_readable/ai/PLACEHOLDERS.adoc" # the token vocabulary itself + "EXPLAINME.adoc" # prose explaining that tokens exist + "scripts/check-no-placeholders.sh" # this file (the pattern above) + "tests/e2e/template_instantiation_test.sh" # names tokens in its answer list +) + +is_allowed() { + local rel="$1" + for a in "${ALLOWED[@]}"; do + [ "$rel" = "$a" ] && return 0 + done + # just owns brace tokens inside justfiles — an ARGS token there is + # interpolation, not a placeholder. Justfiles are not only at the root: + # the contractiles ship one too. + case "$rel" in + Justfile|justfile|*/Justfile|*/justfile|*.just) return 0 ;; + esac + return 1 +} + +# Metasyntactic tokens: prose *about* tokens, not tokens. "Replace all +# {{PLACEHOLDER}} values" names the concept — there is no PLACEHOLDER variable +# for init to fill, so these can never be a leak, and flagging them would only +# teach people that this gate cries wolf. Real tokens name a real init variable. +META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + +LEAKS=() +while IFS= read -r hit; do + rel="${hit#"$REPO_ROOT"/}" + is_allowed "$rel" && continue + # Re-check the file for at least one non-metasyntactic token. + if grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$hit" \ + | grep -qvE "^\{\{($META_TOKENS)\}\}$"; then + LEAKS+=("$rel") + fi +done < <(grep -rlE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT" \ + --exclude-dir=.git --binary-files=without-match 2>/dev/null | sort) + +if [ ${#LEAKS[@]} -eq 0 ]; then + echo "PASS: no unfilled {{PLACEHOLDER}} tokens" + exit 0 +fi + +echo "FAIL: ${#LEAKS[@]} file(s) contain unfilled {{PLACEHOLDER}} tokens:" >&2 +for leak in "${LEAKS[@]}"; do + tokens=$(grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT/$leak" \ + | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ') + echo " - $leak: $tokens" >&2 +done +echo "" >&2 +echo "Each token must either be filled by build/just/init.just's SED_ARGS, or" >&2 +echo "removed from the shipped file. A token with no possible value (a PGP key" >&2 +echo "the estate does not hold) makes this gate unsatisfiable — delete the" >&2 +echo "section instead of leaving the gate permanently red." >&2 +exit 1 diff --git a/czech-file-knife/scripts/check-no-vlang.sh b/czech-file-knife/scripts/check-no-vlang.sh new file mode 100755 index 000000000..aeb62fcb8 --- /dev/null +++ b/czech-file-knife/scripts/check-no-vlang.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Enforce the estate ban on the V programming language. Zig is the supported +# systems/FFI language and must never be matched by this check. + +set -euo pipefail + +REPO_ROOT="${1:-.}" +if [ ! -d "$REPO_ROOT" ]; then + echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 + exit 2 +fi + +PATTERN='gen-v-connector|V-TRIPLE|v-triple|vlang|connectors/v-|import[[:space:]]+vweb' +HITS="" +V_MODS="" + +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + # Search tracked content only. The exclusions are the policy and its + # enforcement/tests, which necessarily name the forbidden patterns. + HITS=$(git -C "$REPO_ROOT" grep -n -i -E "$PATTERN" -- \ + . \ + ':(exclude)affinescript/**' \ + ':(exclude)scripts/check-no-vlang.sh' \ + ':(exclude)tests/workflows/check_no_vlang_test.sh' \ + ':(exclude).github/workflows/estate-rules.yml' \ + ':(exclude).machine_readable/descriptiles/PLAYBOOK.a2ml' \ + 2>/dev/null || true) + V_MODS=$(git -C "$REPO_ROOT" ls-files -- 'v.mod' '**/v.mod' 2>/dev/null || true) +else + HITS=$(grep -rni -E "$PATTERN" "$REPO_ROOT" \ + --exclude-dir=.git \ + --exclude-dir=affinescript \ + --exclude-dir=node_modules \ + --exclude=check-no-vlang.sh \ + --exclude=check_no_vlang_test.sh \ + --exclude=estate-rules.yml \ + --exclude=PLAYBOOK.a2ml \ + 2>/dev/null || true) + V_MODS=$(find "$REPO_ROOT" -type f -name v.mod \ + -not -path '*/.git/*' -not -path '*/affinescript/*' \ + -printf '%P\n' 2>/dev/null || true) +fi + +# Drop self-references. A line whose only match is this checker's own file name +# is an INVOCATION, not a V-language artefact. The :(exclude) list above can +# only name call sites that already exist, so without this filter the gate +# false-positives the moment a repo invokes it from a new place -- a Justfile, a +# pre-push hook, a different workflow. Proven 2026-09-02: a Justfile line +# `bash scripts/check-no-vlang.sh .` was reported as a V-language reference. +SELF_REF='check[-_]no[-_]vlang(_test)?[.]sh' +if [ -n "$HITS" ]; then + HITS=$(printf '%s\n' "$HITS" | awk -v self="$SELF_REF" -v pat="$PATTERN" ' + { + line = tolower($0) + gsub(self, "", line) + if (line ~ tolower(pat)) { print } + }') +fi + +if [ -z "$HITS" ] && [ -z "$V_MODS" ]; then + echo "PASS: no V-language references in the inspected repository" + exit 0 +fi + +COUNT=0 +if [ -n "$HITS" ]; then + CONTENT_COUNT=$(printf '%s\n' "$HITS" | awk 'NF { count++ } END { print count + 0 }') + COUNT=$((COUNT + CONTENT_COUNT)) +fi +if [ -n "$V_MODS" ]; then + FILE_COUNT=$(printf '%s\n' "$V_MODS" | awk 'NF { count++ } END { print count + 0 }') + COUNT=$((COUNT + FILE_COUNT)) +fi + +echo "FAIL: $COUNT V-language reference(s) found (estate policy forbids V):" >&2 +if [ -n "$HITS" ]; then + printf '%s\n' "$HITS" | sed 's/^/ /' >&2 +fi +if [ -n "$V_MODS" ]; then + printf '%s\n' "$V_MODS" | sed 's/^/ tracked module file: /' >&2 +fi +echo >&2 +echo "Remove the V-language remnants; use the supported Zig adapter where an FFI/API bridge is needed." >&2 +exit 1 diff --git a/czech-file-knife/scripts/check-proofs.sh b/czech-file-knife/scripts/check-proofs.sh new file mode 100755 index 000000000..0135dfed8 --- /dev/null +++ b/czech-file-knife/scripts/check-proofs.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-proofs.sh prover in: idris2 | lean4 | agda | coq +# +# The single source of truth for "do the proofs in this repo compile?". +# build/just/proofs.just calls this (`just proof-check-`), and CI should +# too, so a green local run and a green CI run mean the same thing. +# +# It replaces four separate "checks that could not fail" that let non-compiling +# proofs sit in estate repos for months while every status file said "proved": +# +# 1. `command -v || { echo SKIP; exit 0; }` — a MISSING TOOLCHAIN +# reported success. A gate that cannot run must never report OK: it +# manufactures false confidence, which is worse than having no gate. +# 2. ` --check ` — Idris2 (and friends) derive the +# expected module name from the path they are handed, so a module checked +# from the wrong directory fails on a name mismatch rather than its real +# errors, and verdicts invert. Every module here is checked from its own +# SOURCE ROOT (declared in the MANIFEST). +# 3. Path-filtered CI that only looked at one directory — nothing checked the +# rest. Here, a proof file present on disk but absent from the MANIFEST is +# an ERROR, so new proofs are gated by default, not by remembering. +# 4. `idris2 --check X && ok` — `idris2 --check` EXITS 0 ON A MISSING IMPORT +# (verified against 0.7.0) while printing `Error: ...`. Testing the exit +# code alone is unsound; for idris2 we require exit 0 AND no `Error:` line. +# +# They share one shape: a null check that emits reassuring text. If you extend +# this script, the test to apply is not "does it pass?" but "have I watched it +# fail?". +# +# CONVENTION: proofs live under verification/proofs// ; the MANIFEST for +# a prover is verification/proofs//MANIFEST, one entry per line: +# +# ||gated|quarantine| +# +# source-root : directory the prover is invoked from, chosen so the module's +# declared name matches its path (getting this wrong is hole #2). +# gated : MUST compile. A failure fails this script and CI. +# quarantine : known-broken, tracked in STATE.a2ml. Must CONTINUE to fail; +# if one starts compiling the script fails and tells you to +# promote it, so the list cannot rot into a permanent excuse. +# +# Blank lines and lines starting with # are ignored. +# +# Exit: 0 = every gated module compiles AND every quarantined module still fails +# AND every proof file on disk is listed; 1 = otherwise; 2 = misuse. + +set -euo pipefail + +PROVER="${1:-}" +case "$PROVER" in + idris2|lean4|agda|coq) ;; + *) echo "usage: $(basename "$0") " >&2; exit 2 ;; +esac + +# This script lives in /scripts/ ; run everything from the repo root. +cd "$(dirname "${BASH_SOURCE[0]}")/.." +ROOT="$PWD" +PROOF_DIR="verification/proofs/$PROVER" +MANIFEST_FILE="$PROOF_DIR/MANIFEST" + +# --- per-prover configuration ------------------------------------------------- +# CMD : executable that must be on PATH (absent => FAIL, never skip). +# EXT : file extension, for the "unlisted proof" coverage scan. +# ERROR_RE : if non-empty, output must not match it even when the exit code is 0. +# Only idris2 needs this (its --check exits 0 on a missing import); +# for lean4 it is a harmless belt-and-braces guard. +case "$PROVER" in + idris2) CMD=idris2; EXT=idr; ERROR_RE='^Error:' ;; + lean4) CMD=lean; EXT=lean; ERROR_RE='error:' ;; + agda) CMD=agda; EXT=agda; ERROR_RE='' ;; + coq) CMD=coqc; EXT=v; ERROR_RE='' ;; +esac + +check_one() { + # $1 source-root (rel to ROOT), $2 module (rel to source-root). + # Sets LAST_OUT to the tool output on failure; returns 0 iff the module compiles. + local root="$1" rel="$2" out rc + set +e + case "$PROVER" in + idris2) out="$(cd "$ROOT/$root" && idris2 --check "$rel" 2>&1)"; rc=$? ;; + lean4) out="$(cd "$ROOT/$root" && lean "$rel" 2>&1)"; rc=$? ;; + agda) out="$(cd "$ROOT/$root" && agda --safe "$rel" 2>&1)"; rc=$? ;; + coq) out="$(cd "$ROOT/$root" && coqc "$rel" 2>&1)"; rc=$? ;; + esac + set -e + if [ "$rc" -eq 0 ] && { [ -z "$ERROR_RE" ] || ! grep -qE "$ERROR_RE" <<<"$out"; }; then + LAST_OUT=""; return 0 + fi + LAST_OUT="$out"; return 1 +} + +echo "=== $PROVER proof check ===" + +# --- toolchain: absent means FAIL, never skip --------------------------------- +if ! command -v "$CMD" >/dev/null 2>&1; then + { + echo "FAIL: '$CMD' not found on PATH." + echo + echo "This is deliberately fatal. The previous recipe did 'exit 0' here with" + echo "\"SKIP: $CMD not installed\", so every $PROVER proof reported green on any" + echo "machine that could not check it. Install the $PROVER toolchain, or run" + echo "this in CI where the workflow installs it." + } >&2 + exit 1 +fi +"$CMD" --version 2>/dev/null | head -1 || true +echo + +# --- a repo with proofs but no MANIFEST is itself a failure ------------------- +if [ ! -f "$MANIFEST_FILE" ]; then + if [ -d "$PROOF_DIR" ] && [ -n "$(find "$PROOF_DIR" -name "*.$EXT" 2>/dev/null)" ]; then + echo "FAIL: $PROOF_DIR contains .$EXT proofs but has no MANIFEST." >&2 + echo " Create $MANIFEST_FILE listing each as 'gated' or 'quarantine'." >&2 + exit 1 + fi + echo "no $PROOF_DIR/*.$EXT proofs and no MANIFEST — nothing to check." + exit 0 +fi + +fails=0 +unexpected_pass=0 +listed_tmp="$(mktemp)" +trap 'rm -f "$listed_tmp"' EXIT + +while IFS='|' read -r root rel status note; do + # skip blank lines and comments + [ -z "${root// }" ] && continue + case "${root#"${root%%[![:space:]]*}"}" in \#*) continue ;; esac + printf ' %-30s %-24s ' "$root" "$rel" + echo "$root/$rel" >>"$listed_tmp" + if check_one "$root" "$rel"; then + if [ "$status" = gated ]; then + echo "PASS" + else + echo "PASS -- UNEXPECTED (quarantined module now compiles)" + echo " Promote '$rel' to 'gated' in $MANIFEST_FILE and update STATE.a2ml." + unexpected_pass=$((unexpected_pass + 1)) + fi + else + if [ "$status" = gated ]; then + echo "FAIL" + printf '%s\n' "${LAST_OUT//$'\n'/$'\n '}" | sed '1s/^/ /' + fails=$((fails + 1)) + else + echo "fail (quarantined, expected)" + [ -n "${note// }" ] && echo " reason:$note" + fi + fi +done < "$MANIFEST_FILE" + +# --- coverage: every proof on disk must be listed ----------------------------- +# The anti-recurrence rule: proofs went unchecked for months because nothing +# forced them onto anyone's list. A file absent from the MANIFEST is an error. +echo +echo "=== manifest coverage ($PROOF_DIR) ===" +listed="$(sort -u "$listed_tmp")" +found="$(cd "$ROOT" && find "$PROOF_DIR" -name "*.$EXT" -not -path '*/build/*' 2>/dev/null | sort)" +unlisted="$(comm -13 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" +missing="$(comm -23 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" + +if [ -n "${unlisted//[[:space:]]/}" ]; then + echo "FAIL: .$EXT proofs on disk but absent from $MANIFEST_FILE:" + printf ' %s\n' $unlisted + echo " List each as 'gated' or 'quarantine'; new proofs are gated by default." + fails=$((fails + 1)) +fi +if [ -n "${missing//[[:space:]]/}" ]; then + echo "FAIL: MANIFEST lists modules that do not exist (stale entries):" + printf ' %s\n' $missing + fails=$((fails + 1)) +fi +[ -z "${unlisted//[[:space:]]/}${missing//[[:space:]]/}" ] && \ + echo " all $(printf '%s\n' "$found" | grep -c .) .$EXT file(s) accounted for" + +echo +if [ "$fails" -gt 0 ] || [ "$unexpected_pass" -gt 0 ]; then + echo "RESULT: FAIL ($fails failure(s), $unexpected_pass unexpected pass(es))" + exit 1 +fi +echo "RESULT: PASS -- gated modules compile; quarantined modules still fail as recorded" diff --git a/czech-file-knife/scripts/check-root-shape.sh b/czech-file-knife/scripts/check-root-shape.sh new file mode 100755 index 000000000..6ffdb7dad --- /dev/null +++ b/czech-file-knife/scripts/check-root-shape.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-root-shape.sh — enforce the canonical root shape, in BOTH directions, +# against the repository root allowlist, under EITHER canonical spelling: +# .machine_readable/root-allow.txt (dotted, CANONICAL ESTATE-WIDE) +# machine-readable/root-allow.txt (hyphenated, accepted legacy) +# +# The dotted form is the standard (owner ruling 2026-09-17; estate census 48 +# repos dotted vs 9 hyphenated). The hyphenated form is still RESOLVED, not +# rejected, because the checker is shared with repos that have not migrated. +# Removing this branch would turn 9 working gates into exit-2 setup errors. +# +# * an entry at root that is not listed -> drift (extra) +# * a listed entry WITHOUT '?' that is missing -> drift (missing) +# +# The second direction was absent until 2026-08, and its absence is why the +# allowlist rotted: it accumulated 19 permissions for files the April root +# cleanup had already moved into docs/, and nothing could ever notice. A +# one-directional allowlist only ratchets open, so over time it licenses +# exactly the drift it was written to prevent. +# +# '?' marks an entry that is legitimately absent in some conforming repo — +# template-only material removed at mint, or a capability-gated module. +# +# Companion to scripts/validate-template.sh: that script enforces required +# files; this one enforces the shape as a whole. +# +# Exit codes: +# 0 — root matches allowlist +# 1 — drift (extras at root, or required entries missing) +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" +REPO_ROOT_DOTTED="${REPO_ROOT}/.machine_readable/root-allow.txt" +REPO_ROOT_HYPHEN="${REPO_ROOT}/machine-readable/root-allow.txt" + +# Both spellings are estate contract. Resolve whichever exists; if BOTH exist +# that is itself drift (two sources of truth) and is refused. +if [ -f "$REPO_ROOT_DOTTED" ] && [ -f "$REPO_ROOT_HYPHEN" ]; then + echo "ERROR: both .machine_readable/ and machine-readable/ carry a root-allow.txt;" >&2 + echo " pick one spelling — two allowlists cannot both be canonical." >&2 + exit 2 +elif [ -f "$REPO_ROOT_DOTTED" ]; then + ALLOW_FILE="$REPO_ROOT_DOTTED" +elif [ -f "$REPO_ROOT_HYPHEN" ]; then + ALLOW_FILE="$REPO_ROOT_HYPHEN" +else + echo "ERROR: allowlist not found at either $REPO_ROOT_DOTTED or $REPO_ROOT_HYPHEN" >&2 + exit 2 +fi + +# Build the allow set: strip comments, trailing slashes, and blank lines. +# A leading '?' marks the entry optional; it is not part of the name. +mapfile -t ALLOW_RAW < <( + sed -E 's/[[:space:]]*#.*$//' "$ALLOW_FILE" \ + | sed -E 's|/$||' \ + | awk 'NF' \ + | sed -E 's/[[:space:]]+$//' +) + +declare -A ALLOW_SET=() +REQUIRED=() +ALLOW=() +for raw in "${ALLOW_RAW[@]}"; do + if [[ "$raw" == '?'* ]]; then + entry="${raw#\?}" + else + entry="$raw" + REQUIRED+=("$entry") + fi + ALLOW_SET["$entry"]=1 + ALLOW+=("$entry") +done + +# Enumerate everything at the repository root, EXCLUDING git-ignored entries. +# +# This was a bare `find`, which contradicted the contract root-allow.txt states +# ("Anything tracked at root that is not in this list is drift"): a plain +# filesystem scan also sees build output. Any repo with a root-level build +# directory -- `target/` for Cargo, `node_modules/`, `_build/` for Mix -- +# therefore failed this gate the moment someone built before running it, and +# the tempting "fix" was to allowlist an artifact directory that must never be +# committed. +# +# Filtering through `git check-ignore` makes the check mean what it says. The +# fallback keeps the script working outside a git worktree. +mapfile -t ACTUAL < <( + cd "$REPO_ROOT" && \ + find . -mindepth 1 -maxdepth 1 \ + ! -name '.' \ + -printf '%f\n' \ + | { if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git check-ignore --stdin --non-matching --verbose 2>/dev/null \ + | sed -n 's/^::[[:space:]]//p' + else + cat + fi; } \ + | sort +) + +declare -A ACTUAL_SET=() +for entry in "${ACTUAL[@]}"; do + ACTUAL_SET["$entry"]=1 +done + +# Direction 1 — present at root but not permitted. +EXTRAS=() +for entry in "${ACTUAL[@]}"; do + if [ -z "${ALLOW_SET[$entry]+x}" ]; then + EXTRAS+=("$entry") + fi +done + +# Direction 2 — required by the allowlist but not present. +MISSING=() +for entry in "${REQUIRED[@]}"; do + if [ -z "${ACTUAL_SET[$entry]+x}" ]; then + MISSING+=("$entry") + fi +done + +if [ ${#EXTRAS[@]} -eq 0 ] && [ ${#MISSING[@]} -eq 0 ]; then + OPTIONAL_COUNT=$(( ${#ALLOW[@]} - ${#REQUIRED[@]} )) + echo "PASS: root matches allowlist (${#ACTUAL[@]} entries; ${#REQUIRED[@]} required, ${OPTIONAL_COUNT} optional)" + exit 0 +fi + +if [ ${#EXTRAS[@]} -gt 0 ]; then + echo "FAIL: ${#EXTRAS[@]} root entries are not on the allowlist:" >&2 + for e in "${EXTRAS[@]}"; do + if [ -d "$REPO_ROOT/$e" ]; then + echo " - $e/ (directory)" >&2 + else + echo " - $e" >&2 + fi + done + echo "" >&2 + echo "Either move them into the appropriate subdirectory, or add a justified" >&2 + echo "entry to ${ALLOW_FILE#"$REPO_ROOT"/}." >&2 +fi + +if [ ${#MISSING[@]} -gt 0 ]; then + echo "FAIL: ${#MISSING[@]} allowlist entries are required but absent:" >&2 + for e in "${MISSING[@]}"; do + echo " - $e" >&2 + done + echo "" >&2 + echo "Either restore them, or - if they are legitimately absent in this repo -" >&2 + echo "mark the entry optional with a leading '?' in root-allow.txt and say why." >&2 + echo "Do not mark an entry optional merely to silence this." >&2 +fi +exit 1 diff --git a/czech-file-knife/scripts/check-template-conformance.sh b/czech-file-knife/scripts/check-template-conformance.sh new file mode 100755 index 000000000..88345d5f4 --- /dev/null +++ b/czech-file-knife/scripts/check-template-conformance.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-template-conformance.sh — the CHECK half of the template contract. +# +# ── Why this exists ────────────────────────────────────────────────────────── +# +# The estate's own comment in build/just/repo-init.just reads: +# +# "Copier/Cruft solved it with an answer-file + update + check; this estate +# hand-simulated it as recurring standardisation-PR campaigns. This is the +# answer-file." +# +# It built the answer-file — .machine_readable/PROVENANCE.a2ml — and then built +# neither the update nor the check. Nothing in this repository read that file. +# It was write-only. +# +# Four open defects are all the same missing half: +# +# #200 CONTRIBUTING.md shipped hardcoded template identity into children +# #201 the generated CLAUDE arrival pack retained the TEMPLATE's uuid, +# clade and "canonical template" purpose in a Julia child +# #203 a template work branch was copied into knot-knot with no common +# ancestor — git proved the trees byte-identical +# +# Each is a repo asserting provenance it does not have, and none could be +# detected because no check read the record. +# +# ── What it checks ─────────────────────────────────────────────────────────── +# +# Structural invariants (always, no network): +# T1 PROVENANCE.a2ml exists +# T2 it does not name THIS repo as its own template (self-parent) +# T3 template_branch / template_commit / template_tree are not UNASSIGNED +# T4 the repo carries no branches beyond its declared extra_branches +# +# T2 is the #200/#201 class. T4 is #203. +# +# Drift report (only with --template PATH, and only ADVISORY): +# D1 template-owned paths missing from this repo +# D2 template-owned paths that differ from the template checkout +# +# Drift is advisory on purpose. A child is SUPPOSED to diverge; that is what +# minting is for. Reporting divergence as failure is how a gate becomes +# unpassable and then gets `continue-on-error`-ed, which is how the estate's +# Hypatia gate ended up unable to fire at all (measured twice: #49, #64). +# +# ── Usage ──────────────────────────────────────────────────────────────────── +# bash scripts/check-template-conformance.sh [--repo PATH] [--template PATH] +# [--report-only] [--quiet] +# +# --repo PATH the minted repo to check (default: this script's repo) +# --template PATH a template checkout, to enable the advisory drift report +# --report-only never exit non-zero (for rollout onto existing children) +# --quiet suppress the per-check PASS lines +# +# Exit: 0 conforming (or report-only), 1 findings, 2 usage/environment error. + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +TARGET="$REPO_DIR" +TEMPLATE="" +REPORT_ONLY=0 +QUIET=0 + +while [ $# -gt 0 ]; do + case "$1" in + --repo) TARGET="${2:-}"; shift 2 ;; + --template) TEMPLATE="${2:-}"; shift 2 ;; + --report-only) REPORT_ONLY=1; shift ;; + --quiet) QUIET=1; shift ;; + -h|--help) sed -n '2,60p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown option: $1" >&2; exit 2 ;; + esac +done + +[ -d "$TARGET" ] || { echo "check-template-conformance: not a directory: $TARGET" >&2; exit 2; } +TARGET="$(cd "$TARGET" && pwd)" + +PASS=0; FAIL=0; WARN=0 +ok() { PASS=$((PASS+1)); [ "$QUIET" -eq 1 ] || printf ' \033[32mPASS\033[0m %s\n' "$1"; } +bad() { FAIL=$((FAIL+1)); printf ' \033[31mFAIL\033[0m %s\n' "$1"; } +warn() { WARN=$((WARN+1)); printf ' \033[33mWARN\033[0m %s\n' "$1"; } +note() { [ "$QUIET" -eq 1 ] || printf ' %s\n' "$1"; } + +PROV="$TARGET/.machine_readable/PROVENANCE.a2ml" + +finish() { + echo + if [ "$FAIL" -gt 0 ]; then + printf 'conformance: \033[31m%d failed\033[0m, %d passed, %d warnings\n' "$FAIL" "$PASS" "$WARN" + else + printf 'conformance: \033[32m%d passed\033[0m, %d warnings\n' "$PASS" "$WARN" + fi + if [ "$REPORT_ONLY" -eq 1 ]; then + echo "(report-only: not failing the run)" + exit 0 + fi + [ "$FAIL" -eq 0 ] +} + +# ── Self-skip: this IS the template ────────────────────────────────────────── +# Same convention as tests/e2e/template_instantiation_test.sh. archetypes/ and +# build/templates/ are template-only and are removed at mint, so their presence +# means this repo has not been instantiated and has no parent to conform to. +if [ -d "$TARGET/archetypes" ] || [ -d "$TARGET/build/templates" ]; then + echo "SKIP: $TARGET is a template (archetypes/ or build/templates/ present) — nothing to conform to." + exit 0 +fi + +echo "template conformance: $TARGET" +echo + +# ── T1: provenance exists ──────────────────────────────────────────────────── +if [ ! -f "$PROV" ]; then + bad "T1 no .machine_readable/PROVENANCE.a2ml — this repo cannot state what it was minted from" + note "A repo minted through the GitHub template UI, or by copying a tree, never" + note "runs repo-init and so never writes this file. That is the #203 mechanism." + note "Recreate it by hand from the template commit you actually took." + finish + exit $? +else + ok "T1 provenance present" +fi + +# a2ml is a flat key = "value" format as far as this check needs. +prov_get() { + sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"\(.*\)\"[[:space:]]*$/\1/p" "$PROV" | head -1 +} + +T_REPO="$(prov_get template_repo)" +T_BRANCH="$(prov_get template_branch)" +T_COMMIT="$(prov_get template_commit)" +T_TREE="$(prov_get template_tree)" + +# ── T2: no self-parent ─────────────────────────────────────────────────────── +SELF_SLUG="" +if command -v git >/dev/null 2>&1 && git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then + url="$(git -C "$TARGET" remote get-url origin 2>/dev/null || true)" + # normalise git@github.com:o/r.git and https://github.com/o/r(.git) to o/r. + # Strip the .git suffix FIRST: the previous single-sed form left it on, so + # SELF_SLUG became "owner/repo.git" and the T2 self-parent check silently + # never matched. Caught by its own negative control. + SELF_SLUG="$(printf '%s' "$url" \ + | sed -E 's|\.git$||; s|^[a-zA-Z][a-zA-Z0-9+.-]*://||; s|^git@||; s|:|/|' \ + | awk -F/ 'NF>=2 {print $(NF-1)"/"$NF}' || true)" +fi + +if [ -z "$T_REPO" ]; then + bad "T2 provenance has no template_repo" +elif [ -n "$SELF_SLUG" ] && [ "$T_REPO" = "$SELF_SLUG" ]; then + bad "T2 provenance names THIS repo as its own template ($T_REPO) — self-parent" + note "This is the #200/#201 class: the repo is asserting template identity." + note "A child must point at the template it came from, not at itself." +else + ok "T2 parent is external${SELF_SLUG:+ (self=$SELF_SLUG, parent=$T_REPO)}" +fi + +# ── T3: the pin is real ────────────────────────────────────────────────────── +for pair in "template_branch:$T_BRANCH" "template_commit:$T_COMMIT" "template_tree:$T_TREE"; do + key="${pair%%:*}"; val="${pair#*:}" + if [ -z "$val" ] || [ "$val" = "UNASSIGNED" ]; then + bad "T3 $key is ${val:-missing} — the parent pin is not resolvable" + note "UNASSIGNED is honest at mint time when offline, but it must be filled" + note "in before the repo is published, or drift can never be detected." + else + ok "T3 $key = $val" + fi +done + +# ── T4: branch contract (#203) ─────────────────────────────────────────────── +# The template must never leak its work branches into a child. knot-knot got a +# byte-identical copy of coderabbit/fix-hypatia-scan-failures/f36ac704 with no +# common ancestor; git proved the tree equality. +if git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then + DEFAULT="$(git -C "$TARGET" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')" + if [ -z "$DEFAULT" ]; then + for cand in main master; do + git -C "$TARGET" show-ref --verify --quiet "refs/heads/$cand" && { DEFAULT="$cand"; break; } + done + fi + + # Declared extras: parse the bracketed list on the extra_branches line. + DECLARED="$(sed -n 's/^[[:space:]]*extra_branches[[:space:]]*=[[:space:]]*\[\(.*\)\].*/\1/p' "$PROV" | head -1 | tr ',' '\n' | tr -d ' "' | grep -v '^$' || true)" + + UNEXPECTED="" + while IFS= read -r br; do + [ -z "$br" ] && continue + [ "$br" = "$DEFAULT" ] && continue + printf '%s\n' "$DECLARED" | grep -qxF "$br" && continue + UNEXPECTED="$UNEXPECTED $br" + done < <(git -C "$TARGET" for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null) + + if [ -n "$UNEXPECTED" ]; then + bad "T4 branch(es) not in the mint contract:${UNEXPECTED}" + note "Declared extra_branches: ${DECLARED:-}" + note "This is the #203 signature. A template work branch (coderabbit/," + note "chore/, bot-task) copied wholesale into a child. Review each: keep it" + note "deliberately, or delete it. Do not force unrelated histories together." + else + ok "T4 branch contract honoured (default=${DEFAULT:-?}, declared extras=${DECLARED:-none})" + fi + + # Byte-identical-tree detector: the cheapest proof of a leaked snapshot. + # If a non-default branch's tree equals an ancestor's tree exactly, it very + # likely arrived by copy rather than by work. + while IFS= read -r br; do + [ -z "$br" ] && continue + [ "$br" = "$DEFAULT" ] && continue + t="$(git -C "$TARGET" rev-parse "$br^{tree}" 2>/dev/null || true)" + p="$(git -C "$TARGET" merge-base "$br" "${DEFAULT:-HEAD}" 2>/dev/null || true)" + if [ -n "$t" ] && [ -z "$p" ]; then + warn "T4b '$br' has NO common ancestor with ${DEFAULT:-HEAD} (unrelated history)" + note "tree=$t — this is the exact knot-knot signature." + fi + done < <(git -C "$TARGET" for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null) +else + warn "T4 skipped — not a git checkout" +fi + +# ── D1/D2: advisory drift against a template checkout ──────────────────────── +if [ -n "$TEMPLATE" ]; then + if [ ! -d "$TEMPLATE" ]; then + echo " (--template path not a directory: $TEMPLATE)"; TEMPLATE="" + elif [ ! -f "$TEMPLATE/Justfile" ]; then + echo " (--template path does not look like the template: no Justfile)"; TEMPLATE="" + fi +fi + +if [ -n "$TEMPLATE" ]; then + TEMPLATE="$(cd "$TEMPLATE" && pwd)" + echo + echo "advisory drift vs $TEMPLATE" + MISSING=0; DRIFTED=0 + + # Paths the template owns: everything it ships except what it deliberately + # drops at mint (archetypes/, build/, and the answer-file itself). + while IFS= read -r rel; do + case "$rel" in + archetypes/*|build/*|.git/*|.machine_readable/PROVENANCE.a2ml) continue ;; + esac + if [ ! -e "$TARGET/$rel" ]; then + MISSING=$((MISSING+1)); [ "$MISSING" -le 15 ] && echo " MISSING $rel" + elif ! cmp -s "$TEMPLATE/$rel" "$TARGET/$rel"; then + DRIFTED=$((DRIFTED+1)); [ "$DRIFTED" -le 15 ] && echo " differs $rel" + fi + done < <(cd "$TEMPLATE" && git ls-files 2>/dev/null || find . -type f | sed 's|^\./||') + + [ "$MISSING" -eq 0 ] && [ "$DRIFTED" -eq 0 ] && echo " (none — no template-owned path diverged)" + [ "$MISSING" -gt 0 ] && warn "D1 $MISSING template-owned path(s) missing from this repo" + [ "$DRIFTED" -gt 0 ] && warn "D2 $DRIFTED template-owned path(s) differ from the template" + note "Advisory only. A child is SUPPOSED to diverge — but a path that diverged" + note "once is never healed by a later template update, and copier's own 3-way" + note "merge carries such a difference forward silently, with no conflict marker." + note "Decide per path: conform it, or record why it is deliberately forked." +fi + +finish +exit $? diff --git a/czech-file-knife/scripts/check-variant-drift.sh b/czech-file-knife/scripts/check-variant-drift.sh new file mode 100755 index 000000000..c8ada3c0a --- /dev/null +++ b/czech-file-knife/scripts/check-variant-drift.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-variant-drift.sh — verify the shared RSR spine of this variant +# template stays convergent with its parent at the pinned commit. +# +# Reads the contract at .machine_readable/descriptiles/VARIANT.a2ml: +# - every tracked file NOT declared added/removed/diverged/pending/operational +# must be identical to the parent's copy at parent-pin, modulo the +# [normalise] rules (action-pin SHAs, self-name substitution); +# - declared additions must exist here and not in the parent; +# - declared removals must exist in the parent and not here. +# +# Usage: check-variant-drift.sh [self-dir] +# Exit: 0 = spine convergent; 1 = undeclared drift (listed on stdout). + +set -euo pipefail + +PARENT_DIR="${1:?usage: check-variant-drift.sh [self-dir]}" +SELF_DIR="${2:-.}" +CONTRACT="$SELF_DIR/.machine_readable/descriptiles/VARIANT.a2ml" + +[ -f "$CONTRACT" ] || { echo "FAIL: contract not found: $CONTRACT"; exit 1; } + +SELF_NAME=$(sed -n 's/^project = "\(.*\)"/\1/p' "$CONTRACT" | head -1) +PARENT_SLUG=$(sed -n 's/^parent = "\(.*\)"/\1/p' "$CONTRACT" | head -1) +PARENT_NAME="${PARENT_SLUG##*/}" +PIN=$(sed -n 's/^parent-pin = "\([0-9a-f]*\)".*/\1/p' "$CONTRACT" | head -1) + +# Extract the paths array of one [paths.
] block. +section_paths() { + awk -v sec="[paths.$1]" ' + $0 == sec { insec = 1; next } + insec && /^\[/ { insec = 0 } + insec && /^ *"/ { + line = $0 + sub(/^ *"/, "", line); sub(/".*$/, "", line) + print line + } + ' "$CONTRACT" +} + +ADDED=$(section_paths added) +REMOVED=$(section_paths removed) +SKIP=$(printf '%s\n' "$(section_paths diverged)" \ + "$(section_paths diverged-pending-upstream)" \ + "$(section_paths operational-state)") + +in_list() { # $1 = path, $2 = newline list (entries ending in / are prefixes) + local p="$1" e + while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) case "$p" in "$e"*) return 0;; esac ;; + *) [ "$p" = "$e" ] && return 0 ;; + esac + done <<< "$2" + return 1 +} + +# Fold operational state out of a file before comparison: action-pin SHAs, +# then BOTH repo names → SELF (variant name first — it does not contain the +# parent name as a substring, so order is safe). Folding both names on both +# sides keeps inherited files that legitimately mention the parent by name +# convergent, while still matching self-identity substitutions. +normalise() { # $1 = file + sed -E -e 's/@[0-9a-f]{40}[^ ]*( # v[^ ]*)?/@PIN/g' \ + -e "s/$SELF_NAME/SELF/g" -e "s/$PARENT_NAME/SELF/g" "$1" +} + +DRIFT=0 +report() { DRIFT=1; echo "DRIFT: $*"; } + +if [ -n "$PIN" ] && [ -d "$PARENT_DIR/.git" ]; then + ACTUAL=$(git -C "$PARENT_DIR" rev-parse HEAD) + [ "$ACTUAL" = "$PIN" ] || echo "WARN: parent checkout is $ACTUAL, contract pins $PIN" +fi + +# 1. Spine files must match, modulo normalisation. +while IFS= read -r f; do + in_list "$f" "$ADDED" && continue + in_list "$f" "$SKIP" && continue + if [ ! -f "$PARENT_DIR/$f" ]; then + report "$f exists here but not in parent (declare in paths.added or remove)" + continue + fi + if ! diff -q <(normalise "$PARENT_DIR/$f") \ + <(normalise "$SELF_DIR/$f") >/dev/null 2>&1; then + report "$f differs from parent (declare in paths.diverged or re-converge)" + fi +done < <(git -C "$SELF_DIR" ls-files) + +# 2. Parent files absent here must be declared removed. +while IFS= read -r f; do + [ -f "$SELF_DIR/$f" ] && continue + in_list "$f" "$REMOVED" && continue + in_list "$f" "$SKIP" && continue + report "parent has $f but it is absent here (declare in paths.removed)" +done < <(git -C "$PARENT_DIR" ls-files) + +# 3. Declared additions must exist (and not silently exist in parent). +while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) [ -d "$SELF_DIR/$e" ] || report "declared-added directory $e is missing" ;; + *) [ -f "$SELF_DIR/$e" ] || report "declared-added file $e is missing" + [ -e "$PARENT_DIR/$e" ] && report "declared-added $e also exists in parent (not an addition)" ;; + esac +done <<< "$ADDED" + +# 4. Declared removals must still exist in the parent. +while IFS= read -r e; do + [ -z "$e" ] && continue + [ -e "$PARENT_DIR/$e" ] || report "declared-removed $e no longer exists in parent (stale entry)" +done <<< "$REMOVED" + +if [ "$DRIFT" -eq 0 ]; then + echo "PASS: spine convergent with $PARENT_SLUG@${PIN:0:12} (modulo declared variant paths)" +else + echo "FAIL: undeclared drift against $PARENT_SLUG@${PIN:0:12} — update VARIANT.a2ml or re-converge" + exit 1 +fi diff --git a/czech-file-knife/scripts/gen-repo-map.sh b/czech-file-knife/scripts/gen-repo-map.sh new file mode 100755 index 000000000..a9cdb64f6 --- /dev/null +++ b/czech-file-knife/scripts/gen-repo-map.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# gen-repo-map.sh — generate docs/architecture/REPOSITORY-MAP.adoc from the +# tree plus the annotations in .machine_readable/root-allow.txt. +# +# WHY THIS IS GENERATED. Before this existed the repo carried FIVE hand-written +# maps and not one was accurate: README's table omitted 12 directories, +# .github/DIRECTORY.adoc was a 3-line stub, .github/CONTRIBUTING.md drew a tree +# naming lib/, extensions/, plugins/ and spec/ (none of which exist), +# docs/README.adoc was wrong on every path it named, and docs/RSR_OUTLINE.adoc +# was a second whole-repo README contradicting the first. They rotted because +# nothing checked them. A map that is regenerated and diffed in CI cannot. +# +# Usage: bash scripts/gen-repo-map.sh [repo_root] +set -euo pipefail + +# Byte order, everywhere. `sort` is LOCALE-DEPENDENT: under en_US.UTF-8 it +# ignores leading punctuation, so dotfiles interleave with ordinary names; +# under LC_ALL=C (what CI runs) they sort first. The generator was therefore +# deterministic WITHIN an environment but not ACROSS environments, and the +# CI freshness check caught precisely that on its first real run. Running the +# generator twice in one shell cannot detect it - the check must vary the +# locale, which is what tests/shape/repo_map_determinism_test.sh now does. +export LC_ALL=C + +REPO_ROOT="${1:-.}" +cd "$REPO_ROOT" +ALLOW=".machine_readable/root-allow.txt" +OUT="docs/architecture/REPOSITORY-MAP.adoc" + +[ -f "$ALLOW" ] || { echo "ERROR: $ALLOW not found" >&2; exit 2; } +mkdir -p "$(dirname "$OUT")" + +# Root entries, tracked shape only (mirrors check-root-shape.sh). +mapfile -t ENTRIES < <(git ls-files | awk -F/ '{print $1}' | sort -u) + +# name -> annotation, harvested from the allowlist's own comments. +declare -A NOTE=() OPTIONAL=() +while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// }" ]] && continue + raw="${line%%#*}"; raw="$(echo "$raw" | xargs || true)" + [ -z "$raw" ] && continue + comment="${line#*#}"; [ "$comment" = "$line" ] && comment="" + comment="$(echo "$comment" | xargs || true)" + key="${raw%/}" + if [[ "$key" == '?'* ]]; then key="${key#\?}"; OPTIONAL["$key"]=1; fi + NOTE["$key"]="$comment" +done < "$ALLOW" + +# Directories whose own level holds nothing but a stub README (or .gitkeep). +# The owner ruling (2026-08-26) is that these stay: they are DECLARED STRUCTURE, +# a statement of intended shape for repos minted from this template, not +# abandoned work. Saying so explicitly is the difference between the two. +# Recursive: a directory counts as unfilled only when its WHOLE subtree holds +# no substantive file. Checking just its own level would flag archetypes/, whose +# julia-library/ child is real content. (The ply manifests that once padded +# these counts were folded into the repo deed — standards#837 pilot.) +declared_only() { + local d="$1" n + n=$(git ls-files "$d" | awk -F/ '{print $NF}' \ + | grep -vxE 'README\.adoc|\.gitkeep' | wc -l) + [ "$n" -eq 0 ] +} + +{ + echo "// SPDX-License-Identifier: CC-BY-SA-4.0" + echo "// Copyright (c) 2026 Jonathan D.A. Jewell " + echo "//" + echo "// GENERATED by scripts/gen-repo-map.sh - do not hand-edit." + echo "// Regenerate with \`just repo-map\`. CI fails if this file is stale." + echo "= Repository map" + echo ":toc:" + echo + echo "The single authoritative map of this repository. It is generated from the" + echo "tree and from the annotations in \`.machine_readable/root-allow.txt\`, and CI" + echo "fails if it drifts, so it cannot rot the way its five hand-written" + echo "predecessors did." + echo + echo "== Root" + echo + echo '[cols="2,1,4",options="header"]' + echo '|===' + echo "| Path | Required | What it is, and who reads it" + echo + for e in "${ENTRIES[@]}"; do + [ -d "$e" ] && disp="\`$e/\`" || disp="\`$e\`" + req="yes"; [ -n "${OPTIONAL[$e]+x}" ] && req="optional" + note="${NOTE[$e]:-}" + [ -z "$note" ] && note="-" + echo "| $disp" + echo "| $req" + echo "| $note" + echo + done + echo '|===' + echo + echo "== Declared structure not yet filled" + echo + echo "These directories currently hold only a stub \`README.adoc\` and a level" + echo "manifest. That is deliberate. They declare the shape a repository minted" + echo "from this template is expected to grow into; they are *intended" + echo "structure, not abandoned work*. Add content, or delete the directory in" + echo "your own repo - but do not read their emptiness as neglect here." + echo + first=1 + for d in $(git ls-files | grep '/' | sed 's|/[^/]*$||' | sort -u); do + if declared_only "$d"; then + [ $first -eq 1 ] && { echo "[cols=\"1\"]"; echo '|==='; first=0; } + echo "| \`$d/\`" + fi + done + [ $first -eq 0 ] && echo '|===' + echo + echo "== Where things are enforced" + echo + echo "* Root shape - \`scripts/check-root-shape.sh\` against" + echo " \`.machine_readable/root-allow.txt\`, run by \`.github/workflows/estate-rules.yml\`." + echo " The check is bidirectional: unlisted entries fail, and required entries" + echo " that are absent also fail." + echo "* This map - \`just repo-map\` must produce no diff." + echo "* Community health - GitHub reads \`.github/\` and no other path." + echo "* Variant divergence - \`scripts/check-variant-drift.sh\` compares a child" + echo " to its parent BY PATH, so any move here invalidates every child's" + echo " declared path lists until they are re-anchored." +} > "$OUT" + +echo "repo-map: wrote $OUT" diff --git a/czech-file-knife/scripts/invariant-path.sh b/czech-file-knife/scripts/invariant-path.sh new file mode 100755 index 000000000..f46953400 --- /dev/null +++ b/czech-file-knife/scripts/invariant-path.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +IP_ROOT="${REPO_ROOT}/../invariant-path" + +if [[ ! -f "${IP_ROOT}/Cargo.toml" ]]; then + echo "invariant-path workspace not found at ${IP_ROOT}" >&2 + exit 1 +fi + +if [[ $# -eq 0 ]]; then + set -- scan --profile generic --file "${REPO_ROOT}/README.adoc" --artifact-uri "repo://README.adoc" +elif [[ "$1" == "scan" ]]; then + shift + has_profile="false" + for arg in "$@"; do + if [[ "$arg" == "--profile" ]]; then + has_profile="true" + break + fi + done + if [[ "${has_profile}" == "true" ]]; then + set -- scan "$@" + else + set -- scan --profile generic "$@" + fi +fi + +exec cargo run --manifest-path "${IP_ROOT}/Cargo.toml" -p invariant-path-cli -- "$@" diff --git a/czech-file-knife/scripts/migrate-wellknown-to-www.sh b/czech-file-knife/scripts/migrate-wellknown-to-www.sh new file mode 100755 index 000000000..aa9c2e245 --- /dev/null +++ b/czech-file-knife/scripts/migrate-wellknown-to-www.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# migrate-wellknown-to-www.sh — conflict-safe migration of a repository-root +# .well-known/ tree into www/.well-known/ (czech-file-knife#53). +# +# Stage 1 established the canonical location. Stage 5 (#119) sweeps it across +# ~270 repositories, which changes what "conflict-safe" has to mean: a sweep +# is unattended, so a conflict may not halt it, and may not be silent either. +# +# Semantics, per the #53 migration requirements: +# * identical content in both locations -> root copy removed, www kept; +# * content only at the root -> moved into www/.well-known/; +# * content only under www/ -> no-op; +# * DIVERGENT content in both -> the ROOT copy is quarantined to +# www/.legacy-well-known-/ and the www/ copy is left untouched: +# never overwritten, never silent. Exit 0 by default so a batch sweep can +# continue; --strict turns a quarantine into a non-zero exit. +# +# --in-place restores the pre-stage-5 contract (both copies stay where they +# are, exit 1) for anyone who prefers to resolve divergence by hand before the +# tree is touched at all. +# +# Why the quarantine lives under www/ and not at the root: +# #106's commit message specifies `.legacy-well-known-YYYYMMDD/` at the +# repository root. The root allowlist (.machine_readable/root-allow.txt) is +# checked BIDIRECTIONALLY by scripts/check-root-shape.sh and matches entries +# literally, with no glob support — a root directory whose name carries a +# date can never be allowlisted, so every swept repository would report root +# drift the moment the migrator ran. Under www/ it is inside the site- +# operations bundle the allowlist already permits, and it is NOT under +# www/public/, so the publication boundary still holds. +# +# Run from the repository root. Uses `git mv`/`git rm` when the tree is a git +# checkout and the files are tracked, plain mv/rm otherwise, so it is safe on +# minted-but-uncommitted trees too. Propagation runners should combine this +# with a `git status --porcelain` / unpushed-commit check of their own; this +# script compares CONTENT, and content comparison is what "divergent" means +# here. +# +# Exit codes: +# 0 — clean migration (quarantines may have occurred; see the report) +# 1 — divergence left unresolved (--in-place), --strict and something was +# quarantined, or files unexpectedly remain at the root +# 2 — usage / setup error + +set -euo pipefail + +ROOT=".well-known" +DEST="www/.well-known" + +IN_PLACE=0 +STRICT=0 +DRY_RUN=0 +REPORT="" + +usage() { + cat <<'EOF' +Usage: scripts/migrate-wellknown-to-www.sh [options] + +Migrate a repository-root .well-known/ into www/.well-known/. + +Options: + --dry-run report what would change; write nothing (always exit 0) + --in-place divergent content stays put, both copies kept, exit 1 + (pre-stage-5 contract; for hand resolution) + --strict exit 1 if anything had to be quarantined + --report FILE write a TSV report: actionpathdetail + -h, --help this message + +Report actions: moved, deduped, quarantined, conflict, left. +EOF +} + +die() { echo "migrate-wellknown: ERROR — $*" >&2; exit 2; } + +while [ $# -gt 0 ]; do + case "$1" in + --in-place) IN_PLACE=1; shift ;; + --strict) STRICT=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --report) [ $# -ge 2 ] || die "--report requires a path"; REPORT="$2"; shift 2 ;; + --report=*) REPORT="${1#--report=}"; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown option: $1" ;; + esac +done + +if [ "$IN_PLACE" -eq 1 ] && [ "$STRICT" -eq 1 ]; then + die "--in-place and --strict are mutually exclusive (--in-place already fails on conflict)" +fi + +# The report is written under --dry-run too: a dry run that only prints is +# useless to a batch driver, which needs the planned actions in a form it can +# read. Under --dry-run the rows describe what WOULD happen. +if [ -n "$REPORT" ]; then + repdir="$(dirname "$REPORT")" + if [ ! -d "$repdir" ]; then mkdir -p "$repdir"; fi + printf 'action\tpath\tdetail\n' > "$REPORT" +fi + +rep() { + if [ -n "$REPORT" ]; then + printf '%s\t%s\t%s\n' "$1" "$2" "${3:-}" >> "$REPORT" + fi + return 0 +} + +if [ ! -d "$ROOT" ]; then + echo "migrate-wellknown: no $ROOT/ at repository root — nothing to migrate." + exit 0 +fi + +STAMP="$(date -u +%Y%m%d)" +QUARANTINE="www/.legacy-well-known-$STAMP" + +git_tracked() { git ls-files --error-unmatch "$1" >/dev/null 2>&1; } + +move_file() { # src dst — never overwrites; caller guarantees dst is free + local src="$1" dst="$2" + # The dry-run guard precedes every filesystem effect, mkdir included: + # an empty directory is invisible to `git status` but is still a change + # to the tree, and --dry-run promises to leave nothing behind. + if [ "$DRY_RUN" -eq 1 ]; then return 0; fi + mkdir -p "$(dirname "$dst")" + if git_tracked "$src"; then + git mv "$src" "$dst" + else + mv "$src" "$dst" + fi +} + +remove_file() { # src + local src="$1" + if [ "$DRY_RUN" -eq 1 ]; then return 0; fi + if git_tracked "$src"; then + git rm -q "$src" + else + rm "$src" + fi +} + +# A quarantine target is never overwritten: if the dated name is taken, the +# next free `.N` suffix is used instead. +quarantine_dest() { # rel -> path + # Declared one per line on purpose: in `local a="$1" b="$a"` every + # expansion happens before any assignment, so $a is still unset when it + # is read — fatal under `set -u`. + local rel="$1" + local base="$QUARANTINE/$rel" + local cand="$base" + local n=1 + while [ -e "$cand" ]; do + cand="$base.$n" + n=$((n + 1)) + done + printf '%s' "$cand" +} + +if [ "$DRY_RUN" -eq 0 ] && [ ! -d "$DEST" ]; then + mkdir -p "$DEST" +fi + +moved=0; deduped=0; conflicts=0; quarantined=0 + +while IFS= read -r -d '' src; do + rel="${src#"$ROOT"/}" + dst="$DEST/$rel" + + if [ -f "$dst" ]; then + if cmp -s "$src" "$dst"; then + # Identical: the duplicate root copy goes; www is canonical. + remove_file "$src" + deduped=$((deduped + 1)) + echo " identical, root copy removed: $rel" + rep deduped "$rel" "identical to $DEST/$rel; root copy removed" + elif [ "$IN_PLACE" -eq 1 ]; then + conflicts=$((conflicts + 1)) + echo " CONFLICT (both preserved): $rel differs between $ROOT/ and $DEST/" >&2 + echo " root sha256: $(sha256sum "$src" | cut -d' ' -f1)" >&2 + echo " www sha256: $(sha256sum "$dst" | cut -d' ' -f1)" >&2 + rep conflict "$rel" "divergent; both preserved in place" + else + qdst="$(quarantine_dest "$rel")" + if [ -e "$qdst" ]; then + die "refusing to overwrite quarantine target $qdst" + fi + # Hashes are read BEFORE the move: afterwards $src no longer + # exists, and a failing sha256sum inside $( ) would abort the + # script under `set -e` before the conflict was ever reported. + root_sha="$(sha256sum "$src" | cut -d' ' -f1)" + www_sha="$(sha256sum "$dst" | cut -d' ' -f1)" + move_file "$src" "$qdst" + quarantined=$((quarantined + 1)) + echo " DIVERGENT — root copy quarantined: $rel -> $qdst" >&2 + echo " root sha256: $root_sha" >&2 + echo " www sha256: $www_sha (kept, untouched)" >&2 + rep quarantined "$rel" "divergent; root copy -> $qdst" + fi + else + if [ -e "$dst" ]; then + die "refusing to overwrite existing $dst" + fi + move_file "$src" "$dst" + moved=$((moved + 1)) + echo " moved: $rel -> $dst" + rep moved "$rel" "root-only; -> $DEST/$rel" + fi +done < <(find "$ROOT" -type f -print0 | sort -z) + +# Drop the root directory only when it is fully empty of files. +left=0 +if [ -z "$(find "$ROOT" -type f -print -quit 2>/dev/null)" ]; then + if [ "$DRY_RUN" -eq 0 ]; then + find "$ROOT" -depth -type d -empty -delete 2>/dev/null || true + fi +else + left=$(find "$ROOT" -type f | wc -l | tr -d '[:space:]') +fi + +echo "migrate-wellknown: moved=$moved deduped=$deduped quarantined=$quarantined conflicts=$conflicts left_in_root=$left" + +if [ "$DRY_RUN" -eq 1 ]; then + echo "migrate-wellknown: dry run — nothing written." + exit 0 +fi + +if [ "$left" -gt 0 ]; then + echo "migrate-wellknown: WARNING — $left file(s) remain under $ROOT/ (unexpected)." >&2 + exit 1 +fi + +if [ "$conflicts" -gt 0 ]; then + echo "migrate-wellknown: DIVERGENT CONTENT — both locations preserved in place; resolve by hand." >&2 + exit 1 +fi + +if [ "$quarantined" -gt 0 ]; then + echo "migrate-wellknown: $quarantined divergent file(s) quarantined under $QUARANTINE/ —" >&2 + echo "migrate-wellknown: resolve by hand, then delete that directory. Nothing was overwritten." >&2 + if [ "$STRICT" -eq 1 ]; then + exit 1 + fi +fi + +exit 0 diff --git a/czech-file-knife/scripts/prune-dependabot-ecosystems.rs b/czech-file-knife/scripts/prune-dependabot-ecosystems.rs new file mode 100644 index 000000000..fb0cacf39 --- /dev/null +++ b/czech-file-knife/scripts/prune-dependabot-ecosystems.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// Keep only explicitly selected Dependabot ecosystems after project minting. +// +// Ported from prune-dependabot-ecosystems.rb: Ruby is not an estate-authorised +// language, and this runs on every mint. Single file, std only, no +// dependencies — compiled on demand by `just repo-init` (see the rust_tool +// helper in build/just/repo-init.just). +// +// Two rules that are easy to get wrong and are load-bearing: +// +// * Nix is not a valid Dependabot ecosystem, so it is dropped from the +// keep-list rather than matched against entries. +// * Pruning to an EMPTY updates list is refused. A dependabot.yml with no +// ecosystems is worse than one with unused entries: it silently stops +// watching everything. Refusing is the safe failure. +// +// Usage: prune-dependabot-ecosystems.rs + +use std::env; +use std::fs; +use std::path::Path; + +/// True for a line that begins a new `updates:` entry, i.e. matches +/// `^[ \t]*-[ \t]*package-ecosystem:`. +fn is_entry_start(line: &str) -> bool { + let t = line.trim_start_matches(|c| c == ' ' || c == '\t'); + let t = match t.strip_prefix('-') { + Some(t) => t, + None => return false, + }; + t.trim_start_matches(|c| c == ' ' || c == '\t') + .starts_with("package-ecosystem:") +} + +/// Extract the ecosystem name from one entry: `package-ecosystem: "cargo"`. +fn ecosystem_name(entry: &str) -> String { + let needle = "package-ecosystem:"; + match entry.find(needle) { + Some(p) => { + let rest = &entry[p + needle.len()..]; + let rest = rest.trim_start_matches(|c| c == ' ' || c == '\t'); + let rest = rest.trim_start_matches(|c| c == '\'' || c == '"'); + rest.chars() + .take_while(|c| c.is_ascii_alphabetic() || *c == '-') + .collect() + } + None => "?".to_string(), + } +} + +fn main() { + let args: Vec = env::args().skip(1).collect(); + if args.len() < 2 { + eprintln!("Usage: prune-dependabot-ecosystems.rs "); + std::process::exit(1); + } + let path = &args[0]; + + // Nix is not a Dependabot ecosystem; keeping it would match nothing and + // could only ever produce an empty keep-list. + let keep: Vec<&str> = args[1..] + .iter() + .map(|s| s.as_str()) + .filter(|s| *s != "nix") + .collect(); + + if !Path::new(path).is_file() { + println!(" dependabot: {} absent, nothing to prune", path); + return; + } + + let text = match fs::read_to_string(path) { + Ok(t) => t, + Err(_) => return, + }; + + // Split the document at each entry start, keeping the preamble separate. + // `split_inclusive` retains the newline, so re-joining is byte-exact for + // anything we do not drop. + let mut head = String::new(); + let mut entries: Vec = Vec::new(); + for line in text.split_inclusive('\n') { + if is_entry_start(line) { + entries.push(String::new()); + } + match entries.last_mut() { + Some(e) => e.push_str(line), + None => head.push_str(line), + } + } + + if entries.is_empty() { + println!(" dependabot: no ecosystem entries found"); + return; + } + + let mut kept: Vec = Vec::new(); + let mut kept_names: Vec = Vec::new(); + let mut dropped_names: Vec = Vec::new(); + + for entry in &entries { + let name = ecosystem_name(entry); + if keep.contains(&name.as_str()) { + kept_names.push(name); + kept.push(entry.clone()); + } else { + dropped_names.push(name); + } + } + + if kept.is_empty() { + println!(" dependabot: refusing to prune every entry; left unchanged"); + return; + } + if dropped_names.is_empty() { + println!(" dependabot: nothing to prune"); + return; + } + + let mut out = head; + for k in &kept { + out.push_str(k); + } + if fs::write(path, out).is_err() { + return; + } + println!( + " dependabot: kept {} / dropped {}", + kept_names.join(", "), + dropped_names.join(", ") + ); +} diff --git a/czech-file-knife/scripts/rsr-campaign.sh b/czech-file-knife/scripts/rsr-campaign.sh new file mode 100644 index 000000000..27431f6ed --- /dev/null +++ b/czech-file-knife/scripts/rsr-campaign.sh @@ -0,0 +1,415 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# rsr-campaign.sh — the RSR update mechanism. +# +# WHY THIS EXISTS +# +# ADR-0003 (Forge and Sustain) says a forged repository is *sustained* in four +# modes, of which one is: +# +# adaptive — parent-pin bumps + fan-out campaigns +# +# The check half shipped: scripts/check-variant-drift.sh verifies a child is +# convergent with its parent at the pinned commit, modulo declared divergences. +# The *update* half never existed. That is the gap ADR-0001 lists as a negative +# consequence in one line ("Template updates need propagation mechanism to +# existing repos") while listing "fix once in template, propagate to all repos" +# as a positive one. Both statements were in the same document; only the +# pessimistic one was true. +# +# WHY IT IS A CAMPAIGN AND NOT A MERGE +# +# Measured 2026-09-18 against three representative estate repositories: +# +# repo tracked shared with spine byte-identical +# Axiom.jl 328 55 6 +# wokelang 447 56 6 +# zotero-tools 959 36 6 +# +# and the spine has 546 files. So ~490 spine files are absent from a typical +# repository and only SIX agree exactly. "Converge the estate to the template" +# would therefore rewrite ~50 files and add ~490 in every one of 269 repos — +# roughly 130,000 file additions, against repositories that have legitimately +# spent years diverging. That is not an update mechanism; it is a re-mint, and +# it would destroy customisation at a scale nobody could review. +# +# The honest conclusion: the estate cannot be *converged*, only *updated along +# declared axes*. So a campaign declares exactly what travels. Everything else +# is left alone, by construction rather than by care. +# +# WHAT A CAMPAIGN IS +# +# [meta] +# name / description / why +# [paths] +# spine-relative paths to propagate ('dir/' means the whole directory) +# [exclude] +# paths never to touch, even inside a propagated directory +# +# Per repository, per path: +# +# declared diverged -> SKIPPED (the repo's VARIANT.a2ml says so) +# absent in repo -> ADDED +# present, differs -> UPDATED +# present, identical -> UNCHANGED +# +# SAFETY PROPERTIES, each deliberate: +# +# * Dry-run by default. --push is required to write anything at all. +# * Nothing is ever deleted. A campaign adds and converges; deletion is a +# different operation with a different blast radius and needs its own gate. +# * A repository that declares a path diverged is never touched there, so a +# campaign cannot silently reverse a decision the repo already recorded. +# * Files containing an unfilled {{TOKEN}} are REFUSED by default, because +# propagating one plants a placeholder in a repo whose own placeholder gate +# will then fail its every push. --allow-tokens overrides, deliberately +# loudly. +# * A repository with no changes is not committed to. +# * The spine's own name is rewritten to the target's name, so the campaign +# does not re-plant template identity (the ADR-0003 mint criterion). +# +# Usage: +# rsr-campaign.sh --manifest FILE --repos-file FILE [options] +# +# --manifest FILE campaign manifest (required) +# --repos-file FILE repositories, one per line (required) +# --spine DIR spine checkout; default: the repo this script is in +# --work-dir DIR where to clone; default: a temp dir +# --batch N batch number, 1-based +# --batch-size M repositories per batch (default 25) +# --push commit and push; without it, --apply changes nothing +# --apply write changes into the checkout (still no push) +# --base-ref REF branch to stack on (default: the repo default branch). +# Set this when the campaign depends on work that is not +# merged yet — e.g. the stage-5 sweep branches. +# --branch NAME branch to push to (default: the campaign name) +# --report FILE write a TSV report +# --allow-tokens permit propagating files with {{TOKEN}} placeholders +# --no-substitute do not rewrite the spine name to the repo name +# --quiet less per-repo output +# +# Exit: 0 = every targeted repo reached a terminal state; 1 = at least one failed. + +set -euo pipefail + +PROG="$(basename "$0")" + +MANIFEST=""; REPOS_FILE=""; SPINE=""; WORK_DIR="" +BATCH=""; BATCH_SIZE=25; PUSH=0; APPLY=0; BRANCH=""; REPORT=""; BASE_REF="" +ALLOW_TOKENS=0; SUBSTITUTE=1; QUIET=0 + +die() { printf '%s: %s\n' "$PROG" "$1" >&2; exit 2; } +note() { [ "$QUIET" -eq 1 ] || printf '%s\n' "$*"; } +hr() { [ "$QUIET" -eq 1 ] || printf -- '------------------------------------------------------------\n'; } + +while [ $# -gt 0 ]; do + case "$1" in + --manifest) MANIFEST="${2:?}"; shift 2 ;; + --repos-file) REPOS_FILE="${2:?}"; shift 2 ;; + --spine) SPINE="${2:?}"; shift 2 ;; + --work-dir) WORK_DIR="${2:?}"; shift 2 ;; + --batch) BATCH="${2:?}"; shift 2 ;; + --batch-size) BATCH_SIZE="${2:?}"; shift 2 ;; + --branch) BRANCH="${2:?}"; shift 2 ;; + --base-ref) BASE_REF="${2:?}"; shift 2 ;; + --report) REPORT="${2:?}"; shift 2 ;; + --push) PUSH=1; shift ;; + --apply) APPLY=1; shift ;; + --allow-tokens) ALLOW_TOKENS=1; shift ;; + --no-substitute) SUBSTITUTE=0; shift ;; + --quiet|-q) QUIET=1; shift ;; + -h|--help) sed -n '2,80p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +[ -n "$MANIFEST" ] || die "missing --manifest" +[ -n "$REPOS_FILE" ] || die "missing --repos-file" +[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST" +[ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" + +# The spine is the source of truth. Default to the checkout this script lives in. +if [ -z "$SPINE" ]; then + SPINE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +fi +[ -d "$SPINE/.git" ] || die "spine is not a git checkout: $SPINE" + +if [ -z "$WORK_DIR" ]; then + WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rsr-campaign.XXXXXX")" +fi +mkdir -p "$WORK_DIR" + +# --------------------------------------------------------------------------- +# Manifest parsing +# --------------------------------------------------------------------------- +# Deliberately a tiny INI reader rather than a dependency: the manifest is a +# declaration, not a program, and a campaign that needs a parser installed is a +# campaign that cannot be run during an incident. +MANIFEST_SECTION="" +CAMP_NAME="${MANIFEST##*/}"; CAMP_NAME="${CAMP_NAME%.campaign}" +CAMP_DESC="" +PATHS=(); EXCLUDES=() + +while IFS= read -r line || [ -n "$line" ]; do + # strip comments and surrounding space + line="${line%%#*}" + line="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + [ -z "$line" ] && continue + case "$line" in + \[*\]) MANIFEST_SECTION="$(printf '%s' "$line" | tr -d '[]')"; continue ;; + esac + case "$MANIFEST_SECTION" in + meta) + key="${line%%=*}"; val="${line#*=}" + key="$(printf '%s' "$key" | tr -d '[:space:]')" + val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + case "$key" in + name) [ -n "$val" ] && CAMP_NAME="$val" ;; + description) CAMP_DESC="$val" ;; + esac + ;; + paths) PATHS+=("$line") ;; + exclude) EXCLUDES+=("$line") ;; + *) die "line outside a known section in $MANIFEST: $line" ;; + esac +done < "$MANIFEST" + +[ "${#PATHS[@]}" -gt 0 ] || die "manifest declares no [paths]: $MANIFEST" +[ -n "$BRANCH" ] || BRANCH="$CAMP_NAME" + +note "campaign: $CAMP_NAME" +[ -n "$CAMP_DESC" ] && note " $CAMP_DESC" +note " spine: $SPINE" +note " manifest: $MANIFEST" +note " paths: ${#PATHS[@]} declared, ${#EXCLUDES[@]} excluded" +note " branch: $BRANCH" +[ -n "$BASE_REF" ] && note " stacked on: $BASE_REF" +if [ "$PUSH" -eq 1 ]; then + note " mode: PUSH (commit and push each repository)" +elif [ "$APPLY" -eq 1 ]; then + note " mode: apply (write locally, do not push)" +else + note " mode: dry-run (nothing written)" +fi +hr + +# --------------------------------------------------------------------------- +# Repo selection — same batch arithmetic as the stage-5 sweep, so operators +# only have to know one set of flags. +# --------------------------------------------------------------------------- +mapfile -t ALL_REPOS < <(grep -vE '^\s*(#|$)' "$REPOS_FILE" | sed -e 's/[[:space:]]*$//') +TOTAL=${#ALL_REPOS[@]} +[ "$TOTAL" -gt 0 ] || die "no repositories in $REPOS_FILE" + +if [ -n "$BATCH" ]; then + start=$(( (BATCH - 1) * BATCH_SIZE )) + end=$(( start + BATCH_SIZE )) + [ "$start" -lt "$TOTAL" ] || die "batch $BATCH is past the end ($TOTAL repositories)" + SELECTED=("${ALL_REPOS[@]:start:end-start}") + note "batch $BATCH of size $BATCH_SIZE: ${#SELECTED[@]} of $TOTAL repositories" +else + SELECTED=("${ALL_REPOS[@]}") +fi +hr + +# --------------------------------------------------------------------------- +# Token safety +# --------------------------------------------------------------------------- +# {{TOKEN}} and friends are META tokens: they name a placeholder kind rather +# than being one, and the spine's own gate exempts them. A repository running an +# OLDER check-no-placeholders.sh has no such exemption, so a propagated comment +# mentioning the token would fail that repo's every push. Refusing is the safe +# default; the override exists for a deliberate decision, not a convenience. +META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + +# --------------------------------------------------------------------------- +# Self-name substitution +# --------------------------------------------------------------------------- +# ADR-0003's mint criterion: no template identity survives in the child. The +# spine's own name is a literal, so propagating it verbatim would re-plant the +# identity the mint pass exists to remove. +SPINE_NAME="$(basename "$SPINE")" +substitute() { # src-file repo-name -> stdout + local f="$1" repo="$2" + if [ "$SUBSTITUTE" -eq 1 ] && [ "$repo" != "$SPINE_NAME" ]; then + sed "s/\b${SPINE_NAME}\b/${repo}/g" "$f" + else + cat "$f" + fi +} + +# --------------------------------------------------------------------------- +# Divergence declarations +# --------------------------------------------------------------------------- +# If the repository carries a VARIANT.a2ml, honour it: a path it declares +# diverged is a decision already made, and a campaign must not silently reverse +# a decision. Matches check-variant-drift.sh's in_list semantics, including the +# 'dir/' prefix form. +declared_diverged() { # repo-path path -> 0 if declared diverged + local dir="$1" want="$2" contract="$1/.machine_readable/descriptiles/VARIANT.a2ml" + [ -f "$contract" ] || return 1 + local e + while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) case "$want" in "$e"*) return 0 ;; esac ;; + *) [ "$want" = "$e" ] && return 0 ;; + esac + done < <(awk ' + $0 == "[paths.diverged]" || $0 == "[paths.diverged-pending-upstream]" || + $0 == "[paths.operational-state]" { insec = 1; next } + insec && /^\[/ { insec = 0 } + insec && /^ *"/ { line = $0; sub(/^ *"/,"",line); sub(/".*$/,"",line); print line } + ' "$contract") + return 1 +} + +excluded() { # path -> 0 if excluded by the manifest + local want="$1" e + for e in ${EXCLUDES+"${EXCLUDES[@]}"}; do + case "$e" in + */) case "$want" in "$e"*) return 0 ;; esac ;; + *) [ "$want" = "$e" ] && return 0 ;; + esac + done + return 1 +} + +# --------------------------------------------------------------------------- +# The campaign itself +# --------------------------------------------------------------------------- +REPORT="${REPORT:-$WORK_DIR/campaign-report.tsv}" +printf 'repo\tpath\toutcome\tdetail\n' > "$REPORT" + +ADDED=0; UPDATED=0; UNCHANGED=0; SKIPPED=0; REFUSED=0; NOCHANGE=0; FAILED=0 + +for repo in "${SELECTED[@]}"; do + [ -z "$repo" ] && continue + dir="$WORK_DIR/repos/$repo" + rm -rf "$dir" + mkdir -p "$WORK_DIR/repos" + + # A repos file normally lists bare names under one owner; owner/name is + # accepted too, so a campaign can span owners without a second flag. + case "$repo" in + */*) slug="$repo" ;; + *) slug="${RSR_OWNER:-hyperpolymath}/$repo" ;; + esac + + # --base-ref stacks the campaign on work that is not merged yet. Without it + # the campaign branches from the default branch, which is the honest + # default: a campaign should not silently depend on an unmerged branch. + clone_args=(-q --depth 1) + [ -n "$BASE_REF" ] && clone_args+=(-b "$BASE_REF") + if ! git clone "${clone_args[@]}" "https://github.com/$slug.git" "$dir" 2>/dev/null; then + if [ -n "$BASE_REF" ]; then + printf '%s\t-\tCLONE-FAIL\tbase ref %s not found\n' "$repo" "$BASE_REF" >> "$REPORT" + note " $repo: CLONE-FAIL (no $BASE_REF)" + else + printf '%s\t-\tCLONE-FAIL\t-\n' "$repo" >> "$REPORT" + note " $repo: CLONE-FAIL" + fi + FAILED=$((FAILED+1)); continue + fi + + repo_changed=0 + repo_added=0; repo_updated=0; repo_skipped=0; repo_refused=0 + + # Collect the spine's file list for the declared paths. + while IFS= read -r rel; do + [ -z "$rel" ] && continue + case "$rel" in + *.gitkeep|*/.gitkeep) continue ;; + esac + if excluded "$rel"; then + printf '%s\t%s\tEXCLUDED\tmanifest exclude\n' "$repo" "$rel" >> "$REPORT" + repo_skipped=$((repo_skipped+1)); continue + fi + if declared_diverged "$dir" "$rel"; then + printf '%s\t%s\tDIVERGED\tdeclared in VARIANT.a2ml\n' "$repo" "$rel" >> "$REPORT" + repo_skipped=$((repo_skipped+1)); continue + fi + + src="$SPINE/$rel" + dst="$dir/$rel" + [ -f "$src" ] || continue + + if [ "$ALLOW_TOKENS" -eq 0 ]; then + # Name the actual tokens. A report that says "carries {{TOKEN}}" + # whatever it found is a claim wider than its evidence, which is + # the specific failure mode this estate keeps auditing itself for. + toks="$(grep -ohE '\{\{[A-Z_]+\}\}' "$src" 2>/dev/null \ + | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ')" + if [ -n "$toks" ]; then + # shellcheck disable=SC2086 # deliberate word split for -n + printf '%s\t%s\tREFUSED\tcarries unfilled token(s): %s\n' \ + "$repo" "$rel" "${toks% }" >> "$REPORT" + repo_refused=$((repo_refused+1)); continue + fi + fi + + tmp="$(mktemp)" + substitute "$src" "${repo##*/}" > "$tmp" + + if [ ! -f "$dst" ]; then + printf '%s\t%s\tADDED\t-\n' "$repo" "$rel" >> "$REPORT" + repo_added=$((repo_added+1)); repo_changed=1 + if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then + mkdir -p "$(dirname "$dst")"; cp "$tmp" "$dst" + fi + elif cmp -s "$tmp" "$dst"; then + printf '%s\t%s\tUNCHANGED\t-\n' "$repo" "$rel" >> "$REPORT" + else + printf '%s\t%s\tUPDATED\t-\n' "$repo" "$rel" >> "$REPORT" + repo_updated=$((repo_updated+1)); repo_changed=1 + if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then + cp "$tmp" "$dst" + fi + fi + rm -f "$tmp" + done < <(git -C "$SPINE" ls-files -- "${PATHS[@]}" 2>/dev/null | sort) + + ADDED=$((ADDED+repo_added)); UPDATED=$((UPDATED+repo_updated)) + SKIPPED=$((SKIPPED+repo_skipped)); REFUSED=$((REFUSED+repo_refused)) + + if [ "$repo_changed" -eq 0 ]; then + note " $repo: no change ($repo_skipped skipped, $repo_refused refused)" + NOCHANGE=$((NOCHANGE+1)) + continue + fi + + detail="+$repo_added ~$repo_updated" + if [ "$APPLY" -eq 0 ] && [ "$PUSH" -eq 0 ]; then + note " $repo: would change ($detail$([ "$repo_skipped" -gt 0 ] && printf ', %s skipped' "$repo_skipped"))" + continue + fi + + if [ "$PUSH" -eq 1 ]; then + ( cd "$dir" \ + && git checkout -q -b "$BRANCH" \ + && git add -A -- . \ + && git -c user.name="${RSR_COMMIT_NAME:-rsr-campaign}" \ + -c user.email="${RSR_COMMIT_EMAIL:-rsr-campaign@users.noreply.github.com}" \ + commit -q -m "chore(rsr): campaign '$CAMP_NAME' — $detail" \ + && git push -q origin "$BRANCH" ) >/dev/null 2>&1 || { + printf '%s\t-\tPUSH-FAIL\t-\n' "$repo" >> "$REPORT" + note " $repo: PUSH-FAIL" + FAILED=$((FAILED+1)); continue + } + note " $repo: pushed ($detail)" + else + note " $repo: applied ($detail)" + fi +done + +hr +note "campaign '$CAMP_NAME' complete" +note " paths ADDED: $ADDED" +note " paths UPDATED: $UPDATED" +note " paths SKIPPED: $SKIPPED (declared diverged / excluded)" +note " paths REFUSED: $REFUSED (carried an unfilled placeholder)" +note " repos unchanged: $NOCHANGE" +note " repos failed: $FAILED" +note " report: $REPORT" +[ "$FAILED" -eq 0 ] || exit 1 diff --git a/czech-file-knife/scripts/rust-tool.sh b/czech-file-knife/scripts/rust-tool.sh new file mode 100644 index 000000000..436fdbaa7 --- /dev/null +++ b/czech-file-knife/scripts/rust-tool.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# rust-tool.sh — compile-on-demand runner for the single-file Rust mint tools. +# +# Ruby was the previous implementation of these tools; it is not an +# estate-authorised language, so they are Rust now. Each tool is a single +# std-only file, which `rustc` compiles in well under a second with no +# dependency resolution and no build system. +# +# Binaries are cached, and rebuilt only when the source is newer, so a machine +# pays the compile cost once rather than once per mint. Override the cache +# location with RSR_MINT_TOOL_CACHE. +# +# Usage: scripts/rust-tool.sh [args...] + +set -euo pipefail + +name="${1:-}" +[ -n "$name" ] || { echo "rust-tool: usage: rust-tool.sh [args...]" >&2; exit 2; } +shift + +scripts_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +src="$scripts_dir/$name.rs" + +[ -f "$src" ] || { echo "rust-tool: no such tool: $src" >&2; exit 2; } + +cache="${RSR_MINT_TOOL_CACHE:-${TMPDIR:-/tmp}/rsr-mint-tools}" +mkdir -p "$cache" +bin="$cache/$name" + +# Recompile only when the binary is missing or older than its source, so the +# common case is a straight exec. +if [ ! -x "$bin" ] || [ "$src" -nt "$bin" ]; then + # The toolchain is needed to BUILD, not to RUN. Checking for rustc up + # front made a cached binary unusable on a machine without one, which is + # the common case in CI: the run is a straight exec. Only ask for rustc + # when a build actually has to happen. + command -v rustc >/dev/null 2>&1 || { + echo "rust-tool: rustc not found, and $name is not built yet." >&2 + echo " The mint tools are Rust and need a Rust toolchain." >&2 + echo " Install one from https://rustup.rs, then re-run." >&2 + exit 2 + } + rustc -O -o "$bin" "$src" >&2 || { + echo "rust-tool: failed to compile $src" >&2 + exit 2 + } +fi + +exec "$bin" "$@" diff --git a/czech-file-knife/scripts/scan-dangerous.sh b/czech-file-knife/scripts/scan-dangerous.sh new file mode 100755 index 000000000..9377ce722 --- /dev/null +++ b/czech-file-knife/scripts/scan-dangerous.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# scan-dangerous.sh — flag dangerous/unsafe constructs USED in proof code. +# +# Dangerous constructs (believe_me, assert_total, postulate, sorry, Admitted, +# unsafeCoerce, Obj.magic) escape the proof obligation and must not appear in +# real proofs. BUT the previous `proof-scan-dangerous` recipe grepped raw +# lines, so a comment that merely NAMED a banned construct — +# -- All proofs MUST be constructive (no believe_me, no assert_total). +# — tripped the gate. A check that fires on its own documentation is a +# false-positive gate: it cries wolf, trains people to override it (violating +# "squabble, don't bypass"), and wired into CI it turns the tree red for +# nothing. This version strips comments first, so only real usage is flagged. +# +# Comment syntax handled: `--` line + `{- -}` block (Idris2/Lean4/Agda), +# `(* *)` block (Coq). Comment bodies are blanked in place, so reported line +# numbers still match the source. +# +# Exit: 0 = clean; 1 = a proof uses a dangerous construct in code. + +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +PATTERNS='believe_me|assert_total|postulate|sorry|Admitted|unsafeCoerce|Obj\.magic' +dangerous=0 + +# Blank comment content while preserving line count (so grep -n stays accurate). +strip_comments() { + local ext="${1##*.}" lc bo bc + case "$ext" in + idr|lean|agda) lc='--'; bo='{-'; bc='-}' ;; # line + block comments + v) lc=''; bo='(*'; bc='*)' ;; # Coq block comments only + *) lc=''; bo=''; bc='' ;; + esac + awk -v lc="$lc" -v bo="$bo" -v bc="$bc" ' + BEGIN { inblk = 0 } + { + line = $0; out = ""; i = 1; n = length(line) + while (i <= n) { + if (inblk) { + if (bc != "" && substr(line,i,length(bc)) == bc) { inblk = 0; i += length(bc) } + else { i++ } + } else if (bo != "" && substr(line,i,length(bo)) == bo) { + inblk = 1; i += length(bo) + } else if (lc != "" && substr(line,i,length(lc)) == lc) { + break # rest of the line is a line-comment + } else { + out = out substr(line,i,1); i++ + } + } + print out + }' "$1" +} + +while IFS= read -r f; do + [ -z "$f" ] && continue + matches="$(strip_comments "$f" | grep -nE "$PATTERNS" || true)" + if [ -n "$matches" ]; then + echo " DANGEROUS (used in code): $f" + printf '%s\n' "$matches" | sed 's/^/ /' + dangerous=$((dangerous + 1)) + fi +done < <(find verification/proofs \ + \( -name '*.idr' -o -name '*.lean' -o -name '*.agda' -o -name '*.v' \) \ + -not -path '*/build/*' 2>/dev/null | sort) + +echo +if [ "$dangerous" -gt 0 ]; then + echo "FAIL: $dangerous file(s) use dangerous constructs in proof CODE (not comments)" + exit 1 +fi +echo "PASS: no dangerous constructs used in proof code" diff --git a/czech-file-knife/scripts/strip-instruction-blocks.rs b/czech-file-knife/scripts/strip-instruction-blocks.rs new file mode 100644 index 000000000..eb2060d20 --- /dev/null +++ b/czech-file-knife/scripts/strip-instruction-blocks.rs @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// Remove only HTML comments containing TEMPLATE INSTRUCTIONS after minting. +// +// Ported from strip-instruction-blocks.rb: Ruby is not an estate-authorised +// language, and this runs on every mint. Single file, std only, no +// dependencies — compiled on demand by `just repo-init` (see the rust_tool +// helper in build/just/repo-init.just). +// +// The match is deliberately tempered: an HTML comment is only removed when the +// marker appears BEFORE its own `-->`, so a block cannot swallow the +// terminator of a preceding comment. That is what keeps an SPDX comment that +// sits immediately above an instructions block intact. +// +// Usage: strip-instruction-blocks.rs [ROOT] (default ROOT = .) + +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +const MARKER: &str = "TEMPLATE INSTRUCTIONS"; +const OPEN: &str = ""; + +/// Directories never descended into. Mirrors the Ruby original. +const SKIP_DIRS: [&str; 5] = [".git", "node_modules", ".venv", "target", "dist"]; + +/// Remove every HTML comment whose body contains MARKER, then collapse runs of +/// three or more newlines to two. +/// +/// Indexing is by byte, which is safe because every boundary we slice at is an +/// ASCII delimiter (``); a multi-byte character can never be split. +fn strip_blocks(text: &str) -> String { + let bytes = text.as_bytes(); + let mut out = String::with_capacity(text.len()); + let mut i = 0usize; + + while i < bytes.len() { + let start = match find_from(text, OPEN, i) { + Some(p) => p, + None => { + out.push_str(&text[i..]); + break; + } + }; + // Everything before this comment is copied verbatim. + out.push_str(&text[i..start]); + + let end = match find_from(text, CLOSE, start + OPEN.len()) { + Some(p) => p, + // Unterminated comment: not ours to touch. + None => { + out.push_str(&text[start..]); + break; + } + }; + + let body = &text[start + OPEN.len()..end]; + if body.contains(MARKER) { + // Drop the comment, plus trailing horizontal space, plus one + // newline, so a removed block does not leave a blank line behind. + let mut j = end + CLOSE.len(); + while j < bytes.len() && (bytes[j] == b' ' || bytes[j] == b'\t') { + j += 1; + } + if j < bytes.len() && bytes[j] == b'\n' { + j += 1; + } + i = j; + } else { + // A comment without the marker is preserved exactly. + out.push_str(&text[start..end + CLOSE.len()]); + i = end + CLOSE.len(); + } + } + + collapse_blank_runs(&out) +} + +fn find_from(haystack: &str, needle: &str, from: usize) -> Option { + haystack[from..].find(needle).map(|p| from + p) +} + +/// Ruby's `gsub(/\n{3,}/, "\n\n")`: three or more newlines become two. +fn collapse_blank_runs(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + let mut run = 0usize; + for ch in s.chars() { + if ch == '\n' { + run += 1; + if run <= 2 { + out.push(ch); + } + } else { + run = 0; + out.push(ch); + } + } + out +} + +fn process(path: &Path, changed: &mut usize) { + let bytes = match fs::read(path) { + Ok(b) => b, + Err(_) => return, + }; + // Non-UTF-8 and unreadable files are skipped, never rewritten blind. + let text = match String::from_utf8(bytes) { + Ok(t) => t, + Err(_) => return, + }; + if !text.contains(MARKER) { + return; + } + let updated = strip_blocks(&text); + if updated == text { + return; + } + if fs::write(path, updated).is_err() { + return; + } + println!(" instruction block: stripped from {}", path.display()); + *changed += 1; +} + +fn walk(root: &Path, changed: &mut usize) { + // Explicit stack rather than recursion: deep trees must not blow the stack. + let mut stack: Vec = vec![root.to_path_buf()]; + while let Some(dir) = stack.pop() { + let entries = match fs::read_dir(&dir) { + Ok(e) => e, + Err(_) => continue, + }; + for entry in entries.flatten() { + let path = entry.path(); + let ft = match entry.file_type() { + Ok(ft) => ft, + Err(_) => continue, + }; + if ft.is_symlink() { + continue; + } + if ft.is_dir() { + let skip = path + .file_name() + .and_then(|n| n.to_str()) + .map(|n| SKIP_DIRS.contains(&n)) + .unwrap_or(false); + if skip { + continue; + } + stack.push(path); + continue; + } + if ft.is_file() { + process(&path, changed); + } + } + } +} + +fn main() { + let root = env::args().nth(1).unwrap_or_else(|| ".".to_string()); + let mut changed = 0usize; + walk(Path::new(&root), &mut changed); + if changed > 0 { + println!(" instruction blocks: {} file(s) cleaned", changed); + } else { + println!(" instruction blocks: none found"); + } +} diff --git a/czech-file-knife/scripts/sweep-wellknown.sh b/czech-file-knife/scripts/sweep-wellknown.sh new file mode 100755 index 000000000..733b56513 --- /dev/null +++ b/czech-file-knife/scripts/sweep-wellknown.sh @@ -0,0 +1,421 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# sweep-wellknown.sh — stage 5 (#119) batch driver. +# +# #119 enumerated the denominator: 270 of 348 owner repositories carry a root +# .well-known/. This drives the sweep the issue asked for — +# +# per repo: classify -> run migrator -> run suite -> commit +# +# in batches, rather than as 270 hand-made pull requests. It is deliberately +# a driver and not a bot: it never pushes unless you pass --push, and it +# refuses to guess on the one class of repository where migrating is not +# obviously correct. +# +# Classification (one GitHub API call per repo) decides who gets swept: +# +# sweep — identifiably RSR-derived and not served from the repo +# root: migrate it. RSR-ness is judged on a tiered marker +# (root-allow.txt under either spelling, else a +# .machine_readable/ tree), because the allowlist only +# entered the template in August 2026 and most of the +# estate predates it. The marker that fired is recorded. +# review-pages — GitHub Pages / CDN-served from the repo root. #119 names +# these as undecided: for a Pages repo the root +# .well-known/ may be a SERVING REQUIREMENT rather than +# legacy layout, and moving it out of the served root can +# break live discovery. Not swept without --include-review. +# review-other — has a root .well-known/ but no RSR marker at all, so the +# arrangement is not known to be template-derived. +# Not swept without --include-review. +# +# Requirements: bash, git, curl, jq. A token with `repo` scope (public repos +# need only `public_repo`) in GITHUB_TOKEN, or a signed-in `gh` — or pass +# --no-auth with --trees-dir to do everything except push, unauthenticated. +# +# Exit codes: +# 0 — every selected repository ended clean or already-clean +# 1 — at least one repository failed, or quarantined content needs a human +# (suppress the latter with --allow-conflicts) +# 2 — usage / setup error + +set -euo pipefail + +OWNER="hyperpolymath" +REPOS_FILE="" +BATCH="" +BATCH_SIZE=25 +LIMIT="" +DRY_RUN=0 +PUSH=0 +INCLUDE_REVIEW=0 +ALLOW_CONFLICTS=0 +CLASSIFY_ONLY=0 +NO_AUTH=0 +TREES_DIR="" +WORK="" +BRANCH="chore/well-known-to-www" +MIGRATOR="" +SUITE="" + +usage() { + cat <<'EOF' +Usage: scripts/sweep-wellknown.sh [options] [repo ...] + +Classify, migrate and test repositories in batches (czech-file-knife#119). + +Selecting repositories: + --repos-file FILE one repository name per line ('#' comments allowed) + --owner OWNER default: hyperpolymath + --batch N process only batch N (1-based) of --batch-size + --batch-size N default: 25 + --limit N process only the first N selected repositories + +What to do: + --classify-only classify and write classification.csv; migrate nothing + --include-review also sweep review-pages / review-other (see header) + --dry-run classify, migrate --dry-run, run the suite; commit nothing + --push push the branch to origin (default: commit only) + --branch NAME branch to commit on (default: chore/well-known-to-www) + --allow-conflicts exit 0 even where content was quarantined + --work-dir DIR where to put clones and reports (default: mktemp -d) + --no-auth run without a token: clone read-only over https and + classify from --trees-dir. Classify, migrate, test and + commit all work; only --push needs a credential. + --trees-dir DIR classify from cached `git/trees` listings, one file per + repository, one path per line, as written by a previous + run's /trees/. Required by --no-auth: the + unauthenticated API is capped at 60 requests/hour. + +Environment: + GITHUB_TOKEN / GH_TOKEN required; falls back to `gh auth token` +EOF +} + +die() { echo "sweep: ERROR — $*" >&2; exit 2; } +note() { echo "sweep: $*"; } + +POSITIONAL=() + +while [ $# -gt 0 ]; do + case "$1" in + --owner) [ $# -ge 2 ] || die "--owner requires a value"; OWNER="$2"; shift 2 ;; + --repos-file) [ $# -ge 2 ] || die "--repos-file requires a path"; REPOS_FILE="$2"; shift 2 ;; + --batch) [ $# -ge 2 ] || die "--batch requires a number"; BATCH="$2"; shift 2 ;; + --batch-size) [ $# -ge 2 ] || die "--batch-size requires a number"; BATCH_SIZE="$2"; shift 2 ;; + --limit) [ $# -ge 2 ] || die "--limit requires a number"; LIMIT="$2"; shift 2 ;; + --classify-only) CLASSIFY_ONLY=1; shift ;; + --include-review) INCLUDE_REVIEW=1; shift ;; + --no-auth) NO_AUTH=1; shift ;; + --trees-dir) [ $# -ge 2 ] || die "--trees-dir requires a path"; TREES_DIR="$2"; shift 2 ;; + --dry-run) DRY_RUN=1; shift ;; + --push) PUSH=1; shift ;; + --allow-conflicts) ALLOW_CONFLICTS=1; shift ;; + --branch) [ $# -ge 2 ] || die "--branch requires a value"; BRANCH="$2"; shift 2 ;; + --work-dir) [ $# -ge 2 ] || die "--work-dir requires a path"; WORK="$2"; shift 2 ;; + --migrator) [ $# -ge 2 ] || die "--migrator requires a path"; MIGRATOR="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + -*) die "unknown option: $1" ;; + *) POSITIONAL+=("$1"); shift ;; + esac +done + +for c in git curl jq; do + command -v "$c" >/dev/null 2>&1 || die "$c is required but not installed" +done + +# Fail before cloning anything: discovering on repo 200 of 270 that commits +# cannot be made wastes the whole run and leaves 199 half-swept checkouts. +# `git var` honours config and GIT_COMMITTER_* alike, so either setup passes. +if [ "$CLASSIFY_ONLY" -eq 0 ] && [ "$DRY_RUN" -eq 0 ]; then + if ! git var GIT_COMMITTER_IDENT >/dev/null 2>&1; then + die "git cannot determine a committer identity, and the sweep commits. Either: + git config --global user.name \"Your Name\" + git config --global user.email \"you@example.com\" +or export GIT_COMMITTER_NAME / GIT_COMMITTER_EMAIL (plus GIT_AUTHOR_*)." + fi +fi + +# ── token ─────────────────────────────────────────────────────────────────── +TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" +if [ -z "$TOKEN" ] && command -v gh >/dev/null 2>&1; then + TOKEN="$(gh auth token 2>/dev/null || true)" +fi +if [ -z "$TOKEN" ]; then + if [ "$NO_AUTH" -eq 0 ]; then + die "no token: set GITHUB_TOKEN (repo scope), sign in with gh, or pass --no-auth to run read-only" + fi + note "no token available; running unauthenticated (--no-auth) — push unavailable" + [ -n "$TREES_DIR" ] || die "--no-auth requires --trees-dir (unauthenticated API is 60 req/hour)" + [ -d "$TREES_DIR" ] || die "trees dir not found: $TREES_DIR" +fi +if [ "$PUSH" -eq 1 ] && [ -z "$TOKEN" ]; then + die "--push requires a token; re-run with GITHUB_TOKEN set" +fi + +# ── paths ─────────────────────────────────────────────────────────────────── +# The migrator is located rather than assumed: this script is run from a +# template checkout, from a copy on $PATH, or from an unrelated directory, and +# each of those puts the scripts/ directory somewhere different. +resolve_migrator() { + if [ -n "$MIGRATOR" ]; then printf '%s' "$MIGRATOR"; return; fi + local c + for c in "$(git rev-parse --show-toplevel 2>/dev/null || true)" \ + "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" \ + "$PWD"; do + if [ -n "$c" ] && [ -f "$c/scripts/migrate-wellknown-to-www.sh" ]; then + printf '%s' "$c/scripts/migrate-wellknown-to-www.sh" + return + fi + done + printf '' +} +MIGRATOR="$(resolve_migrator)" +[ -n "$MIGRATOR" ] || die "migrator not found; pass --migrator /path/to/migrate-wellknown-to-www.sh" + +if [ -z "$WORK" ]; then + WORK="$(mktemp -d "${TMPDIR:-/tmp}/wellknown-sweep.XXXXXX")" +else + mkdir -p "$WORK" +fi +mkdir -p "$WORK/reports" "$WORK/repos" + +CLASS_CSV="$WORK/classification.csv" +RESULT_CSV="$WORK/sweep-results.csv" +printf 'repo,classification,root_wellknown,www_wellknown,notes\n' > "$CLASS_CSV" +printf 'repo,classification,status,detail\n' > "$RESULT_CSV" + +# ── repository list ───────────────────────────────────────────────────────── +REPOS=() +if [ -n "$REPOS_FILE" ]; then + [ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" + while IFS= read -r line; do + line="${line%%#*}" + line="$(printf '%s' "$line" | tr -d '[:space:]')" + [ -n "$line" ] && REPOS+=("$line") + done < "$REPOS_FILE" +fi +REPOS+=(${POSITIONAL[@]+"${POSITIONAL[@]}"}) + +if [ ${#REPOS[@]} -eq 0 ]; then + die "no repositories selected: pass --repos-file FILE or names as arguments" +fi + +if [ -n "$BATCH" ]; then + start=$(( (BATCH - 1) * BATCH_SIZE )) + REPOS=(${REPOS[@]:$start:$BATCH_SIZE}) + [ ${#REPOS[@]} -gt 0 ] || die "batch $BATCH is empty" + note "batch $BATCH of size $BATCH_SIZE: ${#REPOS[@]} repository(ies)" +fi +if [ -n "$LIMIT" ] && [ "$LIMIT" -lt ${#REPOS[@]} ]; then + REPOS=(${REPOS[@]:0:$LIMIT}) +fi +note "selected ${#REPOS[@]} repository(ies) from $OWNER; work dir $WORK" + +# ── classification ───────────────────────────────────────────────────────── +gh_api() { # path... + curl -sS --fail --retry 2 --retry-delay 1 --max-time 60 \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "$@" +} + +classify_repo() { # repo -> "class|root_wk|www_wk|notes"; paths cached in $WORK/trees/ + local repo="$1" + local json paths="" + mkdir -p "$WORK/trees" + + # A cached tree listing is the same data the API would return, so prefer + # it: unauthenticated classification is capped at 60 requests/hour. + if [ -n "$TREES_DIR" ] && [ -f "$TREES_DIR/$repo" ]; then + paths="$(cat "$TREES_DIR/$repo")" + fi + if [ -n "$paths" ]; then + printf '%s\n' "$paths" > "$WORK/trees/$repo" + else + if ! json="$(gh_api "https://api.github.com/repos/$OWNER/$repo/git/trees/HEAD?recursive=1" 2>/dev/null)"; then + printf 'unknown|?|?|API error (missing/empty/private, or rate limited)\n' + return 0 + fi + if [ "$(printf '%s' "$json" | jq -r '.truncated // false')" = "true" ]; then + printf 'review-other|?|?|tree truncated by API — classify by hand\n' + return 0 + fi + paths="$(printf '%s' "$json" | jq -r '.tree[]?.path // empty')" + printf '%s\n' "$paths" > "$WORK/trees/$repo" + fi + + has() { grep -qxF "$1" <<<"$paths"; } + + local root_wk=no www_wk=no rsr=no pages=no notes="" + has ".well-known/security.txt" && root_wk=yes + has "www/.well-known/security.txt" && www_wk=yes + # Any file under either location counts, not just security.txt. + grep -qx "\.well-known/..*" <<<"$paths" && root_wk=yes + grep -qx "www/\.well-known/..*" <<<"$paths" && www_wk=yes + + # RSR-derived, tiered by evidence strength. The root allowlist is the + # marker check-root-shape.sh itself resolves — but it only entered the + # template in August 2026, so a repository minted before then is still + # RSR-derived and simply does not carry it. Measured over the #119 + # denominator: 269/270 have a .machine_readable/ tree while only 59 have + # root-allow.txt. Treating the allowlist as the sole marker therefore + # misclassifies two thirds of the estate as "not an RSR instance", which + # is wrong in the safe direction only by accident. The marker used is + # recorded in the notes column so the call is auditable. + local marker="" + if has ".machine_readable/root-allow.txt" || has "machine-readable/root-allow.txt"; then + rsr=yes + marker="root-allow.txt" + elif grep -q "^\.machine_readable/" <<<"$paths" \ + || grep -q "^machine-readable/" <<<"$paths"; then + rsr=yes + marker=".machine_readable/ (predates root-allow.txt)" + fi + + # Pages / CDN served from the repo root: migrating .well-known/ out of the + # served root is not obviously safe here (#119 special cases). + case "$repo" in + *.github.io) pages=yes; notes="repo name is a Pages site" ;; + esac + if has "CNAME"; then pages=yes; notes="${notes:+$notes; }CNAME at root (custom domain)" ; fi + for f in netlify.toml vercel.json _config.yml wrangler.toml wrangler.jsonc; do + if has "$f"; then pages=yes; notes="${notes:+$notes; }$f present" ; fi + done + + local class + if [ "$pages" = yes ]; then + class=review-pages + elif [ "$rsr" = yes ]; then + class=sweep + else + class=review-other + notes="${notes:+$notes; }no RSR marker found" + fi + [ -n "$marker" ] && notes="${notes:+$notes; }marker: $marker" + printf '%s|%s|%s|%s\n' "$class" "$root_wk" "$www_wk" "$notes" +} + +# ── migration ─────────────────────────────────────────────────────────────── +sweep_repo() { # repo class -> appends to RESULT_CSV + local repo="$1" class="$2" + local dir="$WORK/repos/$repo" + rm -rf "$dir" + + if [ "$CLASSIFY_ONLY" -eq 1 ]; then + printf '%s,%s,skipped,classify-only\n' "$repo" "$class" >> "$RESULT_CSV" + return 0 + fi + + export GIT_TERMINAL_PROMPT=0 + # Public repositories clone fine over plain https; the token only buys + # push access (and a higher rate limit), so do not require it to read. + local clone_url="https://github.com/${OWNER}/${repo}.git" + [ -n "$TOKEN" ] && clone_url="https://x-access-token:${TOKEN}@github.com/${OWNER}/${repo}.git" + if ! git clone --depth 1 -q "$clone_url" "$dir" 2>"$WORK/reports/$repo.clone.log"; then + printf '%s,%s,failed,clone failed (see %s)\n' "$repo" "$class" "$WORK/reports/$repo.clone.log" >> "$RESULT_CSV" + return 0 + fi + + if [ ! -d "$dir/.well-known" ]; then + printf '%s,%s,clean,no root .well-known/ (already migrated or never had one)\n' "$repo" "$class" >> "$RESULT_CSV" + return 0 + fi + + local report="$WORK/reports/$repo.tsv" + local migflags=() + [ "$DRY_RUN" -eq 1 ] && migflags+=(--dry-run) + + local status=migrated detail="" + if (cd "$dir" && bash "$MIGRATOR" "${migflags[@]}" --report "$report") >"$WORK/reports/$repo.migrate.log" 2>&1; then + if [ "$DRY_RUN" -eq 1 ]; then + status=would-migrate + fi + if [ -f "$report" ] && grep -qP '^quarantined\t' "$report"; then + status=quarantined + detail="$(grep -cP '^quarantined\t' "$report") divergent file(s) quarantined — needs a human" + fi + else + status=failed + detail="migrator exited non-zero (see $WORK/reports/$repo.migrate.log)" + fi + + # Post-migration suite: only meaningful where the repo carries the bundle. + if [ -f "$dir/www/tests/run-all.sh" ]; then + if ! (cd "$dir" && bash www/tests/run-all.sh) >"$WORK/reports/$repo.tests.log" 2>&1; then + status=failed + detail="${detail:+$detail; }www/tests/run-all.sh failed (see $WORK/reports/$repo.tests.log)" + fi + else + detail="${detail:+$detail; }no www/tests bundle — run the RSR update mechanism first" + fi + + if [ "$DRY_RUN" -eq 0 ] && [ "$status" != failed ]; then + if [ -n "$(cd "$dir" && git status --porcelain)" ]; then + # A commit failure is recorded, never fatal: one repository with an + # unusual hook or an unwritable ref must not abandon the batch. + if (cd "$dir" && git add -A && git commit -qm "chore(www): migrate root .well-known/ to www/.well-known/ (czech-file-knife#119)"); then + if [ "$PUSH" -eq 1 ]; then + (cd "$dir" && git push -q origin "HEAD:$BRANCH") \ + || detail="${detail:+$detail; }push failed" + fi + else + status=failed + detail="${detail:+$detail; }commit failed" + fi + else + [ "$status" = migrated ] && status=clean + fi + fi + + printf '%s,%s,%s,%s\n' "$repo" "$class" "$status" "$detail" >> "$RESULT_CSV" + return 0 +} + +# ── main ──────────────────────────────────────────────────────────────────── +declare -i total=0 swept=0 review=0 +for repo in "${REPOS[@]}"; do + total+=1 + IFS='|' read -r class root_wk www_wk notes <<<"$(classify_repo "$repo")" + printf '%s,%s,%s,%s,%s\n' "$repo" "$class" "$root_wk" "$www_wk" "$notes" >> "$CLASS_CSV" + printf ' %-40s %s\n' "$repo" "$class${notes:+ — $notes}" + + if [ "$class" = sweep ] || [ "$INCLUDE_REVIEW" -eq 1 ]; then + swept+=1 + # Belt and braces: sweep_repo records its own failures and returns 0, + # but an unexpected abort must still not take the batch down with it. + if ! sweep_repo "$repo" "$class"; then + printf '%s,%s,failed,unexpected abort (see %s)\n' "$repo" "$class" "$WORK" >> "$RESULT_CSV" + fi + else + review+=1 + printf '%s,%s,skipped,%s\n' "$repo" "$class" "needs a decision: $class" >> "$RESULT_CSV" + fi +done + +echo +note "classification: $CLASS_CSV" +note "results: $RESULT_CSV" +echo +echo "--- results by status ---" +tail -n +2 "$RESULT_CSV" | cut -d, -f3 | sort | uniq -c | sort -rn + +failed=$(tail -n +2 "$RESULT_CSV" | grep -c ',failed,' || true) +quarantined=$(tail -n +2 "$RESULT_CSV" | grep -c ',quarantined,' || true) + +if [ "$failed" -gt 0 ]; then + echo + echo "sweep: $failed repository(ies) FAILED:" >&2 + grep ',failed,' "$RESULT_CSV" >&2 + exit 1 +fi +if [ "$quarantined" -gt 0 ] && [ "$ALLOW_CONFLICTS" -eq 0 ]; then + echo + echo "sweep: $quarantined repository(ies) quarantined divergent content — resolve before merging." >&2 + grep ',quarantined,' "$RESULT_CSV" >&2 + exit 1 +fi +note "done: $total classified, $swept swept, $review held for review" +exit 0 diff --git a/czech-file-knife/scripts/validate-session-contracts.sh b/czech-file-knife/scripts/validate-session-contracts.sh new file mode 100644 index 000000000..2fe653101 --- /dev/null +++ b/czech-file-knife/scripts/validate-session-contracts.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Validate the two session policies with their actual Nickel evaluator. +set -euo pipefail +command -v nickel >/dev/null || { + echo "nickel is required to validate .k9.ncl session policies" >&2 + exit 2 +} +if [[ "${1:-}" == --typecheck ]]; then + shift + [[ $# -gt 0 ]] || { echo 'Supply the instantiated Nickel or K9 files to typecheck' >&2; exit 2; } + for file in "$@"; do + envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") + if [[ -n "$envelope_line" ]]; then + sed "${envelope_line}d" "$file" | (cd -- "$(dirname -- "$file")" && nickel typecheck) + else + nickel typecheck "$file" + fi + echo "$file: Nickel typecheck passed (deployment not executed)" + done + exit 0 +fi +[[ $# -eq 0 ]] || { echo 'Usage: validate-session-contracts.sh [--typecheck FILE...]' >&2; exit 2; } +for file in coordination.k9.ncl session/custom-checks.k9.ncl; do + envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") + if [[ -z "$envelope_line" ]]; then + echo "$file: missing K9! envelope" >&2 + exit 1 + fi + # K9! is a transport envelope, not a Nickel expression. These standalone + # records have no imports; evaluation also exercises their field contracts. + sed "${envelope_line}d" "$file" | nickel export --format json >/dev/null + echo "$file: Nickel evaluation passed" +done diff --git a/czech-file-knife/scripts/validate-template.sh b/czech-file-knife/scripts/validate-template.sh new file mode 100755 index 000000000..30d0c7a02 --- /dev/null +++ b/czech-file-knife/scripts/validate-template.sh @@ -0,0 +1,491 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Template Validation Script +# Verifies that a repository follows the RSR template structure and contains all required files +# +# Exit codes: +# 0 = validation passed +# 1 = validation failed with errors +# 2 = validation failed with warnings (but can proceed) + +set -euo pipefail + +REPO_ROOT="${1:-.}" +VERBOSE="${2:-0}" +ERRORS=0 +WARNINGS=0 + +# ANSI colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_error() { + echo -e "${RED}ERROR${NC}: $*" >&2 + ERRORS=$((ERRORS + 1)) +} + +log_warning() { + echo -e "${YELLOW}WARN${NC}: $*" >&2 + WARNINGS=$((WARNINGS + 1)) +} + +log_info() { + echo -e "${BLUE}INFO${NC}: $*" >&2 +} + +log_pass() { + echo -e "${GREEN}PASS${NC}: $*" >&2 +} + +check_file_exists() { + local file="$1" + local description="${2:-}" + if [ -f "$REPO_ROOT/$file" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: $file" + return 0 + else + log_error "Required file missing: $file ${description:+(${description})}" + return 1 + fi +} + +# The repo deed's FILENAME carries the repository name (filename dispatch: +# _chora.deed, stem = repo slug — deed.abnf v1.0.0), so the check is a +# glob, not a literal. Pre-deed era this slot was 0-AI-MANIFEST.a2ml; the +# family-7 allocation manifest and the ply tree folded into the deed +# (standards#837 pilot). +check_deed_exists() { + local description="${1:-}" + local f + for f in "$REPO_ROOT"/*_chora.deed; do + if [ -f "$f" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Repo deed exists: ${f#"$REPO_ROOT"/}" + return 0 + fi + done + log_error "Required repo deed missing: no _chora.deed at root ${description:+(${description})}" + return 1 +} + +check_file_either() { + local first="$1" + local second="$2" + local description="${3:-}" + if [ -f "$REPO_ROOT/$first" ] || [ -f "$REPO_ROOT/$second" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: $first or $second" + return 0 + fi + log_error "Required file missing: $first or $second ${description:+($description)}" + return 1 +} + +check_dir_exists() { + local dir="$1" + local description="${2:-}" + if [ -d "$REPO_ROOT/$dir" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $dir" + return 0 + else + log_error "Required directory missing: $dir ${description:+(${description})}" + return 1 + fi +} + +# The machine tree has two estate spellings: `.machine_readable/` is canonical, +# `machine-readable/` is the minority form ~9 repositories still carry. This +# file already had check_file_either for exactly this reason; directories had no +# such helper, and the gap let the matrix check below name the hyphenated form +# while its own message said `.machine_readable/`. Naming only one spelling +# fails whichever half of the estate has not migrated. +check_dir_either() { + local first="$1" + local second="$2" + local description="${3:-}" + if [ -d "$REPO_ROOT/$first" ] || [ -d "$REPO_ROOT/$second" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $first or $second" + return 0 + fi + log_error "Required directory missing: $first or $second ${description:+(${description})}" + return 1 +} + +# Case-tolerant ABI seam checks: accept the canonical case-consistent +# src/interface/Abi/ (matches `module Abi.*`) OR a lowercase src/interface/abi/ +# that some downstream repos ship. Never require BOTH (that would be a case-fold +# collision on case-insensitive filesystems). +check_abi_dir_exists() { + local description="${1:-}" + if [ -d "$REPO_ROOT/src/interface/Abi" ] || [ -d "$REPO_ROOT/src/interface/abi" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: src/interface/{Abi,abi}" + return 0 + fi + log_error "Required directory missing: src/interface/Abi ${description:+(${description})}" + return 1 +} +check_abi_file_exists() { + local fname="$1" + local description="${2:-}" + if [ -f "$REPO_ROOT/src/interface/Abi/$fname" ] || [ -f "$REPO_ROOT/src/interface/abi/$fname" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: src/interface/{Abi,abi}/$fname" + return 0 + fi + log_error "Required file missing: src/interface/Abi/$fname ${description:+(${description})}" + return 1 +} + +has_spdx_header() { + local file="$1" + if head -10 "$file" | grep -q "SPDX-License-Identifier"; then + return 0 + fi + return 1 +} + +has_placeholder() { + local file="$1" + if grep -q "{{REPO\|{{OWNER\|{{FORGE\|{{PROJECT\|{{project\|{{AUTHOR" "$file" 2>/dev/null; then + return 0 + fi + return 1 +} + +#============================================================================== +# VALIDATION PHASE 1: CORE STRUCTURE +#============================================================================== + +echo "" +log_info "Phase 1: Core repository structure" +echo "" + +# Root files +check_deed_exists "repo deed (universal AI entry point)" +check_file_exists "README.adoc" "High-level pitch" +check_file_either "EXPLAINME.adoc" "docs/EXPLAINME.adoc" "Developer deep-dive" +check_file_exists "LICENSE" "License file" +check_file_exists "Justfile" "Task runner" +check_file_either "AUDIT.adoc" "docs/AUDIT.adoc" "Release audit gate" + +# Directories +check_dir_either ".machine_readable" "machine-readable" "Machine-readable metadata" +check_dir_exists ".github" "GitHub community metadata" +check_abi_dir_exists "Idris2 ABI definitions" +check_dir_exists "src/interface/ffi" "Zig FFI implementation" +check_dir_exists "src/interface/generated/abi" "Generated C headers" +check_dir_exists "docs" "Documentation" + +#============================================================================== +# VALIDATION PHASE 2: MACHINE-READABLE METADATA +#============================================================================== + +echo "" +log_info "Phase 2: Machine-readable metadata (.machine_readable/)" +echo "" + +check_file_exists ".machine_readable/descriptiles/STATE.a2ml" "Project state" +check_file_exists ".machine_readable/descriptiles/META.a2ml" "Architecture decisions" +check_file_exists ".machine_readable/descriptiles/ECOSYSTEM.a2ml" "Ecosystem position" +check_file_exists ".machine_readable/descriptiles/anchors/ANCHOR.a2ml" "Semantic boundary anchor" +check_file_exists ".machine_readable/policies/MAINTENANCE-AXES.a2ml" "Maintenance axes" + +#============================================================================== +# VALIDATION PHASE 3: REQUIRED WORKFLOWS (17 minimum) +#============================================================================== + +echo "" +log_info "Phase 3: GitHub Actions workflows" +echo "" + +REQUIRED_WORKFLOWS=( + "hypatia-scan.yml" + "codeql.yml" + "scorecard.yml" + "quality.yml" + "mirror.yml" + "guix-policy.yml" + "security-policy.yml" + "wellknown-enforcement.yml" + "workflow-linter.yml" + # npm-bun-blocker.yml and ts-blocker.yml were retired in #14 and replaced by + # runtime-policy.yml: the old gate failed any build carrying bun.lockb with + # "Use Deno instead", which blocked the estate's new first-choice runtime, so + # none of the 55 repos carrying it ever adopted Bun. This list was not + # updated, so it has required two files the template deliberately no longer + # ships — which is why validate-template.sh has been red since that merge. + "runtime-policy.yml" + "secret-scanner.yml" +) + +# A workflow renamed upstream must not read as a missing workflow here. #106 +# moved wellknown-enforcement.yml to dot-wellknown-enforcement.yml to match the +# .well-known/ URL convention and left this list requiring the old name, so this +# gate failed on main — the same way it failed when the two retired files above +# stayed listed. Resolve EITHER spelling and let the alias be dropped once the +# rename has propagated, exactly as check-root-shape.sh resolves both root +# spellings instead of assuming the migration is finished everywhere. +WORKFLOW_ALIASES=( + "wellknown-enforcement.yml:dot-wellknown-enforcement.yml" +) + +resolve_required_workflow() { + local want="$1" pair alt + if [ -f "$REPO_ROOT/.github/workflows/$want" ]; then + printf '%s\n' "$want" + return 0 + fi + for pair in "${WORKFLOW_ALIASES[@]}"; do + [ "${pair%%:*}" = "$want" ] || continue + alt="${pair#*:}" + if [ -f "$REPO_ROOT/.github/workflows/$alt" ]; then + printf '%s\n' "$alt" + return 0 + fi + done + return 1 +} + +# Check required workflows +for workflow in "${REQUIRED_WORKFLOWS[@]}"; do + if found="$(resolve_required_workflow "$workflow")"; then + [ "$VERBOSE" = "1" ] && log_pass "Workflow found: $found" + else + log_error "Required workflow missing: $workflow" + fi +done + +# Verify all workflows have SPDX headers and proper structure +WORKFLOW_FILES=$(find "$REPO_ROOT/.github/workflows" -name "*.yml" -type f 2>/dev/null || true) +WORKFLOW_COUNT=$(echo "$WORKFLOW_FILES" | grep -c "." || true) + +if [ "$WORKFLOW_COUNT" -ge 15 ]; then + log_pass "Found $WORKFLOW_COUNT workflows (>= 15 expected)" +else + log_warning "Found only $WORKFLOW_COUNT workflows (expected >= 15)" +fi + +# Spot-check workflow files for issues +while IFS= read -r workflow_file; do + if [ -z "$workflow_file" ]; then continue; fi + + # Check for SPDX header (optional in YAML workflows, but best practice) + if ! head -5 "$workflow_file" | grep -q "SPDX-License-Identifier"; then + log_warning "Workflow missing SPDX header: $(basename "$workflow_file")" + fi + + # Check for proper YAML structure + if ! grep -q "^name:" "$workflow_file"; then + log_error "Workflow missing 'name' field: $(basename "$workflow_file")" + fi +done <<< "$WORKFLOW_FILES" + +#============================================================================== +# VALIDATION PHASE 4: ABI/FFI SOURCE FILES +#============================================================================== + +echo "" +log_info "Phase 4: Idris2 ABI and Zig FFI source files" +echo "" + +# Idris2 ABI files +check_abi_file_exists "Types.idr" "Core type definitions" +check_abi_file_exists "Layout.idr" "Memory layout specifications" +check_abi_file_exists "Foreign.idr" "FFI foreign declarations" + +# Zig FFI files +check_file_exists "src/interface/ffi/build.zig" "Zig build configuration" +check_file_exists "src/interface/ffi/src/main.zig" "Zig implementation" +check_file_exists "src/interface/ffi/test/integration_test.zig" "Integration tests" + +#============================================================================== +# VALIDATION PHASE 5: PLACEHOLDER TOKENS +#============================================================================== + +echo "" +# The heading is unconditional; the skip below is not. It previously read +# "(skipped in template repo)" on every run, so in an instantiated repo — where +# the check DOES run — the log said it had been skipped. A live check that +# reports itself as skipped is worse than a silent one: it invites people to +# stop reading the phase. The skip announces itself on the line that performs it. +log_info "Phase 5: Placeholder token replacement" +echo "" + +# Note: Template repo is allowed to have placeholders +# For derived repos, we'd check that placeholders are replaced +if [ "$(basename "$REPO_ROOT")" = "czech-file-knife" ]; then + log_pass "Skipping placeholder check for template repo" +else + # Check that key files don't have unresolved placeholders + for file in "$REPO_ROOT/README.adoc" "$REPO_ROOT/Justfile" "$REPO_ROOT/.machine_readable/descriptiles/STATE.a2ml"; do + if [ -f "$file" ]; then + if has_placeholder "$file"; then + log_warning "File contains unresolved placeholders: $(basename "$file")" + fi + fi + done +fi + +#============================================================================== +# VALIDATION PHASE 6: SPDX LICENSE HEADERS +#============================================================================== + +echo "" +log_info "Phase 6: SPDX License Headers" +echo "" + +# Check source files for SPDX headers (excluding build artifacts). +# +# Scans the whole repository, not just src/, and every estate source language — +# not just Idris2 and Zig. +# +# This used to be `find "$REPO_ROOT/src" ... \( -name "*.idr" -o -name "*.zig" \)`. +# That is fine for the template, whose only sources are src/interface/{abi,ffi}, +# but wrong for the repos instantiated from it: a multi-language project keeps +# code in core-zig/, beam/, clients/, normalizer/ and so on, so the scan saw a +# handful of files and printed "SPDX headers: 10/10 (100%)". Measured across all +# languages the same repo was at 172/188 (91%) — the number was not wrong, it was +# answering a much narrower question than it appeared to. +# +# Uses `git ls-files` so it follows .gitignore and never descends into vendored +# or build directories; falls back to `find` outside a work tree. +SPDX_EXTS='idr|zig|rs|ex|exs|res|resi|factor|fs|lean|jl|gleam|ml|mli|hs|sh|nix|scm' +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + SOURCE_FILES=$(git -C "$REPO_ROOT" ls-files \ + | grep -E "\.($SPDX_EXTS)$" \ + | sed "s|^|$REPO_ROOT/|" || true) +else + SOURCE_FILES=$(find "$REPO_ROOT" -type f \ + ! -path "*/.git/*" ! -path "*/.zig-cache/*" ! -path "*/zig-cache/*" \ + ! -path "*/node_modules/*" ! -path "*/target/*" ! -path "*/_build/*" \ + ! -path "*/.lake/*" ! -path "*/deps/*" \ + 2>/dev/null | grep -E "\.($SPDX_EXTS)$" || true) +fi +SOURCE_COUNT=$(echo "$SOURCE_FILES" | grep -c "." || true) +SPDX_COUNT=0 + +while IFS= read -r src_file; do + if [ -z "$src_file" ]; then continue; fi + if has_spdx_header "$src_file"; then + SPDX_COUNT=$((SPDX_COUNT + 1)) + else + log_warning "Source file missing SPDX header: $(basename "$src_file")" + fi +done <<< "$SOURCE_FILES" + +if [ "$SOURCE_COUNT" -gt 0 ]; then + PERCENT=$((SPDX_COUNT * 100 / SOURCE_COUNT)) + log_pass "SPDX headers: $SPDX_COUNT/$SOURCE_COUNT ($PERCENT%)" + if [ "$PERCENT" -lt 100 ]; then + log_warning "Not all source files have SPDX headers" + fi +fi + +#============================================================================== +# VALIDATION PHASE 7: BUILD VERIFICATION +#============================================================================== + +echo "" +log_info "Phase 7: Build system verification" +echo "" + +# Check Zig build +if [ -f "$REPO_ROOT/src/interface/ffi/build.zig" ]; then + if command -v zig &> /dev/null; then + cd "$REPO_ROOT/src/interface/ffi" + if zig build 2>&1 | grep -q "error"; then + log_error "Zig build failed" + else + log_pass "Zig build successful" + fi + cd - > /dev/null + else + log_warning "Zig compiler not found - skipping Zig build check" + fi +else + log_error "Zig build.zig not found" +fi + +# Check Idris2. Prefer a REAL typecheck via the package (abi.ipkg sets the +# sourcedir so the `module Abi.*` namespace resolves); this catches namespace / +# path / import breakage that a bare per-file `idris2 --check` masks as a +# tolerated "module name does not match file name" warning. +if command -v idris2 &> /dev/null; then + if [ -f "$REPO_ROOT/src/interface/abi.ipkg" ]; then + if (cd "$REPO_ROOT" && idris2 --typecheck src/interface/abi.ipkg) > /dev/null 2>&1; then + log_pass "Idris2 ABI typechecks (abi.ipkg)" + else + log_error "Idris2 ABI does NOT typecheck (abi.ipkg)" + fi + else + # No package: fall back to a best-effort per-file syntax check (warns on + # the expected namespace/path mismatch). Look in either case of the dir. + IDS_FILES=$(find "$REPO_ROOT/src/interface/Abi" "$REPO_ROOT/src/interface/abi" -name "*.idr" -type f 2>/dev/null || true) + while IFS= read -r ids_file; do + if [ -z "$ids_file" ]; then continue; fi + if ! idris2 --check "$ids_file" 2>&1 | grep -q "Error"; then + log_pass "Idris2 syntax OK: $(basename "$ids_file")" + else + log_warning "Idris2 syntax issue: $(basename "$ids_file")" + fi + done <<< "$IDS_FILES" + fi +else + log_warning "Idris2 compiler not found - skipping Idris2 syntax checks" +fi + +#============================================================================== +# VALIDATION PHASE 8: DOCUMENTATION +#============================================================================== + +echo "" +log_info "Phase 8: Documentation requirements" +echo "" + +check_file_exists "docs/developer/ABI-FFI-README.adoc" "ABI/FFI documentation" +# TOPOLOGY may live at root or under docs/architecture/, .md or .adoc +if [ -f "$REPO_ROOT/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/TOPOLOGY.md" ] || \ + [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.md" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Architecture topology found" +else + log_error "Required file missing: TOPOLOGY (root or docs/architecture/, .adoc or .md)" +fi +# CONTRIBUTING.md may live at root or in .github/ (GitHub auto-discovers either) +if [ -f "$REPO_ROOT/CONTRIBUTING.md" ] || [ -f "$REPO_ROOT/.github/CONTRIBUTING.md" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Contribution guide found" +else + log_error "Required file missing: CONTRIBUTING.md (root or .github/)" +fi + +# Governance can be at root or in docs/governance/ +if [ -f "$REPO_ROOT/GOVERNANCE.adoc" ] || [ -f "$REPO_ROOT/GOVERNANCE.md" ] || [ -d "$REPO_ROOT/docs/governance" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Governance files found" +else + log_warning "Governance documentation not found" +fi + +#============================================================================== +# VALIDATION SUMMARY +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "VALIDATION SUMMARY" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" +echo -e "Errors: ${RED}${ERRORS}${NC}" +echo -e "Warnings: ${YELLOW}${WARNINGS}${NC}" +echo "" + +if [ "$ERRORS" -eq 0 ]; then + echo -e "${GREEN}✓ Validation PASSED${NC}" + [ "$WARNINGS" -gt 0 ] && echo -e " (with $WARNINGS warnings)" + exit 0 +else + echo -e "${RED}✗ Validation FAILED${NC}" + echo " Please fix the errors above." + exit 1 +fi diff --git a/czech-file-knife/session/README.adoc b/czech-file-knife/session/README.adoc new file mode 100644 index 000000000..ffa0c27e4 --- /dev/null +++ b/czech-file-knife/session/README.adoc @@ -0,0 +1,50 @@ +== Session Bindings (Thin Local Layer) + +This directory provides local integration for central session-management +standards. + +Authoritative protocols live in: + +* `+../standards/3-practice/session-management-standards/+` (or +`+$SESSION_STANDARDS_DIR+`) + +This repo keeps only thin bindings: + +* `+dispatch.sh+` maps canonical commands to central protocol paths. +* `+custom-checks.k9.ncl+` defines repo-local policy checks as a Nickel record. +* `+local-hooks.sh+` provides optional repo-specific hook behavior. + +Run `bash scripts/validate-session-contracts.sh` from the repository root +with Nickel installed to evaluate both session records. The script removes +the `K9!` transport envelope before invoking the actual Nickel evaluator; +a missing evaluator or invalid expression fails validation. + +=== Canonical Commands + +* `+intake repo +` +* `+checkpoint change +` +* `+verify maintenance +` +* `+verify substantial +` +* `+verify release +` +* `+close planned +` +* `+close urgent +` +* `+recover repo +` +* `+handover full +` +* `+handover split +` +* `+handover model +` +* `+handover human +` + +=== Justfile Aliases + +Run `+just session-help+` to list aliases, then use recipes such as: + +* `+just intake-repo path=.+` +* `+just checkpoint-change path=.+` +* `+just verify-maintenance path=.+` +* `+just close-planned path=.+` +* `+just handover-model path=.+` + +=== Runtime Artifacts + +Runtime files are generated per repository in `+.session/+` and are not +canonical standards text. diff --git a/czech-file-knife/session/custom-checks.k9.ncl b/czech-file-knife/session/custom-checks.k9.ncl new file mode 100644 index 000000000..6f6f36b2b --- /dev/null +++ b/czech-file-knife/session/custom-checks.k9.ncl @@ -0,0 +1,51 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Local repository session checks (thin policy layer) + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "custom-session-checks", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + checks = [ + { + id = "session-state-has-next-action", + applies_to = [ + "close planned", + "close urgent", + "handover full", + "handover split", + "handover model", + "handover human", + ], + requirement = "LAST-CANONICAL-COMMAND.md contains next intended action", + }, + { + id = "session-state-has-residual-risks", + applies_to = [ + "verify maintenance", + "verify substantial", + "verify release", + "recover repo", + ], + requirement = "Residual risks field is not left blank", + }, + { + id = "session-state-has-recommended-next-protocol", + applies_to = [ + "intake repo", + "checkpoint change", + "recover repo", + "handover full", + ], + requirement = "Recommended next protocol is set", + }, + ], +} diff --git a/czech-file-knife/session/dispatch.sh b/czech-file-knife/session/dispatch.sh new file mode 100755 index 000000000..dd4126c28 --- /dev/null +++ b/czech-file-knife/session/dispatch.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +if [ "$#" -lt 3 ]; then + cat >&2 <<'USAGE' +Usage: ./session/dispatch.sh + +Canonical commands: + intake repo + checkpoint change + verify maintenance + verify substantial + verify release + close planned + close urgent + recover repo + handover full + handover split + handover model + handover human +USAGE + exit 2 +fi + +verb="$1" +object="$2" +repo_path="$3" +cmd_pair="$verb $object" + +if [ ! -d "$repo_path" ]; then + echo "error: repository path '$repo_path' does not exist" >&2 + exit 2 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$script_dir/.." && pwd)" + +standards_dir="${SESSION_STANDARDS_DIR:-}" +if [ -z "$standards_dir" ]; then + if [ -d "$repo_root/../standards/3-practice/session-management-standards" ]; then + standards_dir="$repo_root/../standards/3-practice/session-management-standards" + elif [ -d "$repo_root/standards/3-practice/session-management-standards" ]; then + standards_dir="$repo_root/standards/3-practice/session-management-standards" + fi +fi + +case "$cmd_pair" in + "intake repo") + protocol_rel="continuity/repo-intake" + ;; + "checkpoint change") + protocol_rel="continuity/checkpoint-before-major-change" + ;; + "verify maintenance") + protocol_rel="verify/maintenance-sweep" + ;; + "verify substantial") + protocol_rel="verify/substantial-completion" + ;; + "verify release") + protocol_rel="verify/release-audit" + ;; + "close planned") + protocol_rel="continuity/planned-session-close" + ;; + "close urgent") + protocol_rel="continuity/emergency-termination" + ;; + "recover repo") + protocol_rel="continuity/recovery-operation" + ;; + "handover full") + protocol_rel="handover/full-transfer" + ;; + "handover split") + protocol_rel="handover/collaborative-transfer" + ;; + "handover model") + protocol_rel="handover/model-transfer" + ;; + "handover human") + protocol_rel="handover/human-transfer" + ;; + *) + echo "error: unsupported canonical command '$cmd_pair'" >&2 + exit 2 + ;; +esac + +session_dir="$repo_path/.session" +mkdir -p "$session_dir" + +command_record="$session_dir/LAST-CANONICAL-COMMAND.md" + +cat > "$command_record" <&2 + echo "canonical: $cmd_pair $repo_path" + echo "mapped protocol: $protocol_rel" +fi + +hooks="$script_dir/local-hooks.sh" +if [ -x "$hooks" ]; then + "$hooks" "$verb" "$object" "$repo_path" +fi + +echo "recorded: $command_record" diff --git a/czech-file-knife/session/local-hooks.sh b/czech-file-knife/session/local-hooks.sh new file mode 100755 index 000000000..a726387b9 --- /dev/null +++ b/czech-file-knife/session/local-hooks.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +verb="${1:-}" +object="${2:-}" +repo_path="${3:-.}" + +session_dir="$repo_path/.session" +mkdir -p "$session_dir" +log_file="$session_dir/local-hooks.log" + +echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) hook: $verb $object $repo_path" >> "$log_file" + +case "$verb $object" in + "verify release") + echo "release hook: ensure AUDIT.adoc and session reports are reviewed" >> "$log_file" + ;; + "close urgent") + echo "urgent hook: prioritize EMERGENCY-CHECKPOINT.md generation" >> "$log_file" + ;; +esac diff --git a/czech-file-knife/sonar-project.properties b/czech-file-knife/sonar-project.properties new file mode 100644 index 000000000..add715fe3 --- /dev/null +++ b/czech-file-knife/sonar-project.properties @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MPL-2.0 +# SonarQube Cloud (SonarCloud) configuration. +# Project: https://sonarcloud.io/project/overview?id=hyperpolymath_czech-file-knife +# Requires the SONAR_TOKEN repository secret + a SonarCloud project (owner setup). +sonar.organization=hyperpolymath +sonar.projectKey=hyperpolymath_czech-file-knife + +# Analysable surface = shell scripts + any JS/TS the template carries. Idris2, +# Zig, Elixir and AffineScript have no SonarCloud analyser; vendored, generated, +# build, proof, doc-template and dependency trees are excluded to keep findings +# signal-rich (mirrors the boj-server arrangement). +# .machine_readable/ (formerly machine-readable/) is DELIBERATELY NOT excluded. +# Un-hiding that tree exposed 108 files to analysis for the first time, 11 of +# them shell scripts. Excluding them here would have bought human legibility +# while preserving the scanner blind spot, which is the defect - not the fix. +# Any findings it surfaces were always there; they were merely unscanned. +# NOTE: the finding-count delta against main must still be measured on the +# branch before merge, since it is unverified whether each scanner skipped +# dot-directories by default. +# +# The rename back to the dotted spelling (2026-09-17) makes that question live +# again rather than moot. Un-hiding the tree is what exposed 108 files to +# analysis; re-hiding it may restore the blind spot this whole decision was +# about, IF a scanner skips dot-directories. Measure before assuming. +sonar.exclusions=build/**,generated/**,**/node_modules/**,**/_build/**,**/deps/**,**/.lake/**,verification/**,build/docs-seed/**,machine-readable-design/**,**/*.idr,**/*.ipkg,**/*.zig +sonar.coverage.exclusions=tests/**,**/*test* diff --git a/czech-file-knife/src/README.adoc b/czech-file-knife/src/README.adoc new file mode 100644 index 000000000..9e5bd3ce7 --- /dev/null +++ b/czech-file-knife/src/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += src Pillar diff --git a/czech-file-knife/src/aspects/README.adoc b/czech-file-knife/src/aspects/README.adoc new file mode 100644 index 000000000..2b39ea820 --- /dev/null +++ b/czech-file-knife/src/aspects/README.adoc @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Aspects Pillar +:icons: font + +[.lead] +The **Aspects Pillar** is where a project declares the cross-cutting +_capabilities_ it wears — the **Aspect-Oriented Language Design (AOLD)** seam of +an RSR repository. + +== What an "aspect" is + +An aspect is a capability woven in from a _specialist language_ without adopting +that language wholesale: distribution (Chapel), GPU kernels (Futhark), +data-race freedom (Pony), fault tolerance (OTP), correct-by-construction proof +(Dafny), energy-awareness (Eclexia), reversibility (Oblíbený), and so on. + +Each aspect is delivered by an **-iser** — a Rust CLI that injects the +capability through a uniform, proof-carrying pipeline: + +[literal] +.... + manifest ──► Idris2 ABI ──► Zig FFI ──► target-language codegen ──► build/run +.... + +Your application stays itself; the aspect is an _addable, removable, reversible_ +exoskeleton bolted on at the boundary (the "mech suit" model). Multiple aspects +compose over the same Idris2-ABI/Zig-FFI seam — the _Integrated Stack of Stacks_ +(iSOS). + +== What lives in `src/aspects/` + +Sub-pillars for the cross-cutting concerns this repo weaves in, e.g.: + +* `integrity/` — attestation, tamper-evidence, provenance. +* `observability/` — logging, metrics, tracing. +* `security/` — authz, sandboxing, supply-chain controls. + +A project records _which_ aspects it wears (and the manifest that configures +each -iser) here, so the augmentation is explicit, auditable, and reversible — +never hidden in the core. + +== Keeping aspects honest + +When an aspect crosses the FFI seam into the host, its proven invariant must not +be silently over-generalised (`theorem → guarantee → "universal claim"`). The +**invariant-path** tool is the conscience of this pillar: it traces each aspect's +proven invariant from its Idris2 ABI into the host call sites and flags any point +where the guarantee is carried further than it was proved. + +== See also + +* https://github.com/hyperpolymath/iseriser/blob/main/docs/ATLAS.adoc[The -iser Atlas] — route a need to the right aspect. +* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/AOLD.adoc[AOLD] — the design philosophy. +* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/iSOS.adoc[iSOS] — composing aspects. +* https://github.com/hyperpolymath/invariant-path[invariant-path] — the claim-path conscience. diff --git a/czech-file-knife/src/aspects/integrity/README.adoc b/czech-file-knife/src/aspects/integrity/README.adoc new file mode 100644 index 000000000..d3e1ee1a7 --- /dev/null +++ b/czech-file-knife/src/aspects/integrity/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Integrity Aspect diff --git a/czech-file-knife/src/aspects/observability/README.adoc b/czech-file-knife/src/aspects/observability/README.adoc new file mode 100644 index 000000000..8a2151cd0 --- /dev/null +++ b/czech-file-knife/src/aspects/observability/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Observability Aspect diff --git a/czech-file-knife/src/aspects/security/README.adoc b/czech-file-knife/src/aspects/security/README.adoc new file mode 100644 index 000000000..e28b5dabf --- /dev/null +++ b/czech-file-knife/src/aspects/security/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Security Aspect diff --git a/czech-file-knife/.nojekyll b/czech-file-knife/src/bridges/.gitkeep similarity index 100% rename from czech-file-knife/.nojekyll rename to czech-file-knife/src/bridges/.gitkeep diff --git a/czech-file-knife/cfk-cache/Cargo.toml b/czech-file-knife/src/cfk-cache/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-cache/Cargo.toml rename to czech-file-knife/src/cfk-cache/Cargo.toml diff --git a/czech-file-knife/cfk-cache/src/blob_store.rs b/czech-file-knife/src/cfk-cache/src/blob_store.rs similarity index 99% rename from czech-file-knife/cfk-cache/src/blob_store.rs rename to czech-file-knife/src/cfk-cache/src/blob_store.rs index 97ab22578..26a552d5e 100644 --- a/czech-file-knife/cfk-cache/src/blob_store.rs +++ b/czech-file-knife/src/cfk-cache/src/blob_store.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Content-addressed blob storage //! //! Stores file content using BLAKE3 hashes for deduplication. diff --git a/czech-file-knife/cfk-cache/src/lib.rs b/czech-file-knife/src/cfk-cache/src/lib.rs similarity index 99% rename from czech-file-knife/cfk-cache/src/lib.rs rename to czech-file-knife/src/cfk-cache/src/lib.rs index 6f715002c..b9a1f42df 100644 --- a/czech-file-knife/cfk-cache/src/lib.rs +++ b/czech-file-knife/src/cfk-cache/src/lib.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Offline caching layer for Czech File Knife //! //! Features: diff --git a/czech-file-knife/cfk-cache/src/metadata_cache.rs b/czech-file-knife/src/cfk-cache/src/metadata_cache.rs similarity index 100% rename from czech-file-knife/cfk-cache/src/metadata_cache.rs rename to czech-file-knife/src/cfk-cache/src/metadata_cache.rs diff --git a/czech-file-knife/cfk-cache/src/policy.rs b/czech-file-knife/src/cfk-cache/src/policy.rs similarity index 100% rename from czech-file-knife/cfk-cache/src/policy.rs rename to czech-file-knife/src/cfk-cache/src/policy.rs diff --git a/czech-file-knife/cfk-cache/src/sled_backend.rs b/czech-file-knife/src/cfk-cache/src/sled_backend.rs similarity index 100% rename from czech-file-knife/cfk-cache/src/sled_backend.rs rename to czech-file-knife/src/cfk-cache/src/sled_backend.rs diff --git a/czech-file-knife/cfk-cli/Cargo.toml b/czech-file-knife/src/cfk-cli/Cargo.toml similarity index 85% rename from czech-file-knife/cfk-cli/Cargo.toml rename to czech-file-knife/src/cfk-cli/Cargo.toml index b9c3b1e79..6cf9e0399 100644 --- a/czech-file-knife/cfk-cli/Cargo.toml +++ b/czech-file-knife/src/cfk-cli/Cargo.toml @@ -34,7 +34,7 @@ bytes.workspace = true [package.metadata.deb] maintainer = "hyperpolymath " copyright = "2025, hyperpolymath" -license-file = ["../LICENSE", "0"] +license-file = ["../../LICENSE", "0"] extended-description = """ Czech File Knife (cfk) is a universal file management toolkit with cloud provider integration and virtual filesystem support. It provides a unified @@ -45,14 +45,14 @@ section = "utils" priority = "optional" assets = [ ["target/release/cfk", "usr/bin/", "755"], - ["../README.adoc", "usr/share/doc/czech-file-knife/README.adoc", "644"], + ["../../README.adoc", "usr/share/doc/czech-file-knife/README.adoc", "644"], ] # Packaging metadata for cargo-generate-rpm [package.metadata.generate-rpm] assets = [ { source = "target/release/cfk", dest = "/usr/bin/cfk", mode = "755" }, - { source = "../README.adoc", dest = "/usr/share/doc/czech-file-knife/README.adoc", mode = "644" }, + { source = "../../README.adoc", dest = "/usr/share/doc/czech-file-knife/README.adoc", mode = "644" }, ] [package.metadata.generate-rpm.requires] diff --git a/czech-file-knife/cfk-cli/src/commands.rs b/czech-file-knife/src/cfk-cli/src/commands.rs similarity index 100% rename from czech-file-knife/cfk-cli/src/commands.rs rename to czech-file-knife/src/cfk-cli/src/commands.rs diff --git a/czech-file-knife/cfk-cli/src/main.rs b/czech-file-knife/src/cfk-cli/src/main.rs similarity index 100% rename from czech-file-knife/cfk-cli/src/main.rs rename to czech-file-knife/src/cfk-cli/src/main.rs diff --git a/czech-file-knife/cfk-core/Cargo.toml b/czech-file-knife/src/cfk-core/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-core/Cargo.toml rename to czech-file-knife/src/cfk-core/Cargo.toml diff --git a/czech-file-knife/cfk-core/src/backend.rs b/czech-file-knife/src/cfk-core/src/backend.rs similarity index 99% rename from czech-file-knife/cfk-core/src/backend.rs rename to czech-file-knife/src/cfk-core/src/backend.rs index 93007d090..dc538f382 100644 --- a/czech-file-knife/cfk-core/src/backend.rs +++ b/czech-file-knife/src/cfk-core/src/backend.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Storage backend trait use async_trait::async_trait; diff --git a/czech-file-knife/cfk-core/src/entry.rs b/czech-file-knife/src/cfk-core/src/entry.rs similarity index 98% rename from czech-file-knife/cfk-core/src/entry.rs rename to czech-file-knife/src/cfk-core/src/entry.rs index 7f6292659..a8a1ff470 100644 --- a/czech-file-knife/cfk-core/src/entry.rs +++ b/czech-file-knife/src/cfk-core/src/entry.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! File system entries use crate::{Metadata, VirtualPath}; diff --git a/czech-file-knife/cfk-core/src/error.rs b/czech-file-knife/src/cfk-core/src/error.rs similarity index 99% rename from czech-file-knife/cfk-core/src/error.rs rename to czech-file-knife/src/cfk-core/src/error.rs index da1c3df7d..3e60f1da0 100644 --- a/czech-file-knife/cfk-core/src/error.rs +++ b/czech-file-knife/src/cfk-core/src/error.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Error types for Czech File Knife use thiserror::Error; diff --git a/czech-file-knife/cfk-core/src/lib.rs b/czech-file-knife/src/cfk-core/src/lib.rs similarity index 93% rename from czech-file-knife/cfk-core/src/lib.rs rename to czech-file-knife/src/cfk-core/src/lib.rs index f7f3d3de5..cf17d5870 100644 --- a/czech-file-knife/cfk-core/src/lib.rs +++ b/czech-file-knife/src/cfk-core/src/lib.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Czech File Knife Core //! //! Core traits, types, and abstractions for the unified filesystem interface. diff --git a/czech-file-knife/cfk-core/src/metadata.rs b/czech-file-knife/src/cfk-core/src/metadata.rs similarity index 97% rename from czech-file-knife/cfk-core/src/metadata.rs rename to czech-file-knife/src/cfk-core/src/metadata.rs index b260fb7b2..8a7c89e0b 100644 --- a/czech-file-knife/cfk-core/src/metadata.rs +++ b/czech-file-knife/src/cfk-core/src/metadata.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! File and directory metadata use chrono::{DateTime, Utc}; diff --git a/czech-file-knife/cfk-core/src/operations.rs b/czech-file-knife/src/cfk-core/src/operations.rs similarity index 96% rename from czech-file-knife/cfk-core/src/operations.rs rename to czech-file-knife/src/cfk-core/src/operations.rs index 2682c00d0..58151e5c9 100644 --- a/czech-file-knife/cfk-core/src/operations.rs +++ b/czech-file-knife/src/cfk-core/src/operations.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Operation options use serde::{Deserialize, Serialize}; diff --git a/czech-file-knife/cfk-core/src/path.rs b/czech-file-knife/src/cfk-core/src/path.rs similarity index 99% rename from czech-file-knife/cfk-core/src/path.rs rename to czech-file-knife/src/cfk-core/src/path.rs index 9b680fea4..64164821d 100644 --- a/czech-file-knife/cfk-core/src/path.rs +++ b/czech-file-knife/src/cfk-core/src/path.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Virtual path abstraction use serde::{Deserialize, Serialize}; diff --git a/czech-file-knife/cfk-core/src/platform.rs b/czech-file-knife/src/cfk-core/src/platform.rs similarity index 99% rename from czech-file-knife/cfk-core/src/platform.rs rename to czech-file-knife/src/cfk-core/src/platform.rs index e47c587f1..b1bebac53 100644 --- a/czech-file-knife/cfk-core/src/platform.rs +++ b/czech-file-knife/src/cfk-core/src/platform.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Platform-specific abstractions //! //! Supports: Linux, macOS, Windows, iOS, Android, Minix, z/OS, RISC-V diff --git a/czech-file-knife/cfk-core/src/reversible.rs b/czech-file-knife/src/cfk-core/src/reversible.rs similarity index 100% rename from czech-file-knife/cfk-core/src/reversible.rs rename to czech-file-knife/src/cfk-core/src/reversible.rs diff --git a/czech-file-knife/cfk-integrations/Cargo.toml b/czech-file-knife/src/cfk-integrations/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-integrations/Cargo.toml rename to czech-file-knife/src/cfk-integrations/Cargo.toml diff --git a/czech-file-knife/cfk-integrations/src/agrep.rs b/czech-file-knife/src/cfk-integrations/src/agrep.rs similarity index 98% rename from czech-file-knife/cfk-integrations/src/agrep.rs rename to czech-file-knife/src/cfk-integrations/src/agrep.rs index afb8aef82..025fe920f 100644 --- a/czech-file-knife/cfk-integrations/src/agrep.rs +++ b/czech-file-knife/src/cfk-integrations/src/agrep.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! agrep integration for approximate/fuzzy grep //! //! agrep allows errors in pattern matching (Levenshtein distance) diff --git a/czech-file-knife/cfk-integrations/src/aria2.rs b/czech-file-knife/src/cfk-integrations/src/aria2.rs similarity index 98% rename from czech-file-knife/cfk-integrations/src/aria2.rs rename to czech-file-knife/src/cfk-integrations/src/aria2.rs index d836ce628..9f1aa7718 100644 --- a/czech-file-knife/cfk-integrations/src/aria2.rs +++ b/czech-file-knife/src/cfk-integrations/src/aria2.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! aria2 integration for high-speed downloads //! //! aria2 supports: HTTP/HTTPS, FTP, SFTP, BitTorrent, Metalink diff --git a/czech-file-knife/cfk-integrations/src/lib.rs b/czech-file-knife/src/cfk-integrations/src/lib.rs similarity index 97% rename from czech-file-knife/cfk-integrations/src/lib.rs rename to czech-file-knife/src/cfk-integrations/src/lib.rs index 9ac2a09c0..2d21760ab 100644 --- a/czech-file-knife/cfk-integrations/src/lib.rs +++ b/czech-file-knife/src/cfk-integrations/src/lib.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! External tool integrations for Czech File Knife //! //! Integrates: aria2, agrep, pandoc, tesseract OCR, eza diff --git a/czech-file-knife/cfk-integrations/src/pandoc.rs b/czech-file-knife/src/cfk-integrations/src/pandoc.rs similarity index 98% rename from czech-file-knife/cfk-integrations/src/pandoc.rs rename to czech-file-knife/src/cfk-integrations/src/pandoc.rs index fd840a007..ddef8bbad 100644 --- a/czech-file-knife/cfk-integrations/src/pandoc.rs +++ b/czech-file-knife/src/cfk-integrations/src/pandoc.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! pandoc integration for document format conversion //! //! Supports: markdown, docx, pdf, html, epub, rst, latex, and 40+ formats diff --git a/czech-file-knife/cfk-ios/Cargo.toml b/czech-file-knife/src/cfk-ios/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-ios/Cargo.toml rename to czech-file-knife/src/cfk-ios/Cargo.toml diff --git a/czech-file-knife/cfk-ios/src/domain.rs b/czech-file-knife/src/cfk-ios/src/domain.rs similarity index 99% rename from czech-file-knife/cfk-ios/src/domain.rs rename to czech-file-knife/src/cfk-ios/src/domain.rs index d79d1328b..ade75ab74 100644 --- a/czech-file-knife/cfk-ios/src/domain.rs +++ b/czech-file-knife/src/cfk-ios/src/domain.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! File Provider Domain management //! //! Maps to NSFileProviderDomain in iOS. diff --git a/czech-file-knife/cfk-ios/src/error.rs b/czech-file-knife/src/cfk-ios/src/error.rs similarity index 100% rename from czech-file-knife/cfk-ios/src/error.rs rename to czech-file-knife/src/cfk-ios/src/error.rs diff --git a/czech-file-knife/cfk-ios/src/ffi.rs b/czech-file-knife/src/cfk-ios/src/ffi.rs similarity index 99% rename from czech-file-knife/cfk-ios/src/ffi.rs rename to czech-file-knife/src/cfk-ios/src/ffi.rs index 0c3a42b19..4cf93d83c 100644 --- a/czech-file-knife/cfk-ios/src/ffi.rs +++ b/czech-file-knife/src/cfk-ios/src/ffi.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! C FFI layer for iOS integration //! //! This module exposes a C API that can be called from Swift/Objective-C. diff --git a/czech-file-knife/cfk-ios/src/item.rs b/czech-file-knife/src/cfk-ios/src/item.rs similarity index 100% rename from czech-file-knife/cfk-ios/src/item.rs rename to czech-file-knife/src/cfk-ios/src/item.rs diff --git a/czech-file-knife/cfk-ios/src/lib.rs b/czech-file-knife/src/cfk-ios/src/lib.rs similarity index 98% rename from czech-file-knife/cfk-ios/src/lib.rs rename to czech-file-knife/src/cfk-ios/src/lib.rs index 80f1758df..0b1a3ea0e 100644 --- a/czech-file-knife/cfk-ios/src/lib.rs +++ b/czech-file-knife/src/cfk-ios/src/lib.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! iOS File Provider extension for Czech File Knife //! //! This crate provides iOS integration via Apple's File Provider framework. diff --git a/czech-file-knife/cfk-ios/src/provider.rs b/czech-file-knife/src/cfk-ios/src/provider.rs similarity index 100% rename from czech-file-knife/cfk-ios/src/provider.rs rename to czech-file-knife/src/cfk-ios/src/provider.rs diff --git a/czech-file-knife/cfk-ios/swift/CfkBridge.h b/czech-file-knife/src/cfk-ios/swift/CfkBridge.h similarity index 100% rename from czech-file-knife/cfk-ios/swift/CfkBridge.h rename to czech-file-knife/src/cfk-ios/swift/CfkBridge.h diff --git a/czech-file-knife/cfk-ios/swift/CfkFileProviderExtension.swift b/czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift similarity index 100% rename from czech-file-knife/cfk-ios/swift/CfkFileProviderExtension.swift rename to czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift diff --git a/czech-file-knife/cfk-ios/swift/CfkFileProviderItem.swift b/czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift similarity index 100% rename from czech-file-knife/cfk-ios/swift/CfkFileProviderItem.swift rename to czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift diff --git a/czech-file-knife/cfk-providers/Cargo.toml b/czech-file-knife/src/cfk-providers/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-providers/Cargo.toml rename to czech-file-knife/src/cfk-providers/Cargo.toml diff --git a/czech-file-knife/cfk-providers/src/afs.rs b/czech-file-knife/src/cfk-providers/src/afs.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/afs.rs rename to czech-file-knife/src/cfk-providers/src/afs.rs index c94eae4de..b86088dc0 100644 --- a/czech-file-knife/cfk-providers/src/afs.rs +++ b/czech-file-knife/src/cfk-providers/src/afs.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Andrew File System (AFS) storage backend //! //! OpenAFS client implementation with Kerberos authentication. diff --git a/czech-file-knife/cfk-providers/src/box_com.rs b/czech-file-knife/src/cfk-providers/src/box_com.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/box_com.rs rename to czech-file-knife/src/cfk-providers/src/box_com.rs index d89eb54f7..22cde768b 100644 --- a/czech-file-knife/cfk-providers/src/box_com.rs +++ b/czech-file-knife/src/cfk-providers/src/box_com.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Box.com storage backend //! //! Box API implementation with OAuth 2.0 authentication. diff --git a/czech-file-knife/cfk-providers/src/ceph.rs b/czech-file-knife/src/cfk-providers/src/ceph.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/ceph.rs rename to czech-file-knife/src/cfk-providers/src/ceph.rs index 1e259c435..d95598180 100644 --- a/czech-file-knife/cfk-providers/src/ceph.rs +++ b/czech-file-knife/src/cfk-providers/src/ceph.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Ceph storage backend //! //! Distributed object storage via RADOS, CephFS, or S3/Swift gateway. diff --git a/czech-file-knife/cfk-providers/src/dropbox.rs b/czech-file-knife/src/cfk-providers/src/dropbox.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/dropbox.rs rename to czech-file-knife/src/cfk-providers/src/dropbox.rs index dfd2ab032..bc1b65549 100644 --- a/czech-file-knife/cfk-providers/src/dropbox.rs +++ b/czech-file-knife/src/cfk-providers/src/dropbox.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Dropbox storage backend //! //! Full implementation of Dropbox API v2 with OAuth 2.0 + PKCE authentication. diff --git a/czech-file-knife/cfk-providers/src/gdrive.rs b/czech-file-knife/src/cfk-providers/src/gdrive.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/gdrive.rs rename to czech-file-knife/src/cfk-providers/src/gdrive.rs index 2d872f5e9..afb300275 100644 --- a/czech-file-knife/cfk-providers/src/gdrive.rs +++ b/czech-file-knife/src/cfk-providers/src/gdrive.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Google Drive storage backend //! //! Full implementation of Google Drive API v3 with OAuth 2.0 + PKCE authentication. diff --git a/czech-file-knife/cfk-providers/src/ipfs.rs b/czech-file-knife/src/cfk-providers/src/ipfs.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/ipfs.rs rename to czech-file-knife/src/cfk-providers/src/ipfs.rs index bb527a17c..a79de001a 100644 --- a/czech-file-knife/cfk-providers/src/ipfs.rs +++ b/czech-file-knife/src/cfk-providers/src/ipfs.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! IPFS storage backend //! //! Content-addressed distributed file system. diff --git a/czech-file-knife/cfk-providers/src/lib.rs b/czech-file-knife/src/cfk-providers/src/lib.rs similarity index 98% rename from czech-file-knife/cfk-providers/src/lib.rs rename to czech-file-knife/src/cfk-providers/src/lib.rs index 7cb254246..423bf5e98 100644 --- a/czech-file-knife/cfk-providers/src/lib.rs +++ b/czech-file-knife/src/cfk-providers/src/lib.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Storage providers for Czech File Knife //! //! Supports 15+ backends: local, cloud, distributed, and exotic filesystems. diff --git a/czech-file-knife/cfk-providers/src/local.rs b/czech-file-knife/src/cfk-providers/src/local.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/local.rs rename to czech-file-knife/src/cfk-providers/src/local.rs index 941652493..01b39c221 100644 --- a/czech-file-knife/cfk-providers/src/local.rs +++ b/czech-file-knife/src/cfk-providers/src/local.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Local filesystem backend use async_trait::async_trait; diff --git a/czech-file-knife/cfk-providers/src/nfs.rs b/czech-file-knife/src/cfk-providers/src/nfs.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/nfs.rs rename to czech-file-knife/src/cfk-providers/src/nfs.rs index 5bcd36b57..0fad098eb 100644 --- a/czech-file-knife/cfk-providers/src/nfs.rs +++ b/czech-file-knife/src/cfk-providers/src/nfs.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! NFS storage backend //! //! Network File System client implementation. diff --git a/czech-file-knife/cfk-providers/src/ninep.rs b/czech-file-knife/src/cfk-providers/src/ninep.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/ninep.rs rename to czech-file-knife/src/cfk-providers/src/ninep.rs index 2206bf5c2..2838f23d2 100644 --- a/czech-file-knife/cfk-providers/src/ninep.rs +++ b/czech-file-knife/src/cfk-providers/src/ninep.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! 9P/Plan 9 filesystem protocol backend //! //! Used in WSL2 (drvfs), QEMU/KVM (virtio-9p), and Plan 9/Inferno systems. diff --git a/czech-file-knife/cfk-providers/src/onedrive.rs b/czech-file-knife/src/cfk-providers/src/onedrive.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/onedrive.rs rename to czech-file-knife/src/cfk-providers/src/onedrive.rs index 82166eb0a..330e0bfb3 100644 --- a/czech-file-knife/cfk-providers/src/onedrive.rs +++ b/czech-file-knife/src/cfk-providers/src/onedrive.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! OneDrive storage backend //! //! Microsoft Graph API implementation for OneDrive Personal and Business. diff --git a/czech-file-knife/cfk-providers/src/protocols.rs b/czech-file-knife/src/cfk-providers/src/protocols.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/protocols.rs rename to czech-file-knife/src/cfk-providers/src/protocols.rs index 7bd963d80..ece607d4f 100644 --- a/czech-file-knife/cfk-providers/src/protocols.rs +++ b/czech-file-knife/src/cfk-providers/src/protocols.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Exotic protocol support //! //! Additional protocols beyond standard cloud/file systems: diff --git a/czech-file-knife/cfk-providers/src/s3.rs b/czech-file-knife/src/cfk-providers/src/s3.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/s3.rs rename to czech-file-knife/src/cfk-providers/src/s3.rs index b634e168a..3f7c077a7 100644 --- a/czech-file-knife/cfk-providers/src/s3.rs +++ b/czech-file-knife/src/cfk-providers/src/s3.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! S3-compatible storage backend //! //! Works with AWS S3, MinIO, Wasabi, DigitalOcean Spaces, Backblaze B2, diff --git a/czech-file-knife/cfk-providers/src/sftp.rs b/czech-file-knife/src/cfk-providers/src/sftp.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/sftp.rs rename to czech-file-knife/src/cfk-providers/src/sftp.rs index a99c43b9d..9a278f2f5 100644 --- a/czech-file-knife/cfk-providers/src/sftp.rs +++ b/czech-file-knife/src/cfk-providers/src/sftp.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! SFTP storage backend //! //! SSH File Transfer Protocol implementation. diff --git a/czech-file-knife/cfk-providers/src/smb.rs b/czech-file-knife/src/cfk-providers/src/smb.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/smb.rs rename to czech-file-knife/src/cfk-providers/src/smb.rs index fd70bcee3..538639e4e 100644 --- a/czech-file-knife/cfk-providers/src/smb.rs +++ b/czech-file-knife/src/cfk-providers/src/smb.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! SMB/CIFS storage backend //! //! Server Message Block / Common Internet File System protocol. diff --git a/czech-file-knife/cfk-providers/src/syncthing.rs b/czech-file-knife/src/cfk-providers/src/syncthing.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/syncthing.rs rename to czech-file-knife/src/cfk-providers/src/syncthing.rs index 971da21cf..f6b832ba0 100644 --- a/czech-file-knife/cfk-providers/src/syncthing.rs +++ b/czech-file-knife/src/cfk-providers/src/syncthing.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Syncthing storage backend //! //! Connects to Syncthing's REST API to expose synced folders. diff --git a/czech-file-knife/cfk-providers/src/transport.rs b/czech-file-knife/src/cfk-providers/src/transport.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/transport.rs rename to czech-file-knife/src/cfk-providers/src/transport.rs index b0fc1e582..bb169c761 100644 --- a/czech-file-knife/cfk-providers/src/transport.rs +++ b/czech-file-knife/src/cfk-providers/src/transport.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! Transport layer support //! //! Low-level transport protocols: diff --git a/czech-file-knife/cfk-providers/src/webdav.rs b/czech-file-knife/src/cfk-providers/src/webdav.rs similarity index 99% rename from czech-file-knife/cfk-providers/src/webdav.rs rename to czech-file-knife/src/cfk-providers/src/webdav.rs index 48e9edc4b..3b5e8fb29 100644 --- a/czech-file-knife/cfk-providers/src/webdav.rs +++ b/czech-file-knife/src/cfk-providers/src/webdav.rs @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 //! WebDAV storage backend //! //! HTTP-based distributed authoring and versioning protocol. diff --git a/czech-file-knife/cfk-search/Cargo.toml b/czech-file-knife/src/cfk-search/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-search/Cargo.toml rename to czech-file-knife/src/cfk-search/Cargo.toml diff --git a/czech-file-knife/cfk-search/src/lib.rs b/czech-file-knife/src/cfk-search/src/lib.rs similarity index 100% rename from czech-file-knife/cfk-search/src/lib.rs rename to czech-file-knife/src/cfk-search/src/lib.rs diff --git a/czech-file-knife/cfk-tui/cfk_tui.gpr b/czech-file-knife/src/cfk-tui/cfk_tui.gpr similarity index 100% rename from czech-file-knife/cfk-tui/cfk_tui.gpr rename to czech-file-knife/src/cfk-tui/cfk_tui.gpr diff --git a/czech-file-knife/cfk-tui/src/cfk-tui-application.ads b/czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads similarity index 100% rename from czech-file-knife/cfk-tui/src/cfk-tui-application.ads rename to czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads diff --git a/czech-file-knife/cfk-tui/src/cfk_tui_main.adb b/czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb similarity index 100% rename from czech-file-knife/cfk-tui/src/cfk_tui_main.adb rename to czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb diff --git a/czech-file-knife/cfk-vfs/Cargo.toml b/czech-file-knife/src/cfk-vfs/Cargo.toml similarity index 100% rename from czech-file-knife/cfk-vfs/Cargo.toml rename to czech-file-knife/src/cfk-vfs/Cargo.toml diff --git a/czech-file-knife/cfk-vfs/src/lib.rs b/czech-file-knife/src/cfk-vfs/src/lib.rs similarity index 100% rename from czech-file-knife/cfk-vfs/src/lib.rs rename to czech-file-knife/src/cfk-vfs/src/lib.rs diff --git a/czech-file-knife/src/contracts/README.adoc b/czech-file-knife/src/contracts/README.adoc new file mode 100644 index 000000000..657be1fe8 --- /dev/null +++ b/czech-file-knife/src/contracts/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Contracts Unit diff --git a/czech-file-knife/src/core/.gitkeep b/czech-file-knife/src/core/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/czech-file-knife/src/definitions/README.adoc b/czech-file-knife/src/definitions/README.adoc new file mode 100644 index 000000000..02ecc4fd7 --- /dev/null +++ b/czech-file-knife/src/definitions/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Definitions Unit diff --git a/czech-file-knife/src/errors/README.adoc b/czech-file-knife/src/errors/README.adoc new file mode 100644 index 000000000..b03a1c45c --- /dev/null +++ b/czech-file-knife/src/errors/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Errors Unit diff --git a/czech-file-knife/tests/aspect_tests.sh b/czech-file-knife/tests/aspect_tests.sh new file mode 100755 index 000000000..e5ca45831 --- /dev/null +++ b/czech-file-knife/tests/aspect_tests.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard Aspect Test Template +# +# Aspect tests validate cross-cutting architectural invariants that span +# the entire codebase. These are NOT functional tests — they verify that +# coding standards, safety rules, and structural contracts hold. +# +# Usage: +# bash tests/aspect_tests.sh +# just aspect +# +# Standard aspects (enable what applies to your project): +# 1. SPDX compliance — all source files have license headers +# 2. Dangerous patterns — no believe_me, assert_total, sorry, unsafeCoerce, etc. +# 3. ABI/FFI contract — declarations match exports +# 4. Thread safety — mutex in FFI modules +# 5. Error handling — no panic/unreachable in production paths + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_DIR" + +PASS=0 +FAIL=0 +WARN=0 + +green() { printf '\033[32m%s\033[0m\n' "$*"; } +red() { printf '\033[31m%s\033[0m\n' "$*"; } +yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } +bold() { printf '\033[1m%s\033[0m\n' "$*"; } + +pass() { green " PASS: $1"; PASS=$((PASS + 1)); } +fail() { red " FAIL: $1"; FAIL=$((FAIL + 1)); } +warn() { yellow " WARN: $1"; WARN=$((WARN + 1)); } + +echo "═══════════════════════════════════════════════════════════════" +echo " CZECH_FILE_KNIFE — Aspect Tests (Cross-Cutting Concerns)" +echo "═══════════════════════════════════════════════════════════════" +echo "" + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 1: SPDX License Headers +# ═══════════════════════════════════════════════════════════════════════ +bold "Aspect 1: SPDX license headers" + +MISSING_SPDX=0 +while IFS= read -r -d '' f; do + if ! head -5 "$f" | grep -q "SPDX-License-Identifier"; then + warn "Missing SPDX header: $f" + MISSING_SPDX=$((MISSING_SPDX + 1)) + fi +done < <(find src/ -type f \( -name "*.rs" -o -name "*.zig" -o -name "*.res" -o -name "*.ex" -o -name "*.exs" -o -name "*.gleam" -o -name "*.idr" -o -name "*.sh" \) -print0 2>/dev/null) + +if [ "$MISSING_SPDX" -eq 0 ]; then + pass "All source files have SPDX headers" +else + fail "$MISSING_SPDX files missing SPDX headers" +fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 2: Dangerous Patterns (BANNED) +# ═══════════════════════════════════════════════════════════════════════ +bold "Aspect 2: Dangerous patterns" + +# Idris2 dangerous patterns +DANGEROUS_IDRIS=$(grep -rn 'believe_me\|assert_total\|really_believe_me' src/abi/ 2>/dev/null | grep -v "^Binary" | grep -v "test" || true) +if [ -n "$DANGEROUS_IDRIS" ]; then + fail "Dangerous Idris2 patterns found:" + echo "$DANGEROUS_IDRIS" | head -5 +else + pass "No dangerous Idris2 patterns (believe_me, assert_total)" +fi + +# Coq/Lean dangerous patterns +DANGEROUS_PROOF=$(grep -rn '\bAdmitted\b\|\bsorry\b\|\bunsafeCoerce\b\|\bObj\.magic\b' src/ verification/ 2>/dev/null | grep -v "test" | grep -v "comment" || true) +if [ -n "$DANGEROUS_PROOF" ]; then + fail "Dangerous proof patterns found:" + echo "$DANGEROUS_PROOF" | head -5 +else + pass "No dangerous proof patterns (Admitted, sorry, unsafeCoerce)" +fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 3: ABI/FFI Contract (if applicable) +# ═══════════════════════════════════════════════════════════════════════ +# Uncomment if your project has Idris2 ABI + Zig FFI: + +# bold "Aspect 3: ABI/FFI contract" +# if [ -d "src/abi" ] && [ -d "ffi/zig" ]; then +# # Check that every exported function in Idris2 ABI has a Zig FFI implementation +# ABI_EXPORTS=$(grep -h 'export' src/abi/*.idr 2>/dev/null | wc -l) +# FFI_EXPORTS=$(grep -h 'pub export fn' ffi/zig/src/*.zig 2>/dev/null | wc -l) +# if [ "$ABI_EXPORTS" -gt 0 ] && [ "$FFI_EXPORTS" -gt 0 ]; then +# pass "ABI ($ABI_EXPORTS exports) and FFI ($FFI_EXPORTS exports) both present" +# else +# fail "ABI/FFI mismatch: $ABI_EXPORTS ABI exports, $FFI_EXPORTS FFI exports" +# fi +# else +# pass "ABI/FFI not applicable (no src/abi or ffi/zig)" +# fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 4: Error Handling (no raw panic in production code) +# ═══════════════════════════════════════════════════════════════════════ +# Uncomment for Rust projects: + +# bold "Aspect 4: Error handling" +# UNWRAP_COUNT=$(grep -rn '\.unwrap()' src/ 2>/dev/null | grep -v "test" | grep -v "example" | wc -l) +# if [ "$UNWRAP_COUNT" -gt 20 ]; then +# warn "$UNWRAP_COUNT .unwrap() calls in src/ — consider replacing with ? or expect()" +# else +# pass "Acceptable unwrap count: $UNWRAP_COUNT" +# fi + +# ═══════════════════════════════════════════════════════════════════════ +# Summary +# ═══════════════════════════════════════════════════════════════════════ +echo "" +echo "═══════════════════════════════════════════════════════════════" +printf " Results: " +green "PASS=$PASS" | tr -d '\n' +echo -n " " +if [ "$FAIL" -gt 0 ]; then red "FAIL=$FAIL" | tr -d '\n'; else echo -n "FAIL=0"; fi +echo -n " " +if [ "$WARN" -gt 0 ]; then yellow "WARN=$WARN"; else echo "WARN=0"; fi +echo "" +echo "═══════════════════════════════════════════════════════════════" + +exit "$FAIL" diff --git a/czech-file-knife/tests/e2e.sh b/czech-file-knife/tests/e2e.sh new file mode 100755 index 000000000..14f65edef --- /dev/null +++ b/czech-file-knife/tests/e2e.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# End-to-end: build the `cfk` binary, drive it against a scratch directory, +# and verify the reversible journal restores every destructive operation. +# +# Usage: bash tests/e2e.sh (or: just e2e) + +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PASS=0 +FAIL=0 + +ok() { printf ' PASS: %s\n' "$1"; PASS=$((PASS + 1)); } +bad() { printf ' FAIL: %s\n' "$1"; FAIL=$((FAIL + 1)); } +expect_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (expected '$2', got '$3')"; fi; } + +echo "Building cfk..." +cargo build --quiet --manifest-path "$PROJECT_DIR/Cargo.toml" -p cfk-cli +CFK="$PROJECT_DIR/target/debug/cfk" + +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT +export CFK_JOURNAL_DIR="$WORK/.journal" +cd "$WORK" + +echo "one" > a.txt + +# 1. rm + undo restores content +"$CFK" rm a.txt +[ ! -e a.txt ] && ok "rm removes file" || bad "rm removes file" +"$CFK" undo +expect_eq "undo restores deleted file" "one" "$(cat a.txt 2>/dev/null || true)" + +# 2. mv + undo restores original name +"$CFK" mv a.txt b.txt +"$CFK" undo +[ -e a.txt ] && [ ! -e b.txt ] && ok "undo reverses move" || bad "undo reverses move" + +# 3. cp over existing file + undo restores the overwritten content +echo "two" > c.txt +"$CFK" cp --force a.txt c.txt +expect_eq "cp overwrote destination" "one" "$(cat c.txt)" +"$CFK" undo +expect_eq "undo restores overwritten destination" "two" "$(cat c.txt)" + +# 4. recursive rm + undo restores tree +mkdir -p d/sub && echo x > d/x && echo y > d/sub/y +"$CFK" rm -r d +"$CFK" undo +expect_eq "undo restores nested file" "y" "$(cat d/sub/y 2>/dev/null || true)" + +# 5. history records operations and undos +HIST="$("$CFK" history -n 50)" +printf '%s\n' "$HIST" | grep -q "(undone)" && ok "history marks undone ops" || bad "history marks undone ops" + +# 6. nothing left to undo is an error, not a silent success +if "$CFK" undo >/dev/null 2>&1; then bad "empty undo fails"; else ok "empty undo fails"; fi + +echo +echo "E2E: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/e2e/julia_mint_test.sh b/czech-file-knife/tests/e2e/julia_mint_test.sh new file mode 100644 index 000000000..9c10b2e27 --- /dev/null +++ b/czech-file-knife/tests/e2e/julia_mint_test.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# julia_mint_test.sh — e2e: the julia-library overlay mints a working package. +# +# Simulates `just repo-init julia-library` end to end (overlay copy -> +# token substitution -> .in renames -> placeholder gate -> lock coverage -> +# Pkg.instantiate -> Pkg.test -> uuid derivation checks), on the real +# toolchain. This is the acceptance test for the overlay and for the two +# repo-init hunks it depends on (PACKAGE_UUID derivation; .in renames). +# +# Owner rulings exercised here (2026-09-19): +# D7 - the package uuid is generated at mint (derived, stable, v5) +# D6 - the minted workflows are lock-SSOT compliant (actions.lock ships) +# +# Usage: +# JULIA_BIN=/path/to/julia bash tests/e2e/julia_mint_test.sh +# (JULIA_BIN defaults to `julia` on PATH; the estate runner pins it via +# julia-actions/setup-julia) + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +OVERLAY="$REPO_DIR/archetypes/julia-library/overlay" +JULIA="${JULIA_BIN:-julia}" +SCRATCH="${SCRATCH:-$(mktemp -d /tmp/julia-mint-test.XXXXXX)}" +trap 'rm -rf "$SCRATCH"' EXIT + +PASS=0; FAIL=0 +ok() { echo " PASS: $1"; PASS=$((PASS+1)); } +bad() { echo " FAIL: $1"; FAIL=$((FAIL+1)); } + +command -v "$JULIA" >/dev/null 2>&1 || { echo "SKIP: no julia binary (JULIA_BIN)"; exit 0; } +[ -d "$OVERLAY" ] || { echo "FAIL: overlay not found at $OVERLAY"; exit 1; } + +cd "$SCRATCH" + +# ── 1. overlay copy (as repo-init does) ──────────────────────────────── +cp -a "$OVERLAY/." . + +# ── 2. token substitution (as repo-init does) + hunk A (uuid derivation) +OWNER=hyperpolymath; REPO=mintcheck; PROJECT_NAME=MintCheck +AUTHOR="Jonathan D.A. Jewell"; AUTHOR_EMAIL="owner@hyperpolymath.dev" +FORGE=https://github.com; CURRENT_YEAR=$(date +%Y) +LB='{{'; RB='}}' +REPO_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, '${FORGE}/${OWNER}/${REPO}'))") +if command -v uuidgen >/dev/null 2>&1; then + PACKAGE_UUID=$(uuidgen --sha1 --namespace "$REPO_UUID" --name "julia:${REPO}") +else + PACKAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") +fi +for f in $(grep -rl "{{" . 2>/dev/null || true); do + tmp=$(mktemp) + sed -e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g" \ + -e "s|${LB}OWNER${RB}|${OWNER}|g" \ + -e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g" \ + -e "s|${LB}PACKAGE_UUID${RB}|${PACKAGE_UUID}|g" \ + -e "s|${LB}AUTHOR${RB}|${AUTHOR}|g" \ + -e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g" \ + -e "s|${LB}PROJECT_DESCRIPTION${RB}|Mint check of the julia-library archetype overlay.|g" \ + "$f" > "$tmp" && mv "$tmp" "$f" +done + +# ── 3. rename rule (patch hunk B) ────────────────────────────────────── +TOML_FILE=Project.toml +[ -f "$TOML_FILE" ] || TOML_FILE=Project.toml.in +if [ -f src/PACKAGE.jl.in ] && [ -f "$TOML_FILE" ]; then + PKG_NAME=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' "$TOML_FILE" | head -1) + [ -n "$PKG_NAME" ] && [ "$PKG_NAME" != "UNASSIGNED" ] && mv "src/PACKAGE.jl.in" "src/${PKG_NAME}.jl" +fi +for f in Project.toml.in .github/workflows/julia-ci.yml.in .github/workflows/julia-docs.yml.in; do + [ -f "$f" ] && mv "$f" "${f%.in}" +done + +# ── 4. placeholder gate (roster only — GHA ${{ }} is not a token) ───── +if grep -rnE "${LB}(PROJECT_NAME|PROJECT_DESCRIPTION|OWNER|AUTHOR|AUTHOR_EMAIL|CURRENT_YEAR|PACKAGE_UUID|REPO|FORGE)${RB}" . 2>/dev/null; then + bad "unfilled template tokens remain" +else + ok "no unfilled tokens" +fi + +# ── 4b. lock coverage (D6: actions.lock is the pin truth) ────────────── +if python3 - <<'PY' +import re, sys, pathlib +wfdir = pathlib.Path(".github/workflows") +lock_text = (wfdir / "actions.lock").read_text() +for wf in sorted(wfdir.glob("*.yml")): + uses = {m.group(1) for line in wf.read_text().splitlines() + if (m := re.match(r'\s*uses:\s*(\S+)', line))} + sect = re.search(r"'" + re.escape(str(wf)) + r"':\s*\n((?:\s+-\s+'[^']+'\n?)*)", lock_text) + locked = set(re.findall(r"-\s+'([^']+)'", sect.group(1))) if sect else set() + if uses - locked: + print(f" MISSING in lock: {wf.name}: {sorted(uses - locked)}") + sys.exit(1) +PY +then ok "every uses: ref is in actions.lock"; else bad "lockfile coverage gap"; fi + +# ── 5. Pkg.instantiate + Pkg.test (Test + Aqua) ──────────────────────── +if "$JULIA" --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.precompile()' >/tmp/julia-mint-inst.log 2>&1; then + ok "Pkg.instantiate" +else + bad "Pkg.instantiate (see /tmp/julia-mint-inst.log)"; tail -5 /tmp/julia-mint-inst.log +fi +if "$JULIA" --project=. -e 'using Pkg; Pkg.test()' >/tmp/julia-mint-test.log 2>&1; then + ok "Pkg.test (Test + Aqua)" +else + bad "Pkg.test (see /tmp/julia-mint-test.log)"; tail -8 /tmp/julia-mint-test.log +fi + +# ── 6. uuid derivation (D7) ──────────────────────────────────────────── +MINTED_UUID=$(sed -n 's/^uuid = "\(.*\)".*/\1/p' Project.toml | head -1) +if python3 - "$MINTED_UUID" "$PACKAGE_UUID" <<'PY' +import sys, uuid +minted, derived = sys.argv[1], sys.argv[2] +assert minted == derived, f"minted {minted} != derived {derived}" +assert uuid.UUID(minted).version == 5 +PY +then ok "package uuid is the derived v5"; else bad "package uuid derivation"; fi +RE_MINT=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") +[ "$RE_MINT" = "$MINTED_UUID" ] && ok "re-mint derives the same uuid (stable identity)" || bad "uuid not stable across re-mints" + +# ── 7. JET analysis — the SHIPPED invocation, actually executed ──────── +# +# Issue #202: the overlay emitted `JET.test_package(path=".", julia_version="1")` +# — the path/string form REMOVED in JET v0.12.0. It threw MethodError before +# analysing anything, and nothing in this repository ever executed it, so it +# shipped to every minted Julia repo. Syntax-checking the YAML could not catch +# it; only running it can. +# +# Both controls are mandatory: +# positive — the clean minted package passes. +# negative — an injected inference error IS detected. If the negative control +# passes, this test is vacuous and its "PASS" means nothing. +# +# JET goes in its own environment so the package's Project.toml is untouched, +# mirroring what the shipped workflow now does. +JET_PKG=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' Project.toml | head -1) +JET_SRC="src/${JET_PKG}.jl" +JET_ENV="$SCRATCH/.jet-env" + +run_jet() { + "$JULIA" -e " + using Pkg + Pkg.activate(\"$JET_ENV\") + Pkg.develop(path=\"$SCRATCH\") + Pkg.add(Pkg.PackageSpec(name=\"JET\", version=\"0.12\")) + using ${JET_PKG}, JET + if !hasmethod(JET.test_package, (Module,)) + error(\"JET \", pkgversion(JET), + \" has no test_package(::Module); the overlay pins an API that moved\") + end + JET.test_package(${JET_PKG}) + " +} + +if [ -n "$JET_PKG" ] && [ -f "$JET_SRC" ]; then + # positive control + if run_jet >/tmp/julia-mint-jet.log 2>&1; then + ok "JET: clean package passes (positive control)" + else + bad "JET: clean package failed — inspect; if this is an API/infra failure it is NOT a findings failure" + tail -12 /tmp/julia-mint-jet.log | sed 's/^/ /' + fi + + # negative control — inject a concrete inference error INSIDE the module. + # Inside a function body, so the module still loads and JET can analyse it; + # a bare top-level call would throw at load time and prove nothing. + cp "$JET_SRC" "$JET_SRC.bak" + sed -i '/^end # module/i neg_control() = "string" + 1' "$JET_SRC" + if run_jet >/tmp/julia-mint-jet-neg.log 2>&1; then + bad "JET: injected inference error was NOT detected — this canary cannot fail, so its PASS is vacuous" + else + ok "JET: injected inference error detected (negative control)" + fi + mv "$JET_SRC.bak" "$JET_SRC" +else + bad "JET: could not locate the minted package module (name=${JET_PKG:-}, src=$JET_SRC)" +fi + +echo +echo "julia mint test: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/e2e/template_instantiation_test.sh b/czech-file-knife/tests/e2e/template_instantiation_test.sh new file mode 100755 index 000000000..decbdb2f2 --- /dev/null +++ b/czech-file-knife/tests/e2e/template_instantiation_test.sh @@ -0,0 +1,422 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# E2E Test: Template Instantiation +# Verifies that the template can be cloned and instantiated into a working project +# +# This test: +# 1. Clones the template to a temp directory +# 2. Replaces all placeholder tokens with test values +# 3. Validates the resulting repository structure +# 4. Verifies builds work after instantiation +# 5. Cleans up + +# Test configuration +TEMPLATE_ROOT="${1:-.}" +TEST_DIR="${TMPDIR:-/tmp}/rsr-template-test-$$" +TEST_REPO_NAME="test-instantiated-repo" +TEST_OWNER="test-owner" +TEST_FORGE="github" +TEST_AUTHOR="Test Author" +TEST_AUTHOR_EMAIL="test@example.com" +TEST_PROJECT_NAME="Test Project" +TEST_DESCRIPTION="A test project instantiated from the RSR template" +TEST_PRIMARY_LANGUAGE="Rust" + +if [ ! -d "$TEMPLATE_ROOT/archetypes" ]; then + echo "Self-skipping: archetypes/ not found (repo is already instantiated)." + exit 0 +fi + +# ANSI colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_step() { + echo "" + echo -e "${BLUE}→${NC} $*" +} + +log_pass() { + echo -e "${GREEN}✓${NC} $*" +} + +log_error() { + echo -e "${RED}✗${NC} $*" >&2 +} + +log_warn() { + echo -e "${YELLOW}!${NC} $*" >&2 +} + +cleanup() { + if [ -d "$TEST_DIR" ]; then + log_step "Cleaning up test directory: $TEST_DIR" + rm -rf "$TEST_DIR" + log_pass "Cleanup complete" + fi +} + +trap cleanup EXIT + +#============================================================================== +# PHASE 1: SETUP +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "E2E TEST: Template Instantiation" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" + +log_step "Creating test directory: $TEST_DIR" +mkdir -p "$TEST_DIR" +log_pass "Test directory created" + +#============================================================================== +# PHASE 2: CLONE TEMPLATE +#============================================================================== + +log_step "Cloning template from $TEMPLATE_ROOT" + +# Copy template to test location (simulating git clone) +TEST_REPO_PATH="$TEST_DIR/$TEST_REPO_NAME" +cp -r "$TEMPLATE_ROOT" "$TEST_REPO_PATH" +log_pass "Template cloned to $TEST_REPO_PATH" + +# Local ignored agent state is not part of the template product. A filesystem +# copy sees it anyway, unlike a real GitHub-template instantiation, and can make +# the placeholder gate judge unrelated nested worktrees. Remove it from the +# fixture before exercising the mint. +rm -rf "$TEST_REPO_PATH/.claude" + +# Remove .git directory for clean state, then re-init as the INSTANTIATED repo. +# +# The re-init is not cosmetic. check-no-placeholders.sh identifies the repo from +# `git config --get remote.origin.url`, so that a worktree whose basename is not +# `*-template-repo` still gets the template exemption. With no git repo at all +# that lookup fails and the script exits 1 having printed NOTHING — so this test +# reported "left unfilled placeholder tokens (see above)" with nothing above, +# and had been failing for a reason that has nothing to do with placeholders. +# +# That is worth stating plainly: the one gate whose whole job is "no placeholder +# may survive instantiation" was inoperative, which is exactly consistent with +# 211 estate repos shipping an un-deleted template instruction block. +# +# The origin is set to the INSTANTIATED name deliberately, for the same reason +# the check below clears GITHUB_REPOSITORY: we want the checker to judge this as +# a real minted repo, not to exempt itself as a template. +if [ -d "$TEST_REPO_PATH/.git" ]; then + rm -rf "$TEST_REPO_PATH/.git" + log_pass ".git directory removed (fresh clone)" +fi +if git -C "$TEST_REPO_PATH" init -q 2>/dev/null \ + && git -C "$TEST_REPO_PATH" remote add origin \ + "git@github.com:${TEST_OWNER}/${TEST_REPO_NAME}.git" 2>/dev/null; then + log_pass "re-initialised as ${TEST_OWNER}/${TEST_REPO_NAME} (checker needs a remote)" +else + log_error "could not re-init the test repo — check-no-placeholders.sh will exit 1 silently" + exit 1 +fi + +#============================================================================== +# PHASE 3: PLACEHOLDER REPLACEMENT +#============================================================================== + +log_step "Replacing placeholder tokens" + +# Guix package names cannot contain spaces, capitals, underscores, or doubled +# separators. Fail before mutating the fixture when the repository slug cannot +# be used as a valid Guix name. +if (cd "$TEST_REPO_PATH" && \ + RSR_NON_INTERACTIVE=1 \ + PROJECT_NAME="$TEST_PROJECT_NAME" \ + REPO="Invalid Guix_Name" \ + OWNER="$TEST_OWNER" \ + AUTHOR="$TEST_AUTHOR" \ + AUTHOR_EMAIL="$TEST_AUTHOR_EMAIL" \ + just repo-init) > "$TEST_DIR/invalid-slug.log" 2>&1; then + log_error "just repo-init accepted a repository slug that is invalid for Guix" + exit 1 +fi +if ! grep -q 'repo slug must be lowercase alphanumeric words separated by single hyphens' \ + "$TEST_DIR/invalid-slug.log"; then + log_error "invalid repository slug failed for the wrong reason" + cat "$TEST_DIR/invalid-slug.log" >&2 + exit 1 +fi +log_pass "Invalid Guix package slug rejected before instantiation" + +# Substitution is `just repo-init`'s job. This test MUST drive the real recipe: +# a second, hand-rolled replacement list here would be a mock that silently +# diverges from init.just (it did — it carried {{REPO_DESCRIPTION}} and +# {{PRIMARY_LANGUAGE}}, tokens init has never defined), so the test passed +# while real instantiation leaked placeholders into every new repo. +if ! command -v just >/dev/null 2>&1; then + log_error "just is not installed — cannot exercise the real init recipe" + exit 1 +fi + +# Answers, in the exact order init.just prompts for them. +INIT_ANSWERS=( + "$TEST_PROJECT_NAME" # Project name + "$TEST_REPO_NAME" # Repository slug + "$TEST_OWNER" # Owner + "$TEST_AUTHOR" # Author full name + "$TEST_AUTHOR_EMAIL" # Author email + "" # Author organization + "" # Previous/alt email + "$TEST_DESCRIPTION" # Project description + "" # Forge domain -> default + "" # Security email -> default + "" # Conduct email -> default + "library" # Project type + "" # Website URL -> default + "" # OpenSSF BP ID +) +# init only asks the container questions when build/container/ exists. +# +# The path here must match the recipe's own guard in build/just/repo-init.just +# exactly. It did not: the recipe tests `build/container`, this test tested +# `container`. On a full checkout — which is what CI clones — the recipe +# therefore asked three questions this test had no answers for, `read` hit +# EOF, and the recipe exited 1. The clone is complete here, so the mismatch +# is invisible on a tree missing build/ and unavoidable on one that has it. +if [ -d "$TEST_REPO_PATH/build/container" ]; then + INIT_ANSWERS+=("" "" "") # service name, port, registry -> defaults +fi +INIT_ANSWERS+=("Y") # Proceed? + +if ! (cd "$TEST_REPO_PATH" && printf '%s\n' "${INIT_ANSWERS[@]}" | just repo-init) > "$TEST_DIR/init.log" 2>&1; then + log_error "just repo-init failed:" + cat "$TEST_DIR/init.log" >&2 + exit 1 +fi + +log_pass "just repo-init completed" + +#============================================================================== +# PHASE 3a: GUIX IDENTITY MUST BE RENDERED FROM THE REPOSITORY SLUG +#============================================================================== + +log_step "Checking rendered Guix package identity" + +for guix_file in build/guix.scm; do + if [ ! -f "$TEST_REPO_PATH/$guix_file" ]; then + log_error "$guix_file is missing after instantiation" + exit 1 + fi + if ! grep -qF "(name \"$TEST_REPO_NAME\")" "$TEST_REPO_PATH/$guix_file"; then + log_error "$guix_file does not use the lowercase repository slug as its Guix name" + exit 1 + fi + if ! grep -qF "(home-page \"https://github.com/$TEST_OWNER/$TEST_REPO_NAME\")" "$TEST_REPO_PATH/$guix_file"; then + log_error "$guix_file does not contain the rendered project home page" + exit 1 + fi + if grep -q 'czech-file-knife' "$TEST_REPO_PATH/$guix_file"; then + log_error "$guix_file still contains the template repository identity" + exit 1 + fi +done + +log_pass "Guix package names and home pages were rendered correctly" + +#============================================================================== +# PHASE 3b: NO PLACEHOLDER MAY SURVIVE INSTANTIATION +#============================================================================== + +log_step "Checking for placeholders that survived instantiation" + +# Same script the openssf-compliance workflow runs, deliberately: a second +# copy of this logic here is what let the two drift last time. GITHUB_REPOSITORY +# is cleared so the check does not mistake the instantiated repo for a template +# repo and skip itself — the instantiated name is what we want it to judge. +if ! env -u GITHUB_REPOSITORY bash "$TEMPLATE_ROOT/scripts/check-no-placeholders.sh" "$TEST_REPO_PATH"; then + log_error "just repo-init left unfilled placeholder tokens (see above)" + exit 1 +fi + +log_pass "No placeholders survived instantiation" + +#============================================================================== +# PHASE 3c: NO TEMPLATE INSTRUCTION BLOCK MAY SURVIVE INSTANTIATION +#============================================================================== + +log_step "Checking for un-deleted template instruction blocks" + +# This is a SEPARATE assertion from 3b on purpose, and the two cannot be merged. +# +# check-no-placeholders.sh exempts metasyntactic tokens (PLACEHOLDER, TOKEN, +# ANYTHING, UPPER_SNAKE) so that prose ABOUT tokens does not fail the build. It +# has to: without that exemption README.adoc, EXPLAINME.adoc and both +# descriptiles fail on every mint, and a gate that always fires gets switched +# off. But the instruction block's ONLY doubled-brace text is the metasyntactic +# PLACEHOLDER token, so 3b is structurally incapable of seeing it. +# +# That gap is why 211 estate repos shipped the block, 206 of them in +# CODE_OF_CONDUCT.md, each one naming "Squisher Corpus" as the project it +# protects and routing conduct reports to the wrong repository. Measured +# 2026-08-04. Catch it by name instead. +LEFTOVER=$(grep -rl 'TEMPLATE INSTRUCTIONS' "$TEST_REPO_PATH" \ + --exclude-dir=.git 2>/dev/null \ + | grep -vE '/(scripts/strip-instruction-blocks\.rs|build/just/repo-init\.just|tests/e2e/template_instantiation_test\.sh|tests/workflows/mint_cleanup_test\.sh)$' || true) +if [ -n "$LEFTOVER" ]; then + log_error "just repo-init left a TEMPLATE INSTRUCTIONS block in:" + echo "$LEFTOVER" | sed 's/^/ /' >&2 + exit 1 +fi + +log_pass "No template instruction blocks survived instantiation" + +#============================================================================== +# PHASE 4: VALIDATE STRUCTURE +#============================================================================== + +log_step "Validating instantiated repository structure" + +# Run validation script on the instantiated repo +if [ -f "$TEMPLATE_ROOT/scripts/validate-template.sh" ]; then + bash "$TEMPLATE_ROOT/scripts/validate-template.sh" "$TEST_REPO_PATH" 0 + log_pass "Repository structure validation passed" +else + log_error "Validation script not found" + exit 1 +fi + +#============================================================================== +# PHASE 5: VERIFY BUILD +#============================================================================== + +log_step "Verifying build system works after instantiation" + +if [ -f "$TEST_REPO_PATH/src/interface/ffi/build.zig" ]; then + if command -v zig &> /dev/null; then + cd "$TEST_REPO_PATH/src/interface/ffi" + if ZIG_GLOBAL_CACHE_DIR="$TEST_DIR/zig-global-cache" \ + ZIG_LOCAL_CACHE_DIR="$TEST_DIR/zig-local-cache" zig build 2>&1; then + log_pass "Zig build successful" + else + log_error "Zig build failed" + exit 1 + fi + cd - > /dev/null + else + # Zig is OPTIONAL at this point. Everything this e2e exists to prove + # about instantiation - placeholders, instruction blocks, structure - + # is already established by the phases above. Hard-failing on an + # absent Zig toolchain means the e2e can only ever pass on a machine + # that has every FFI compiler, which is how it came to sit red: it + # reported "no zig" as though it were an instantiation failure. + # Warn loudly and continue. A Zig build that RUNS and fails is fatal. + log_warn "zig not installed - FFI build verification SKIPPED" + log_warn " instantiation verified; install zig to check src/interface/ffi" + fi +fi + +#============================================================================== +# PHASE 7: VERIFY CRITICAL FILES ARE NOT TEMPLATES +#============================================================================== + +log_step "Verifying critical files have been instantiated" + +CRITICAL_FILES=( + "README.adoc" + "docs/EXPLAINME.adoc" + "Justfile" +) + +for file in "${CRITICAL_FILES[@]}"; do + if [ -f "$TEST_REPO_PATH/$file" ]; then + # Check that it's not just a template (contains some actual content) + if grep -q "$TEST_PROJECT_NAME\|$TEST_AUTHOR\|$TEST_REPO_NAME" "$TEST_REPO_PATH/$file" 2>/dev/null || \ + [ $(wc -l < "$TEST_REPO_PATH/$file") -gt 10 ]; then + log_pass "File instantiated: $file" + else + log_error "File appears to be a template: $file" + exit 1 + fi + else + log_error "Critical file missing: $file" + exit 1 + fi +done + +#============================================================================== +# PHASE 8: VERIFY METADATA +#============================================================================== + +log_step "Verifying machine-readable metadata" + +METADATA_FILES=( + ".machine_readable/descriptiles/STATE.a2ml" + ".machine_readable/descriptiles/META.a2ml" +) + +for file in "${METADATA_FILES[@]}"; do + if [ -f "$TEST_REPO_PATH/$file" ]; then + log_pass "Metadata file exists: $file" + else + log_error "Metadata file missing: $file" + exit 1 + fi +done + +#============================================================================== +# WWW SITE-OPERATIONS BUNDLE (issue #53) +#============================================================================== + +# The mint must carry the bundle, and the legacy root location must be gone. +if [ -d "$TEST_REPO_PATH/.well-known" ]; then + log_error "minted repo still has root .well-known/ — canonical location is www/.well-known/" + exit 1 +fi +WWW_FILES=( + "README.adoc" + ".well-known/security.txt" + ".well-known/ai.txt" + ".well-known/humans.txt" + "schemas/publishable-paths.txt" + "tests/run-all.sh" +) +for file in "${WWW_FILES[@]}"; do + if [ -f "$TEST_REPO_PATH/www/$file" ]; then + log_pass "www bundle file exists: www/$file" + else + log_error "www bundle file missing: www/$file" + exit 1 + fi +done + +# The minted bundle must pass its own planted-control tests. +if (cd "$TEST_REPO_PATH" && bash www/tests/run-all.sh); then + log_pass "www bundle self-test passed in the minted repo" +else + log_error "www/tests/run-all.sh failed in the minted repo" + exit 1 +fi + +#============================================================================== +# SUMMARY +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo -e "${GREEN}✓ E2E TEMPLATE INSTANTIATION TEST PASSED${NC}" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" +echo "Summary:" +echo " - Template cloned successfully" +echo " - All placeholders replaced" +echo " - Repository structure valid" +echo " - Build system works" +echo " - No remaining placeholders" +echo " - Metadata intact" +echo "" +echo "Test repository: $TEST_REPO_PATH (will be cleaned up)" +echo "" diff --git a/czech-file-knife/fuzz/Cargo.toml b/czech-file-knife/tests/fuzz/Cargo.toml similarity index 93% rename from czech-file-knife/fuzz/Cargo.toml rename to czech-file-knife/tests/fuzz/Cargo.toml index b0902daa3..4ea590441 100644 --- a/czech-file-knife/fuzz/Cargo.toml +++ b/czech-file-knife/tests/fuzz/Cargo.toml @@ -14,7 +14,7 @@ libfuzzer-sys = "0.4" arbitrary = { version = "1", features = ["derive"] } [dependencies.cfk-core] -path = "../cfk-core" +path = "../../src/cfk-core" [[bin]] name = "fuzz_path" diff --git a/czech-file-knife/tests/fuzz/README.adoc b/czech-file-knife/tests/fuzz/README.adoc new file mode 100644 index 000000000..00188a01b --- /dev/null +++ b/czech-file-knife/tests/fuzz/README.adoc @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Fuzz Testing +:toc: + +== Status + +This directory is a scaffold for fuzz tests. +**No fuzz harness is configured yet.** Add one when your project has parsers, +deserializers, protocol handlers, or other input-processing code worth fuzzing. + +== Adding Fuzz Tests + +Choose the harness that matches your project's primary language: + +=== Rust (cargo-fuzz / libFuzzer) + +[source,bash] +---- +# Install cargo-fuzz (one-time) +cargo install cargo-fuzz + +# Initialise fuzz targets in this repo +cargo fuzz init + +# Create a target +cargo fuzz add my_target + +# Run +cargo fuzz run my_target -- -max_total_time=300 +---- + +The `cargo fuzz init` command creates `fuzz/Cargo.toml` and `fuzz/fuzz_targets/`. +Move or symlink those into `tests/fuzz/` to keep the RSR directory layout. + +=== Zig (built-in fuzzing, Zig 0.14+) + +[source,zig] +---- +// tests/fuzz/fuzz_parser.zig +const std = @import("std"); + +test "fuzz parser" { + // Zig's built-in fuzz testing + const input = std.testing.fuzzInput(.{}); + // Call your parser with arbitrary input + _ = mylib.parse(input) catch {}; +} +---- + +[source,bash] +---- +zig build test --fuzz +---- + +=== Elixir (stream_data property-based testing) + +[source,elixir] +---- +# mix.exs — add {:stream_data, "~> 1.0", only: :test} + +# tests/fuzz/my_property_test.exs +defmodule MyPropertyTest do + use ExUnit.Case + use ExUnitProperties + + property "parser never crashes on arbitrary input" do + check all input <- binary() do + # Should not raise + MyApp.Parser.parse(input) + end + end +end +---- + +=== / (fast-check) + +[source,javascript] +---- +// tests/fuzz/fuzz_parser.test.mjs +import fc from "npm:fast-check"; +import { parse } from "../../src/parser.mjs"; + +.test("parser handles arbitrary strings", () => { + fc.assert( + fc.property(fc.string(), (input) => { + // Should not throw + try { parse(input); } catch (_) { /* parse errors OK */ } + }), + { numRuns: 10000 } + ); +}); +---- + +== When to Add Fuzzing + +Fuzz testing is most valuable for code that: + +* Parses untrusted input (file formats, network protocols, user data) +* Deserializes structured data (JSON, binary formats, ASN.1) +* Performs complex string/byte manipulation +* Has safety-critical invariants + +If your project is purely a library of pure functions with typed inputs, +property-based testing (see `tests/property/`) may be more appropriate than +byte-level fuzzing. + +== CI Integration + +Once you have a fuzz harness, add a CI job that runs it for a bounded time +(e.g., 5 minutes) on each PR. This catches regressions without blocking merges +for hours. diff --git a/czech-file-knife/fuzz/fuzz_targets/fuzz_path.rs b/czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs similarity index 100% rename from czech-file-knife/fuzz/fuzz_targets/fuzz_path.rs rename to czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs diff --git a/czech-file-knife/tests/invisible-characters-test.sh b/czech-file-knife/tests/invisible-characters-test.sh new file mode 100755 index 000000000..f73e3f2a1 --- /dev/null +++ b/czech-file-knife/tests/invisible-characters-test.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +fixture_root="$(mktemp -d)" # TMPDIR-respecting; Hypatia hardcoded_tmp (alerts #125/#126) +# cleanup removes the temporary fixture directory only when its path is an +# existing absolute directory (the exact path mktemp -d just created). +cleanup() { + case "$fixture_root" in + /*) [ -d "$fixture_root" ] && rm -rf -- "$fixture_root" ;; + *) echo "refusing unsafe cleanup target: $fixture_root" >&2 ;; + esac +} +trap cleanup EXIT + +scanner="$repo_root/scripts/check-invisible-characters.sh" +results="$fixture_root/results.bin" +blocking_results="$fixture_root/blocking-results.bin" +fixtures="$fixture_root/fixtures" +mkdir -p "$fixtures" + +printf 'tab\tline\ncarriage\rreturn\n' > "$fixtures/safe.md" +printf 'nbsp:\302\240\n' > "$fixtures/nbsp.md" +printf 'soft-hyphen:\302\255\n' > "$fixtures/soft-hyphen.adoc" +printf 'zero-width:\342\200\213\n' > "$fixtures/zero-width.json" +printf 'bidi:\342\200\256\n' > "$fixtures/bidi.toml" +printf 'word-joiner:\342\201\240\n' > "$fixtures/word-joiner.yml" +printf '\357\273\277leading bom\n' > "$fixtures/bom.sh" +printf 'nul:\000byte\n' > "$fixtures/nul.rs" +printf 'backspace:\010byte\n' > "$fixtures/backspace.rs" +printf 'invalid:\377 then nbsp:\302\240\n' > "$fixtures/invalid-utf8.md" +printf 'newline name:\302\240\n' > "$fixtures/with +newline.md" + +# Estate-shaped files that the extension filter did not cover until 2026-09-02: +# .a2ml carries repo IDENTITY (uuid, forge, lineage), and the recipe files are +# extensionless. An invisible character in either is exactly the kind of damage +# this scanner exists to find, and both were silently skipped. +printf 'uuid\302\240= "x"\n' > "$fixtures/IDENTITY.a2ml" +printf 'test:\n\techo\302\240hi\n' > "$fixtures/Justfile" + +"$scanner" "$fixtures" "$results" "$blocking_results" + +count=0 +safe_seen=false +newline_seen=false +while IFS= read -r -d '' filepath; do + count=$((count + 1)) + [[ "$filepath" == "$fixtures/safe.md" ]] && safe_seen=true + [[ "$filepath" == "$fixtures/with"$'\n'"newline.md" ]] && newline_seen=true +done < "$results" + +[[ "$count" -eq 12 ]] || { + echo "expected 12 findings, got $count" >&2 + exit 1 +} +[[ "$safe_seen" == false ]] || { + echo "TAB/LF/CR-only safe fixture was incorrectly reported" >&2 + exit 1 +} +[[ "$newline_seen" == true ]] || { + echo "newline-containing filename was not preserved as one record" >&2 + exit 1 +} + +blocking_count=0 +nul_blocked=false +backspace_blocked=false +while IFS= read -r -d '' filepath; do + blocking_count=$((blocking_count + 1)) + [[ "$filepath" == "$fixtures/nul.rs" ]] && nul_blocked=true + [[ "$filepath" == "$fixtures/backspace.rs" ]] && backspace_blocked=true +done < "$blocking_results" +[[ "$blocking_count" -eq 2 && "$nul_blocked" == true && "$backspace_blocked" == true ]] || { + echo "expected only NUL and backspace fixtures in the blocking set" >&2 + exit 1 +} + +if "$scanner" "$fixture_root/missing" "$results"; then + echo "missing scan root did not fail closed" >&2 + exit 1 +fi + +failing_grep="$fixture_root/failing-grep" +printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_grep" +chmod +x "$failing_grep" +if INVISIBLE_GREP_BIN="$failing_grep" "$scanner" "$fixtures" "$results"; then + echo "grep execution errors did not fail closed" >&2 + exit 1 +fi + +failing_find="$fixture_root/failing-find" +printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_find" +chmod +x "$failing_find" +if INVISIBLE_FIND_BIN="$failing_find" "$scanner" "$fixtures" "$results"; then + echo "find execution errors did not fail closed" >&2 + exit 1 +fi + +echo "invisible-character scanner positive and negative controls passed" diff --git a/czech-file-knife/tests/shape/check_root_shape_test.sh b/czech-file-knife/tests/shape/check_root_shape_test.sh new file mode 100755 index 000000000..83fe22b48 --- /dev/null +++ b/czech-file-knife/tests/shape/check_root_shape_test.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check_root_shape_test.sh — prove scripts/check-root-shape.sh can FAIL. +# +# The gate went one-directional for months and nobody noticed, because a gate +# that only ever passes looks identical to a gate that works. These cases pin +# both directions and the '?' optional marker. + +set -euo pipefail + +CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-root-shape.sh" +FIXTURE=$(mktemp -d) +trap 'rm -rf "$FIXTURE"' EXIT + +git -C "$FIXTURE" init -q +git -C "$FIXTURE" config user.name "RSR fixture" +git -C "$FIXTURE" config user.email "fixture@example.invalid" + +mkdir -p "$FIXTURE/machine-readable" +cat > "$FIXTURE/machine-readable/root-allow.txt" <<'ALLOW' +# fixture allowlist +.git/ +machine-readable/ +README.adoc +?OPTIONAL-THING.adoc +ALLOW +printf 'fixture\n' > "$FIXTURE/README.adoc" + +fail() { echo "FAIL: $1" >&2; exit 1; } + +# 1. A conforming root passes. +bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "conforming fixture did not pass" + +# 2. An entry at root that is not allow-listed fails, and is named. +printf 'stray\n' > "$FIXTURE/STRAY.adoc" +if out=$(bash "$CHECKER" "$FIXTURE" 2>&1); then + fail "stray root entry did not fail the gate" +fi +grep -q 'STRAY.adoc' <<<"$out" || fail "failure did not name the stray entry" +rm "$FIXTURE/STRAY.adoc" + +# 3. A REQUIRED allowlist entry that is absent fails, and is named. +# This is the direction that was missing, and the reason the allowlist rotted. +mv "$FIXTURE/README.adoc" "$FIXTURE/machine-readable/README.adoc.parked" +if out=$(bash "$CHECKER" "$FIXTURE" 2>&1); then + fail "missing required entry did not fail the gate" +fi +grep -q 'README.adoc' <<<"$out" || fail "failure did not name the missing entry" +mv "$FIXTURE/machine-readable/README.adoc.parked" "$FIXTURE/README.adoc" + +# 4. An OPTIONAL entry that is absent passes. Capability-gated and template-only +# material is legitimately missing in a conforming repo. +bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "absent ?optional entry wrongly failed" + +# 5. A git-ignored root entry is not drift: the allowlist governs tracked shape, +# not build output. (A .tmp probe once made this gate look broken when it +# was the probe that was wrong.) +printf '*.tmp\n' > "$FIXTURE/.gitignore" +printf 'x\n' > "$FIXTURE/build-output.tmp" +sed -i 's|^README.adoc$|README.adoc\n.gitignore|' "$FIXTURE/machine-readable/root-allow.txt" +bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "git-ignored root entry was wrongly treated as drift" + + +# --------------------------------------------------------------------------- +# Both-path resolution. The estate contract admits two spellings of the +# machine-readable directory, and the great majority of repos use the dotted +# one. A gate that reads only the hyphenated path exits 2 on those repos -- +# indistinguishable, to a caller, from a broken setup. +# --------------------------------------------------------------------------- + +DOTTED=$(mktemp -d) +trap 'rm -rf "$FIXTURE" "$DOTTED"' EXIT +git -C "$DOTTED" init -q +git -C "$DOTTED" config user.name "RSR fixture" +git -C "$DOTTED" config user.email "fixture@example.invalid" +mkdir -p "$DOTTED/.machine_readable" +cat > "$DOTTED/.machine_readable/root-allow.txt" <<'ALLOW' +# fixture allowlist, dotted spelling +.git/ +.machine_readable/ +README.adoc +ALLOW +printf 'fixture\n' > "$DOTTED/README.adoc" + +# 6. The dotted spelling resolves and a conforming root passes. +bash "$CHECKER" "$DOTTED" | grep -q '^PASS:' || fail "dotted .machine_readable/ allowlist was not resolved" + +# 7. The dotted spelling still FAILS on drift. Resolving the file is not the +# same as enforcing against it; without this case, case 6 would also pass +# for a gate that found the allowlist and then ignored it. +printf 'stray\n' > "$DOTTED/STRAY.adoc" +if out=$(bash "$CHECKER" "$DOTTED" 2>&1); then + fail "dotted-spelling repo did not fail on a stray root entry" +fi +grep -q 'STRAY.adoc' <<<"$out" || fail "dotted-spelling failure did not name the stray entry" +rm "$DOTTED/STRAY.adoc" + +# 8. BOTH spellings present is refused as setup error (2), not silently +# resolved: two allowlists cannot both be canonical. +mkdir -p "$DOTTED/machine-readable" +cp "$DOTTED/.machine_readable/root-allow.txt" "$DOTTED/machine-readable/root-allow.txt" +set +e +bash "$CHECKER" "$DOTTED" >/dev/null 2>&1 +rc=$? +set -e +[ "$rc" -eq 2 ] || fail "two competing allowlists returned $rc, expected 2" +rm -rf "$DOTTED/machine-readable" + +# 9. NEITHER spelling present is a setup error (2), and the message names both +# paths so the operator knows which two were tried. +rm -rf "$DOTTED/.machine_readable" +set +e +out=$(bash "$CHECKER" "$DOTTED" 2>&1) +rc=$? +set -e +[ "$rc" -eq 2 ] || fail "absent allowlist returned $rc, expected 2" +grep -q '.machine_readable/root-allow.txt' <<<"$out" || fail "error did not name the dotted path" +grep -q 'machine-readable/root-allow.txt' <<<"$out" || fail "error did not name the hyphenated path" + +echo "PASS: check-root-shape.sh fails in both directions, honours '?', and resolves both spellings" diff --git a/czech-file-knife/tests/shape/repo_map_determinism_test.sh b/czech-file-knife/tests/shape/repo_map_determinism_test.sh new file mode 100755 index 000000000..66175a859 --- /dev/null +++ b/czech-file-knife/tests/shape/repo_map_determinism_test.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# repo_map_determinism_test.sh — the repository map must generate identically +# in every environment, not merely repeatably in one. +# +# The CI freshness check compares a committed map against a freshly generated +# one. That check is only meaningful if the generator is environment-independent. +# It was not: `sort` is locale-dependent, so under en_US.UTF-8 dotfiles +# interleaved with ordinary names while under LC_ALL=C they sorted first. The +# generator produced stable output when run twice in one shell and DIFFERENT +# output in CI — which the freshness gate caught on its first real run. +# +# Running the generator twice cannot detect that. The locale must be varied. + +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +GEN="$ROOT/scripts/gen-repo-map.sh" +MAP="$ROOT/docs/architecture/REPOSITORY-MAP.adoc" + +[ -x "$GEN" ] || [ -f "$GEN" ] || { echo "FAIL: generator not found at $GEN" >&2; exit 1; } + +ORIGINAL=$(mktemp); trap 'cp "$ORIGINAL" "$MAP" 2>/dev/null; rm -f "$ORIGINAL"' EXIT +cp "$MAP" "$ORIGINAL" + +prev="" +for loc in C en_US.UTF-8 C.UTF-8 POSIX; do + LC_ALL="$loc" bash "$GEN" "$ROOT" >/dev/null 2>&1 || { + echo "FAIL: generator errored under LC_ALL=$loc" >&2; exit 1; } + sum=$(sha256sum "$MAP" | cut -d' ' -f1) + if [ -n "$prev" ] && [ "$sum" != "$prev" ]; then + echo "FAIL: map differs under LC_ALL=$loc — the generator is locale-dependent." >&2 + echo " Sort with LC_ALL=C (or export it) so output is byte-identical everywhere." >&2 + exit 1 + fi + prev="$sum" +done + +# And the committed map must match a fresh generation, or CI is already stale. +if ! diff -q "$ORIGINAL" "$MAP" >/dev/null 2>&1; then + echo "FAIL: committed REPOSITORY-MAP.adoc is stale — run: just repo-map" >&2 + exit 1 +fi + +echo "PASS: repository map is byte-identical across locales, and committed copy is current" diff --git a/czech-file-knife/tests/workflows/check_adoc_renders_test.sh b/czech-file-knife/tests/workflows/check_adoc_renders_test.sh new file mode 100755 index 000000000..303280895 --- /dev/null +++ b/czech-file-knife/tests/workflows/check_adoc_renders_test.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +# Self-test for scripts/check-adoc-renders.sh. +# +# Four of the six fixtures below are defect classes found in this very repo. +# EVERY ONE OF THEM EXITS 0 UNDER BARE asciidoctor, because --failure-level +# defaults to FATAL. That is why each defective fixture is asserted twice: +# once to prove bare asciidoctor is blind to it, and once to prove the checker +# catches it. If the first assertion ever fails, the --failure-level flag has +# stopped being the load-bearing part and this gate needs rereading. + +set -euo pipefail + +CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-adoc-renders.sh" +FIXTURE=$(mktemp -d) +trap 'rm -rf "$FIXTURE"' EXIT + +git -C "$FIXTURE" init -q +git -C "$FIXTURE" config user.name "RSR fixture" +git -C "$FIXTURE" config user.email "fixture@example.invalid" + +# A defective fixture must be invisible to bare asciidoctor, or it is measuring +# something other than the flag. +assert_bare_asciidoctor_is_blind() { + local f="$1" + if ! asciidoctor --backend=html5 -o /dev/null "$FIXTURE/$f" >/dev/null 2>&1; then + echo "FAIL: bare asciidoctor already rejects $f; fixture no longer proves the flag" >&2 + exit 1 + fi +} + +# Replace the single defective file under test, so each case is isolated. +stage_only() { + local f="$1" + rm -f "$FIXTURE"/defect-*.adoc + git -C "$FIXTURE" rm -q --cached --ignore-unmatch 'defect-*.adoc' >/dev/null + cat > "$FIXTURE/$f" + git -C "$FIXTURE" add "$f" +} + +expect_rejected() { + local f="$1" label="$2" + assert_bare_asciidoctor_is_blind "$f" + if "$CHECKER" "$FIXTURE" > "$FIXTURE/out" 2>&1; then + echo "FAIL: checker accepted $label ($f)" >&2 + exit 1 + fi + grep -q "$f" "$FIXTURE/out" +} + +# 1. An empty repository is a PASS, not a failure. A stripped mint of this +# template may legitimately carry no .adoc at all. +"$CHECKER" "$FIXTURE" | grep -q '^PASS: no tracked .adoc' + +# 2. A well-formed document passes. +cat > "$FIXTURE/README.adoc" <<'EOF' += Well Formed Fixture + +A paragraph of prose. + +* one +* two +EOF +git -C "$FIXTURE" add README.adoc +"$CHECKER" "$FIXTURE" | grep -q '^PASS:' + +# 3. A stray cell separator shifts the row width. The blank line after the +# title is load-bearing: without it, [cols=] is swallowed into the document +# header and the fixture measures the swallowed-attribute defect instead. +stage_only defect-table.adoc <<'EOF' += Table Fixture + +[cols="2*"] +|=== +| one | two +| three | four | five +|=== +EOF +expect_rejected defect-table.adoc "a table dropping cells" + +# 4. A Markdown body inside a .adoc: '# H' parses as a level 0 section, which +# collides with the '= Title' the file already has. +stage_only defect-markdown.adoc <<'EOF' += Markdown Body Fixture + +# Heading One + +## Sub Heading + +Some prose. +EOF +expect_rejected defect-markdown.adoc "a Markdown body carried inside a .adoc" + +# 5. An attribute line flush against '= Title' is read as the author line and +# the FIRST '----' as the revision line, so the SECOND '----' opens a block +# nothing closes. The closing delimiter is what makes this fixture bite: +# without it the file renders clean and the case is vacuous. +stage_only defect-swallowed.adoc <<'EOF' += Swallowed Attribute Fixture +[source,bash] +---- +echo hello +---- + +Prose after the block. +EOF +expect_rejected defect-swallowed.adoc "a listing block swallowed into the header" + +# 6. [[word]] in prose is an inline anchor, processed even inside backticks, +# so the second occurrence redefines an id that is already in use. +stage_only defect-anchor.adoc <<'EOF' += Anchor Fixture + +The token [[widget]] is used here. + +The token [[widget]] is used again here. +EOF +expect_rejected defect-anchor.adoc "a repeated prose anchor" + +# The repository is clean again once the defective file is withdrawn. +rm -f "$FIXTURE"/defect-*.adoc +git -C "$FIXTURE" rm -q --cached --ignore-unmatch 'defect-*.adoc' >/dev/null +"$CHECKER" "$FIXTURE" | grep -q '^PASS:' + +echo "PASS: clean and empty repos accepted; four defect classes rejected that bare asciidoctor exits 0 on" diff --git a/czech-file-knife/tests/workflows/check_no_vlang_test.sh b/czech-file-knife/tests/workflows/check_no_vlang_test.sh new file mode 100755 index 000000000..cb4b477dc --- /dev/null +++ b/czech-file-knife/tests/workflows/check_no_vlang_test.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 + +set -euo pipefail + +CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-no-vlang.sh" +FIXTURE=$(mktemp -d) +trap 'rm -rf "$FIXTURE"' EXIT + +git -C "$FIXTURE" init -q +git -C "$FIXTURE" config user.name "RSR fixture" +git -C "$FIXTURE" config user.email "fixture@example.invalid" + +printf '%s\n' 'pub fn main() void {}' > "$FIXTURE/build.zig" +printf '%s\n' 'Zig is the supported FFI language.' > "$FIXTURE/README.adoc" +git -C "$FIXTURE" add build.zig README.adoc + +"$CHECKER" "$FIXTURE" | grep -q '^PASS:' + +printf '%s\n' 'import vweb' > "$FIXTURE/legacy.txt" +git -C "$FIXTURE" add legacy.txt +if "$CHECKER" "$FIXTURE" > "$FIXTURE/content.out" 2>&1; then + echo "FAIL: checker accepted tracked V-language content" >&2 + exit 1 +fi +grep -q 'legacy.txt' "$FIXTURE/content.out" + +git -C "$FIXTURE" reset -q legacy.txt +rm "$FIXTURE/legacy.txt" +printf '%s\n' 'Module {}' > "$FIXTURE/v.mod" +git -C "$FIXTURE" add v.mod +if "$CHECKER" "$FIXTURE" > "$FIXTURE/module.out" 2>&1; then + echo "FAIL: checker accepted a tracked v.mod" >&2 + exit 1 +fi +grep -q 'tracked module file: v.mod' "$FIXTURE/module.out" + +git -C "$FIXTURE" reset -q v.mod +rm "$FIXTURE/v.mod" + +# A NEW call site must not be reported as a V-language reference. The +# :(exclude) list in the checker can only name call sites that already exist, +# so a repo that invokes the gate from its Justfile (or a hook, or another +# workflow) used to fail with a false positive on the invocation line itself. +printf 'test:\n\tbash scripts/check-no-vlang.sh .\n' > "$FIXTURE/Justfile" +git -C "$FIXTURE" add Justfile +if ! "$CHECKER" "$FIXTURE" | grep -q '^PASS:'; then + echo "FAIL: checker false-positived on its own invocation line" >&2 + exit 1 +fi + +# ...but a real reference on the SAME line as an invocation is still caught, +# so the self-reference filter cannot be used to smuggle V past the gate. +printf 'test:\n\tbash scripts/check-no-vlang.sh . && vlang build\n' > "$FIXTURE/Justfile" +if "$CHECKER" "$FIXTURE" > "$FIXTURE/mixed.out" 2>&1; then + echo "FAIL: checker missed a V reference sharing a line with its invocation" >&2 + exit 1 +fi +grep -q 'Justfile' "$FIXTURE/mixed.out" + +echo "PASS: Zig allowed; V content and v.mod rejected; new call sites not false-positived" diff --git a/czech-file-knife/tests/workflows/k9_typecheck_test.sh b/czech-file-knife/tests/workflows/k9_typecheck_test.sh new file mode 100644 index 000000000..d1e80a48f --- /dev/null +++ b/czech-file-knife/tests/workflows/k9_typecheck_test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Verify plain Nickel and K9!-enveloped inputs for validate-session-contracts.sh. +set -euo pipefail +root="$(cd "$(dirname "$0")/../.." && pwd)" +fixture="$(mktemp -d)" +trap 'rm -rf "$fixture"' EXIT +printf '%s\n' '{ value = 1 }' > "$fixture/plain.ncl" +printf '%s\n' 'K9!' '{ value = 1 }' > "$fixture/wrapped.k9.ncl" +printf '%s\n' '' ' ' 'K9!' '{ value = 1 }' > "$fixture/leading-blank-wrapped.k9.ncl" +printf '%s\n' 'K9!' '{ value = }' > "$fixture/bad.k9.ncl" +bash "$root/scripts/validate-session-contracts.sh" --typecheck "$fixture/plain.ncl" "$fixture/wrapped.k9.ncl" "$fixture/leading-blank-wrapped.k9.ncl" +if bash "$root/scripts/validate-session-contracts.sh" --typecheck "$fixture/bad.k9.ncl"; then + echo 'Invalid Nickel was accepted' >&2 + exit 1 +fi +echo 'PASS: plain and wrapped Nickel accepted, including leading blanks; malformed Nickel rejected' diff --git a/czech-file-knife/tests/workflows/mint_cleanup_test.sh b/czech-file-knife/tests/workflows/mint_cleanup_test.sh new file mode 100644 index 000000000..398c63da7 --- /dev/null +++ b/czech-file-knife/tests/workflows/mint_cleanup_test.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +set -euo pipefail +repo=$(git rev-parse --show-toplevel) +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT +cat > "$fixture/dependabot.yml" <<'YAML' +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + - package-ecosystem: nix + directory: / + - package-ecosystem: cargo + directory: / +YAML +bash "$repo/scripts/rust-tool.sh" prune-dependabot-ecosystems "$fixture/dependabot.yml" github-actions +grep -q 'github-actions' "$fixture/dependabot.yml" +if grep -qE 'nix|cargo' "$fixture/dependabot.yml"; then exit 1; fi +cp "$fixture/dependabot.yml" "$fixture/before" +bash "$repo/scripts/rust-tool.sh" prune-dependabot-ecosystems "$fixture/dependabot.yml" nix +cmp "$fixture/dependabot.yml" "$fixture/before" +cat > "$fixture/policy.md" <<'DOC' + +# Policy +Prose mentions TEMPLATE INSTRUCTIONS and must survive. +Actual policy. +DOC +bash "$repo/scripts/rust-tool.sh" strip-instruction-blocks "$fixture" +grep -q SPDX "$fixture/policy.md" +grep -q '^# Policy' "$fixture/policy.md" +grep -q '^Prose mentions' "$fixture/policy.md" +if grep -q 'delete only this' "$fixture/policy.md"; then exit 1; fi +cp "$fixture/policy.md" "$fixture/before" +bash "$repo/scripts/rust-tool.sh" strip-instruction-blocks "$fixture" +cmp "$fixture/policy.md" "$fixture/before" +echo 'PASS: selected ecosystems, nonempty updates, comment boundaries, and idempotence' diff --git a/czech-file-knife/tests/workflows/session_contracts_test.sh b/czech-file-knife/tests/workflows/session_contracts_test.sh new file mode 100644 index 000000000..116fa2d48 --- /dev/null +++ b/czech-file-knife/tests/workflows/session_contracts_test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Exercise envelope handling using the actual Nickel evaluator. +set -euo pipefail +repo=$(git rev-parse --show-toplevel) +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT +mkdir -p "$fixture/session" +cp "$repo/coordination.k9.ncl" "$fixture/coordination.k9.ncl" +cp "$repo/session/custom-checks.k9.ncl" "$fixture/session/custom-checks.k9.ncl" +cd "$fixture" +bash "$repo/scripts/validate-session-contracts.sh" +{ printf '\n \n'; cat "$repo/coordination.k9.ncl"; } > coordination.k9.ncl +bash "$repo/scripts/validate-session-contracts.sh" +printf '\n{ value = 1 }\n' > coordination.k9.ncl +if bash "$repo/scripts/validate-session-contracts.sh"; then + echo 'FAIL: missing envelope accepted' >&2 + exit 1 +fi +printf '\nK9!\n{ invalid = }\n' > coordination.k9.ncl +if bash "$repo/scripts/validate-session-contracts.sh"; then + echo 'FAIL: invalid Nickel accepted' >&2 + exit 1 +fi +echo 'PASS: leading blanks, missing envelope, and invalid Nickel controls' diff --git a/czech-file-knife/tests/workflows/template_conformance_test.sh b/czech-file-knife/tests/workflows/template_conformance_test.sh new file mode 100755 index 000000000..36159e863 --- /dev/null +++ b/czech-file-knife/tests/workflows/template_conformance_test.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# template_conformance_test.sh — prove check-template-conformance.sh can fail. +# +# The estate has twice shipped a gate that could never fire (#49: the +# invisible-character gate matched nothing; #64: the Hypatia gate was +# unconditionally vacuous). A gate whose only evidence is that it passes is not +# evidence. Every check below therefore has a NEGATIVE control: a fixture that +# MUST be rejected. If a negative control passes, this test fails. +# +# Usage: bash tests/workflows/template_conformance_test.sh [--keep] + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" +CHECK="$REPO_DIR/scripts/check-template-conformance.sh" +KEEP="${1:-}" + +SCRATCH="$(mktemp -d /tmp/tmpl-conformance.XXXXXX)" +[ "$KEEP" = "--keep" ] || trap 'rm -rf "$SCRATCH"' EXIT + +PASS=0; FAIL=0 +ok() { echo " PASS: $1"; PASS=$((PASS+1)); } +bad() { echo " FAIL: $1"; FAIL=$((FAIL+1)); } + +command -v git >/dev/null 2>&1 || { echo "SKIP: no git"; exit 0; } +[ -x "$CHECK" ] || [ -f "$CHECK" ] || { echo "FAIL: checker not found at $CHECK"; exit 1; } + +# ── fixture: a template and a minted child ─────────────────────────────────── +make_template() { + local d="$1" + mkdir -p "$d/.github" "$d/scripts" + printf 'name: demo\n' > "$d/Justfile" + printf 'body\n' > "$d/README.adoc" + printf 'ci: []\n' > "$d/.github/workflows.yml" + git -C "$d" init -q -b main + git -C "$d" -c user.email=t@t -c user.name=t add -A + git -C "$d" -c user.email=t@t -c user.name=t commit -qm "template" +} + +make_child() { + local d="$1" parent_slug="$2" branch="$3" commit="$4" tree="$5" + mkdir -p "$d/.machine_readable" "$d/.github" "$d/scripts" + cp "$2_README" /dev/null 2>/dev/null || true + printf 'name: demo\n' > "$d/Justfile" + printf 'body\n' > "$d/README.adoc" + printf 'ci: []\n' > "$d/.github/workflows.yml" + cp "$CHECK" "$d/scripts/check-template-conformance.sh" + cat > "$d/.machine_readable/PROVENANCE.a2ml" <"$SCRATCH/out" 2>&1 + else + bash "$CHECK" --repo "$repo" --quiet >"$SCRATCH/out" 2>&1 + fi + echo $? +} + +say() { echo; echo "── $1 ──"; } + +TMPL="$SCRATCH/template"; make_template "$TMPL" +T_COMMIT="$(git -C "$TMPL" rev-parse HEAD)" +T_TREE="$(git -C "$TMPL" rev-parse 'HEAD^{tree}')" + +# ───────────────────────────────────────────────────────────────────────────── +say "control: a conforming child must PASS" +CHILD="$SCRATCH/child-good" +make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" +rc=$(run_check "$CHILD") +if [ "$rc" -eq 0 ]; then ok "conforming child accepted (positive control)"; else bad "conforming child was rejected (rc=$rc)"; cat "$SCRATCH/out"; fi + +# ───────────────────────────────────────────────────────────────────────────── +say "negative control 1: missing provenance must FAIL (#203)" +CHILD="$SCRATCH/child-noprov" +make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" +rm "$CHILD/.machine_readable/PROVENANCE.a2ml" +rc=$(run_check "$CHILD") +if [ "$rc" -ne 0 ]; then ok "child with no provenance rejected (T1)"; else bad "child with no provenance ACCEPTED — T1 is vacuous"; fi + +# ───────────────────────────────────────────────────────────────────────────── +say "negative control 2: self-parent must FAIL (#200/#201 class)" +CHILD="$SCRATCH/child-self" +make_child "$CHILD" "metadatastician/knot-knot" "main" "$T_COMMIT" "$T_TREE" +rc=$(run_check "$CHILD") +if [ "$rc" -ne 0 ]; then ok "self-parenting repo rejected (T2)"; else bad "self-parenting repo ACCEPTED — T2 is vacuous"; fi + +# ───────────────────────────────────────────────────────────────────────────── +say "negative control 3: UNASSIGNED pin must FAIL" +CHILD="$SCRATCH/child-unassigned" +make_child "$CHILD" "hyperpolymath/czech-file-knife" "UNASSIGNED" "UNASSIGNED" "UNASSIGNED" +rc=$(run_check "$CHILD") +if [ "$rc" -ne 0 ]; then ok "unresolved parent pin rejected (T3)"; else bad "unresolved parent pin ACCEPTED — T3 is vacuous"; fi + +# ───────────────────────────────────────────────────────────────────────────── +say "negative control 4: a leaked work branch must FAIL (#203)" +CHILD="$SCRATCH/child-leak" +make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" +# reproduce the knot-knot shape: a parentless branch whose tree is copied +git -C "$CHILD" checkout -q --orphan coderabbit/fix-hypatia-scan-failures/f36ac704 +git -C "$CHILD" -c user.email=t@t -c user.name=t commit -qm "Initialize coderabbit/fix-hypatia-scan-failures/f36ac704" +git -C "$CHILD" checkout -q main +rc=$(run_check "$CHILD") +if [ "$rc" -ne 0 ]; then ok "leaked work branch rejected (T4)"; else bad "leaked work branch ACCEPTED — T4 is vacuous"; fi +if grep -qi "no common ancestor" "$SCRATCH/out"; then ok "unrelated-history signature reported (T4b)"; else bad "unrelated-history signature not reported"; fi + +# ───────────────────────────────────────────────────────────────────────────── +say "negative control 5: drift must be REPORTED (advisory) and visibly found" +CHILD="$SCRATCH/child-drift" +make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" +printf 'tampered\n' > "$CHILD/README.adoc" # a template-owned file changed +rm "$CHILD/.github/workflows.yml" # a template-owned file deleted +rc=$(run_check "$CHILD" "$TMPL") +if grep -q "MISSING" "$SCRATCH/out" && grep -q "differs" "$SCRATCH/out"; then + ok "drift report found both a missing and a changed template-owned path (D1/D2)" +else + bad "drift report missed the injected divergence"; cat "$SCRATCH/out" +fi +[ "$rc" -eq 0 ] && ok "drift alone does not fail the gate (advisory, by design)" || bad "drift failed the gate — that is how a gate becomes unpassable, then continue-on-error'd" + +# ───────────────────────────────────────────────────────────────────────────── +say "self-skip: the template itself must not be checked" +SKIPDIR="$SCRATCH/template-with-archetypes" +mkdir -p "$SKIPDIR/archetypes" +out=$(bash "$CHECK" --repo "$SKIPDIR" 2>&1); rc=$? +if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "SKIP"; then ok "template self-skips"; else bad "template did not self-skip (rc=$rc): $out"; fi + +echo +echo "template conformance test: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/workflows/validate_workflows_test.sh b/czech-file-knife/tests/workflows/validate_workflows_test.sh new file mode 100755 index 000000000..4e6e32172 --- /dev/null +++ b/czech-file-knife/tests/workflows/validate_workflows_test.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Test: GitHub Workflows Validation +# Verifies that all workflows follow the standards + +set -euo pipefail + +WORKFLOWS_DIR="${1:-.github/workflows}" +ERRORS=0 +WARNINGS=0 + +# ANSI colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_error() { + echo -e "${RED}ERROR${NC}: $*" >&2 + ERRORS=$((ERRORS + 1)) +} + +log_warning() { + echo -e "${YELLOW}WARN${NC}: $*" >&2 + WARNINGS=$((WARNINGS + 1)) +} + +log_pass() { + echo -e "${GREEN}PASS${NC}: $*" >&2 +} + +log_info() { + echo -e "${BLUE}INFO${NC}: $*" >&2 +} + +# Verify workflows directory exists +if [ ! -d "$WORKFLOWS_DIR" ]; then + log_error "Workflows directory not found: $WORKFLOWS_DIR" + exit 1 +fi + +echo "" +log_info "Validating workflows in: $WORKFLOWS_DIR" +echo "" + +#============================================================================== +# TEST 1: CHECK EACH WORKFLOW FILE +#============================================================================== + +WORKFLOW_COUNT=0 +while IFS= read -r workflow_file; do + [ -z "$workflow_file" ] && continue + WORKFLOW_COUNT=$((WORKFLOW_COUNT + 1)) +done < <(find "$WORKFLOWS_DIR" \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | sort) + +echo "Found $WORKFLOW_COUNT workflow file(s)" +echo "" + +while IFS= read -r workflow_file; do + [ -z "$workflow_file" ] && continue + + WORKFLOW_NAME=$(basename "$workflow_file") + + # TEST 1a: SPDX Header + if head -10 "$workflow_file" 2>/dev/null | grep -q "SPDX-License-Identifier"; then + log_pass " $WORKFLOW_NAME: SPDX header present" + else + log_warning " $WORKFLOW_NAME: No SPDX header" + fi + + # TEST 1b: Has 'name' field + if grep -q "^name:" "$workflow_file" 2>/dev/null; then + log_pass " $WORKFLOW_NAME: Has 'name' field" + else + log_error " $WORKFLOW_NAME: Missing 'name' field" + fi + +done < <(find "$WORKFLOWS_DIR" \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | sort) + +#============================================================================== +# TEST 2: REQUIRED WORKFLOWS +#============================================================================== + +echo "" +log_info "Checking for required workflows" +echo "" + +REQUIRED_WORKFLOWS=( + "hypatia-scan.yml" + "codeql.yml" + "scorecard.yml" + "quality.yml" + "mirror.yml" + "instant-sync.yml" + "guix-policy.yml" + "security-policy.yml" + "wellknown-enforcement.yml" + "workflow-linter.yml" + # Retired in #14, replaced by runtime-policy.yml — see the note in + # scripts/validate-template.sh. Keeping the old names here required two + # files the template no longer ships. + "runtime-policy.yml" + "secret-scanner.yml" +) + +FOUND_COUNT=0 +for required in "${REQUIRED_WORKFLOWS[@]}"; do + if [ -f "$WORKFLOWS_DIR/$required" ]; then + log_pass "Found: $required" + FOUND_COUNT=$((FOUND_COUNT + 1)) + else + log_warning "Missing: $required" + WARNINGS=$((WARNINGS + 1)) + fi +done + +echo "" +echo "Found $FOUND_COUNT/${#REQUIRED_WORKFLOWS[@]} required workflows" +echo "" + +#============================================================================== +# SUMMARY +#============================================================================== + +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "WORKFLOW VALIDATION SUMMARY" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" +echo -e "Errors: ${RED}${ERRORS}${NC}" +echo -e "Warnings: ${YELLOW}${WARNINGS}${NC}" +echo "" + +if [ "$ERRORS" -eq 0 ]; then + echo -e "${GREEN}✓ Workflow validation PASSED${NC}" + [ "$WARNINGS" -gt 0 ] && echo -e " (with $WARNINGS recommendations)" + exit 0 +else + echo -e "${RED}✗ Workflow validation FAILED${NC}" + echo " Please fix the errors above." + exit 1 +fi diff --git a/czech-file-knife/verification/README.adoc b/czech-file-knife/verification/README.adoc new file mode 100644 index 000000000..f0e6aa767 --- /dev/null +++ b/czech-file-knife/verification/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Verification Pillar diff --git a/czech-file-knife/verification/benchmarks/README.adoc b/czech-file-knife/verification/benchmarks/README.adoc new file mode 100644 index 000000000..3f4ce4f58 --- /dev/null +++ b/czech-file-knife/verification/benchmarks/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Benchmarks Unit diff --git a/czech-file-knife/verification/coverage/README.adoc b/czech-file-knife/verification/coverage/README.adoc new file mode 100644 index 000000000..60b580482 --- /dev/null +++ b/czech-file-knife/verification/coverage/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Coverage Unit diff --git a/czech-file-knife/verification/fuzzing/README.adoc b/czech-file-knife/verification/fuzzing/README.adoc new file mode 100644 index 000000000..48036b6de --- /dev/null +++ b/czech-file-knife/verification/fuzzing/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Fuzzing Unit diff --git a/czech-file-knife/verification/proofs/README.adoc b/czech-file-knife/verification/proofs/README.adoc new file mode 100644 index 000000000..eb22d4c40 --- /dev/null +++ b/czech-file-knife/verification/proofs/README.adoc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Formal Verification Proofs + +This directory contains formal proofs organised by proof assistant. + +== Directory Structure + +[source] +---- +proofs/ +├── idris2/ # Idris2 proofs (ABI, dependent types) +│ ├── ABI/ # ABI-specific proofs (mandatory) +│ │ ├── Pointers.idr # Non-null pointer safety +│ │ ├── Layout.idr # Memory layout correctness +│ │ ├── Platform.idr # Platform type size proofs +│ │ ├── Foreign.idr # FFI return type proofs +│ │ └── Compliance.idr # C ABI compliance +│ └── Types.idr # Core data type well-formedness +├── lean4/ # Lean4 proofs (algebra, lattices) +│ └── ApiTypes.lean +├── agda/ # Agda proofs (induction, metatheory) +│ └── Properties.agda +├── coq/ # Coq proofs (type systems, compilation) +│ └── TypeSafety.v +└── tlaplus/ # TLA+ specs (distributed protocols) + └── StateMachine.tla +---- + +== Verification Commands + +[source,bash] +---- +just proof-check-all # Run all proof checkers +just proof-check-idris2 # Idris2 only +just proof-check-lean4 # Lean4 only +just proof-check-agda # Agda only +just proof-check-coq # Coq only +---- + +== Banned Patterns + +The following MUST NOT appear in any proof file: + +- `believe_me` (Idris2) +- `assert_total` (Idris2) +- `postulate` (Idris2/Agda) +- `sorry` (Lean4) +- `Admitted` (Coq) +- `unsafeCoerce` (Haskell) + +CI enforces this via `panic-attack assail --proofs-only`. + +== Adding New Proofs + +1. Choose the appropriate prover (see PROOF-NEEDS.md) +2. Create the `.idr`/`.lean`/`.agda`/`.v`/`.tla` file in the right directory +3. Ensure `%default total` (Idris2) or equivalent +4. Run the verification command +5. Update PROOF-STATUS.md diff --git a/czech-file-knife/verification/proofs/agda/MANIFEST b/czech-file-knife/verification/proofs/agda/MANIFEST new file mode 100644 index 000000000..2e18dc332 --- /dev/null +++ b/czech-file-knife/verification/proofs/agda/MANIFEST @@ -0,0 +1,4 @@ +# Agda proof manifest — read by scripts/check-proofs.sh agda +# Format: ||gated|quarantine| +# Ground-truthed 2026-07-17 with agda (agda --safe). +verification/proofs/agda|Properties.agda|gated| compiles under `agda --safe` diff --git a/czech-file-knife/verification/proofs/agda/Properties.agda b/czech-file-knife/verification/proofs/agda/Properties.agda new file mode 100644 index 000000000..d78d9d0f7 --- /dev/null +++ b/czech-file-knife/verification/proofs/agda/Properties.agda @@ -0,0 +1,37 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- Agda Proof Template: Inductive and coinductive properties +-- Replace with your project's domain-specific proofs. +-- All proofs must be total (no postulate, no {-# TERMINATING #-}). + +module Properties where + +open import Data.Nat using (ℕ; zero; suc; _+_; _≤_; z≤n; s≤s; _<_) +open import Data.Nat.Properties using (+-comm; +-assoc; ≤-refl; ≤-trans) +open import Data.List using (List; []; _∷_; length; _++_) +open import Data.List.Properties using (length-++ ) +open import Relation.Binary.PropositionalEquality using (_≡_; refl; cong; sym; trans) + +-- Example: Proof that list append preserves total length +-- Replace with your project's domain proofs. + +append-length : ∀ {A : Set} (xs ys : List A) → + length (xs ++ ys) ≡ length xs + length ys +append-length xs ys = length-++ xs + +-- Example: Monotonicity proof template +-- Use for state machines, confidence scores, trust levels +record Monotone {A : Set} (_≤A_ : A → A → Set) (f : A → A) : Set where + field + preserves : ∀ {x y} → x ≤A y → f x ≤A f y + +-- Example: Idempotence proof template +-- Use for normalisation, deduplication, formatting +record Idempotent {A : Set} (_≡A_ : A → A → Set) (f : A → A) : Set where + field + idem : ∀ (x : A) → f (f x) ≡A f x + +-- Example: Natural number successor is monotone +suc-monotone : Monotone _≤_ suc +suc-monotone = record { preserves = s≤s } diff --git a/czech-file-knife/verification/proofs/coq/MANIFEST b/czech-file-knife/verification/proofs/coq/MANIFEST new file mode 100644 index 000000000..aa71524e6 --- /dev/null +++ b/czech-file-knife/verification/proofs/coq/MANIFEST @@ -0,0 +1,4 @@ +# Coq proof manifest — read by scripts/check-proofs.sh coq +# Format: ||gated|quarantine| +# Ground-truthed 2026-07-17 with coqc. +verification/proofs/coq|TypeSafety.v|gated| compiles under `coqc` diff --git a/czech-file-knife/verification/proofs/coq/TypeSafety.v b/czech-file-knife/verification/proofs/coq/TypeSafety.v new file mode 100644 index 000000000..8f5b41894 --- /dev/null +++ b/czech-file-knife/verification/proofs/coq/TypeSafety.v @@ -0,0 +1,73 @@ +(* SPDX-License-Identifier: MPL-2.0 *) +(* Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) *) +(* + Coq Proof Template: Type system soundness + Replace with your project's type system proofs. + All proofs must be complete — NO Admitted allowed. +*) + +Require Import Coq.Lists.List. +Require Import Coq.Arith.Arith. +Require Import Coq.Bool.Bool. +Import ListNotations. + +(** * Example: Simple expression language with type safety *) +(** Replace this entire section with your project's type system. *) + +(** Types *) +Inductive ty : Type := + | TyNat : ty + | TyBool : ty. + +(** Expressions *) +Inductive expr : Type := + | EConst : nat -> expr + | ETrue : expr + | EFalse : expr + | EPlus : expr -> expr -> expr + | EEq : expr -> expr -> expr. + +(** Values *) +Inductive value : Type := + | VNat : nat -> value + | VBool : bool -> value. + +(** Typing relation *) +Inductive has_type : expr -> ty -> Prop := + | T_Const : forall n, has_type (EConst n) TyNat + | T_True : has_type ETrue TyBool + | T_False : has_type EFalse TyBool + | T_Plus : forall e1 e2, + has_type e1 TyNat -> has_type e2 TyNat -> + has_type (EPlus e1 e2) TyNat + | T_Eq : forall e1 e2, + has_type e1 TyNat -> has_type e2 TyNat -> + has_type (EEq e1 e2) TyBool. + +(** Evaluation *) +Inductive eval : expr -> value -> Prop := + | E_Const : forall n, eval (EConst n) (VNat n) + | E_True : eval ETrue (VBool true) + | E_False : eval EFalse (VBool false) + | E_Plus : forall e1 e2 n1 n2, + eval e1 (VNat n1) -> eval e2 (VNat n2) -> + eval (EPlus e1 e2) (VNat (n1 + n2)) + | E_Eq : forall e1 e2 n1 n2, + eval e1 (VNat n1) -> eval e2 (VNat n2) -> + eval (EEq e1 e2) (VBool (Nat.eqb n1 n2)). + +(** Value typing *) +Definition value_has_type (v : value) (t : ty) : Prop := + match v, t with + | VNat _, TyNat => True + | VBool _, TyBool => True + | _, _ => False + end. + +(** Type soundness: well-typed expressions evaluate to well-typed values *) +Theorem type_soundness : forall e t v, + has_type e t -> eval e v -> value_has_type v t. +Proof. + intros e t v Htype Heval. + induction Htype; inversion Heval; subst; simpl; auto. +Qed. diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr b/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr new file mode 100644 index 000000000..b94a5dbfd --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr @@ -0,0 +1,41 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- ABI Proof: C ABI compliance +-- Proves that struct layouts are C ABI compliant. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module ABI.Compliance + +import ABI.Layout +import ABI.Platform + +%default total + +||| Evidence that every field in a layout is correctly aligned. +public export +data AllFieldsAligned : List StructField -> Type where + AFANil : AllFieldsAligned [] + AFACons : FieldAligned f -> AllFieldsAligned fs -> AllFieldsAligned (f :: fs) + +||| Evidence that every field is within the struct bounds. +public export +data AllFieldsInBounds : (size : Nat) -> List StructField -> Type where + AFBNil : AllFieldsInBounds size [] + AFBCons : FieldInBounds size f -> AllFieldsInBounds size fs -> AllFieldsInBounds size (f :: fs) + +||| A struct layout is C ABI compliant when: +||| 1. All fields are aligned to their natural alignment +||| 2. All fields are within bounds of the struct size +||| 3. The struct size is a multiple of the struct alignment +public export +record CABICompliant (layout : StructLayout) where + constructor MkCompliant + fieldsAligned : AllFieldsAligned (layoutFields layout) + fieldsInBounds : AllFieldsInBounds (layoutSize layout) (layoutFields layout) + sizeAligned : modNatNZ (layoutSize layout) (layoutAlignment layout) SIsNonZero = 0 + +||| An empty struct is trivially compliant (size=1, alignment=1). +export +emptyStructCompliant : CABICompliant (MkLayout "empty" [] 1 1) +emptyStructCompliant = MkCompliant AFANil AFBNil Refl diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr b/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr new file mode 100644 index 000000000..1e550dd9e --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr @@ -0,0 +1,53 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- ABI Proof: FFI function return type proofs +-- Proves that all FFI functions return expected types. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module ABI.Foreign + +%default total + +||| Result type for FFI operations. +||| All FFI functions must return through this type. +public export +data FFIResult : Type -> Type where + FFISuccess : (value : a) -> FFIResult a + FFIError : (code : Int) -> (msg : String) -> FFIResult a + +||| Proof that FFIResult is a functor (map preserves structure). +export +mapFFIResult : (a -> b) -> FFIResult a -> FFIResult b +mapFFIResult f (FFISuccess value) = FFISuccess (f value) +mapFFIResult f (FFIError code msg) = FFIError code msg + +||| Proof that mapping identity preserves the result. +export +mapIdPreserves : (r : FFIResult a) -> mapFFIResult Prelude.id r = r +mapIdPreserves (FFISuccess value) = Refl +mapIdPreserves (FFIError code msg) = Refl + +||| An FFI function specification: name, argument types, return type. +public export +record FFISpec where + constructor MkFFISpec + ffiName : String + ffiReturnType : Type + +||| Proof that an FFI spec has a specific return type. +||| Use this to verify at compile time that FFI functions return the +||| types we expect across the C ABI boundary. +public export +FFIReturns : FFISpec -> Type -> Type +FFIReturns spec ty = ffiReturnType spec = ty + +||| C calling convention marker. +||| Proofs about calling convention compatibility. +public export +data CallingConv = CDecl | StdCall | FastCall + +||| All hyperpolymath FFI uses CDecl. +public export +defaultCallingConv : CallingConv +defaultCallingConv = CDecl diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr b/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr new file mode 100644 index 000000000..9040a5e9a --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr @@ -0,0 +1,63 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- ABI Proof: Memory layout correctness +-- Proves struct size, alignment, and padding properties. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module ABI.Layout + +%default total + +||| Witness that a type has a known size in bytes at compile time. +public export +interface HasSize (ty : Type) where + sizeOf : Nat + +||| Witness that a type has a known alignment in bytes. +public export +interface HasAlignment (ty : Type) where + alignOf : Nat + +||| Calculate padding needed to reach the next aligned offset. +||| paddingFor offset alignment = bytes to add so (offset + padding) `mod` alignment == 0 +public export +paddingFor : (offset : Nat) -> (alignment : Nat) -> {auto 0 ok : NonZero alignment} -> Nat +paddingFor offset alignment = let r = modNatNZ offset alignment ok + in case r of + Z => Z + (S _) => minus alignment r + +||| Proof that an offset with zero remainder needs zero padding. +export +alignedNeedsPadding : (n : Nat) -> (a : Nat) -> {auto 0 ok : NonZero a} -> + modNatNZ n a ok = 0 -> paddingFor n a = 0 +alignedNeedsPadding n a prf = rewrite prf in Refl + +||| A field within a struct, carrying its offset and size. +public export +record StructField where + constructor MkField + fieldName : String + fieldOffset : Nat + fieldSize : Nat + fieldAlignment : Nat + +||| Proof that a field is correctly aligned within a struct. +public export +FieldAligned : StructField -> Type +FieldAligned f = modNatNZ (fieldOffset f) (fieldAlignment f) SIsNonZero = 0 + +||| Proof that a field does not overflow past a given struct size. +public export +FieldInBounds : (structSize : Nat) -> StructField -> Type +FieldInBounds sz f = LTE (fieldOffset f + fieldSize f) sz + +||| A struct layout is a list of fields with a total size. +public export +record StructLayout where + constructor MkLayout + layoutName : String + layoutFields : List StructField + layoutSize : Nat + layoutAlignment : Nat diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr b/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr new file mode 100644 index 000000000..a8d6b947a --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr @@ -0,0 +1,63 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- ABI Proof: Platform-specific type size proofs +-- Proves that C type sizes are correct per platform. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module ABI.Platform + +%default total + +||| Supported target platforms for ABI verification. +public export +data Platform = Linux64 | LinuxARM64 | MacOS64 | MacOSARM64 + | Windows64 | FreeBSD64 | WASM32 + +||| Pointer size in bytes for each platform. +public export +ptrSize : Platform -> Nat +ptrSize WASM32 = 4 +ptrSize _ = 8 + +||| C `int` size in bytes. +public export +cIntSize : Platform -> Nat +cIntSize _ = 4 + +||| C `size_t` size in bytes (matches pointer size). +public export +cSizeT : Platform -> Nat +cSizeT = ptrSize + +||| Proof that size_t always equals pointer size on all platforms. +export +sizeTEqPtrSize : (p : Platform) -> cSizeT p = ptrSize p +sizeTEqPtrSize _ = Refl + +||| Proof that pointer size is always 4 or 8 bytes. +export +ptrSizeValid : (p : Platform) -> Either (ptrSize p = 4) (ptrSize p = 8) +ptrSizeValid WASM32 = Left Refl +ptrSizeValid Linux64 = Right Refl +ptrSizeValid LinuxARM64 = Right Refl +ptrSizeValid MacOS64 = Right Refl +ptrSizeValid MacOSARM64 = Right Refl +ptrSizeValid Windows64 = Right Refl +ptrSizeValid FreeBSD64 = Right Refl + +||| Proof that C int is always 4 bytes on all platforms. +export +cIntAlways4 : (p : Platform) -> cIntSize p = 4 +cIntAlways4 _ = Refl + +||| Proof that pointer size is always at least 4 bytes. +export +ptrSizeAtLeast4 : (p : Platform) -> LTE 4 (ptrSize p) +ptrSizeAtLeast4 WASM32 = lteRefl +ptrSizeAtLeast4 Linux64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) +ptrSizeAtLeast4 LinuxARM64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) +ptrSizeAtLeast4 MacOS64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) +ptrSizeAtLeast4 MacOSARM64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) +ptrSizeAtLeast4 Windows64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) +ptrSizeAtLeast4 FreeBSD64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr b/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr new file mode 100644 index 000000000..31b6c5f29 --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr @@ -0,0 +1,52 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- ABI Proof: Non-null pointer safety +-- Template proof — customise for your project's pointer types. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module ABI.Pointers + +import Data.So + +%default total + +||| A pointer value that has been proven non-null. +||| The `So` constraint carries a compile-time witness that `ptr /= 0`. +public export +record SafePtr where + constructor MkSafePtr + ptr : Bits64 + {auto 0 nonNull : So (ptr /= 0)} + +||| Proof that SafePtr can never hold a null (zero) value. +||| This is enforced by the `So` constraint in the record. +export +safePtrNeverNull : (sp : SafePtr) -> So (sp.ptr /= 0) +safePtrNeverNull sp = sp.nonNull + +||| Wrap a raw pointer with a runtime null check. +||| Returns Nothing if the pointer is null. +export +checkPtr : (raw : Bits64) -> Maybe SafePtr +checkPtr 0 = Nothing +checkPtr raw = case choose (raw /= 0) of + Left prf => Just (MkSafePtr raw) + Right _ => Nothing + +||| Proof that checkPtr 0 always returns Nothing. +export +checkPtrZeroIsNothing : checkPtr 0 = Nothing +checkPtrZeroIsNothing = Refl + +||| An opaque handle backed by a non-null pointer. +||| Use this for FFI resource handles (file descriptors, sockets, etc.). +public export +record Handle (tag : String) where + constructor MkHandle + safePtr : SafePtr + +||| Proof that two handles with equal pointers are equal. +export +handlePtrEq : (h1, h2 : Handle tag) -> h1.safePtr.ptr = h2.safePtr.ptr -> h1 = h2 +handlePtrEq (MkHandle (MkSafePtr p)) (MkHandle (MkSafePtr p)) Refl = Refl diff --git a/czech-file-knife/verification/proofs/idris2/MANIFEST b/czech-file-knife/verification/proofs/idris2/MANIFEST new file mode 100644 index 000000000..ae378fec4 --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/MANIFEST @@ -0,0 +1,10 @@ +# Idris2 proof manifest — read by scripts/check-proofs.sh idris2 +# Format: ||gated|quarantine| +# gated = must compile. quarantine = known-broken, must keep failing. +# Ground-truthed 2026-07-17 with idris2 0.7.0 (developer/tools/opt/pack). +verification/proofs/idris2|ABI/Foreign.idr|gated| the one shipped Idris2 module that compiles — real content, not a stub +verification/proofs/idris2|Types.idr|quarantine| Undefined name LTE (needs Data.Nat); Idris1-era template, has never compiled +verification/proofs/idris2|ABI/Layout.idr|quarantine| Undefined name NonZero (needs Data.Nat); then a genuine unification failure (S ?x vs f .fieldAlignment) +verification/proofs/idris2|ABI/Platform.idr|quarantine| Undefined name LTE (needs Data.Nat); then Undefined name lteRefl +verification/proofs/idris2|ABI/Pointers.idr|quarantine| .nonNull declared at quantity 0 (erased) yet projected into a value position, plus a unification failure — design decision, not a typo +verification/proofs/idris2|ABI/Compliance.idr|quarantine| depends on quarantined ABI.Layout / ABI.Platform diff --git a/czech-file-knife/verification/proofs/idris2/Types.idr b/czech-file-knife/verification/proofs/idris2/Types.idr new file mode 100644 index 000000000..f631b74ea --- /dev/null +++ b/czech-file-knife/verification/proofs/idris2/Types.idr @@ -0,0 +1,40 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- Typing Proof: Core data type well-formedness +-- Template — replace with your project's core types. +-- All proofs MUST be constructive (no believe_me, no assert_total). + +module Types + +import Data.Nat + +%default total + +||| Example: A bounded natural number (0 to max). +||| Replace with your project's core types. +public export +record Bounded (max : Nat) where + constructor MkBounded + value : Nat + {auto inBounds : LTE value max} + +||| Proof that a Bounded value is always <= max. +export +boundedLeMax : (b : Bounded max) -> LTE b.value max +boundedLeMax b = b.inBounds + +||| Proof that zero is always a valid Bounded value. +export +zeroIsBounded : {max : Nat} -> Bounded (S max) +zeroIsBounded = MkBounded 0 + +||| Example: A non-empty list with a compile-time guarantee. +public export +data NonEmpty : List a -> Type where + IsNonEmpty : NonEmpty (x :: xs) + +||| Proof that cons always produces a non-empty list. +export +consIsNonEmpty : (x : a) -> (xs : List a) -> NonEmpty (x :: xs) +consIsNonEmpty _ _ = IsNonEmpty diff --git a/czech-file-knife/verification/proofs/lean4/ApiTypes.lean b/czech-file-knife/verification/proofs/lean4/ApiTypes.lean new file mode 100644 index 000000000..f02f259c3 --- /dev/null +++ b/czech-file-knife/verification/proofs/lean4/ApiTypes.lean @@ -0,0 +1,45 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +-- +-- Typing Proof: Public API type safety +-- Template — replace with your project's API types. +-- Proves properties about exported function signatures. + +-- Example: Result type used across API boundaries +inductive ApiResult (α : Type) where + | ok : α → ApiResult α + | error : Nat → String → ApiResult α + +namespace ApiResult + -- Proof: map preserves structure (functor law: map id = id) + def map (f : α → β) : ApiResult α → ApiResult β + | .ok v => .ok (f v) + | .error c m => .error c m + + theorem map_id : ∀ (r : ApiResult α), map id r = r := by + intro r + cases r with + | ok v => simp [map] + | error c m => simp [map] + + -- Proof: map composition (functor law: map (g ∘ f) = map g ∘ map f) + theorem map_comp (f : α → β) (g : β → γ) : + ∀ (r : ApiResult α), map (g ∘ f) r = map g (map f r) := by + intro r + cases r with + | ok v => simp [map, Function.comp] + | error c m => simp [map] +end ApiResult + +-- Example: Bounded confidence value (0.0 to 1.0 modelled as Nat/1000) +-- Replace with your project's numeric invariants +structure BoundedNat (max : Nat) where + val : Nat + le_max : val ≤ max + +theorem bounded_nat_le {max : Nat} (b : BoundedNat max) : b.val ≤ max := + b.le_max + +-- Proof: zero is always bounded +def zeroBounded {max : Nat} (h : 0 < max) : BoundedNat max := + ⟨0, Nat.zero_le max⟩ diff --git a/czech-file-knife/verification/proofs/lean4/MANIFEST b/czech-file-knife/verification/proofs/lean4/MANIFEST new file mode 100644 index 000000000..0f3adc273 --- /dev/null +++ b/czech-file-knife/verification/proofs/lean4/MANIFEST @@ -0,0 +1,4 @@ +# Lean4 proof manifest — read by scripts/check-proofs.sh lean4 +# Format: ||gated|quarantine| +# Ground-truthed 2026-07-17 with Lean leanprover/lean4:v4.15.0 (via elan). +verification/proofs/lean4|ApiTypes.lean|quarantine| ApiTypes.lean:44 `Nat.zero_le max` application type mismatch — `max` used where Nat is expected; has never compiled diff --git a/czech-file-knife/verification/proofs/lean4/lean-toolchain b/czech-file-knife/verification/proofs/lean4/lean-toolchain new file mode 100644 index 000000000..d0eb99ff6 --- /dev/null +++ b/czech-file-knife/verification/proofs/lean4/lean-toolchain @@ -0,0 +1 @@ +leanprover/lean4:v4.15.0 diff --git a/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla b/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla new file mode 100644 index 000000000..f34849477 --- /dev/null +++ b/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla @@ -0,0 +1,91 @@ +--------------------------- MODULE StateMachine ---------------------------- +(* SPDX-License-Identifier: MPL-2.0 *) +(* Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) *) +(* *) +(* TLA+ Specification Template: State Machine *) +(* Replace with your project's distributed protocol or state machine. *) +(* Use TLC model checker to verify properties. *) +(* *) +(* Example: A simple request pipeline with safety properties. *) +(* Replace States, Init, Next with your project's actual states. *) +(***************************************************************************) + +EXTENDS Naturals, Sequences, FiniteSets + +CONSTANTS + MaxRequests \* Upper bound on concurrent requests (for model checking) + +VARIABLES + state, \* Current pipeline state + processed, \* Number of processed requests + queue \* Request queue + +vars == <> + +\* Pipeline states — replace with your project's states +States == {"idle", "scanning", "routing", "dispatching", "done", "failed"} + +\* Valid transitions — replace with your project's transition rules +ValidTransition(from, to) == + \/ from = "idle" /\ to = "scanning" + \/ from = "scanning" /\ to = "routing" + \/ from = "scanning" /\ to = "failed" + \/ from = "routing" /\ to = "dispatching" + \/ from = "routing" /\ to = "failed" + \/ from = "dispatching" /\ to = "done" + \/ from = "dispatching" /\ to = "failed" + \/ from = "done" /\ to = "idle" + \/ from = "failed" /\ to = "idle" + +\* Initial state +Init == + /\ state = "idle" + /\ processed = 0 + /\ queue = <<>> + +\* Transition action +Transition(newState) == + /\ ValidTransition(state, newState) + /\ state' = newState + /\ IF newState = "done" + THEN processed' = processed + 1 + ELSE processed' = processed + /\ UNCHANGED queue + +\* Enqueue a request (only when idle or scanning) +Enqueue == + /\ state \in {"idle", "scanning"} + /\ Len(queue) < MaxRequests + /\ queue' = Append(queue, "request") + /\ UNCHANGED <> + +\* Next-state relation +Next == + \/ \E s \in States : Transition(s) + \/ Enqueue + +\* Fairness: the system must eventually process +Spec == Init /\ [][Next]_vars /\ WF_vars(Next) + +\* ---- SAFETY PROPERTIES ---- + +\* State is always valid +TypeInvariant == state \in States + +\* Processed count never decreases (monotonicity) +ProcessedMonotonic == processed >= 0 + +\* Queue never exceeds max +QueueBounded == Len(queue) <= MaxRequests + +\* No impossible transitions (e.g., idle -> done) +NoSkipStates == + [][state' # state => + ValidTransition(state, state')]_state + +\* ---- LIVENESS PROPERTIES ---- + +\* Every request eventually completes or fails +EventualCompletion == <>(state = "done" \/ state = "failed") + +============================================================================ diff --git a/czech-file-knife/verification/safety_case/README.adoc b/czech-file-knife/verification/safety_case/README.adoc new file mode 100644 index 000000000..2d74c688e --- /dev/null +++ b/czech-file-knife/verification/safety_case/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Safety case Unit diff --git a/czech-file-knife/verification/simulations/README.adoc b/czech-file-knife/verification/simulations/README.adoc new file mode 100644 index 000000000..772749d97 --- /dev/null +++ b/czech-file-knife/verification/simulations/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Simulations Unit diff --git a/czech-file-knife/verification/tests/README.adoc b/czech-file-knife/verification/tests/README.adoc new file mode 100644 index 000000000..f49aadf3c --- /dev/null +++ b/czech-file-knife/verification/tests/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Tests Unit diff --git a/czech-file-knife/verification/traceability/README.adoc b/czech-file-knife/verification/traceability/README.adoc new file mode 100644 index 000000000..05e6dcc28 --- /dev/null +++ b/czech-file-knife/verification/traceability/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Traceability Unit diff --git a/czech-file-knife/www/.well-known/ai.txt b/czech-file-knife/www/.well-known/ai.txt new file mode 100644 index 000000000..96e122700 --- /dev/null +++ b/czech-file-knife/www/.well-known/ai.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: MPL-2.0 +# ai.txt - AI interaction policy +# See: https://site.spawning.ai/spawning-ai-txt + +User-Agent: * +Disallow-Training: yes +Disallow-Summarization: no +Disallow-Generation: yes + +# This project's code is licensed under MPL-2.0. +# AI agents may read and analyze this code for assisting contributors. +# AI agents must NOT use this code for model training without explicit consent. +# +# For AI agent integration instructions, see: +# _chora.deed — the repo deed: universal AI entry point (allocation + ply tree) +# AI.a2ml (Claude-specific instructions) +# .machine_readable/ (structured project state) diff --git a/czech-file-knife/www/.well-known/aibdp.json b/czech-file-knife/www/.well-known/aibdp.json new file mode 100644 index 000000000..2fe3b2444 --- /dev/null +++ b/czech-file-knife/www/.well-known/aibdp.json @@ -0,0 +1,79 @@ +{ + "aibdp_version": "0.2", + "status": "experimental \u2014 declaration-only; observing, never enforcing (see www/policies/ai-use.adoc)", + "canonical_uri": "https://github.com/hyperpolymath/czech-file-knife/blob/main/www/.well-known/aibdp.json", + "contact": "mailto:j.d.a.jewell@open.ac.uk", + "policy_uri": "https://github.com/hyperpolymath/czech-file-knife/blob/main/www/policies/ai-use.adoc", + "policies": { + "indexing": { + "status": "allowed", + "scope": "all", + "rationale": "The site is published to be found; indexing public material crosses no boundary." + }, + "summarization": { + "status": "allowed", + "scope": "all", + "rationale": "Mirrors ai.txt Disallow-Summarization: no \u2014 describing published material serves readers." + }, + "question_answering": { + "status": "allowed", + "scope": "all", + "rationale": "Mirrors ai.txt: agents may read and analyse to assist contributors." + }, + "embedding": { + "status": "conditional", + "scope": "all", + "conditions": [ + "Embeddings may serve retrieval over the public material with attribution preserved.", + "Embedding corpora must not be redistributed or used as training input without explicit consent." + ], + "rationale": "Retrieval aids discovery; corpus redistribution carries the licences (MPL-2.0 code, CC-BY-SA-4.0 docs)." + }, + "training": { + "status": "disallowed", + "scope": "all", + "rationale": "Mirrors ai.txt Disallow-Training: yes \u2014 model training on this corpus requires explicit consent, which this declaration does not give." + }, + "fine_tuning": { + "status": "disallowed", + "scope": "all", + "rationale": "Fine-tuning is training with a narrower objective; the ai.txt stance applies unchanged." + }, + "commercial_training": { + "status": "disallowed", + "scope": "all", + "rationale": "As training, and additionally: contact the maintainer before any commercial incorporation \u2014 consent is explicit or it is absent.", + "alternatives": "Cite the repository and its documentation instead of training on it." + }, + "generation": { + "status": "disallowed", + "scope": "all", + "rationale": "Mirrors ai.txt Disallow-Generation: yes \u2014 generated text must not be presented as this project's own statements." + } + }, + "enforcement": { + "mechanism": "none", + "note": "Declaration-only. HTTP 430 (draft-jewell-http-430-consent-required-00, EXPERIMENTAL) is the mechanism the draft defines for origins that can emit it; this template deliberately does not: minting or installing never enables 430. Enforcement would require a separate, explicit, versioned, provenance-bearing and independently testable profile choice, which this bundle does not ship.", + "contact_before_litigation": true, + "preferred_resolution": "Correspondence and correction via the contact above." + }, + "metadata": { + "created": "2026-09-28", + "last_modified": "2026-09-28", + "author": "Jonathan D.A. Jewell", + "project": "Czech File Knife", + "repository": "https://github.com/hyperpolymath/czech-file-knife", + "related_standards": [ + "draft-jewell-aibdp-00 (experimental, not a Datatracker submission)", + "draft-jewell-http-430-consent-required-00 (experimental, not a Datatracker submission)", + "AIPREF (mapped alternative representation where they overlap)", + "RFC 8615", + "RFC 9110", + "RFC 9309" + ] + }, + "philosophy": { + "statement": "Without refusal, permission is meaningless.", + "note": "Purpose headers are unverified assertions; User-Agent inference is heuristic; unknown purpose remains unknown." + } +} \ No newline at end of file diff --git a/czech-file-knife/www/.well-known/aibdp.json.license b/czech-file-knife/www/.well-known/aibdp.json.license new file mode 100644 index 000000000..75837903d --- /dev/null +++ b/czech-file-knife/www/.well-known/aibdp.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/czech-file-knife/www/.well-known/humans.txt b/czech-file-knife/www/.well-known/humans.txt new file mode 100644 index 000000000..9eaa949d4 --- /dev/null +++ b/czech-file-knife/www/.well-known/humans.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# humanstxt.org + +/* TEAM */ +Maintainer: Jonathan D.A. Jewell (hyperpolymath) +Contact: j.d.a.jewell@open.ac.uk +From: United Kingdom + +/* SITE */ +Last update: 2026-09-28 +Standards: RSR (Rhodium Standard Repository) +License: MPL-2.0 (code) / CC-BY-SA-4.0 (docs) +Components: Idris2 ABI, Zig FFI +Tools: just, Podman, Guix diff --git a/czech-file-knife/www/.well-known/security.txt b/czech-file-knife/www/.well-known/security.txt new file mode 100644 index 000000000..15b9a4485 --- /dev/null +++ b/czech-file-knife/www/.well-known/security.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# RFC 9116 - security.txt +# https://securitytxt.org/ + +# No Encryption: field. RFC 9116 §2.5.4 requires it to point at an actual key; +# the estate signs with SSH (gpg.format=ssh) and publishes no PGP key, so the +# field had nothing to reference and shipped the literal token instead — which +# also made this file unparseable. Report via the Policy: URL below. + +Contact: mailto:j.d.a.jewell@open.ac.uk +Expires: 2026-12-31T23:59:59.000Z +Preferred-Languages: en +Canonical: https://github.com/hyperpolymath/czech-file-knife/.well-known/security.txt +Policy: https://github.com/hyperpolymath/czech-file-knife/blob/main/.github/SECURITY.md diff --git a/czech-file-knife/www/README.adoc b/czech-file-knife/www/README.adoc new file mode 100644 index 000000000..a431f894d --- /dev/null +++ b/czech-file-knife/www/README.adoc @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `www/` — Site-Operations Bundle +:toc: left +:icons: font + +Canonical source tree for everything a project publishes or operates about a +public web presence: protocol metadata, site policy, security headers, +web-server integration, authoritative DNS, encrypted-DNS/privacy discovery, +TLS policy, validation and runbooks. + +This directory is a *source bundle*, **not** a document root. Never point a +web server at `www/` wholesale — see <<_publication_boundary>>. + +== Layout + +[cols="1,3", options="header"] +|=== +| Path | Role + +| `.well-known/` +| Explicitly public protocol metadata (RFC 8615): `security.txt` (RFC 9116), + `humans.txt`, `ai.txt`, and the experimental `aibdp.json` declaration. + Canonical location — the former repository-root `.well-known/` was migrated + here (see `scripts/migrate-wellknown-to-www.sh`). + +| `public/` +| Ordinary publishable HTML/assets. The site's document root content. + +| `policies/` +| Human-readable policy *sources* (AsciiDoc): privacy, AI use, acceptable + use. Rendered into `public/policies/` at deploy time — see + `runbooks/deploy.adoc`. + +| `errors/` +| Static 4xx/5xx bodies. No server identification, no stack details. + +| `security_headers/` +| CSP/HSTS/etc. source templates. Included by server configuration; + never served as content. + +| `webservers/` +| Caddy, nginx and Apache integration examples implementing the + publication boundary. + +| `dns/bind9/` +| Authoritative-only BIND 9 starting hand. No recursion, no automatically + started daemon, reserved example zones only. + +| `dns/records/` +| Zone and resource-record templates (example.invalid, RFC 3849/RFC 5737 + documentation ranges), DNSSEC and CAA guidance. + +| `dns/privacy/` +| DoT/DoQ/DoH/ODoH/ECH discovery guidance with the roles correctly + distinguished and capability detection documented. + +| `tls/` +| Certificate and rotation policy. **Never** private keys, issued + certificate secrets, TSIG secrets, journals, caches, PID files, logs or + runtime databases — in this tree or in git. + +| `profiles/` +| Explicit opt-in composition (baseline-site, consent-aware-web, + authoritative-dns, privacy-enhanced, full-expert). Nothing is enabled + by minting; no profile hides an enablement. + +| `schemas/` +| Validators and machine-readable declarations for public/generated + artefacts, including the publishable-paths boundary declaration and the + vendored AIBDP 0.2 schema. + +| `tests/` +| Local probes with planted controls: publication boundary, BIND safety, + profile enablement, AIBDP shape, well-known content, root migration. + Run them all: `bash www/tests/run-all.sh`. + +| `runbooks/` +| Deploy, rollback, certificate rotation, DNS change and incident + procedures. +|=== + +[#_publication_boundary] +== Publication boundary (security-critical) + +A supported deployment: + +. serves `www/public/` as ordinary content; +. explicitly exposes `www/.well-known/` at the URL path `/.well-known/`; +. keeps `dns/`, `tls/`, `security_headers/`, `webservers/`, `profiles/`, + `schemas/`, `tests/` and `runbooks/` — and the `policies/` *sources* — + outside the public document surface; +. fails validation if a generated deployment would publish operational + configuration (`tests/check-publication-boundary.sh`, enforced in CI and + wired into the mint smoke test). + +The canonical machine-readable statement of the boundary is +`schemas/publishable-paths.txt`. + +== Experimental material + +`aibdp.json` implements AIBDP 0.2 (`draft-jewell-aibdp-00`, experimental, +not a Datatracker submission; a mapped alternative representation of AIPREF +where they overlap). The `consent-aware-web` profile is +*declaration/observe-only by default*: minting or installing this template +never emits HTTP 430. Enforcement would require a separate, explicit, +versioned, provenance-bearing and independently testable profile choice — +none exists in this bundle. + +Purpose headers are unverified assertions; User-Agent inference is +heuristic; unknown purpose remains unknown. + +== Provenance + +Bundle shape and constraints: `czech-file-knife` issue #53. AIBDP schema +vendored from `metadatastician/consent-aware-web` (MPL-2.0) — see +`schemas/aibdp-schema-v0.2.json.license`. diff --git a/czech-file-knife/www/dns/bind9/README.adoc b/czech-file-knife/www/dns/bind9/README.adoc new file mode 100644 index 000000000..165ac8d27 --- /dev/null +++ b/czech-file-knife/www/dns/bind9/README.adoc @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `dns/bind9/` — Authoritative-Only Starting Hand + +A safe starting hand for the *authoritative* DNS role only. The posture, in +one line: **answers for its zones, does nothing else.** + +== What the example guarantees + +* `recursion no` + `allow-recursion { none; }` — not a resolver, so not an + open resolver: no amplification via queries, no cache-poisoning surface + for clients. +* `allow-query { none; }` globally; each zone opens itself explicitly — + deny by default. +* `allow-transfer { none; }` — no zone walks; name secondaries explicitly + per zone when you have them. +* `listen-on` bound to explicit addresses — never `any`. +* `version "none"` — no banner disclosure. +* `rate-limit` — response rate limiting against floods. +* Reserved example zones only (`.invalid`, documentation IP ranges). +* No keys, no TSIG secrets, no journals, no caches, no PID files — runtime + state lives outside the repository, always. + +== Bring-up is MANUAL, by an operator + +The bundle never starts a daemon, and minting a repository never starts +one. To exercise this configuration deliberately: + +[source,bash] +---- +# syntax + zone validation first — both must pass: +named-checkconf dns/bind9/named.conf.example +named-checkzone example.invalid dns/records/example.invalid.zone + +# foreground bring-up on a test host, with paths adjusted to that host: +sudo named -c /etc/bind/named.conf -g -u bind +---- + +Productionisation (systemd unit, working-directory ownership, AppArmor +profile, log rotation) is an ops decision recorded in your deployment docs — +not something a repository template should silently provide. + +== Roles are separated on purpose + +Authoritative, recursive/stub, ODoH-relay and ODoH-target are different +jobs with different threat models; mixing them in one daemon is how open +resolvers happen. See `../privacy/ENCRYPTED-DNS.adoc` for the role map and +why this bundle ships exactly one of them. + +== Safety check + +`www/tests/check-bind-safety.sh` asserts the posture above on every +`named.conf*` in this directory, rejects key/secret/journal material +anywhere under `www/dns/`, runs `named-checkconf`/`named-checkzone` when +the binaries are available, and proves itself against the planted +open-recursion control (`www/tests/controls/named.conf.open-recursion.control`). diff --git a/czech-file-knife/www/dns/bind9/named.conf.example b/czech-file-knife/www/dns/bind9/named.conf.example new file mode 100644 index 000000000..880cedbdc --- /dev/null +++ b/czech-file-knife/www/dns/bind9/named.conf.example @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// named.conf.example — authoritative-only BIND 9 starting hand (issue #53, +// profile: authoritative-dns). Reserved names only: RFC 2606 .invalid, +// RFC 5737 192.0.2.0/24, RFC 3849 2001:db8::/32. +// +// HARD RULES for this bundle: +// * never started automatically — see README.adoc for manual bring-up; +// * no keys, no TSIG secrets, no live zones, no journals/caches/PIDs in git; +// * no recursion, no open resolver: this server answers for its zones only; +// * recursive/stub, ODoH-relay and ODoH-target roles are SEPARATE profiles +// and are not shipped here (see ../privacy/ENCRYPTED-DNS.adoc). + +options { + // Working and runtime paths live OUTSIDE the repository. + directory "/var/cache/bind"; + pid-file "/run/named/named.pid"; + + // ── Authoritative-only posture ──────────────────────────────────────── + recursion no; // do not resolve for clients, ever + allow-recursion { none; }; // belt and braces with recursion no + allow-query { none; }; // global default: deny; zones open themselves + allow-transfer { none; }; // no zone transfers unless a zone names secondaries + + // ── Minimal exposure ────────────────────────────────────────────────── + // Bind the explicit service addresses; do NOT use 'any'. + listen-on port 53 { 192.0.2.1; }; + listen-on-v6 port 53 { 2001:db8::1; }; + + // Authoritative servers answer from zone data; they do not validate as + // resolvers. Version disclosure off. + dnssec-validation no; + version "none"; + + // Response rate limiting: blunts amplification and flood abuse. + rate-limit { + responses-per-second 10; + errors-per-second 5; + }; +}; + +// ── DNSSEC (guidance; enable deliberately) ───────────────────────────────── +// Sign with an inline dnssec-policy. Key material is generated by named into +// the working directory (or an HSM/KMS in production) and NEVER committed: +// no KSK/ZSK, no .private/.key files, no signing journals in the repository. +// +// dnssec-policy "rsr-standard" { +// keys { +// ksk lifetime P1Y algorithm ecdsa-p256-sha256; +// zsk lifetime P30D algorithm ecdsa-p256-sha256; +// }; +// publish-safety P1H; +// retire-safety P1H; +// signatures-refresh P7D; +// }; +// +// Then add to the zone: dnssec-policy "rsr-standard"; + +zone "example.invalid" { + type primary; + file "zones/example.invalid.zone"; // deploy copies dns/records/*.zone here + allow-query { any; }; // public authoritative answers for THIS zone + allow-transfer { none; }; // add explicit secondary IPs when you have them +}; + +// Reverse zone for the documentation range (template symmetry; adjust or drop). +zone "2.0.192.in-addr.arpa" { + type primary; + file "zones/2.0.192.in-addr.arpa.zone"; + allow-query { any; }; + allow-transfer { none; }; +}; diff --git a/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc b/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc new file mode 100644 index 000000000..2d9dd9e64 --- /dev/null +++ b/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Encrypted DNS and Privacy Discovery — Guidance, Not Promise + +This bundle ships the *authoritative* role only. This document exists so +nobody confuses the roles, overclaims obliviousness, or promises universal +support for protocols that require the other side to cooperate. + +== The role map + +[cols="1,2,2", options="header"] +|=== +| Role | Job | In this bundle? + +| Authoritative server +| Answers for zones it is primary/secondary for (BIND example in + `../bind9/`). +| *Yes* — the only role shipped. + +| Recursive / stub resolver +| Resolves on behalf of clients; the role that DoT/DoQ/DoH *clients* talk + to. +| No — separate profile, separate threat model. + +| DoH/DoT/DoQ server +| Serves recursive answers over an encrypted transport. +| No. + +| ODoH relay +| Forwards oblivious HTTP requests; knows client, not query. +| No. + +| ODoH target +| Answers oblivious HTTP requests; knows query, not client. +| No. +|=== + +== Protocol facts (so the docs can't drift into marketing) + +[cols="1,1,2", options="header"] +|=== +| Protocol | Transport | Notes + +| DoT (RFC 7858) +| TLS over TCP/853 +| Direct: the resolver sees the client. Not oblivious. + +| DoQ (RFC 9250) +| QUIC over UDP/853 +| Direct: same visibility as DoT. Not oblivious. + +| DoH (RFC 8484) +| HTTP(S) over TCP/443 +| Direct: resolver sees client; indistinguishable from ordinary HTTPS to + observers. Not oblivious. + +| ODoH (RFC 9230) +| Oblivious HTTP (RFC 9420): relay + target +| Relay sees client, target sees query; neither sees both. This is the + *only* oblivious mode in this list. + +| ECH (RFC 9460 discovery via HTTPS RR) +| TLS extension +| Hides the SNI/origin name from the network. Requires client + server + + HTTPS RR support; document *capability detection*, never assume it. +|=== + +*There is no "ODoT" and no "ODoQ".* Obliviousness comes from OHTTP wrapping +(ODoH); direct DoT and DoQ modes remain **non-oblivious** and must be +described that way wherever this estate documents them. Where HTTP/3 is +appropriate, ODoH over OHTTP/HTTP3 is the standard path — do not invent +oblivious variants of other transports. + +== Capability detection (document, don't promise) + +Before claiming any encrypted-DNS feature for a host, probe it: + +[source,bash] +---- +# ECH + DoH advertisement via the HTTPS RR (RFC 9460): +dig +short TYPE65 example.invalid # or: dig +https +# DoH reachability of a resolver (RFC 8484 well-known path): +curl -sS -H 'accept: application/dns-json' \ + 'https://resolver.example.invalid/dns-query?name=example.invalid' +# DoT reachability (TCP/853 handshake): +kdig +tls-ca -t example.invalid @resolver.example.invalid # or openssl s_client -connect host:853 +---- + +A capability that is not detected is not supported; publish what the probes +returned, dated, rather than the protocol list you wish were true. + +== What minting does + +Nothing. No daemon starts, no resolver is configured, no declaration about +encrypted DNS is published. The `privacy-enhanced` profile +(`../../profiles/`) selects *this guidance* plus the TLS policy and the ECH +readiness checklist — and even it enables no service by itself. diff --git a/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone b/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone new file mode 100644 index 000000000..a8f24bf16 --- /dev/null +++ b/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone @@ -0,0 +1,14 @@ +; SPDX-License-Identifier: MPL-2.0 +; +; Reverse zone for 192.0.2.0/24 (RFC 5737 documentation range) — template +; symmetry with the forward zone. Bump the serial on every edit. +$TTL 3600 +$ORIGIN 2.0.192.in-addr.arpa. +@ IN SOA ns1.example.invalid. hostmaster.example.invalid. ( + 2026091701 3600 900 604800 300 ) + IN NS ns1.example.invalid. + IN NS ns2.example.invalid. + +1 IN PTR ns1.example.invalid. +2 IN PTR ns2.example.invalid. +10 IN PTR example.invalid. diff --git a/czech-file-knife/www/dns/records/README.adoc b/czech-file-knife/www/dns/records/README.adoc new file mode 100644 index 000000000..5b6fe1f14 --- /dev/null +++ b/czech-file-knife/www/dns/records/README.adoc @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `dns/records/` — Zone and Resource-Record Templates + +Templates for the authoritative-only profile. Everything here uses reserved +names and documentation address ranges: `.invalid` (RFC 2606), +`192.0.2.0/24` (RFC 5737), `2001:db8::/32` (RFC 3849). Nothing in this +directory may contain a live zone, a live journal, or key material. + +== Files + +[cols="1,2", options="header"] +|=== +| File | Purpose + +| `example.invalid.zone` +| Forward zone: SOA/NS glue, A/AAAA, CNAME, CAA (RFC 8659), SPF `-all`, + MTA-STS/TLSRPT declarations for a mail-less domain. + +| `2.0.192.in-addr.arpa.zone` +| Matching reverse zone for the documentation range. +|=== + +== Discipline + +Serials:: `YYYYMMDDnn`, bumped on *every* edit — secondaries and signers key +off it (`runbooks/dns-change.adoc`). +CAA:: Set `issue`/`issuewild` to your actual CA(s); `0 issue ";"` forbids all +issuance. Keep `iodef` pointed at the security contact. +DNSSEC:: Sign via the inline `dnssec-policy` sketched in +`../bind9/named.conf.example`. Keys are generated by named into its working +directory (or an HSM/KMS in production): *never* commit KSK/ZSK material, +`.private`/`.key` files, or `.jnl` journals. The safety check +(`www/tests/check-bind-safety.sh`) rejects them. +Validation:: `named-checkzone example.invalid example.invalid.zone` must +pass before any deployment; the bundle's safety check runs it when the +binary is available. +== What is NOT here + +Recursive/stub resolver configuration, ODoH relay/target configuration, and +DoT/DoQ/DoH *server* configuration are separate roles with separate threat +models — see `../privacy/ENCRYPTED-DNS.adoc`. This bundle ships the +authoritative role only, and minting never starts a daemon. diff --git a/czech-file-knife/www/dns/records/example.invalid.zone b/czech-file-knife/www/dns/records/example.invalid.zone new file mode 100644 index 000000000..023d1ac4a --- /dev/null +++ b/czech-file-knife/www/dns/records/example.invalid.zone @@ -0,0 +1,38 @@ +; SPDX-License-Identifier: MPL-2.0 +; +; example.invalid.zone — forward-zone template (RFC 2606 reserved name). +; Addresses are RFC 5737 / RFC 3849 documentation ranges. Substitute your +; real apex, hosts and CA before deployment; bump the serial on EVERY edit +; (runbooks/dns-change.adoc). +$TTL 3600 +$ORIGIN example.invalid. +@ IN SOA ns1.example.invalid. hostmaster.example.invalid. ( + 2026091701 ; serial YYYYMMDDnn — bump per change + 3600 ; refresh + 900 ; retry + 604800 ; expire + 300 ) ; minimum (negative-cache TTL) + + IN NS ns1.example.invalid. + IN NS ns2.example.invalid. + +ns1 IN A 192.0.2.1 +ns1 IN AAAA 2001:db8::1 +ns2 IN A 192.0.2.2 +ns2 IN AAAA 2001:db8::2 + +@ IN A 192.0.2.10 +@ IN AAAA 2001:db8::10 +www IN CNAME example.invalid. + +; CAA (RFC 8659): which CAs may issue for this name. Placeholder CA — set to +; your actual issuer(s), or 'issue ";"' to forbid issuance entirely. +@ IN CAA 0 issue "ca.example.invalid" +@ IN CAA 0 issuewild "ca.example.invalid" +@ IN CAA 0 iodef "mailto:j.d.a.jewell@open.ac.uk" + +; This zone sends no mail: hard-fail SPF, and declare MTA-STS/TLSRPT so +; senders to a spoofed address get a correct rejection signal (RFC 8461/8460). +@ IN TXT "v=spf1 -all" +_mta-sts IN TXT "v=STSv1; id=20260917000000Z;" +_smtp._tls IN TXT "v=TLSRPTv1; rua=mailto:j.d.a.jewell@open.ac.uk" diff --git a/czech-file-knife/www/errors/403.html b/czech-file-knife/www/errors/403.html new file mode 100644 index 000000000..a8f4b0b52 --- /dev/null +++ b/czech-file-knife/www/errors/403.html @@ -0,0 +1,15 @@ + + + + + + + + 403 Forbidden + + +

403 — Forbidden

+

The server understood the request and refuses to authorise it. No further detail is disclosed by design.

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/errors/404.html b/czech-file-knife/www/errors/404.html new file mode 100644 index 000000000..d28eef1b9 --- /dev/null +++ b/czech-file-knife/www/errors/404.html @@ -0,0 +1,15 @@ + + + + + + + + 404 Not Found + + +

404 — Not Found

+

Nothing is published at this path. If you followed a link from this site, the page may have moved.

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/errors/429.html b/czech-file-knife/www/errors/429.html new file mode 100644 index 000000000..8f3025b42 --- /dev/null +++ b/czech-file-knife/www/errors/429.html @@ -0,0 +1,15 @@ + + + + + + + + 429 Too Many Requests + + +

429 — Too Many Requests

+

Rate limiting is in effect. Back off and retry later; automated clients should honour Retry-After when present.

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/errors/500.html b/czech-file-knife/www/errors/500.html new file mode 100644 index 000000000..9633e0e66 --- /dev/null +++ b/czech-file-knife/www/errors/500.html @@ -0,0 +1,15 @@ + + + + + + + + 500 Internal Server Error + + +

500 — Internal Server Error

+

The request could not be completed. Operators are paged via the runbook; no internals are disclosed here.

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/errors/502.html b/czech-file-knife/www/errors/502.html new file mode 100644 index 000000000..a040d5cd3 --- /dev/null +++ b/czech-file-knife/www/errors/502.html @@ -0,0 +1,15 @@ + + + + + + + + 502 Bad Gateway + + +

502 — Bad Gateway

+

An upstream component returned an invalid response. Retry shortly; persistent failures are an incident (see runbooks/incident.adoc in the source bundle).

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/errors/503.html b/czech-file-knife/www/errors/503.html new file mode 100644 index 000000000..7d008cbd8 --- /dev/null +++ b/czech-file-knife/www/errors/503.html @@ -0,0 +1,15 @@ + + + + + + + + 503 Service Unavailable + + +

503 — Service Unavailable

+

The service is temporarily unavailable, usually for planned maintenance or overload protection. Retry later.

+

Security reports for this site: /.well-known/security.txt.

+ + diff --git a/czech-file-knife/www/policies/acceptable-use.adoc b/czech-file-knife/www/policies/acceptable-use.adoc new file mode 100644 index 000000000..224bf7eba --- /dev/null +++ b/czech-file-knife/www/policies/acceptable-use.adoc @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Acceptable Use — Czech File Knife + +Rendered into `public/policies/acceptable-use.html` at deploy time; this +AsciiDoc file is the source of record. + +== Using this site + +This service is provided for reading published project material. When using +it, do not: + +* attempt to access non-public or operational material (DNS configuration, + TLS material, headers sources, profiles, schemas, tests or runbooks are + deliberately outside the published surface and attempts to reach them are + logged); +* probe, scan or fuzz the service beyond ordinary browsing without prior + written permission (coordinated disclosure is welcome via + link:/.well-known/security.txt[security.txt]); +* exceed rate limits or interfere with the availability of the service for + others; +* republish substantial portions of the site in violation of the project + licences (MPL-2.0 code, CC-BY-SA-4.0 documentation). + +== Enforcement + +Abuse is handled proportionally: rate limiting first, then temporary or +permanent blocking at the edge. Decisions are recorded in the operations +log and can be appealed to the maintainer contact in +link:/.well-known/humans.txt[humans.txt]. diff --git a/czech-file-knife/www/policies/ai-use.adoc b/czech-file-knife/www/policies/ai-use.adoc new file mode 100644 index 000000000..e31f3117b --- /dev/null +++ b/czech-file-knife/www/policies/ai-use.adoc @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += AI Use Policy — Czech File Knife + +Rendered into `public/policies/ai-use.html` at deploy time; this AsciiDoc +file is the source of record. + +== Machine-readable declarations + +This site publishes two machine-readable declarations. They are +*declarations, not enforcement*: neither, by itself, causes this origin to +refuse any request. + +link:/.well-known/ai.txt[ai.txt]:: Plain-text AI interaction policy +(User-Agent scoped training / summarisation / generation stances). +link:/.well-known/aibdp.json[aibdp.json]:: **Experimental.** An AIBDP 0.2 +declaration (`draft-jewell-aibdp-00`, not a Datatracker submission; a +mapped alternative representation of AIPREF where they overlap). It is +published *observe/declaration-only*. HTTP 430 (Consent Required, +`draft-jewell-http-430-consent-required-00`) is **not** emitted by this +site or by any repository minted from this template; enforcement would +require a separate, explicit, versioned and independently testable profile +choice that this bundle does not contain. + +== Interpretation limits + +Purpose headers carried by AI clients are *unverified assertions*. Inference +from User-Agent strings is *heuristic*. Where a purpose is unknown, it +remains unknown — it is not treated as consent. + +== Human terms + +The prose stance for this project's corpus is the one recorded in `ai.txt` +and the repository licences (MPL-2.0 code, CC-BY-SA-4.0 documentation): +reading and analysis to assist contributors is permitted; model training on +this corpus without explicit consent is not. diff --git a/czech-file-knife/www/policies/privacy.adoc b/czech-file-knife/www/policies/privacy.adoc new file mode 100644 index 000000000..0a677d463 --- /dev/null +++ b/czech-file-knife/www/policies/privacy.adoc @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Privacy Policy — Czech File Knife + +Rendered into `public/policies/privacy.html` at deploy time (see +`www/runbooks/deploy.adoc`); this AsciiDoc file is the source of record. + +== Data this site processes + +This is a static site served from published content. It does not require +accounts and does not run server-side application logic beyond ordinary +request handling. + +Access logs:: Requests are logged by the web server (IP address, timestamp, +path, user agent) for security and operations. Logs are retained no longer +than 90 days and are never sold or shared beyond legal obligation. +Cookies:: None are set by the site itself. No advertising, tracking or +third-party analytics are embedded. +Contact data:: Anything you send to the addresses in +link:/.well-known/security.txt[security.txt] or the maintainer contact in +link:/.well-known/humans.txt[humans.txt] is used solely to respond to you. + +== Your rights + +Under UK GDPR / EU GDPR you may request access to, correction of, or erasure +of personal data this site holds about you. Contact the maintainer via the +addresses above; there is no separate data-protection officer for a static +project site. + +== Changes + +Material changes to this policy are recorded in the repository's +`CHANGELOG.adoc` with a dated entry. diff --git a/czech-file-knife/www/profiles/README.adoc b/czech-file-knife/www/profiles/README.adoc new file mode 100644 index 000000000..a8b97aa3a --- /dev/null +++ b/czech-file-knife/www/profiles/README.adoc @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `profiles/` — Explicit Opt-In Composition + +Profiles are how a repository selects parts of this bundle. Selection is +always explicit; **minting a repository enables nothing** — no daemon +starts, no experimental declaration is published, no HTTP 430 enforcement +exists to enable. + +[cols="1,1,2", options="header"] +|=== +| Profile | Status | Adds + +| `baseline-site` +| stable +| Headers, `.well-known` trio, safe errors, publishable content, boundary + validation, deploy/rollback runbooks. + +| `consent-aware-web` +| experimental +| AIBDP 0.2 declaration + AI-use policy page. *Declaration/observe-only:* + `enforcement_http_430 = false` is a hard default; enforcement would + require a separate explicit versioned profile that this bundle does not + ship. + +| `authoritative-dns` +| stable +| BIND 9 authoritative-only hand, zone/RR templates, DNSSEC/CAA guidance, + safety checks, DNS change runbook. `auto_start_daemon = false`. + +| `privacy-enhanced` +| experimental +| Encrypted-DNS/ECH guidance under a *capability-detection-required* rule, + TLS policy, certificate rotation runbook. Claims no ODoT/ODoQ (they do + not exist); ODoH is the only oblivious mode. + +| `full-expert` +| stable-composition +| `includes` the four above — explicitly listed, independently removable. + Composition never escalates flags: 430 stays false, daemons stay off. +|=== + +== Format + +Deliberately a flat, greppable TOML subset (single-line arrays, scalar +flags) so `www/tests/check-profiles.sh` — and the estate propagation +mechanism — can validate profiles without a TOML runtime. Keys the checks +enforce: + +* every profile declares `profile`, `version`, `status`; +* `consent-aware-web` and `full-expert` must carry + `enforcement_http_430 = false`; +* `authoritative-dns` and `full-expert` must carry + `auto_start_daemon = false`; +* `full-expert.includes` must name exactly the other four profiles; +* every path in `components` must exist in the bundle; +* `requires`/`includes` must resolve to sibling profile files. + +The planted controls (`www/tests/controls/profile-enforce-430.control.toml`, +`profile-hidden-start.control.toml`) violate the flag rules and must be +rejected — proof the defaults cannot silently flip. + +== Applying a profile + +Selection means: keep the listed `components` active in your deployment +pipeline (stage them, run their checks, follow their runbooks) and delete or +ignore the rest. Repositories without a website may retain the whole bundle +as inactive template material or decline the capability outright; nothing +here forces a deployment (issue #53, migration rule 5). diff --git a/czech-file-knife/www/profiles/authoritative-dns.toml b/czech-file-knife/www/profiles/authoritative-dns.toml new file mode 100644 index 000000000..63e1d08fa --- /dev/null +++ b/czech-file-knife/www/profiles/authoritative-dns.toml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# authoritative-dns — issue #53 initial profile. +# BIND 9 authoritative configuration, zone/record templates, DNSSEC/CAA +# guidance and the safety checks. NO open recursion and NO automatically +# started daemon: bring-up is a manual operator act (dns/bind9/README.adoc). +# Recursive/stub, ODoH-relay and ODoH-target roles are separate profiles +# and are not shipped here. +profile = "authoritative-dns" +version = "0.1.0" +status = "stable" +requires = [] +auto_start_daemon = false +recursion = "disabled" +components = ["www/dns/bind9/", "www/dns/records/", "www/tests/check-bind-safety.sh", "www/tests/controls/named.conf.open-recursion.control", "www/runbooks/dns-change.adoc"] diff --git a/czech-file-knife/www/profiles/baseline-site.toml b/czech-file-knife/www/profiles/baseline-site.toml new file mode 100644 index 000000000..9024a1e4d --- /dev/null +++ b/czech-file-knife/www/profiles/baseline-site.toml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: MPL-2.0 +# baseline-site — issue #53 initial profile. +# Security headers, the .well-known trio, safe error bodies, publishable +# content and the boundary validation. No service is started by selecting +# this profile; it composes SOURCE material plus the deploy runbook. +profile = "baseline-site" +version = "0.1.0" +status = "stable" +requires = [] +components = ["www/security_headers/csp.conf", "www/.well-known/security.txt", "www/.well-known/humans.txt", "www/.well-known/ai.txt", "www/public/", "www/errors/", "www/policies/privacy.adoc", "www/policies/acceptable-use.adoc", "www/schemas/publishable-paths.txt", "www/webservers/", "www/tests/", "www/runbooks/deploy.adoc", "www/runbooks/rollback.adoc"] diff --git a/czech-file-knife/www/profiles/consent-aware-web.toml b/czech-file-knife/www/profiles/consent-aware-web.toml new file mode 100644 index 000000000..b4a6743fc --- /dev/null +++ b/czech-file-knife/www/profiles/consent-aware-web.toml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: MPL-2.0 +# consent-aware-web — issue #53 initial profile. EXPERIMENTAL material: +# AIBDP 0.2 implements draft-jewell-aibdp-00 (not a Datatracker submission; +# a mapped alternative representation of AIPREF where they overlap). +# +# Declaration/observe-only BY DEFAULT. Selecting this profile publishes the +# declaration and the human policy page; it enforces nothing. HTTP 430 +# (draft-jewell-http-430-consent-required-00) is NEVER emitted by minting, +# installing or selecting this profile — enforcement would require a +# separate, explicit, versioned, provenance-bearing and independently +# testable profile, which this bundle deliberately does not ship. +profile = "consent-aware-web" +version = "0.1.0" +status = "experimental" +requires = ["baseline-site"] +mode = "declaration-only" +enforcement_http_430 = false +components = ["www/.well-known/aibdp.json", "www/schemas/aibdp-schema-v0.2.json", "www/policies/ai-use.adoc"] diff --git a/czech-file-knife/www/profiles/full-expert.toml b/czech-file-knife/www/profiles/full-expert.toml new file mode 100644 index 000000000..d752e08f1 --- /dev/null +++ b/czech-file-knife/www/profiles/full-expert.toml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MPL-2.0 +# full-expert — issue #53 initial profile. Composes the other four through +# EXPLICIT selection only: no hidden enablement. Composition adds +# components; it NEVER escalates flags — enforcement_http_430 stays false +# and auto_start_daemon stays false here exactly as in the profiles being +# composed. Disabling any included profile is a first-class operation +# (delete it from `includes`); nothing else changes. +profile = "full-expert" +version = "0.1.0" +status = "stable-composition" +includes = ["baseline-site", "consent-aware-web", "authoritative-dns", "privacy-enhanced"] +hidden_enablement = "forbidden" +enforcement_http_430 = false +auto_start_daemon = false +components = [] diff --git a/czech-file-knife/www/profiles/privacy-enhanced.toml b/czech-file-knife/www/profiles/privacy-enhanced.toml new file mode 100644 index 000000000..6073e2100 --- /dev/null +++ b/czech-file-knife/www/profiles/privacy-enhanced.toml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MPL-2.0 +# privacy-enhanced — issue #53 initial profile. +# ECH and encrypted-DNS discovery/configuration guidance WHERE ACTUALLY +# SUPPORTED: capability_detection_required means every claim must be backed +# by a dated probe result (dns/privacy/ENCRYPTED-DNS.adoc), never by the +# protocol wish-list. Direct DoT/DoQ remain non-oblivious; ODoH (OHTTP) is +# the only oblivious mode — there is no ODoT/ODoQ and none is claimed. +profile = "privacy-enhanced" +version = "0.1.0" +status = "experimental" +requires = ["baseline-site"] +capability_detection_required = true +claims_oblivious_dot_or_doq = false +components = ["www/dns/privacy/", "www/tls/POLICY.adoc", "www/runbooks/cert-rotation.adoc"] diff --git a/czech-file-knife/www/public/index.html b/czech-file-knife/www/public/index.html new file mode 100644 index 000000000..1a1d7178d --- /dev/null +++ b/czech-file-knife/www/public/index.html @@ -0,0 +1,37 @@ + + + + + + + Czech File Knife + + + + +
+

Czech File Knife

+

The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage.

+
+
+

This site is served from the www/public/ category of the + repository's site-operations bundle. Operational material (DNS, TLS, + headers, profiles, tests, runbooks) is deliberately not part of the + published surface.

+ +
+
+

© 2026 Jonathan D.A. Jewell. Code: MPL-2.0. Docs: CC-BY-SA-4.0.

+
+ + diff --git a/czech-file-knife/www/public/styles/site.css b/czech-file-knife/www/public/styles/site.css new file mode 100644 index 000000000..5f187e488 --- /dev/null +++ b/czech-file-knife/www/public/styles/site.css @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: CC-BY-SA-4.0 */ +/* Minimal, dependency-free site styling. No external fonts or assets. */ +:root { color-scheme: light dark; } +body { font-family: system-ui, sans-serif; margin: 0 auto; max-width: 42rem; + padding: 1.5rem; line-height: 1.55; } +header h1 { margin-bottom: .25rem; } +nav ul { padding-left: 1.2rem; } +footer { margin-top: 3rem; border-top: 1px solid currentColor; padding-top: .75rem; + font-size: .85rem; opacity: .8; } +code { font-family: ui-monospace, monospace; } diff --git a/czech-file-knife/www/runbooks/cert-rotation.adoc b/czech-file-knife/www/runbooks/cert-rotation.adoc new file mode 100644 index 000000000..f791f2eff --- /dev/null +++ b/czech-file-knife/www/runbooks/cert-rotation.adoc @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Runbook: Certificate Rotation (Normal and Emergency) + +Keys never touch the repository (see `../tls/POLICY.adoc`). Everything here +happens on the serving host and in the ACME client's spool. + +== Normal rotation (ACME, automated) + +. Confirm automation is alive: `certbot certificates` (or Caddy's + `caddy list-certs` / data-dir listing) shows the live certificate with + > 30 days remaining. +. Renewal is timer-driven; force a dry run monthly: + `certbot renew --dry-run`. +. After any renewal: reload the server, then verify from outside: ++ +[source,bash] +---- +openssl s_client -connect site.example.invalid:443 -servername site.example.invalid /dev/null | openssl x509 -noout -dates -issuer +---- + +== Emergency replacement (compromise or mis-issuance) + +. *Stop the bleeding*: if the private key is compromised, treat every + session since the last rotation as decryptable. +. *Issue first, revoke second* (avoid downtime): obtain a replacement via + ACME (`certbot certonly --force-renewal -d site.example.invalid ...`), + install, reload, verify from outside as above. +. *Revoke* the compromised certificate with the CA (`certbot revoke + --cert-path ...`); note the CRL/OCSP propagation delay. +. *CT consequence*: both the compromised and replacement certificates are + public in CT logs forever — the record cannot be scrubbed, which is why + rotation speed matters more than discretion. +. *Rotate anything derived*: OCSP stapling caches, pinned fingerprints in + clients/monitoring, and any TSIG/DNSSEC material touched by the same + host compromise (`runbooks/dns-change.adoc`, estate secret-scanner + procedure for history purge). +. *Record*: dated entry in `CHANGELOG.adoc` + incident notes (what was + exposed, for how long, what was rotated). + +== Expiry monitoring + +Alert at 21 days remaining on every served name. An expired certificate is +a self-inflicted outage and, for `security.txt`-contactable projects, an +availability incident worth a postmortem. diff --git a/czech-file-knife/www/runbooks/deploy.adoc b/czech-file-knife/www/runbooks/deploy.adoc new file mode 100644 index 000000000..e9b40d127 --- /dev/null +++ b/czech-file-knife/www/runbooks/deploy.adoc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Runbook: Deploy the Site from `www/` + +Purpose: turn the source bundle into a *staged* document root that satisfies +the publication boundary, validate it, then publish atomically. Never point +a web server at `www/` itself. + +== Procedure + +. *Stage* (on the deploy host, from a checkout of the repository): ++ +[source,bash] +---- +STAGE=/srv/staging/site.example.invalid-$(date +%Y%m%d%H%M%S) +mkdir -p "$STAGE/.well-known" "$STAGE/errors" "$STAGE/policies" +cp -r www/public/. "$STAGE/" +cp -r www/.well-known/. "$STAGE/.well-known/" +cp -r www/errors/. "$STAGE/errors/" +for f in www/policies/*.adoc; do + asciidoctor -o "$STAGE/policies/$(basename "$f" .adoc).html" "$f" +done +---- ++ +Minted repositories already have their `{{TOKEN}}` placeholders substituted; +if deploying from an unminted template, substitute them first +(`.machine_readable/ai/PLACEHOLDERS.adoc`). + +. *Validate the boundary* (must exit 0): ++ +[source,bash] +---- +bash www/tests/check-publication-boundary.sh --stage "$STAGE" +---- + +. *Publish atomically* (generational swap keeps rollback trivial): ++ +[source,bash] +---- +ln -sfn "$STAGE" /srv/site.example.invalid # servers point at the symlink +# then reload: systemctl reload nginx | caddy reload | systemctl reload apache2 +---- + +. *Smoke-check*: `curl -sS https://site.example.invalid/` returns the index; +`/.well-known/security.txt` resolves; `/dns/`, `/tls/`, `/tests/` return 404. + +== Stop conditions + +* Step 2 non-zero: **do not publish.** The stage contains operational + material; find it (the validator names every violation) and rebuild the + stage. +* `asciidoctor` missing or failing: publish without `policies/` rather than + publishing the `.adoc` sources — they are source material, not content. + +== What never enters a stage + +`dns/`, `tls/`, `security_headers/`, `webservers/`, `profiles/`, `schemas/`, +`tests/`, `runbooks/`, the `policies/` *sources*, any `*.key`, `*.pem`, +`*.zone`, `named.conf*`, TSIG material, journals, caches, PID files, logs, +`*.control` planted-control files. The validator enforces exactly this list. diff --git a/czech-file-knife/www/runbooks/dns-change.adoc b/czech-file-knife/www/runbooks/dns-change.adoc new file mode 100644 index 000000000..300aebc4d --- /dev/null +++ b/czech-file-knife/www/runbooks/dns-change.adoc @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Runbook: Authoritative Zone Change + +For the `authoritative-dns` profile. Every change follows the same loop: +validate offline, deploy, verify from outside, keep the rollback artefact. + +== Procedure + +. *Edit the zone source* under `www/dns/records/` (never the deployed + copy), and **bump the serial** (`YYYYMMDDnn`). + +. *Validate before anything else*: ++ +[source,bash] +---- +named-checkzone example.invalid www/dns/records/example.invalid.zone +bash www/tests/check-bind-safety.sh +---- + +. *Commit* the zone change (zone sources are repository content; signed + key material and journals are not, ever). + +. *Deploy to the authoritative host* and reload: ++ +[source,bash] +---- +sudo cp www/dns/records/*.zone /var/cache/bind/zones/ +sudo rndc reload example.invalid # or: rndc retransfer example.invalid +---- ++ +With an inline `dnssec-policy`, named re-signs automatically; watch +`rndc status` and the signer's key-timeline output rather than touching +keys by hand. + +. *Verify from OUTSIDE the host* (authoritative answer, correct serial, + RRSIGs present if signed, CAA as intended): ++ +[source,bash] +---- +dig @ns1.example.invalid example.invalid SOA +short +dig @ns1.example.invalid example.invalid CAA +short +dig @ns1.example.invalid example.invalid DNSKEY +short # if signed +---- + +. *TTL discipline*: wait at least the zone's negative-cache TTL (and, for + removals, the old record's TTL) before considering a change complete. + For DNSSEC algorithm/key changes, follow the publish/retire safety + intervals in the policy — never delete old signatures early. + +== Rollback + +Restore the previous zone source (`git revert` or checkout of the prior +commit), bump the serial *again* (never reuse or lower a serial), reload, +and re-verify from outside. The committed history is the rollback store — +another reason zone sources live in git and runtime artefacts do not. + +== Stop conditions + +* `named-checkzone` fails: do not deploy. +* Serial not bumped: secondaries will not pick the change up — fix before + reload. +* Any key, journal, or TSIG secret found in the repository: treat as an + incident (`runbooks/incident.adoc`), rotate, purge history per the + secret-scanner procedure. diff --git a/czech-file-knife/www/runbooks/rollback.adoc b/czech-file-knife/www/runbooks/rollback.adoc new file mode 100644 index 000000000..373dfbd99 --- /dev/null +++ b/czech-file-knife/www/runbooks/rollback.adoc @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Runbook: Roll Back a Site Deployment + +The deploy runbook publishes through a generational symlink +(`/srv/site.example.invalid -> /srv/staging/site.example.invalid-`), so +rollback is re-pointing the symlink at the previous generation. No rebuild, +no redeploy, no data loss. + +== Procedure + +. *Identify the previous good generation*: ++ +[source,bash] +---- +ls -1dt /srv/staging/site.example.invalid-* | sed -n 2p +---- + +. *Re-point and reload*: ++ +[source,bash] +---- +ln -sfn "$PREV" /srv/site.example.invalid +systemctl reload nginx # or: caddy reload / systemctl reload apache2 +---- + +. *Verify*: index, `/.well-known/security.txt`, and the 404 body all serve; +the failure that prompted the rollback is no longer observable. + +. *Preserve evidence*: do not delete the rejected generation until the +incident is closed — it is the artefact that failed validation or smoke +checks. + +. *Record*: one dated line in the repository `CHANGELOG.adoc` (deployed / +rolled-back generations and reason). + +== When rollback is NOT the answer + +* Compromised TLS material: follow `runbooks/cert-rotation.adoc` and the + incident runbook — rolling the site back does not rotate keys. +* Boundary violation discovered in a *live* docroot: take the operational + paths offline first (the server configs already deny them), then roll + back, then fix the stage pipeline that let them through. diff --git a/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc b/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc new file mode 100644 index 000000000..99f4e8257 --- /dev/null +++ b/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Runbook: Sweep Root `.well-known/` into `www/` Across the Estate + +Purpose: propagate the canonical `www/.well-known/` arrangement (#53 stage 5, +tracked by #119) across the repositories that still carry a root +`.well-known/`, in batches, without hand-making 270 pull requests and without +losing a byte of divergent content. + +Read this before running anything. The sweep is *unattended but not +unsupervised*: it will not overwrite content, and it will not stay quiet about +anything it could not resolve. + +== Before you start + +. A token with `repo` scope (public-only estates need `public_repo`): ++ +[source,bash] +---- +export GITHUB_TOKEN=... +---- ++ +. A committer identity, because the sweep commits: ++ +[source,bash] +---- +git var GIT_COMMITTER_IDENT # must succeed; the driver refuses to start otherwise +---- ++ +. The repository list. `stage5-repos.txt` in the #119 tracker holds the + enumerated denominator (270 repositories as of 2026-09-17). + +== Step 1 — classify (read-only, one API call per repository) + +[source,bash] +---- +bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --classify-only +---- + +This writes `classification.csv` and touches nothing else. Each repository +lands in one of three classes: + +`sweep`:: Identifiably RSR-templated (a root allowlist is present under either +canonical spelling) and not served from the repository root. Safe to sweep. ++ +`review-pages`:: Pages or a CDN serves the repository root, or the name is a +Pages site. For these a root `.well-known/` may be a *serving requirement* +rather than legacy layout, and moving it out of the served root can break live +discovery. #119 names `hyperpolymath.github.io` explicitly and asks for these +to be classified before sweeping, so the driver does not sweep them unless you +pass `--include-review`. ++ +`review-other`:: Carries a root `.well-known/` but shows no RSR marker, so the +arrangement is not known to be template-derived. Held back for the same reason. + +Resolve the `review-*` rows by hand first. That is a decision, not a batch +operation. + +== Step 2 — dry-run a batch + +[source,bash] +---- +bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --batch 1 --dry-run +---- + +Reports what would change, runs the suite, commits nothing. The per-repository +reports land in the work directory as `reports/.tsv`, one row per file, +with the action taken: `moved`, `deduped`, `quarantined`, `conflict`, `left`. + +== Step 3 — sweep the batch for real + +[source,bash] +---- +bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --batch 1 +---- + +Commits locally, on branch `chore/well-known-to-www` by default. It does *not* +push unless you pass `--push`; until you have reviewed a few batches, leave it +off and push by hand. + +Per repository the driver runs the four steps #119 asks for: classify, migrate, +run the suite (`www/tests/run-all.sh` where the repository carries the bundle), +commit. + +== How divergence is handled + +Content that exists at both the root and under `www/` and *differs* is never +overwritten. The root copy is moved aside to: + +[source,text] +---- +www/.legacy-well-known-/ +---- + +The `www/` copy is left untouched, both hashes are printed, and the file is +recorded in the repository's report. The batch continues — an unattended sweep +that halts on the first conflict is not a sweep — but the run *exits non-zero* +so the conflict cannot pass unnoticed. Suppress that with `--allow-conflicts` +only when you have already triaged the reports. + +Two historical notes worth knowing: + +* PR #106's commit message describes the quarantine directory as a *root-level* + `.legacy-well-known-YYYYMMDD/`. It is under `www/` instead, deliberately: + the root allowlist is checked bidirectionally and matches entries literally + with no glob support, so a dated root directory would report as root drift in + every repository the sweep touched. It is also not under `www/public/`, so + the publication boundary still holds. +* `--in-place` restores the pre-stage-5 contract (keep both copies where they + are, exit 1) for anyone who prefers to resolve divergence before the tree is + touched at all. + +Repositories WITHOUT a `www/` bundle are migrated but flagged *"no www/tests +bundle — run the RSR update mechanism first"*. Migration is only half the job +there: the canonical location has to exist before the suite can prove it. + +== Failure handling + +One repository failing never abandons the batch. Failures are recorded in +`sweep-results.csv` with the status `failed` and a path to the relevant log, +the run continues, and the final exit status reflects that something failed. + +The run exits non-zero when any repository failed, or when any content was +quarantined (unless `--allow-conflicts`). Read the summary table it prints: + +[source,text] +---- +--- results by status --- + 2 migrated + 1 quarantined + 1 failed +---- + +== Recovering + +* *A repository was swept and is wrong.* Nothing was pushed unless you asked + for it; the commit is local to the work directory clone. Delete the clone. +* *Content was quarantined.* Decide which copy is canonical, reconcile + `www/.well-known/`, then delete `www/.legacy-well-known-/`. The + file is committed, so nothing is lost before you decide. +* *The API rate limit is hit mid-run.* Re-run the same `--batch`; already-clean + repositories report `clean` and are skipped. + +== Verification + +The migrated tree must satisfy the bundle's own suite: + +[source,bash] +---- +bash www/tests/run-all.sh # 6 checks +bash www/tests/check-migration.sh # 9 scenarios, incl. all four divergence cases +---- + +CI drives the migrator in `--dry-run` on every push that touches +`.well-known/**` (`dot-wellknown-enforcement.yml`), so a repository still +carrying legacy layout reports the divergence as a warning long before the +sweep reaches it. diff --git a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json new file mode 100644 index 000000000..ffacdec3d --- /dev/null +++ b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json @@ -0,0 +1,377 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://consent-aware-web.org/schemas/aibdp-v0.2.json", + "$comment": "Vendored unmodified from metadatastician/consent-aware-web schemas/aibdp-schema-v0.2.json (MPL-2.0) on 2026-09-17 \u2014 see the .license sidecar. AIBDP 0.2 implements draft-jewell-aibdp-00 (EXPERIMENTAL; not a Datatracker submission) and is a mapped alternative representation of AIPREF where they overlap.", + "title": "AI Boundary Declaration Protocol (AIBDP) Manifest Schema", + "description": "JSON Schema for validating AIBDP manifests hosted at /.well-known/aibdp.json", + "type": "object", + "required": [ + "aibdp_version", + "contact", + "policies" + ], + "properties": { + "aibdp_version": { + "type": "string", + "description": "Protocol version number", + "pattern": "^\\d+\\.\\d+$", + "examples": [ + "0.1", + "0.2", + "1.0" + ] + }, + "canonical_uri": { + "type": "string", + "format": "uri", + "description": "Authoritative location of this manifest for cross-domain policies" + }, + "contact": { + "type": "string", + "description": "Contact URI for policy inquiries (mailto:, https:, etc.)", + "pattern": "^(mailto:|https?:).+", + "examples": [ + "mailto:policy@example.org", + "https://example.org/contact" + ] + }, + "expires": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when manifest should be re-fetched" + }, + "policy_uri": { + "type": "string", + "format": "uri", + "description": "Link to human-readable policy document" + }, + "policies": { + "type": "object", + "description": "AI usage policy declarations", + "properties": { + "training": { + "$ref": "#/$defs/policy" + }, + "indexing": { + "$ref": "#/$defs/policy" + }, + "summarization": { + "$ref": "#/$defs/policy" + }, + "question_answering": { + "$ref": "#/$defs/policy" + }, + "generation": { + "$ref": "#/$defs/policy" + }, + "fine_tuning": { + "$ref": "#/$defs/policy" + }, + "embedding": { + "$ref": "#/$defs/policy" + }, + "commercial_training": { + "$ref": "#/$defs/policy" + } + }, + "additionalProperties": { + "$ref": "#/$defs/policy" + }, + "minProperties": 1 + }, + "scope": { + "type": "object", + "description": "Describes what AI systems this manifest addresses", + "properties": { + "applies_to": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Types of AI systems this policy covers" + } + } + }, + "special_provisions": { + "type": "object", + "description": "Special provisions for specific use cases", + "properties": { + "academic_research": { + "$ref": "#/$defs/special_provision" + }, + "educational_use": { + "$ref": "#/$defs/special_provision" + }, + "standards_development": { + "$ref": "#/$defs/special_provision" + } + }, + "additionalProperties": { + "$ref": "#/$defs/special_provision" + } + }, + "enforcement": { + "type": "object", + "description": "Enforcement mechanism information", + "properties": { + "mechanism": { + "type": "string", + "enum": [ + "http_430", + "legal", + "reputational", + "technical", + "none" + ], + "description": "Primary enforcement mechanism" + }, + "note": { + "type": "string" + }, + "contact_before_litigation": { + "type": "boolean", + "description": "Whether to contact before legal action" + }, + "preferred_resolution": { + "type": "string", + "description": "Preferred approach to resolving violations" + } + } + }, + "metadata": { + "type": "object", + "description": "Manifest metadata", + "properties": { + "created": { + "type": "string", + "format": "date", + "description": "Creation date (YYYY-MM-DD)" + }, + "last_modified": { + "type": "string", + "format": "date", + "description": "Last modification date" + }, + "author": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "project": { + "type": "string" + }, + "repository": { + "type": "string", + "format": "uri" + }, + "related_standards": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "philosophy": { + "type": "object", + "description": "Philosophical framing and values", + "properties": { + "core_principle": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "quote": { + "type": "string" + } + } + }, + "signature": { + "type": "object", + "description": "COSE cryptographic signature for manifest verification", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "type": "string", + "enum": [ + "ES256", + "ES384", + "ES512", + "RS256", + "RS384", + "RS512", + "EdDSA" + ], + "description": "Signature algorithm (COSE)" + }, + "public_key_uri": { + "type": "string", + "format": "uri", + "description": "URI to public key (JWK format)" + }, + "value": { + "type": "string", + "description": "Base64-encoded COSE signature" + }, + "note": { + "type": "string" + } + } + } + }, + "additionalProperties": true, + "$defs": { + "policy": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "allowed", + "refused", + "conditional", + "encouraged" + ], + "description": "Permission status for this AI usage mode" + }, + "conditions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Requirements that must be met when status is 'conditional'" + }, + "scope": { + "oneOf": [ + { + "type": "string", + "const": "all" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Path patterns this policy applies to" + } + ] + }, + "exceptions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "status" + ], + "properties": { + "path": { + "type": "string", + "description": "Path pattern for exception" + }, + "status": { + "type": "string", + "enum": [ + "allowed", + "refused", + "conditional", + "encouraged" + ] + }, + "note": { + "type": "string" + }, + "conditions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "rationale": { + "type": "string", + "description": "Human-readable explanation of policy" + }, + "alternatives": { + "type": "string", + "description": "Suggested alternative approaches" + }, + "purpose": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific purposes this policy encourages (when status is 'encouraged')" + }, + "note": { + "type": "string", + "description": "Additional context or clarification" + } + }, + "additionalProperties": false + }, + "special_provision": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "unrestricted", + "encouraged", + "allowed", + "conditional", + "refused" + ] + }, + "note": { + "type": "string" + }, + "conditions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "examples": [ + { + "aibdp_version": "0.2", + "contact": "mailto:policy@example.org", + "policies": { + "training": { + "status": "conditional", + "conditions": [ + "Attribution required", + "Non-commercial use only" + ] + }, + "indexing": { + "status": "allowed", + "scope": "all" + }, + "generation": { + "status": "refused", + "rationale": "Content should not be synthetically replicated" + } + } + } + ] +} \ No newline at end of file diff --git a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license new file mode 100644 index 000000000..8b66169a0 --- /dev/null +++ b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/schemas/publishable-paths.txt b/czech-file-knife/www/schemas/publishable-paths.txt new file mode 100644 index 000000000..4820261c3 --- /dev/null +++ b/czech-file-knife/www/schemas/publishable-paths.txt @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# publishable-paths.txt — machine-readable declaration of the publication +# boundary (issue #53). Exactly the bundle subtrees listed below (one per +# line; '#' comments ignored) may reach a deployed document root: +# +# www/public/ ordinary publishable content, served at / +# www/.well-known/ public protocol metadata, served at /.well-known/ +# +# Staging rules (runbooks/deploy.adoc): +# * www/errors/ bodies are staged INTO /errors/ — they publish as +# public content, not as a third publishable source category; +# * www/policies/*.adoc sources are rendered INTO /policies/*.html; +# * everything else — dns/, tls/, security_headers/, webservers/, +# profiles/, schemas/, tests/, runbooks/ — is OPERATIONAL material and +# must never appear in a deployed document root. +# +# Consumed by: www/tests/check-publication-boundary.sh (CI + mint smoke test). +www/public/ +www/.well-known/ diff --git a/czech-file-knife/www/security_headers/README.adoc b/czech-file-knife/www/security_headers/README.adoc new file mode 100644 index 000000000..625361317 --- /dev/null +++ b/czech-file-knife/www/security_headers/README.adoc @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `security_headers/` — Header Sources (Not Served) + +`csp.conf` is the *source of record* for the site's security headers. +This directory is operational material: it must never be inside a deployed +document root (see `www/schemas/publishable-paths.txt` and +`www/tests/check-publication-boundary.sh`). + +== How each server consumes it + +Caddy:: Values are transcribed into the `header { … }` block of +`www/webservers/caddy/Caddyfile.example`. +nginx:: Convert each `Name: value` line into +`add_header Name "value" always;` inside the server block (the example +already carries the set), or generate a snippet and `include` it. +Apache:: Convert each line into `Header always set Name "value"` with +`mod_headers` enabled (the example already carries the set). + +Whatever the mechanism, the *values* come from `csp.conf`; when you change +one, change it there and re-sync the server example you deploy. + +== Policy notes + +* `style-src 'self'` (no `'unsafe-inline'`): the bundle's HTML uses an + external stylesheet only. If you add inline styles, you weaken CSP — + prefer a stylesheet. +* HSTS includes `preload`; only deploy that once the host is genuinely + HTTPS-everywhere and you accept the multi-month un-preload cycle. +* `Cross-Origin-Resource-Policy: same-origin` is correct for a project + site; relax it deliberately (e.g. `cross-origin` on an assets host) + rather than by accident. diff --git a/czech-file-knife/www/security_headers/csp.conf b/czech-file-knife/www/security_headers/csp.conf new file mode 100644 index 000000000..fc003621a --- /dev/null +++ b/czech-file-knife/www/security_headers/csp.conf @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# csp.conf — canonical security-header set for sites deployed from this +# bundle (source of record; the webservers/ examples embed these values and +# must be kept in sync with this file — see README.adoc in this directory). +# +# Form: one `Header-Name: value` per line, suitable for nginx include +# snippets (add_header), Apache mod_headers (Header always set) and manual +# transcription into Caddy header blocks. +# +# Deliberate omissions: +# X-XSS-Protection — deprecated; modern guidance is to not send it (it is +# an attack surface in legacy browsers). CSP frame-ancestors + XFO cover +# the same ground. +# Expect-CT — obsolete; CT is enforced by browsers via policy. +Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +Referrer-Policy: strict-origin-when-cross-origin +Permissions-Policy: geolocation=(), camera=(), microphone=(), browsing-topics=() +Cross-Origin-Opener-Policy: same-origin +Cross-Origin-Resource-Policy: same-origin diff --git a/czech-file-knife/www/tests/check-aibdp.sh b/czech-file-knife/www/tests/check-aibdp.sh new file mode 100755 index 000000000..9b321aa3e --- /dev/null +++ b/czech-file-knife/www/tests/check-aibdp.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-aibdp.sh — validate www/.well-known/aibdp.json (issue #53: +# "experimental files are labelled as such"; declaration-only by default). +# Requires jq; skips with a note when absent. + +set -uo pipefail +WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FAIL=0 +bad() { echo "AIBDP FAIL: $*" >&2; FAIL=1; } +ok() { echo "ok: $*"; } + +if ! command -v jq >/dev/null 2>&1; then + echo "note: jq unavailable — AIBDP checks skipped" + exit 0 +fi + +validate_aibdp() { # $1 = aibdp json; returns 1 on any violation + local f="$1" v=0 st + jq empty "$f" 2>/dev/null || { echo " not valid JSON" >&2; return 1; } + [ "$(jq -r '.aibdp_version // empty' "$f")" = "0.2" ] \ + || { echo " aibdp_version must be 0.2" >&2; v=1; } + jq -e '.contact | test("^(mailto:|https?:)")' "$f" >/dev/null \ + || { echo " contact must be a mailto:/http(s) URI" >&2; v=1; } + # Experimental labelling (declaration-only provenance must be visible). + st="$(jq -r '.status // empty' "$f")" + case "$st" in *experimental*declaration-only*) : ;; + *) echo " status must label the file experimental AND declaration-only" >&2; v=1 ;; esac + # Policies vocabulary + rationale discipline. + jq -e '.policies | type == "object" and length >= 1' "$f" >/dev/null \ + || { echo " policies must be a non-empty object" >&2; v=1; } + while IFS=$'\t' read -r name status; do + case "$status" in allowed|conditional|disallowed) : ;; + *) echo " policies.$name.status not in {allowed,conditional,disallowed}: $status" >&2; v=1 ;; esac + jq -e --arg n "$name" '.policies[$n].rationale | type == "string" and length > 0' "$f" >/dev/null \ + || { echo " policies.$name missing rationale" >&2; v=1; } + if [ "$status" = "conditional" ]; then + jq -e --arg n "$name" '.policies[$n].conditions | type == "array" and length >= 1' "$f" >/dev/null \ + || { echo " policies.$name is conditional without conditions" >&2; v=1; } + fi + done < <(jq -r '.policies | to_entries[] | [.key, (.value.status // "")] | @tsv' "$f") + # Enforcement: absent or mechanism "none" — never http_430. + if jq -e 'has("enforcement")' "$f" >/dev/null; then + local mech; mech="$(jq -r '.enforcement.mechanism // "none"' "$f")" + [ "$mech" = "none" ] \ + || { echo " enforcement.mechanism is '$mech' — this bundle is declaration-only" >&2; v=1; } + fi + return "$v" +} + +consistency_with_ai_txt() { # declaration must not contradict ai.txt + local ai="$WWW/.well-known/ai.txt" f="$WWW/.well-known/aibdp.json" v=0 + [ -f "$ai" ] || return 0 + local want got + for pair in "Disallow-Training:training" "Disallow-Summarization:summarization" "Disallow-Generation:generation"; do + key="${pair%%:*}"; field="${pair##*:}" + want="$(grep -m1 "^$key:" "$ai" | awk '{print $2}')" + got="$(jq -r --arg n "$field" '.policies[$n].status // "absent"' "$f")" + case "$want" in + yes) [ "$got" = "disallowed" ] || { echo " ai.txt $key yes but aibdp policies.$field.status=$got" >&2; v=1; } ;; + no) [ "$got" = "allowed" ] || { echo " ai.txt $key no but aibdp policies.$field.status=$got" >&2; v=1; } ;; + esac + done + return "$v" +} + +MAIN="$WWW/.well-known/aibdp.json" +AI_FILE="$WWW/.well-known/ai.txt" +# The AIBDP declaration is OPTIONAL (issue #53). A repository that does not +# make it has no AIBDP claims to validate, and demanding the file regardless is +# what made this check fail in every repository the .well-known/ stage-5 +# migration reached: the migration moves ai.txt and security.txt, and aibdp.json +# was never part of it. Silence is not a violation — but ai.txt REFERRING to +# aibdp while the file is absent is a real contradiction, and still fails below. +if [ ! -f "$MAIN" ] && ! grep -qi 'aibdp' "$AI_FILE" 2>/dev/null; then + echo "SKIP: no AIBDP declaration here (www/.well-known/aibdp.json absent and" + echo "SKIP: ai.txt makes no aibdp claim) — there is nothing to validate" + exit 77 +fi +if [ ! -f "$MAIN" ]; then + bad "ai.txt references aibdp but www/.well-known/aibdp.json is missing" +elif validate_aibdp "$MAIN" 2>/dev/null; then + ok "aibdp.json valid, experimental + declaration-only labelled" +else + bad "aibdp.json invalid" + validate_aibdp "$MAIN" 2>&1 | sed 's/^/ /' >&2 || true +fi + +if consistency_with_ai_txt 2>/dev/null; then + ok "aibdp.json consistent with ai.txt stances" +else + bad "aibdp.json contradicts ai.txt" + consistency_with_ai_txt 2>&1 | sed 's/^/ /' >&2 || true +fi + +shopt -s nullglob +for c in "$WWW"/tests/controls/aibdp-*.control.json; do + if validate_aibdp "$c" 2>/dev/null; then + bad "planted control was NOT rejected: tests/controls/$(basename "$c")" + else + ok "planted control rejected: tests/controls/$(basename "$c")" + fi +done + +exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-bind-safety.sh b/czech-file-knife/www/tests/check-bind-safety.sh new file mode 100755 index 000000000..4eb51de21 --- /dev/null +++ b/czech-file-knife/www/tests/check-bind-safety.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-bind-safety.sh — authoritative-only posture proof (issue #53, +# acceptance: "BIND example passes syntax validation and a planted +# open-recursion control is rejected"). +# +# Asserts, for every named.conf* under www/dns/ (examples AND controls are +# scanned by posture_assert; controls must FAIL it): +# * recursion no; present, and no 'recursion yes' +# * no unrestricted allow-recursion +# * no allow-transfer { any; } +# * no listen-on ... { any; } +# * no inline key/secret blocks (TSIG or otherwise) +# * zone names restricted to reserved/documentation names +# * no key/journal/secret FILES anywhere under www/dns/ +# When bind9utils is available: real named-checkconf via a temp jail +# (directory/pid-file rewritten into it, zones copied in) and +# named-checkzone on every *.zone. + +set -uo pipefail +WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DNS="$WWW/dns" +FAIL=0 +bad() { echo "BIND SAFETY FAIL: $*" >&2; FAIL=1; } +ok() { echo "ok: $*"; } + +posture_assert() { # $1 = named.conf file; returns 1 on any violation + local f="$1" v=0 z + grep -Eq '^[[:space:]]*recursion[[:space:]]+no[[:space:]]*;' "$f" \ + || { echo " no 'recursion no;' — resolver posture forbidden" >&2; v=1; } + grep -Eq '^[[:space:]]*recursion[[:space:]]+yes' "$f" \ + && { echo " 'recursion yes' present — OPEN RESOLVER" >&2; v=1; } + if grep -Eq '^[[:space:]]*allow-recursion' "$f"; then + grep -Eq '^[[:space:]]*allow-recursion[[:space:]]*\{[[:space:]]*none[[:space:]]*;' "$f" \ + || { echo " unrestricted allow-recursion" >&2; v=1; } + fi + grep -Eq 'allow-transfer[[:space:]]*\{[[:space:]]*any' "$f" \ + && { echo " allow-transfer { any; } — zone walk exposure" >&2; v=1; } + grep -Eq 'listen-on(-v6)?[[:space:]]*(port[[:space:]]+[0-9]+[[:space:]]*)?\{[[:space:]]*any' "$f" \ + && { echo " listen-on any — bind explicit addresses" >&2; v=1; } + grep -Eq '^[[:space:]]*key[[:space:]]+"' "$f" \ + && { echo " inline key block — secrets never live in the bundle" >&2; v=1; } + grep -Eoq 'secret[[:space:]]+"' "$f" \ + && { echo " inline secret material" >&2; v=1; } + # Zone names must be reserved/documentation names only. + while IFS= read -r z; do + case "$z" in + example.invalid|example.com|example.net|example.org|*.example.invalid|*.example|localhost|2.0.192.in-addr.arpa|*.2.0.192.in-addr.arpa|0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa) : ;; + *) echo " non-reserved zone name in template: $z" >&2; v=1 ;; + esac + done < <(sed -nE 's/^[[:space:]]*zone[[:space:]]+"([^"]+)".*$/\1/p' "$f") + return "$v" +} + +secret_file_scan() { # no key/journal/runtime material under www/dns/ + local f found=0 + while IFS= read -r f; do + case "${f##*/}" in + *.key|*.private|*.jnl|*.journal|*.rndc|*.jbk|tsig*|K*+*+*.key|K*+*+*.private) + echo " secret/runtime file under dns/: $f" >&2; found=1 ;; + esac + done < <(find "$DNS" -type f) + return "$found" +} + +# ── shipped examples must pass posture + real validators when available ───── +shopt -s nullglob +for conf in "$DNS"/bind9/named.conf*; do + case "${conf##*/}" in *.control) continue ;; esac + if posture_assert "$conf" 2>/dev/null; then + ok "posture: ${conf#"$WWW"/}" + else + bad "posture violated: ${conf#"$WWW"/}" + posture_assert "$conf" 2>&1 | sed 's/^/ /' >&2 || true + fi +done + +if secret_file_scan 2>/dev/null; then + ok "no key/journal/secret files under www/dns/" +else + bad "secret/runtime material found under www/dns/" +fi + +if command -v named-checkconf >/dev/null 2>&1; then + for conf in "$DNS"/bind9/named.conf*; do + case "${conf##*/}" in *.control) continue ;; esac + jail="$(mktemp -d)"; mkdir -p "$jail/zones" "$jail/run" + sed -e "s|directory \"/var/cache/bind\";|directory \"$jail\";|" \ + -e "s|pid-file \"/run/named/named.pid\";|pid-file \"$jail/run/named.pid\";|" \ + "$conf" > "$jail/named.conf" + cp "$DNS"/records/*.zone "$jail/zones/" 2>/dev/null || true + if named-checkconf "$jail/named.conf" >/dev/null 2>&1; then + ok "named-checkconf: ${conf#"$WWW"/}" + else + bad "named-checkconf rejected ${conf#"$WWW"/}" + fi + rm -rf "$jail" + done +else + echo "note: named-checkconf unavailable — posture grep checks only" +fi + +if command -v named-checkzone >/dev/null 2>&1; then + for zone in "$DNS"/records/*.zone; do + origin="$(basename "$zone" .zone)" + [ "$origin" = "2.0.192.in-addr.arpa" ] || origin="${origin%.zone}" + if named-checkzone "$origin" "$zone" >/dev/null 2>&1; then + ok "named-checkzone: records/$(basename "$zone")" + else + bad "named-checkzone rejected records/$(basename "$zone")" + fi + done +else + echo "note: named-checkzone unavailable — skipped" +fi + +# ── planted controls MUST be rejected ──────────────────────────────────────── +for ctl in "$WWW"/tests/controls/named.conf*.control; do + [ -f "$ctl" ] || continue + if posture_assert "$ctl" 2>/dev/null; then + bad "planted open-recursion control was NOT rejected: ${ctl#"$WWW"/}" + else + ok "planted control rejected: tests/controls/$(basename "$ctl")" + fi +done + +exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-migration.sh b/czech-file-knife/www/tests/check-migration.sh new file mode 100755 index 000000000..59c89d4de --- /dev/null +++ b/czech-file-knife/www/tests/check-migration.sh @@ -0,0 +1,214 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-migration.sh — prove scripts/migrate-wellknown-to-www.sh honours the +# behaviours stage 5 (#119) depends on (czech-file-knife#53 acceptance: +# "Migration handles identical, missing and divergent root/www copies without +# data loss"). Each scenario runs in a throwaway git repository. +# +# Scenarios 1-3 are the original four-scenario contract from #106. Scenarios +# 4-8 cover the stage 5 additions: the sweep is unattended, so a divergent +# copy must be quarantined rather than left to halt the batch — loudly, and +# with the old in-place contract still available under --in-place. + +set -uo pipefail + +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" \ + || REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +MIGRATOR="$REPO_ROOT/scripts/migrate-wellknown-to-www.sh" +# This check tests the MIGRATOR, not the repository. The migrator performs the +# one-time root .well-known/ -> www/.well-known/ move (issue #119) and is +# template-side tooling: a repository that has already been migrated has +# nothing for it to do and no business carrying it. A repository without it has +# nothing to test, so this is a SKIP, not a failure. Shipping a check that +# fails in every repository it reaches is how a suite stops being read. +if [ ! -f "$MIGRATOR" ]; then + echo "SKIP: scripts/migrate-wellknown-to-www.sh absent here — this check tests" + echo "SKIP: the template-side migrator, which this repository does not carry" + exit 77 +fi + +fail=0 +ok() { echo "ok: $1"; } +bad() { echo "FAIL: $1" >&2; fail=1; } + +scenario() { # name -> fresh git repo path on stdout + local dir; dir="$(mktemp -d)" + git -C "$dir" init -q + git -C "$dir" config user.email t@example.invalid + git -C "$dir" config user.name t + echo "$dir" +} +commit_all() { git -C "$1" add -A >/dev/null && git -C "$1" commit -qm fixture; } + +# The quarantine directory is named for the UTC date, so a scenario must read +# the same clock the migrator will. +qdir() { echo "www/.legacy-well-known-$(date -u +%Y%m%d)"; } + +# ── 1. identical copies: root removed, www kept, exit 0 ───────────────────── +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:s@example.invalid\n' | tee "$d/.well-known/security.txt" > "$d/www/.well-known/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" >/dev/null); then + if [ ! -e "$d/.well-known/security.txt" ] && [ -f "$d/www/.well-known/security.txt" ]; then + ok "identical -> root removed, www kept" + else + bad "identical scenario left wrong tree" + fi +else + bad "identical scenario exited non-zero" +fi +rm -rf "$d" + +# ── 2. root-only: moved into www, exit 0 ───────────────────────────────────── +d="$(scenario)" +mkdir -p "$d/.well-known/groove" +printf '{"service_id":"x"}\n' > "$d/.well-known/groove/manifest.json" +printf 'User-Agent: *\n' > "$d/.well-known/ai.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" >/dev/null); then + if [ -f "$d/www/.well-known/groove/manifest.json" ] && [ -f "$d/www/.well-known/ai.txt" ] \ + && [ ! -d "$d/.well-known" ]; then + ok "root-only -> moved (nested dirs too)" + else + bad "root-only move incomplete" + fi +else + bad "root-only scenario exited non-zero" +fi +rm -rf "$d" + +# ── 3. www-only (no root): no-op, exit 0 ───────────────────────────────────── +d="$(scenario)" +mkdir -p "$d/www/.well-known" +printf 'Contact: mailto:s@example.invalid\n' > "$d/www/.well-known/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" >/dev/null) && [ -f "$d/www/.well-known/security.txt" ]; then + ok "www-only -> no-op" +else + bad "www-only scenario damaged tree or exited non-zero" +fi +rm -rf "$d" + +# ── 4. divergent, default: root copy QUARANTINED, www untouched, exit 0 ────── +# The sweep is unattended, so this must not halt: the batch continues and the +# report names every quarantined file. +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" >/dev/null 2>&1); then + Q="$d/$(qdir)" + if [ -f "$Q/security.txt" ] && grep -q old "$Q/security.txt" \ + && [ -f "$d/www/.well-known/security.txt" ] && grep -q new "$d/www/.well-known/security.txt" \ + && [ ! -e "$d/.well-known/security.txt" ]; then + ok "divergent -> root copy quarantined, www copy untouched, exit 0" + else + bad "divergent scenario lost, overwrote or misplaced content" + fi +else + bad "divergent scenario must exit 0 by default (batch sweeps depend on it)" +fi +rm -rf "$d" + +# ── 5. divergent, --in-place: BOTH preserved where they are, exit 1 ────────── +# The pre-stage-5 contract, kept for hand resolution. +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" --in-place >/dev/null 2>&1); then + bad "divergent --in-place must exit non-zero" +else + if [ -f "$d/.well-known/security.txt" ] && [ -f "$d/www/.well-known/security.txt" ] \ + && grep -q old "$d/.well-known/security.txt" && grep -q new "$d/www/.well-known/security.txt"; then + ok "divergent --in-place -> both preserved, non-zero exit" + else + bad "divergent --in-place lost or overwrote content" + fi +fi +rm -rf "$d" + +# ── 6. divergent, --strict: quarantined AND non-zero exit ──────────────────── +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" --strict >/dev/null 2>&1); then + bad "divergent --strict must exit non-zero" +else + if [ -f "$d/$(qdir)/security.txt" ] && [ ! -e "$d/.well-known/security.txt" ]; then + ok "divergent --strict -> quarantined and non-zero exit" + else + bad "divergent --strict did not quarantine" + fi +fi +rm -rf "$d" + +# ── 7. --dry-run: reports, changes nothing, exit 0 ─────────────────────────── +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +commit_all "$d" +before="$(cd "$d" && git status --porcelain | sort)" +if (cd "$d" && bash "$MIGRATOR" --dry-run >/dev/null 2>&1); then + after="$(cd "$d" && git status --porcelain | sort)" + if [ "$before" = "$after" ] && [ -f "$d/.well-known/security.txt" ] \ + && [ ! -d "$d/$(qdir)" ]; then + ok "--dry-run -> nothing written" + else + bad "--dry-run mutated the tree" + fi +else + bad "--dry-run must exit 0 even when content diverges" +fi +rm -rf "$d" + +# ── 8. quarantine never overwrites: a taken name gets the next free suffix ─── +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" "$d/$(qdir)" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +printf 'PRIOR QUARANTINE — must survive\n' > "$d/$(qdir)/security.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" >/dev/null 2>&1); then + if grep -q 'PRIOR QUARANTINE' "$d/$(qdir)/security.txt" \ + && [ -f "$d/$(qdir)/security.txt.1" ] && grep -q old "$d/$(qdir)/security.txt.1"; then + ok "quarantine collision -> prior file preserved, new one suffixed .1" + else + bad "quarantine collision overwrote an existing file" + fi +else + bad "quarantine collision scenario exited non-zero" +fi +rm -rf "$d" + +# ── 9. --report: machine-readable TSV for batch drivers ────────────────────── +d="$(scenario)" +mkdir -p "$d/.well-known" "$d/www/.well-known" +printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" # divergent -> quarantined +printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" +printf 'User-Agent: *\n' > "$d/.well-known/ai.txt" # root-only -> moved +printf 'User-Agent: *\n' > "$d/www/.well-known/humans.txt" # identical -> deduped +printf 'User-Agent: *\n' > "$d/.well-known/humans.txt" +commit_all "$d" +if (cd "$d" && bash "$MIGRATOR" --report migrate.tsv >/dev/null 2>&1); then + if [ -f "$d/migrate.tsv" ] \ + && grep -qP '^quarantined\tsecurity.txt\t' "$d/migrate.tsv" \ + && grep -qP '^moved\tai.txt\t' "$d/migrate.tsv" \ + && grep -qP '^deduped\thumans.txt\t' "$d/migrate.tsv"; then + ok "--report -> TSV records quarantined/moved/deduped per file" + else + bad "--report TSV missing or incomplete" + fi +else + bad "--report scenario exited non-zero" +fi +rm -rf "$d" + +exit "$fail" diff --git a/czech-file-knife/www/tests/check-profiles.sh b/czech-file-knife/www/tests/check-profiles.sh new file mode 100755 index 000000000..840232acd --- /dev/null +++ b/czech-file-knife/www/tests/check-profiles.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-profiles.sh — explicit-composition proof (issue #53 acceptance: +# "Profiles compose explicitly and can be enabled/disabled independently"; +# "Consent-aware profile defaults to declaration/observe-only and cannot +# silently enable 430"). +# +# Any profile that STATES an enforcement/auto-start flag must state it as +# false; the consent and DNS profiles (and full-expert) MUST state them — +# absence is as much a violation as `true`, so a flag can never be +# smuggled in by deletion. + +set -uo pipefail +WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$WWW/.." && pwd)" +FAIL=0 +bad() { echo "PROFILE FAIL: $*" >&2; FAIL=1; } +ok() { echo "ok: $*"; } + +assert_profile() { # $1 = profile toml; returns 1 on any violation + local f="$1" v=0 prof c + grep -Eq '^profile = "' "$f" || { echo " missing 'profile' key" >&2; v=1; } + grep -Eq '^version = "' "$f" || { echo " missing 'version' key" >&2; v=1; } + grep -Eq '^status = "' "$f" || { echo " missing 'status' key" >&2; v=1; } + prof="$(sed -nE 's/^profile = "([^"]+)".*/\1/p' "$f" | head -1)" + + # Flags: stated -> must be false; required by role -> must be present. + if grep -q '^enforcement_http_430' "$f"; then + grep -Eq '^enforcement_http_430 = false$' "$f" \ + || { echo " enforcement_http_430 stated but not false — 430 cannot be silently enabled" >&2; v=1; } + fi + if grep -q '^auto_start_daemon' "$f"; then + grep -Eq '^auto_start_daemon = false$' "$f" \ + || { echo " auto_start_daemon stated but not false — no daemon may auto-start" >&2; v=1; } + fi + case "$prof" in + consent-aware-web|full-expert) + grep -Eq '^enforcement_http_430 = false$' "$f" \ + || { echo " $prof must carry enforcement_http_430 = false explicitly" >&2; v=1; } ;; + esac + case "$prof" in + authoritative-dns|full-expert) + grep -Eq '^auto_start_daemon = false$' "$f" \ + || { echo " $prof must carry auto_start_daemon = false explicitly" >&2; v=1; } ;; + esac + case "$prof" in + *rogue*|*control*) : ;; # planted controls need not satisfy role rules beyond the flags + esac + + # full-expert composes EXACTLY the four, explicitly. + if [ "$prof" = "full-expert" ]; then + local inc; inc="$(sed -nE 's/^includes = \[(.*)\].*/\1/p' "$f")" + for want in baseline-site consent-aware-web authoritative-dns privacy-enhanced; do + case "$inc" in *"$want"*) : ;; *) echo " full-expert.includes omits $want" >&2; v=1 ;; esac + done + fi + + # Every component path must exist in the bundle. + # Process substitution (not a pipe) so violations set v in THIS shell; + # `|| true` around grep so an empty array is not a pipefail "failure". + while IFS= read -r c; do + [ -n "$c" ] || continue + [ -e "$REPO/$c" ] || { echo " component path does not exist: $c" >&2; v=1; } + done < <(sed -nE 's/^components = \[(.*)\].*/\1/p' "$f" | { grep -o '"[^"]*"' || true; } | tr -d '"') + + # requires/includes must resolve to sibling profile files. + for key in requires includes; do + while IFS= read -r c; do + [ -n "$c" ] || continue + [ -f "$WWW/profiles/$c.toml" ] || { echo " $key references missing profile: $c" >&2; v=1; } + done < <(sed -nE "s/^$key = \[(.*)\].*/\1/p" "$f" | { grep -o '"[^"]*"' || true; } | tr -d '"') + done + return "$v" +} + +shopt -s nullglob +for p in "$WWW"/profiles/*.toml; do + if assert_profile "$p" 2>/dev/null; then + ok "profile valid: profiles/$(basename "$p")" + else + bad "profile invalid: profiles/$(basename "$p")" + assert_profile "$p" 2>&1 | sed 's/^/ /' >&2 || true + fi +done +for c in "$WWW"/tests/controls/profile-*.control.toml; do + if assert_profile "$c" 2>/dev/null; then + bad "planted control was NOT rejected: tests/controls/$(basename "$c")" + else + ok "planted control rejected: tests/controls/$(basename "$c")" + fi +done + +exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-publication-boundary.sh b/czech-file-knife/www/tests/check-publication-boundary.sh new file mode 100755 index 000000000..81da361b5 --- /dev/null +++ b/czech-file-knife/www/tests/check-publication-boundary.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-publication-boundary.sh — prove the publication boundary (issue #53). +# +# Modes: +# (no args) bundle invariants + planted-control self-test: +# A. schemas/publishable-paths.txt declares exactly +# www/public/ and www/.well-known/; +# B. every webservers/ example keeps its document root +# OUTSIDE the bundle and denies all eight operational +# categories; every planted *.control config violates +# B and must be rejected; +# C. the planted good stage passes the stage scan and +# the planted bad stage is rejected. +# --stage scan a staged deployment directory (as produced by +# runbooks/deploy.adoc) and exit non-zero on any +# operational material. This is the check the runbook +# and CI invoke before anything reaches a docroot. +# +# Operational categories (never publishable): dns tls security_headers +# webservers profiles schemas tests runbooks. Secret/runtime patterns are +# rejected anywhere in a stage. + +set -uo pipefail + +WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FORBIDDEN_DIRS="dns tls security_headers webservers profiles schemas tests runbooks" +FAIL=0 +bad() { echo "BOUNDARY FAIL: $*" >&2; FAIL=1; } +ok() { echo "ok: $*"; } + +# ── stage scanner ──────────────────────────────────────────────────────────── +scan_stage() { # $1 = staged dir; returns 1 on any violation (messages to stderr) + local root="$1" v f base found=0 + [ -d "$root" ] || { echo "stage scan: $root is not a directory" >&2; return 1; } + for v in $FORBIDDEN_DIRS; do + if [ -e "$root/$v" ]; then + echo "stage scan: operational category present: $v/" >&2; found=1 + fi + done + while IFS= read -r f; do + base="${f##*/}" + case "$base" in + *.key|*.pem|*.p12|*.pfx|*.jks|*.rndc|*.jbk|*.control|\ + named.conf*|rndc.conf*|tsig*|*.zone|*.journal|*.pid|*.log) + echo "stage scan: operational/secret material present: ${f#"$root"/}" >&2 + found=1 ;; + esac + done < <(find "$root" -type f) + return "$found" +} + +# ── config scanner ─────────────────────────────────────────────────────────── +scan_config() { # $1 = server config; returns 1 if it could publish the bundle + local f="$1" r found=0 v + # Document-root directives must not point at the bundle or into it. + while IFS= read -r r; do + [ -n "$r" ] || continue + case "$r" in + */www|*/www/|*/www/*|www|www/) + echo "config scan: document root points into the www/ bundle: $r" >&2; found=1 ;; + *dns*|*tls*|*security_headers*|*webservers*|*profiles*|*schemas*|*tests*|*runbooks*) + echo "config scan: document root points at operational material: $r" >&2; found=1 ;; + esac + done < <(sed -nE 's/^[[:space:]]*root[[:space:]]+\*[[:space:]]*([^;[:space:]]+).*$/\1/p + s/^[[:space:]]*root[[:space:]]+([^;[:space:]]+).*$/\1/p + s/^[[:space:]]*DocumentRoot[[:space:]]+([^[:space:]]+).*$/\1/p' "$f") + # Every operational category must be named in the config (deny coverage)… + for v in $FORBIDDEN_DIRS; do + grep -q "$v" "$f" || { echo "config scan: no deny coverage for category '$v'" >&2; found=1; } + done + # …and at least one explicit deny verb must be present. + grep -qE 'respond @operational 404|return 404;|Require all denied' "$f" \ + || { echo "config scan: no explicit deny directive" >&2; found=1; } + return "$found" +} + +# ── --stage mode ───────────────────────────────────────────────────────────── +if [ "${1:-}" = "--stage" ]; then + [ -n "${2:-}" ] || { echo "usage: $0 --stage " >&2; exit 2; } + if scan_stage "$2"; then + echo "publication boundary: stage $2 is clean" + exit 0 + else + echo "publication boundary: stage $2 REJECTED" >&2 + exit 1 + fi +fi + +# The publication boundary only exists once a repository PUBLISHES something. +# The bundle's schemas/, webservers/, dns/ and tls/ trees are what a site is +# built from; a library that serves no document root has no boundary to police +# and no shipped config to audit. Sections A, B and C all test that tree, so +# the guard is one test for all three rather than a rewrite of each. +# +# This is the guard whose absence planted three failing checks into every +# swept repository measured on 2026-09-18. `--stage ` mode above is +# unaffected: it is invoked by the deploy runbook against a real stage. +if [ ! -f "$WWW/schemas/publishable-paths.txt" ] && [ ! -d "$WWW/webservers" ]; then + echo "SKIP: this repository publishes nothing (no www/schemas/ and no" + echo "SKIP: www/webservers/) — the publication boundary does not apply" + exit 77 +fi + +# ── A. declaration ─────────────────────────────────────────────────────────── +DECL="$WWW/schemas/publishable-paths.txt" +if [ ! -f "$DECL" ]; then + bad "schemas/publishable-paths.txt missing" +else + declared="$(grep -v '^#' "$DECL" | grep -v '^[[:space:]]*$' | sort)" + expected="$(printf 'www/.well-known/\nwww/public/')" + if [ "$declared" = "$expected" ]; then + ok "publishable-paths.txt declares exactly www/public/ + www/.well-known/" + else + bad "publishable-paths.txt must declare exactly the two publishable categories; found: $(echo "$declared" | tr '\n' ' ')" + fi +fi + +# ── B. shipped configs pass; planted control configs fail ─────────────────── +shopt -s nullglob +for cfg in "$WWW"/webservers/*/*.example; do + if scan_config "$cfg" 2>/dev/null; then + ok "config implements the boundary: ${cfg#"$WWW"/}" + else + bad "shipped config violates the boundary: ${cfg#"$WWW"/}" + scan_config "$cfg" 2>&1 | sed 's/^/ /' >&2 || true + fi +done +for ctl in "$WWW"/tests/controls/*.control; do + if scan_config "$ctl" 2>/dev/null; then + bad "planted control was NOT rejected (control is broken): ${ctl#"$WWW"/}" + else + ok "planted control rejected: ${ctl#"$WWW"/}" + fi +done + +# ── C. staged-tree controls ────────────────────────────────────────────────── +if scan_stage "$WWW/tests/controls/good-deploy" 2>/dev/null; then + ok "planted good stage accepted" +else + bad "planted good stage was rejected (control is broken)" +fi +if scan_stage "$WWW/tests/controls/bad-deploy" 2>/dev/null; then + bad "planted bad stage was NOT rejected (boundary is unenforced)" +else + ok "planted bad stage rejected" +fi + +exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-wellknown.sh b/czech-file-knife/www/tests/check-wellknown.sh new file mode 100755 index 000000000..2f1e62f09 --- /dev/null +++ b/czech-file-knife/www/tests/check-wellknown.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: CC-BY-SA-4.0 +# +# check-wellknown.sh — validate the canonical www/.well-known/ contents. +# Tolerates unminted {{TOKEN}} placeholders (this suite runs in the template +# itself as well as in minted repos). + +set -uo pipefail +WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WK="$WWW/.well-known" +FAIL=0 +bad() { echo "WELLKNOWN FAIL: $*" >&2; FAIL=1; } +ok() { echo "ok: $*"; } + +for f in security.txt ai.txt humans.txt; do + [ -f "$WK/$f" ] && ok "$f present" || bad "$f missing from www/.well-known/" +done + +# RFC 9116: Contact and Expires are required fields. +if [ -f "$WK/security.txt" ]; then + grep -q '^Contact:' "$WK/security.txt" && ok "security.txt has Contact" || bad "security.txt missing Contact" + if grep -q '^Expires:' "$WK/security.txt"; then + EXP=$(grep '^Expires:' "$WK/security.txt" | head -1 | cut -d: -f2- | tr -d ' ') + case "$EXP" in + *'{{'*) ok "security.txt Expires carries a mint placeholder (pre-mint)" ;; + *) + if date -d "$EXP" >/dev/null 2>&1; then + DAYS=$(( ($(date -d "$EXP" +%s) - $(date +%s)) / 86400 )) + [ "$DAYS" -ge 0 ] && ok "security.txt Expires valid ($DAYS days)" || bad "security.txt EXPIRED" + else + bad "security.txt Expires is not a parseable timestamp: $EXP" + fi ;; + esac + else + bad "security.txt missing Expires" + fi +fi + +# ai.txt: the stance lines the estate's ai.txt convention requires. +if [ -f "$WK/ai.txt" ]; then + grep -q '^User-Agent:' "$WK/ai.txt" && ok "ai.txt has User-Agent" || bad "ai.txt missing User-Agent" + grep -q '^Disallow-Training:' "$WK/ai.txt" && ok "ai.txt has Disallow-Training" || bad "ai.txt missing Disallow-Training" +fi + +# humans.txt: humanstxt.org section markers. +if [ -f "$WK/humans.txt" ]; then + grep -q '/\* TEAM \*/' "$WK/humans.txt" && ok "humans.txt has TEAM" || bad "humans.txt missing TEAM section" + grep -q '/\* SITE \*/' "$WK/humans.txt" && ok "humans.txt has SITE" || bad "humans.txt missing SITE section" +fi + +# Migration window: a repository-root .well-known/ alongside the bundle is +# legacy; warn (do not fail) — scripts/migrate-wellknown-to-www.sh resolves it. +if [ -d "$WWW/../.well-known" ]; then + echo "WELLKNOWN WARN: legacy root .well-known/ still present — run scripts/migrate-wellknown-to-www.sh" >&2 +fi + +exit "$FAIL" diff --git a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json new file mode 100644 index 000000000..c2512ee7b --- /dev/null +++ b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json @@ -0,0 +1,7 @@ +{ + "_comment": "PLANTED CONTROL — must be REJECTED by check-aibdp.sh: unknown policy status vocabulary.", + "aibdp_version": "0.2", + "status": "experimental — declaration-only", + "contact": "mailto:sec@example.invalid", + "policies": { "training": { "status": "yolo", "rationale": "planted control" } } +} diff --git a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license new file mode 100644 index 000000000..8b66169a0 --- /dev/null +++ b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json new file mode 100644 index 000000000..01cfef240 --- /dev/null +++ b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json @@ -0,0 +1,8 @@ +{ + "_comment": "PLANTED CONTROL — must be REJECTED by check-aibdp.sh: it claims http_430 enforcement, which no declaration in this bundle may make.", + "aibdp_version": "0.2", + "status": "experimental — declaration-only", + "contact": "mailto:sec@example.invalid", + "policies": { "training": { "status": "disallowed", "rationale": "planted control" } }, + "enforcement": { "mechanism": "http_430" } +} diff --git a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license new file mode 100644 index 000000000..8b66169a0 --- /dev/null +++ b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf b/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf new file mode 100644 index 000000000..8f235aa78 --- /dev/null +++ b/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf @@ -0,0 +1 @@ +options { recursion yes; }; diff --git a/czech-file-knife/www/tests/controls/bad-deploy/index.html b/czech-file-knife/www/tests/controls/bad-deploy/index.html new file mode 100644 index 000000000..7891dc753 --- /dev/null +++ b/czech-file-knife/www/tests/controls/bad-deploy/index.html @@ -0,0 +1 @@ +bad stage diff --git a/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh b/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh new file mode 100644 index 000000000..1bad81edb --- /dev/null +++ b/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +planted diff --git a/czech-file-knife/www/tests/controls/caddy-serve-everything.control b/czech-file-knife/www/tests/controls/caddy-serve-everything.control new file mode 100644 index 000000000..145e40d49 --- /dev/null +++ b/czech-file-knife/www/tests/controls/caddy-serve-everything.control @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MPL-2.0 +# PLANTED CONTROL — must be REJECTED by check-publication-boundary.sh. +# It points Caddy at the www/ bundle wholesale and denies nothing. +site.example.invalid { + root * /srv/repo/www + file_server +} diff --git a/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt b/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt new file mode 100644 index 000000000..6133ffa0d --- /dev/null +++ b/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt @@ -0,0 +1,2 @@ +Contact: mailto:sec@example.invalid +Expires: 2099-01-01T00:00:00.000Z diff --git a/czech-file-knife/www/tests/controls/good-deploy/errors/404.html b/czech-file-knife/www/tests/controls/good-deploy/errors/404.html new file mode 100644 index 000000000..d71ff47e5 --- /dev/null +++ b/czech-file-knife/www/tests/controls/good-deploy/errors/404.html @@ -0,0 +1 @@ +404 diff --git a/czech-file-knife/www/tests/controls/good-deploy/index.html b/czech-file-knife/www/tests/controls/good-deploy/index.html new file mode 100644 index 000000000..89e95fc31 --- /dev/null +++ b/czech-file-knife/www/tests/controls/good-deploy/index.html @@ -0,0 +1 @@ +good stage diff --git a/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html b/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html new file mode 100644 index 000000000..4161a5847 --- /dev/null +++ b/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html @@ -0,0 +1 @@ +privacy diff --git a/czech-file-knife/www/tests/controls/named.conf.open-recursion.control b/czech-file-knife/www/tests/controls/named.conf.open-recursion.control new file mode 100644 index 000000000..c32bccebd --- /dev/null +++ b/czech-file-knife/www/tests/controls/named.conf.open-recursion.control @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MPL-2.0 +// PLANTED CONTROL — must be REJECTED by check-bind-safety.sh. +// This is exactly what an open resolver looks like: recursion on, queries +// and transfers unrestricted, listening everywhere, TSIG secret inline, +// and a live (non-reserved) zone name. +options { + directory "/var/cache/bind"; + recursion yes; + allow-query { any; }; + allow-transfer { any; }; + listen-on port 53 { any; }; +}; +key "leak" { + algorithm hmac-sha256; + secret "cGxhbnRlZC1jb250cm9sLW5vdC1hLXJlYWwtc2VjcmV0"; +}; +zone "real-domain.example" { + type primary; + file "zones/real.zone"; +}; diff --git a/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml b/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml new file mode 100644 index 000000000..40a6d40ec --- /dev/null +++ b/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: MPL-2.0 +# PLANTED CONTROL — must be REJECTED by check-profiles.sh: it tries to +# enable HTTP 430 enforcement, which no profile in this bundle may do. +profile = "consent-aware-web-rogue-control" +version = "0.0.1" +status = "planted-control" +enforcement_http_430 = true +components = [] diff --git a/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml b/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml new file mode 100644 index 000000000..779a653e7 --- /dev/null +++ b/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: MPL-2.0 +# PLANTED CONTROL — must be REJECTED by check-profiles.sh: it tries to +# auto-start the DNS daemon, which minting/selection must never do. +profile = "authoritative-dns-rogue-control" +version = "0.0.1" +status = "planted-control" +auto_start_daemon = true +components = [] diff --git a/czech-file-knife/www/tests/run-all.sh b/czech-file-knife/www/tests/run-all.sh new file mode 100755 index 000000000..5d2294205 --- /dev/null +++ b/czech-file-knife/www/tests/run-all.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# run-all.sh — execute every www/tests/check-*.sh probe in order. +# Exit non-zero on the first failing check (all checks are listed at the end). + +set -uo pipefail +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Three outcomes, not two. A check whose INPUTS DO NOT APPLY to this repository +# (it publishes nothing, it declares no AIBDP, it has no migrator to test) must +# be able to say so. The alternative is what actually happened: the bundle was +# written for the template, and every repository it reached that was not the +# template got a red check it could do nothing about. A suite that fails where +# it does not apply teaches people to ignore it. +# +# SKIP is declared, never inferred. The check must exit 77 AND print a line +# beginning "SKIP:". Exit 77 alone is not enough — a crash can produce any +# status, and a crash reported as a skip would be a lie of exactly the kind +# this estate keeps finding in its own documentation. +fail=0 +ran=0 +skipped=0 +for check in "$here"/check-*.sh; do + [ -f "$check" ] || continue + ran=$((ran + 1)) + name="$(basename "$check")" + out="$(mktemp)" + if bash "$check" >"$out" 2>&1; then + rc=0 + else + rc=$? + fi + cat "$out" + if [ "$rc" -eq 0 ]; then + echo "PASS $name" + elif [ "$rc" -eq 77 ] && grep -q '^SKIP:' "$out"; then + echo "SKIP $name" + skipped=$((skipped + 1)) + else + echo "FAIL $name" >&2 + fail=$((fail + 1)) + fi + rm -f "$out" +done + +echo "www/tests: ran $ran check(s), $fail failure(s), $skipped skipped" +if [ "$skipped" -gt 0 ]; then + echo "www/tests: $skipped check(s) did not apply here — see the SKIP lines above." + echo "www/tests: this is not full coverage of the bundle." +fi +[ "$fail" -eq 0 ] diff --git a/czech-file-knife/www/tls/POLICY.adoc b/czech-file-knife/www/tls/POLICY.adoc new file mode 100644 index 000000000..3b85ff418 --- /dev/null +++ b/czech-file-knife/www/tls/POLICY.adoc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += TLS Policy + +== Key custody — the only rule that matters + +Private keys, issued-certificate secrets, TSIG secrets, CSR material, +keystores (`.key`, `.pem`, `.p12`, `.pfx`, `.jks`), journals, caches, PID +files and logs **never** live in this repository or the bundle. ACME state +lives under the ACME client's own spool (`/var/lib/letsencrypt`, +Caddy's data dir). The stage scanner +(`www/tests/check-publication-boundary.sh --stage`) and the DNS safety +check reject these patterns anywhere they should not be, and the estate +secret scanner covers history. + +== Issuance + +. *ACME first.* Caddy obtains and renews automatically; nginx/Apache via + certbot (or the estate's chosen ACME tooling). DNS-01 challenges for + wildcard or non-public names — which is where the `authoritative-dns` + profile's zone discipline pays off. +. *Manual issuance* is a documented exception with a rotation date in the + calendar, not a default. + +== Parameters + +[cols="1,2", options="header"] +|=== +| Item | Policy + +| Protocols +| TLS 1.2 and 1.3 only. No SSLv3/TLS 1.0/1.1 anywhere. + +| Keys +| ECDSA P-256 preferred; RSA >= 3072 where a counterpart requires RSA. + +| Certificates +| 90-day ACME lifetime; renewal automated at <= 30 days remaining; + monitoring alert at 21 days (a missed renewal is an incident, not a + surprise). + +| HSTS +| `max-age=31536000; includeSubDomains` once HTTPS is stable; add `preload` + only accepting the multi-month un-preload cycle. + +| CT +| Expect all public certificates to appear in CT logs; monitor the name + (e.g. crt.sh-style watch) and treat unexpected certificates as incidents. + +| ECH +| Tracked as *readiness*, not promise: see + `www/dns/privacy/ENCRYPTED-DNS.adoc` for detection discipline (HTTPS RR / + TYPE65). Publish support only where a dated probe demonstrates it. +|=== + +== Rotation + +Normal rotation is automatic (ACME). Emergency replacement (compromise, +mis-issuance) follows `www/runbooks/cert-rotation.adoc`, including +revocation and the CT consequences of both. diff --git a/czech-file-knife/www/webservers/README.adoc b/czech-file-knife/www/webservers/README.adoc new file mode 100644 index 000000000..022275081 --- /dev/null +++ b/czech-file-knife/www/webservers/README.adoc @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += `webservers/` — Integration Examples + +Three starting-hand configurations implementing the publication boundary +for a staged document root (see `www/runbooks/deploy.adoc`): + +[cols="1,2", options="header"] +|=== +| File | Server + +| `caddy/Caddyfile.example` +| Caddy 2 — automatic HTTPS; header block carries the canonical set. + +| `nginx/site.conf.example` +| nginx — `server` block for inclusion from `sites-available`; TLS material + referenced from outside the repository. + +| `apache/vhost.conf.example` +| Apache httpd 2.4 — `VirtualHost` with `mod_headers`. +|=== + +All three: + +. point their document root at a **staged directory** produced by the deploy + runbook — never at the `www/` bundle itself; +. serve `/.well-known/` explicitly; +. deny the operational categories (`dns`, `tls`, `security_headers`, + `webservers`, `profiles`, `schemas`, `tests`, `runbooks`) as defence in + depth, even though a correct stage never contains them; +. map 4xx/5xx statuses onto the staged `errors/` bodies; +. embed the header values from `www/security_headers/csp.conf` (keep in + sync). + +Hostnames use `site.example.invalid` (RFC 2606 reserved). TLS certificate +paths are placeholders; see `www/tls/POLICY.adoc` — keys never live in the +repository. + +`www/tests/check-publication-boundary.sh` statically scans these examples: +a document root inside the bundle, or a missing operational deny, fails the +check. The planted control `www/tests/controls/caddy-serve-everything.control` +demonstrates the rejection. diff --git a/czech-file-knife/www/webservers/apache/vhost.conf.example b/czech-file-knife/www/webservers/apache/vhost.conf.example new file mode 100644 index 000000000..55555c7cb --- /dev/null +++ b/czech-file-knife/www/webservers/apache/vhost.conf.example @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Apache httpd 2.4 example for the www/ site-operations bundle (issue #53). +# Requires: mod_headers, mod_ssl (cert paths are placeholders — see +# www/tls/POLICY.adoc; keys never live in the repository). +# DocumentRoot is the STAGED directory produced by www/runbooks/deploy.adoc — +# NEVER the www/ bundle itself. + + + ServerName site.example.invalid + + DocumentRoot /srv/site.example.invalid + + # SSLEngine on + # SSLCertificateFile /etc/letsencrypt/live/site.example.invalid/fullchain.pem + # SSLCertificateKeyFile /etc/letsencrypt/live/site.example.invalid/privkey.pem + # SSLProtocol -all +TLSv1.2 +TLSv1.3 + + + Require all granted + Options -Indexes + AllowOverride None + + + # Explicit publication of .well-known. + Alias /.well-known/ /srv/site.example.invalid/.well-known/ + + Require all granted + + + # Defence in depth: refuse operational categories (never staged, but a + # bad stage must not publish them). + + Require all denied + + + # Canonical header set — keep in sync with www/security_headers/csp.conf. + Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + Header always set X-Content-Type-Options "nosniff" + Header always set X-Frame-Options "DENY" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" + Header always set Cross-Origin-Opener-Policy "same-origin" + Header always set Cross-Origin-Resource-Policy "same-origin" + Header unset Server + Header unset X-Powered-By + + # Staged error bodies. + ErrorDocument 403 /errors/403.html + ErrorDocument 404 /errors/404.html + ErrorDocument 429 /errors/429.html + ErrorDocument 500 /errors/500.html + ErrorDocument 502 /errors/502.html + ErrorDocument 503 /errors/503.html + + + + ServerName site.example.invalid + Redirect permanent / https://site.example.invalid/ + diff --git a/czech-file-knife/www/webservers/caddy/Caddyfile.example b/czech-file-knife/www/webservers/caddy/Caddyfile.example new file mode 100644 index 000000000..005c28869 --- /dev/null +++ b/czech-file-knife/www/webservers/caddy/Caddyfile.example @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Caddy 2 example for the www/ site-operations bundle (issue #53). +# The document root is the STAGED directory produced by www/runbooks/deploy.adoc +# (public/ content + .well-known/ + errors/ + rendered policies) — NEVER the +# www/ bundle itself. Hostname is RFC 2606 reserved; TLS is automatic once the +# name is real (see www/tls/POLICY.adoc). + +site.example.invalid { + root * /srv/site.example.invalid + encode zstd gzip + + # Canonical header set — keep in sync with www/security_headers/csp.conf. + header { + Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" + Cross-Origin-Opener-Policy "same-origin" + Cross-Origin-Resource-Policy "same-origin" + -Server + } + + # Defence in depth: operational categories are never staged, but if a bad + # stage ever slips through, refuse them explicitly. + @operational path /dns/* /tls/* /security_headers/* /webservers/* /profiles/* /schemas/* /tests/* /runbooks/* + respond @operational 404 + + # .well-known is ordinary staged content (dotfiles included by file_server); + # no special-casing needed beyond keeping it out of any dotfile blocking. + + handle_errors { + rewrite * /errors/{err.status_code}.html + file_server + } + + file_server +} diff --git a/czech-file-knife/www/webservers/nginx/site.conf.example b/czech-file-knife/www/webservers/nginx/site.conf.example new file mode 100644 index 000000000..f9051d60d --- /dev/null +++ b/czech-file-knife/www/webservers/nginx/site.conf.example @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# nginx example for the www/ site-operations bundle (issue #53). +# Place in /etc/nginx/sites-available/ and symlink to sites-enabled/. +# The root is the STAGED directory produced by www/runbooks/deploy.adoc — +# NEVER the www/ bundle itself. TLS material lives outside the repository +# (see www/tls/POLICY.adoc). +# +# Requirements: nginx >= 1.25.1 (for 'http2 on;'). Before `nginx -t` can +# pass, the two ssl_certificate lines MUST be uncommented and pointed at +# real material — 'listen ... ssl' without a certificate is a hard error. +# Verified with nginx 1.26.3 (`nginx -t`: syntax ok) against a throwaway +# self-signed certificate; this file intentionally ships the cert paths +# commented so no environment-specific paths are baked into the template. + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name site.example.invalid; + + root /srv/site.example.invalid; + index index.html; + + # Certificate/key paths — provisioned by ACME or ops tooling, never git. + # ssl_certificate /etc/letsencrypt/live/site.example.invalid/fullchain.pem; + # ssl_certificate_key /etc/letsencrypt/live/site.example.invalid/privkey.pem; + # ssl_protocols TLSv1.2 TLSv1.3; + + # Canonical header set — keep in sync with www/security_headers/csp.conf. + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" always; + add_header Cross-Origin-Opener-Policy "same-origin" always; + add_header Cross-Origin-Resource-Policy "same-origin" always; + + # Defence in depth: refuse operational categories (never staged, but a + # bad stage must not publish them). + location ~ ^/(dns|tls|security_headers|webservers|profiles|schemas|tests|runbooks)(/|$) { + return 404; + } + + # Explicit publication of .well-known (prefix match wins over the + # dotfile deny below via ^~). + location ^~ /.well-known/ { + try_files $uri =404; + } + + # Deny any other dotfile/dotdir that might slip into a stage. + location ~ /\. { + deny all; + } + + # Staged error bodies. + error_page 403 /errors/403.html; + error_page 404 /errors/404.html; + error_page 429 /errors/429.html; + error_page 500 /errors/500.html; + error_page 502 /errors/502.html; + error_page 503 /errors/503.html; + location /errors/ { + internal; + } +} + +server { + listen 80; + listen [::]:80; + server_name site.example.invalid; + return 301 https://$host$request_uri; +} From 60ba3be33f63f9378c7a673d7705668dbf232ae0 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:38:49 +0000 Subject: [PATCH 3/4] chore(czech-file-knife): carry scanner exemptions into the standalone tree; env-var credentials in docs examples --- czech-file-knife/.hypatia-ignore | 5 +++++ .../docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/czech-file-knife/.hypatia-ignore b/czech-file-knife/.hypatia-ignore index 1722a94d1..c3df862a9 100644 --- a/czech-file-knife/.hypatia-ignore +++ b/czech-file-knife/.hypatia-ignore @@ -53,3 +53,8 @@ research_extensions/RE005:.github/workflows/static-analysis-gate.yml # Dependabot workflow uses the unprivileged pull_request event and also verifies # github.event.pull_request.user.login, so that threat model does not apply. research_extensions/RE008:.github/workflows/dependabot-automerge.yml + +# iOS File Provider Extension: NSFileProviderReplicatedExtension is a Swift-only +# Apple API with no Rust/Tauri equivalent. Carried over from +# developer-ecosystem/.hypatia-baseline.json (tracking: developer-ecosystem#111). +cicd_rules/banned_language_file:src/cfk-ios/swift/ diff --git a/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc b/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc index 9954ff73f..67ae12a1b 100644 --- a/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc +++ b/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc @@ -43,7 +43,7 @@ let config = SmbConfig { server: "fileserver".into(), share: "Documents".into(), username: Some("user".into()), - password: Some("pass".into()), + password: std::env::var("CFK_SMB_PASSWORD").ok(), version: SmbVersion::Smb3, ..Default::default() }; @@ -127,8 +127,8 @@ let backend = CephBackend::new("my-ceph", config); let rgw_backend = CephBackend::rgw( "my-rgw", "https://rgw.example.com", - "access_key", - "secret_key", + &std::env::var("CFK_S3_ACCESS_KEY_ID")?, + &std::env::var("CFK_S3_SECRET_ACCESS_KEY")?, "my-bucket" ); ---- @@ -211,8 +211,8 @@ let backend = S3Backend::cloudflare_r2( "my-r2", "account-id", "my-bucket", - "access_key", - "secret_key" + &std::env::var("CFK_S3_ACCESS_KEY_ID")?, + &std::env::var("CFK_S3_SECRET_ACCESS_KEY")? ); ---- @@ -231,7 +231,7 @@ let config = WebDavConfig { base_url: "https://dav.example.com/files/".into(), auth: Some(WebDavAuth::Basic { username: "user".into(), - password: "pass".into(), + password: std::env::var("CFK_WEBDAV_PASSWORD")?, }), ..Default::default() }; @@ -243,7 +243,7 @@ let backend = WebDavBackend::nextcloud( "my-nextcloud", "https://cloud.example.com", "username", - "app-password" + &std::env::var("CFK_NEXTCLOUD_APP_PASSWORD")? ); ---- From dcecac34c55b039dc0c2fc9e32e8b255ed459119 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:38:59 +0000 Subject: [PATCH 4/4] chore: remove czech-file-knife (extracted to hyperpolymath/czech-file-knife) Split point: 60ba3be3 (git subtree split --prefix=czech-file-knife 60ba3be3). --- .gitleaks.toml | 4 - .hypatia-baseline.json | 8 - CHANGELOG.adoc | 8 + czech-file-knife/.cicd-hygiene-allow | 12 - czech-file-knife/.clinerules | 51 - czech-file-knife/.clusterfuzzlite/Dockerfile | 8 - czech-file-knife/.clusterfuzzlite/build.sh | 13 - .../.clusterfuzzlite/project.yaml | 3 - czech-file-knife/.cursorrules | 53 - czech-file-knife/.devcontainer/Containerfile | 34 - czech-file-knife/.devcontainer/README.adoc | 28 - .../.devcontainer/devcontainer.json | 69 - czech-file-knife/.editorconfig | 140 - czech-file-knife/.envrc | 22 - czech-file-knife/.gitattributes | 117 - czech-file-knife/.github/CODEOWNERS | 14 - czech-file-knife/.github/CODE_OF_CONDUCT.md | 318 - czech-file-knife/.github/CONTRIBUTING.md | 103 - czech-file-knife/.github/FUNDING.yml | 17 - czech-file-knife/.github/GOVERNANCE.md | 160 - .../.github/ISSUE_TEMPLATE/bug_report.yml | 58 - .../.github/ISSUE_TEMPLATE/config.yml | 13 - .../ISSUE_TEMPLATE/feature_request.yml | 37 - czech-file-knife/.github/SECURITY.md | 389 - czech-file-knife/.github/SUPPORT.md | 7 - .../.github/copilot-instructions.md | 85 - .../.github/copilot/coding-agent.yml | 6 - czech-file-knife/.github/dependabot.yml | 43 - czech-file-knife/.github/hooks/install.sh | 10 - czech-file-knife/.github/hooks/pre-push | 81 - .../.github/hooks/scan-secrets.sh | 204 - .../.github/hooks/validate-deed.sh | 393 - czech-file-knife/.github/hooks/validate-k9.sh | 389 - .../.github/label-classifier.json | 739 -- czech-file-knife/.github/labels.json | 260 - .../.github/pull_request_template.md | 47 - .../.github/rulesets/Immutable-Tags.json | 28 - czech-file-knife/.github/rulesets/README.adoc | 123 - czech-file-knife/.github/rulesets/base.json | 102 - .../.github/rulesets/branch-floor.json | 16 - .../.github/scripts/classify-issue.jq | 164 - czech-file-knife/.github/settings.yml | 160 - .../.github/workflows/README.adoc | 63 - .../.github/workflows/actions.lock | 296 - .../.github/workflows/build-notification.yml | 50 - .../.github/workflows/cflite_batch.yml | 30 - .../.github/workflows/cflite_pr.yml | 29 - czech-file-knife/.github/workflows/codeql.yml | 47 - .../.github/workflows/deed-validate.yml | 59 - .../workflows/dependabot-automerge.yml | 137 - .../.github/workflows/docker-build.yml | 58 - .../.github/workflows/dogfood-gate.yml | 632 -- .../.github/workflows/dogfood-summary.yml | 99 - .../workflows/dot-wellknown-enforcement.yml | 157 - .../.github/workflows/e2e.yml.template | 224 - .../workflows/eclexiaiser-validate.yml | 64 - .../.github/workflows/empty-linter.yml | 89 - .../.github/workflows/estate-rules.yml | 97 - .../.github/workflows/governance.yml | 21 - .../.github/workflows/groove-check.yml | 91 - .../.github/workflows/guix-policy.yml | 49 - .../.github/workflows/hypatia-scan.yml | 30 - .../.github/workflows/k9-validate.yml | 64 - .../.github/workflows/label-triage.yml | 118 - czech-file-knife/.github/workflows/labels.yml | 107 - .../.github/workflows/lock-sync-gate.yml | 64 - .../.github/workflows/main-estate-audit.yml | 20 - czech-file-knife/.github/workflows/mirror.yml | 28 - .../.github/workflows/ossf-best-practices.yml | 97 - czech-file-knife/.github/workflows/pages.yml | 91 - .../.github/workflows/push-email-notify.yml | 58 - .../.github/workflows/quality.yml | 79 - .../.github/workflows/release.yml | 159 - .../workflows/rsr-compliance-canary.yml | 95 - .../.github/workflows/runtime-policy.yml | 72 - .../.github/workflows/rust-ci.yml | 19 - .../.github/workflows/scorecard.yml | 26 - .../.github/workflows/secret-scanner.yml | 29 - .../.github/workflows/security-policy.yml | 54 - .../.github/workflows/sonarqube.yml | 69 - .../workflows/static-analysis-gate.yml | 454 -- .../.github/workflows/workflow-linter.yml | 178 - czech-file-knife/.gitignore | 146 - czech-file-knife/.gitleaksignore | 4 - czech-file-knife/.gitmessage | 18 - czech-file-knife/.hypatia-ignore | 60 - .../.machine_readable/ENSAID_CONFIG.a2ml | 96 - .../.machine_readable/PROVENANCE.a2ml | 41 - .../.machine_readable/README.adoc | 40 - czech-file-knife/.machine_readable/ai/AI.a2ml | 38 - .../.machine_readable/ai/README.adoc | 24 - .../arrival-pack/README.adoc | 55 - .../arrival-pack/arrival-pack.ncl | 90 - .../arrival-pack/claude-md.k9.ncl | 59 - .../.machine_readable/arrival-pack/extract.sh | 89 - .../arrival-pack/generate.sh | 45 - .../.machine_readable/arrival-pack/verify.sh | 27 - .../bot_directives/README.adoc | 41 - .../bot_directives/coverage.a2ml | 61 - .../bot_directives/debt.a2ml | 49 - .../bot_directives/methodology.a2ml | 115 - .../.machine_readable/bot_directives/rra.a2ml | 55 - .../.machine_readable/coaptation/README.adoc | 147 - .../.machine_readable/coaptation/coapt.k9.ncl | 70 - .../.machine_readable/coaptation/coapt.ncl | 196 - .../.machine_readable/coaptation/coapt.sh | 87 - .../coaptation/core/Coaptation.idr | 112 - .../coaptation/extract-clauses.sh | 104 - .../coaptation/extract-facts.sh | 122 - .../.machine_readable/coaptation/grades.ncl | 45 - .../coaptation/receipts/latest.a2ml | 100 - .../.machine_readable/coaptation/verify.sh | 34 - .../coaptation/witness-map.ncl | 88 - .../.machine_readable/compliance/reuse/dep5 | 62 - .../compliance/rust/deny.toml | 64 - .../.machine_readable/configs/README.adoc | 3 - .../configs/eclexiaiser.toml | 26 - .../configs/git-cliff/cliff.toml | 119 - .../.machine_readable/configs/stapeln.toml | 87 - .../.machine_readable/contractiles/INDEX.a2ml | 133 - .../.machine_readable/contractiles/Justfile | 720 -- .../contractiles/README.adoc | 169 - .../.machine_readable/contractiles/_base.ncl | 140 - .../contractiles/adjust/Adjustfile.a2ml | 72 - .../contractiles/adjust/adjust.k9.ncl | 167 - .../contractiles/adjust/adjust.manifest.a2ml | 47 - .../contractiles/adjust/adjust.ncl | 65 - .../contractiles/bust/Bustfile.a2ml | 52 - .../contractiles/bust/bust.k9.ncl | 162 - .../contractiles/bust/bust.manifest.a2ml | 48 - .../contractiles/bust/bust.ncl | 69 - .../contractiles/dust/Dustfile.a2ml | 75 - .../contractiles/dust/dust.k9.ncl | 172 - .../contractiles/dust/dust.manifest.a2ml | 51 - .../contractiles/dust/dust.ncl | 69 - .../contractiles/intend/Intentfile.a2ml | 99 - .../contractiles/intend/intend.k9.ncl | 252 - .../contractiles/intend/intend.manifest.a2ml | 73 - .../contractiles/intend/intend.ncl | 84 - .../contractiles/must/Mustfile.a2ml | 125 - .../contractiles/must/must.k9.ncl | 238 - .../contractiles/must/must.manifest.a2ml | 59 - .../contractiles/must/must.ncl | 67 - .../contractiles/trust/Trustfile.a2ml | 105 - .../contractiles/trust/trust.k9.ncl | 278 - .../contractiles/trust/trust.manifest.a2ml | 72 - .../contractiles/trust/trust.ncl | 94 - .../descriptiles/AGENTIC.a2ml | 56 - .../.machine_readable/descriptiles/CLADE.a2ml | 127 - .../descriptiles/ECOSYSTEM.a2ml | 30 - .../.machine_readable/descriptiles/META.a2ml | 53 - .../descriptiles/NEUROSYM.a2ml | 23 - .../descriptiles/PLAYBOOK.a2ml | 137 - .../descriptiles/README.adoc | 66 - .../.machine_readable/descriptiles/STATE.a2ml | 62 - .../descriptiles/VARIANT.a2ml | 40 - .../descriptiles/anchors/ANCHOR.a2ml | 62 - .../descriptiles/anchors/README.adoc | 25 - .../integrations/feedback-o-tron.a2ml | 14 - .../integrations/groove.a2ml | 38 - .../integrations/proven.a2ml | 20 - .../integrations/verisimdb.a2ml | 17 - .../integrations/vexometer.a2ml | 19 - .../policies/.maintenance-perms-ignore | 5 - .../policies/MAINTENANCE-AXES.a2ml | 54 - .../policies/MAINTENANCE-CHECKLIST.a2ml | 159 - .../.machine_readable/policies/README.adoc | 3 - .../SOFTWARE-DEVELOPMENT-APPROACH.a2ml | 53 - .../.machine_readable/root-allow.txt | 85 - .../.machine_readable/rsr-profile.a2ml | 30 - .../scripts/forge/README.adoc | 3 - .../scripts/forge/forge-sync.sh | 25 - .../scripts/forge/git-cleanup.sh | 10 - .../scripts/lifecycle/README.adoc | 3 - .../scripts/lifecycle/install-tools.sh | 27 - .../scripts/maintenance/maint-assault.sh | 44 - .../scripts/verification/README.adoc | 3 - .../self-validating/README.adoc | 178 - .../self-validating/examples/ci-config.k9.ncl | 126 - .../examples/project-metadata.k9.ncl | 57 - .../examples/setup-repo.k9.ncl | 167 - .../self-validating/methodology-guard.k9.ncl | 79 - .../self-validating/template-hunt.k9.ncl | 136 - .../self-validating/template-kennel.k9.ncl | 54 - .../self-validating/template-yard.k9.ncl | 84 - czech-file-knife/.mailmap | 1 - czech-file-knife/.tool-versions | 9 - czech-file-knife/.windsurfrules | 51 - czech-file-knife/CHANGELOG.adoc | 85 - czech-file-knife/CITATION.cff | 17 - czech-file-knife/CLAUDE.md | 71 - czech-file-knife/CONTRIBUTING.adoc | 122 - czech-file-knife/Cargo.lock | 6638 ----------------- czech-file-knife/Cargo.toml | 93 - czech-file-knife/GEMINI.md | 8 - czech-file-knife/Justfile | 633 -- czech-file-knife/LICENSE | 373 - czech-file-knife/LICENSES/CC-BY-SA-4.0.txt | 428 -- czech-file-knife/LICENSES/MPL-2.0.txt | 373 - czech-file-knife/README.adoc | 130 - .../benches/czech_file_knife_bench.rs | 14 - czech-file-knife/benches/template_bench.sh | 227 - .../build/container/.containerignore | 59 - .../build/container/Containerfile | 41 - czech-file-knife/build/container/README.adoc | 245 - .../build/container/compose.example.yaml | 108 - czech-file-knife/build/container/compose.yaml | 99 - .../build/container/entrypoint.sh | 63 - .../build/container/stapeln/.gatekeeper.yaml | 122 - .../container/stapeln/compose.example.toml | 135 - .../build/container/stapeln/compose.toml | 94 - .../build/container/stapeln/ct-build.sh | 168 - .../build/container/stapeln/deploy.k9.ncl | 170 - .../build/container/stapeln/manifest.toml | 62 - .../build/container/stapeln/rokur.toml | 81 - .../build/container/stapeln/vordr.toml | 117 - czech-file-knife/build/guix.scm | 81 - czech-file-knife/build/just/assess.just | 270 - czech-file-knife/build/just/container.just | 284 - czech-file-knife/build/just/groove.just | 98 - czech-file-knife/build/just/proofs.just | 61 - czech-file-knife/build/just/repo-init.just | 902 --- czech-file-knife/build/just/validate.just | 135 - .../build/packaging/arch/PKGBUILD | 42 - .../chocolatey/czech-file-knife.nuspec | 33 - .../build/packaging/debian/control | 27 - czech-file-knife/build/packaging/debian/rules | 17 - .../dev.hyperpolymath.CzechFileKnife.yml | 31 - .../build/packaging/macports/Portfile | 36 - .../build/packaging/rpm/czech-file-knife.spec | 52 - .../packaging/scoop/czech-file-knife.json | 23 - .../packaging/winget/czech-file-knife.yaml | 40 - czech-file-knife/ci/.gitlab-ci.yml | 154 - czech-file-knife/ci/.pre-commit-config.yaml | 73 - czech-file-knife/ci/README.adoc | 43 - czech-file-knife/coordination.k9.ncl | 49 - czech-file-knife/czech-file-knife_chora.deed | 152 - czech-file-knife/docs/AFFIRMATION.adoc | 235 - .../docs/AI-INSTALL-README-SECTION.adoc | 20 - .../docs/AI_INSTALLATION_GUIDE.adoc | 133 - czech-file-knife/docs/ARCHITECTURE.adoc | 48 - czech-file-knife/docs/AUDIT.adoc | 48 - czech-file-knife/docs/EXPLAINME.adoc | 118 - czech-file-knife/docs/GOVERNANCE.adoc | 65 - czech-file-knife/docs/MAINTAINERS.adoc | 63 - czech-file-knife/docs/QUICKSTART.adoc | 26 - czech-file-knife/docs/README.adoc | 53 - czech-file-knife/docs/RSR-PHILOSOPHY.adoc | 118 - czech-file-knife/docs/RSR_OUTLINE.adoc | 258 - czech-file-knife/docs/STATE-VISUALIZER.adoc | 131 - czech-file-knife/docs/architecture.adoc | 79 - .../architecture/DISTRIBUTED_FILESYSTEMS.adoc | 380 - .../docs/architecture/HYBRID-OPERATIONS.adoc | 106 - .../docs/architecture/REPOSITORY-MAP.adoc | 267 - .../docs/architecture/THREAT-MODEL.adoc | 197 - .../docs/architecture/TOPOLOGY.adoc | 36 - .../docs/attribution/CFK-CITATIONS.adoc | 36 - .../docs/attribution/CITATIONS.adoc | 37 - .../docs/attribution/CODEOWNERS.adoc | 21 - czech-file-knife/docs/attribution/README.adoc | 3 - czech-file-knife/docs/contributing.adoc | 91 - .../docs/decisions/0000-template.adoc | 37 - .../decisions/0001-adopt-rsr-standard.adoc | 89 - .../docs/decisions/0001-template.adoc | 54 - .../docs/decisions/0002-variant-contract.adoc | 75 - .../0003-forge-and-sustain-lifecycle.adoc | 118 - czech-file-knife/docs/decisions/README.adoc | 3 - .../docs/developer/ABI-FFI-README.adoc | 405 - .../docs/developer/IOS_INTEGRATION.adoc | 394 - czech-file-knife/docs/developer/README.adoc | 3 - .../docs/developer/invariant-path.adoc | 20 - .../docs/governance/CRG-AUDIT-TEMPLATE.adoc | 288 - .../docs/governance/CRG-CRITERIA.a2ml | 108 - .../docs/governance/CRG-CRITERIA.adoc | 41 - .../governance/MAINTENANCE-CHECKLIST.adoc | 571 -- czech-file-knife/docs/governance/README.adoc | 3 - .../SOFTWARE-DEVELOPMENT-APPROACH.adoc | 65 - .../governance/TEMPLATE-LINEAGE-AUDIT.adoc | 230 - .../governance/TEMPLATE-STANDARDS-AUDIT.adoc | 178 - czech-file-knife/docs/governance/TSDM.a2ml | 22 - czech-file-knife/docs/governance/TSDM.adoc | 28 - .../docs/governance/audit/README.adoc | 3 - .../governance/audit/compliance/README.adoc | 3 - .../docs/governance/audit/effects/README.adoc | 3 - .../docs/governance/audit/systems/README.adoc | 3 - .../docs/governance/maintenance/README.adoc | 3 - .../maintenance/adaptive/README.adoc | 3 - .../maintenance/corrective/README.adoc | 3 - .../maintenance/perfective/README.adoc | 3 - .../docs/governance/planning/README.adoc | 3 - .../governance/planning/could/README.adoc | 3 - .../docs/governance/planning/must/README.adoc | 3 - .../governance/planning/should/README.adoc | 3 - .../docs/legal/EXHIBIT-A-ETHICAL-USE.txt | 20 - .../docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt | 104 - czech-file-knife/docs/legal/PALIMPSEST.adoc | 41 - .../docs/onboarding/QUICKSTART-DEV.adoc | 104 - .../onboarding/QUICKSTART-MAINTAINER.adoc | 122 - .../docs/onboarding/QUICKSTART-USER.adoc | 125 - .../docs/onboarding/llm-warmup-dev.adoc | 21 - .../docs/onboarding/llm-warmup-user.adoc | 21 - .../docs/practice/AI-CONVENTIONS.adoc | 102 - czech-file-knife/docs/practice/README.adoc | 3 - .../docs/practice/STATE-VISUALIZER-GUIDE.adoc | 156 - .../docs/practice/ci-cost-reduction.adoc | 278 - .../docs/proposals/root-cleanup.adoc | 231 - czech-file-knife/docs/standards/README.adoc | 3 - czech-file-knife/docs/status/PROOF-NEEDS.adoc | 123 - .../docs/status/PROOF-STATUS.adoc | 101 - czech-file-knife/docs/status/READINESS.adoc | 186 - czech-file-knife/docs/status/ROADMAP.adoc | 34 - czech-file-knife/docs/status/TEST-NEEDS.adoc | 126 - czech-file-knife/docs/theory/README.adoc | 3 - .../docs/theory/computing/README.adoc | 3 - .../docs/theory/formalisms/README.adoc | 3 - .../docs/theory/mathematics/README.adoc | 3 - .../docs/theory/ontologies/README.adoc | 3 - .../docs/theory/other/README.adoc | 3 - .../docs/theory/socio-technical/README.adoc | 3 - czech-file-knife/docs/troubleshooting.adoc | 58 - czech-file-knife/docs/usage.adoc | 82 - czech-file-knife/docs/whitepapers/README.adoc | 3 - .../docs/whitepapers/academic/README.adoc | 3 - .../docs/whitepapers/industry/README.adoc | 3 - .../docs/whitepapers/outreach/README.adoc | 19 - czech-file-knife/docs/wikis/README.md | 17 - czech-file-knife/examples/README.adoc | 3 - czech-file-knife/examples/sample-manifest.ncl | 37 - .../examples/web-project-deno.json | 20 - czech-file-knife/features/README.adoc | 3 - .../features/boj-server/README.adoc | 16 - .../features/panic-attacker/README.adoc | 27 - czech-file-knife/features/ssg/README.adoc | 3 - .../features/ssg/ssg-bootstrap.sh | 90 - czech-file-knife/mise.toml | 67 - .../scripts/campaigns/www-bundle.campaign | 58 - .../scripts/check-action-pinning.js | 105 - .../scripts/check-adoc-renders.sh | 89 - .../scripts/check-invisible-characters.sh | 72 - czech-file-knife/scripts/check-lock-sync.sh | 297 - .../scripts/check-no-md-in-docs.sh | 65 - .../scripts/check-no-placeholders.sh | 178 - czech-file-knife/scripts/check-no-vlang.sh | 87 - czech-file-knife/scripts/check-proofs.sh | 183 - czech-file-knife/scripts/check-root-shape.sh | 156 - .../scripts/check-template-conformance.sh | 263 - .../scripts/check-variant-drift.sh | 122 - czech-file-knife/scripts/gen-repo-map.sh | 130 - czech-file-knife/scripts/invariant-path.sh | 33 - .../scripts/migrate-wellknown-to-www.sh | 246 - .../scripts/prune-dependabot-ecosystems.rs | 133 - czech-file-knife/scripts/rsr-campaign.sh | 415 -- czech-file-knife/scripts/rust-tool.sh | 51 - czech-file-knife/scripts/scan-dangerous.sh | 74 - .../scripts/strip-instruction-blocks.rs | 171 - czech-file-knife/scripts/sweep-wellknown.sh | 421 -- .../scripts/validate-session-contracts.sh | 34 - czech-file-knife/scripts/validate-template.sh | 491 -- czech-file-knife/session/README.adoc | 50 - czech-file-knife/session/custom-checks.k9.ncl | 51 - czech-file-knife/session/dispatch.sh | 139 - czech-file-knife/session/local-hooks.sh | 23 - czech-file-knife/sonar-project.properties | 26 - czech-file-knife/src/README.adoc | 3 - czech-file-knife/src/aspects/README.adoc | 56 - .../src/aspects/integrity/README.adoc | 3 - .../src/aspects/observability/README.adoc | 3 - .../src/aspects/security/README.adoc | 3 - czech-file-knife/src/bridges/.gitkeep | 0 czech-file-knife/src/cfk-cache/Cargo.toml | 36 - .../src/cfk-cache/src/blob_store.rs | 450 -- czech-file-knife/src/cfk-cache/src/lib.rs | 178 - .../src/cfk-cache/src/metadata_cache.rs | 534 -- czech-file-knife/src/cfk-cache/src/policy.rs | 423 -- .../src/cfk-cache/src/sled_backend.rs | 76 - czech-file-knife/src/cfk-cli/Cargo.toml | 59 - czech-file-knife/src/cfk-cli/src/commands.rs | 482 -- czech-file-knife/src/cfk-cli/src/main.rs | 184 - czech-file-knife/src/cfk-core/Cargo.toml | 21 - czech-file-knife/src/cfk-core/src/backend.rs | 124 - czech-file-knife/src/cfk-core/src/entry.rs | 117 - czech-file-knife/src/cfk-core/src/error.rs | 148 - czech-file-knife/src/cfk-core/src/lib.rs | 20 - czech-file-knife/src/cfk-core/src/metadata.rs | 61 - .../src/cfk-core/src/operations.rs | 42 - czech-file-knife/src/cfk-core/src/path.rs | 235 - czech-file-knife/src/cfk-core/src/platform.rs | 220 - .../src/cfk-core/src/reversible.rs | 740 -- .../src/cfk-integrations/Cargo.toml | 26 - .../src/cfk-integrations/src/agrep.rs | 102 - .../src/cfk-integrations/src/aria2.rs | 77 - .../src/cfk-integrations/src/lib.rs | 64 - .../src/cfk-integrations/src/pandoc.rs | 111 - czech-file-knife/src/cfk-ios/Cargo.toml | 52 - czech-file-knife/src/cfk-ios/src/domain.rs | 195 - czech-file-knife/src/cfk-ios/src/error.rs | 139 - czech-file-knife/src/cfk-ios/src/ffi.rs | 475 -- czech-file-knife/src/cfk-ios/src/item.rs | 342 - czech-file-knife/src/cfk-ios/src/lib.rs | 92 - czech-file-knife/src/cfk-ios/src/provider.rs | 499 -- .../src/cfk-ios/swift/CfkBridge.h | 239 - .../swift/CfkFileProviderExtension.swift | 337 - .../cfk-ios/swift/CfkFileProviderItem.swift | 243 - czech-file-knife/src/cfk-providers/Cargo.toml | 43 - czech-file-knife/src/cfk-providers/src/afs.rs | 531 -- .../src/cfk-providers/src/box_com.rs | 722 -- .../src/cfk-providers/src/ceph.rs | 455 -- .../src/cfk-providers/src/dropbox.rs | 583 -- .../src/cfk-providers/src/gdrive.rs | 732 -- .../src/cfk-providers/src/ipfs.rs | 803 -- czech-file-knife/src/cfk-providers/src/lib.rs | 138 - .../src/cfk-providers/src/local.rs | 485 -- czech-file-knife/src/cfk-providers/src/nfs.rs | 390 - .../src/cfk-providers/src/ninep.rs | 961 --- .../src/cfk-providers/src/onedrive.rs | 621 -- .../src/cfk-providers/src/protocols.rs | 266 - czech-file-knife/src/cfk-providers/src/s3.rs | 712 -- .../src/cfk-providers/src/sftp.rs | 326 - czech-file-knife/src/cfk-providers/src/smb.rs | 495 -- .../src/cfk-providers/src/syncthing.rs | 532 -- .../src/cfk-providers/src/transport.rs | 308 - .../src/cfk-providers/src/webdav.rs | 612 -- czech-file-knife/src/cfk-search/Cargo.toml | 30 - czech-file-knife/src/cfk-search/src/lib.rs | 185 - czech-file-knife/src/cfk-tui/cfk_tui.gpr | 38 - .../src/cfk-tui/src/cfk-tui-application.ads | 39 - .../src/cfk-tui/src/cfk_tui_main.adb | 36 - czech-file-knife/src/cfk-vfs/Cargo.toml | 29 - czech-file-knife/src/cfk-vfs/src/lib.rs | 138 - czech-file-knife/src/contracts/README.adoc | 3 - czech-file-knife/src/core/.gitkeep | 0 czech-file-knife/src/definitions/README.adoc | 3 - czech-file-knife/src/errors/README.adoc | 3 - czech-file-knife/tests/aspect_tests.sh | 134 - czech-file-knife/tests/e2e.sh | 64 - czech-file-knife/tests/e2e/julia_mint_test.sh | 183 - .../tests/e2e/template_instantiation_test.sh | 422 -- czech-file-knife/tests/fuzz/Cargo.toml | 24 - czech-file-knife/tests/fuzz/README.adoc | 112 - .../tests/fuzz/fuzz_targets/fuzz_path.rs | 33 - .../tests/invisible-characters-test.sh | 101 - .../tests/shape/check_root_shape_test.sh | 121 - .../tests/shape/repo_map_determinism_test.sh | 46 - .../workflows/check_adoc_renders_test.sh | 126 - .../tests/workflows/check_no_vlang_test.sh | 61 - .../tests/workflows/k9_typecheck_test.sh | 17 - .../tests/workflows/mint_cleanup_test.sh | 39 - .../tests/workflows/session_contracts_test.sh | 25 - .../workflows/template_conformance_test.sh | 147 - .../workflows/validate_workflows_test.sh | 144 - czech-file-knife/verification/README.adoc | 3 - .../verification/benchmarks/README.adoc | 3 - .../verification/coverage/README.adoc | 3 - .../verification/fuzzing/README.adoc | 3 - .../verification/proofs/README.adoc | 60 - .../verification/proofs/agda/MANIFEST | 4 - .../verification/proofs/agda/Properties.agda | 37 - .../verification/proofs/coq/MANIFEST | 4 - .../verification/proofs/coq/TypeSafety.v | 73 - .../proofs/idris2/ABI/Compliance.idr | 41 - .../proofs/idris2/ABI/Foreign.idr | 53 - .../verification/proofs/idris2/ABI/Layout.idr | 63 - .../proofs/idris2/ABI/Platform.idr | 63 - .../proofs/idris2/ABI/Pointers.idr | 52 - .../verification/proofs/idris2/MANIFEST | 10 - .../verification/proofs/idris2/Types.idr | 40 - .../verification/proofs/lean4/ApiTypes.lean | 45 - .../verification/proofs/lean4/MANIFEST | 4 - .../verification/proofs/lean4/lean-toolchain | 1 - .../proofs/tlaplus/StateMachine.tla | 91 - .../verification/safety_case/README.adoc | 3 - .../verification/simulations/README.adoc | 3 - .../verification/tests/README.adoc | 3 - .../verification/traceability/README.adoc | 3 - czech-file-knife/www/.well-known/ai.txt | 17 - czech-file-knife/www/.well-known/aibdp.json | 79 - .../www/.well-known/aibdp.json.license | 2 - czech-file-knife/www/.well-known/humans.txt | 14 - czech-file-knife/www/.well-known/security.txt | 14 - czech-file-knife/www/README.adoc | 117 - czech-file-knife/www/dns/bind9/README.adoc | 56 - .../www/dns/bind9/named.conf.example | 72 - .../www/dns/privacy/ENCRYPTED-DNS.adoc | 97 - .../www/dns/records/2.0.192.in-addr.arpa.zone | 14 - czech-file-knife/www/dns/records/README.adoc | 43 - .../www/dns/records/example.invalid.zone | 38 - czech-file-knife/www/errors/403.html | 15 - czech-file-knife/www/errors/404.html | 15 - czech-file-knife/www/errors/429.html | 15 - czech-file-knife/www/errors/500.html | 15 - czech-file-knife/www/errors/502.html | 15 - czech-file-knife/www/errors/503.html | 15 - .../www/policies/acceptable-use.adoc | 30 - czech-file-knife/www/policies/ai-use.adoc | 36 - czech-file-knife/www/policies/privacy.adoc | 33 - czech-file-knife/www/profiles/README.adoc | 69 - .../www/profiles/authoritative-dns.toml | 14 - .../www/profiles/baseline-site.toml | 10 - .../www/profiles/consent-aware-web.toml | 18 - .../www/profiles/full-expert.toml | 15 - .../www/profiles/privacy-enhanced.toml | 14 - czech-file-knife/www/public/index.html | 37 - czech-file-knife/www/public/styles/site.css | 10 - .../www/runbooks/cert-rotation.adoc | 45 - czech-file-knife/www/runbooks/deploy.adoc | 60 - czech-file-knife/www/runbooks/dns-change.adoc | 65 - czech-file-knife/www/runbooks/rollback.adoc | 43 - .../www/runbooks/stage5-wellknown-sweep.adoc | 157 - .../www/schemas/aibdp-schema-v0.2.json | 377 - .../schemas/aibdp-schema-v0.2.json.license | 2 - .../www/schemas/publishable-paths.txt | 20 - .../www/security_headers/README.adoc | 32 - .../www/security_headers/csp.conf | 23 - czech-file-knife/www/tests/check-aibdp.sh | 106 - .../www/tests/check-bind-safety.sh | 128 - czech-file-knife/www/tests/check-migration.sh | 214 - czech-file-knife/www/tests/check-profiles.sh | 94 - .../www/tests/check-publication-boundary.sh | 150 - czech-file-knife/www/tests/check-wellknown.sh | 57 - .../controls/aibdp-bad-status.control.json | 7 - .../aibdp-bad-status.control.json.license | 2 - .../controls/aibdp-enforce-430.control.json | 8 - .../aibdp-enforce-430.control.json.license | 2 - .../tests/controls/bad-deploy/dns/named.conf | 1 - .../www/tests/controls/bad-deploy/index.html | 1 - .../controls/bad-deploy/tests/run-all.sh | 2 - .../controls/caddy-serve-everything.control | 7 - .../good-deploy/.well-known/security.txt | 2 - .../controls/good-deploy/errors/404.html | 1 - .../www/tests/controls/good-deploy/index.html | 1 - .../good-deploy/policies/privacy.html | 1 - .../named.conf.open-recursion.control | 20 - .../controls/profile-enforce-430.control.toml | 8 - .../profile-hidden-start.control.toml | 8 - czech-file-knife/www/tests/run-all.sh | 52 - czech-file-knife/www/tls/POLICY.adoc | 60 - czech-file-knife/www/webservers/README.adoc | 42 - .../www/webservers/apache/vhost.conf.example | 61 - .../www/webservers/caddy/Caddyfile.example | 40 - .../www/webservers/nginx/site.conf.example | 74 - 540 files changed, 8 insertions(+), 65400 deletions(-) delete mode 100644 czech-file-knife/.cicd-hygiene-allow delete mode 100644 czech-file-knife/.clinerules delete mode 100644 czech-file-knife/.clusterfuzzlite/Dockerfile delete mode 100644 czech-file-knife/.clusterfuzzlite/build.sh delete mode 100644 czech-file-knife/.clusterfuzzlite/project.yaml delete mode 100644 czech-file-knife/.cursorrules delete mode 100644 czech-file-knife/.devcontainer/Containerfile delete mode 100644 czech-file-knife/.devcontainer/README.adoc delete mode 100644 czech-file-knife/.devcontainer/devcontainer.json delete mode 100644 czech-file-knife/.editorconfig delete mode 100644 czech-file-knife/.envrc delete mode 100644 czech-file-knife/.gitattributes delete mode 100644 czech-file-knife/.github/CODEOWNERS delete mode 100644 czech-file-knife/.github/CODE_OF_CONDUCT.md delete mode 100644 czech-file-knife/.github/CONTRIBUTING.md delete mode 100644 czech-file-knife/.github/FUNDING.yml delete mode 100644 czech-file-knife/.github/GOVERNANCE.md delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/config.yml delete mode 100644 czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 czech-file-knife/.github/SECURITY.md delete mode 100644 czech-file-knife/.github/SUPPORT.md delete mode 100644 czech-file-knife/.github/copilot-instructions.md delete mode 100644 czech-file-knife/.github/copilot/coding-agent.yml delete mode 100644 czech-file-knife/.github/dependabot.yml delete mode 100755 czech-file-knife/.github/hooks/install.sh delete mode 100755 czech-file-knife/.github/hooks/pre-push delete mode 100755 czech-file-knife/.github/hooks/scan-secrets.sh delete mode 100755 czech-file-knife/.github/hooks/validate-deed.sh delete mode 100755 czech-file-knife/.github/hooks/validate-k9.sh delete mode 100644 czech-file-knife/.github/label-classifier.json delete mode 100644 czech-file-knife/.github/labels.json delete mode 100644 czech-file-knife/.github/pull_request_template.md delete mode 100644 czech-file-knife/.github/rulesets/Immutable-Tags.json delete mode 100644 czech-file-knife/.github/rulesets/README.adoc delete mode 100644 czech-file-knife/.github/rulesets/base.json delete mode 100644 czech-file-knife/.github/rulesets/branch-floor.json delete mode 100644 czech-file-knife/.github/scripts/classify-issue.jq delete mode 100644 czech-file-knife/.github/settings.yml delete mode 100644 czech-file-knife/.github/workflows/README.adoc delete mode 100644 czech-file-knife/.github/workflows/actions.lock delete mode 100644 czech-file-knife/.github/workflows/build-notification.yml delete mode 100644 czech-file-knife/.github/workflows/cflite_batch.yml delete mode 100644 czech-file-knife/.github/workflows/cflite_pr.yml delete mode 100644 czech-file-knife/.github/workflows/codeql.yml delete mode 100644 czech-file-knife/.github/workflows/deed-validate.yml delete mode 100644 czech-file-knife/.github/workflows/dependabot-automerge.yml delete mode 100644 czech-file-knife/.github/workflows/docker-build.yml delete mode 100644 czech-file-knife/.github/workflows/dogfood-gate.yml delete mode 100644 czech-file-knife/.github/workflows/dogfood-summary.yml delete mode 100644 czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml delete mode 100644 czech-file-knife/.github/workflows/e2e.yml.template delete mode 100644 czech-file-knife/.github/workflows/eclexiaiser-validate.yml delete mode 100644 czech-file-knife/.github/workflows/empty-linter.yml delete mode 100644 czech-file-knife/.github/workflows/estate-rules.yml delete mode 100644 czech-file-knife/.github/workflows/governance.yml delete mode 100644 czech-file-knife/.github/workflows/groove-check.yml delete mode 100644 czech-file-knife/.github/workflows/guix-policy.yml delete mode 100644 czech-file-knife/.github/workflows/hypatia-scan.yml delete mode 100644 czech-file-knife/.github/workflows/k9-validate.yml delete mode 100644 czech-file-knife/.github/workflows/label-triage.yml delete mode 100644 czech-file-knife/.github/workflows/labels.yml delete mode 100644 czech-file-knife/.github/workflows/lock-sync-gate.yml delete mode 100644 czech-file-knife/.github/workflows/main-estate-audit.yml delete mode 100644 czech-file-knife/.github/workflows/mirror.yml delete mode 100644 czech-file-knife/.github/workflows/ossf-best-practices.yml delete mode 100644 czech-file-knife/.github/workflows/pages.yml delete mode 100644 czech-file-knife/.github/workflows/push-email-notify.yml delete mode 100644 czech-file-knife/.github/workflows/quality.yml delete mode 100644 czech-file-knife/.github/workflows/release.yml delete mode 100644 czech-file-knife/.github/workflows/rsr-compliance-canary.yml delete mode 100644 czech-file-knife/.github/workflows/runtime-policy.yml delete mode 100644 czech-file-knife/.github/workflows/rust-ci.yml delete mode 100644 czech-file-knife/.github/workflows/scorecard.yml delete mode 100644 czech-file-knife/.github/workflows/secret-scanner.yml delete mode 100644 czech-file-knife/.github/workflows/security-policy.yml delete mode 100644 czech-file-knife/.github/workflows/sonarqube.yml delete mode 100644 czech-file-knife/.github/workflows/static-analysis-gate.yml delete mode 100644 czech-file-knife/.github/workflows/workflow-linter.yml delete mode 100644 czech-file-knife/.gitignore delete mode 100644 czech-file-knife/.gitleaksignore delete mode 100644 czech-file-knife/.gitmessage delete mode 100644 czech-file-knife/.hypatia-ignore delete mode 100644 czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml delete mode 100644 czech-file-knife/.machine_readable/PROVENANCE.a2ml delete mode 100644 czech-file-knife/.machine_readable/README.adoc delete mode 100644 czech-file-knife/.machine_readable/ai/AI.a2ml delete mode 100644 czech-file-knife/.machine_readable/ai/README.adoc delete mode 100644 czech-file-knife/.machine_readable/arrival-pack/README.adoc delete mode 100644 czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl delete mode 100644 czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl delete mode 100755 czech-file-knife/.machine_readable/arrival-pack/extract.sh delete mode 100755 czech-file-knife/.machine_readable/arrival-pack/generate.sh delete mode 100755 czech-file-knife/.machine_readable/arrival-pack/verify.sh delete mode 100644 czech-file-knife/.machine_readable/bot_directives/README.adoc delete mode 100644 czech-file-knife/.machine_readable/bot_directives/coverage.a2ml delete mode 100644 czech-file-knife/.machine_readable/bot_directives/debt.a2ml delete mode 100644 czech-file-knife/.machine_readable/bot_directives/methodology.a2ml delete mode 100644 czech-file-knife/.machine_readable/bot_directives/rra.a2ml delete mode 100644 czech-file-knife/.machine_readable/coaptation/README.adoc delete mode 100644 czech-file-knife/.machine_readable/coaptation/coapt.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/coaptation/coapt.ncl delete mode 100755 czech-file-knife/.machine_readable/coaptation/coapt.sh delete mode 100644 czech-file-knife/.machine_readable/coaptation/core/Coaptation.idr delete mode 100755 czech-file-knife/.machine_readable/coaptation/extract-clauses.sh delete mode 100755 czech-file-knife/.machine_readable/coaptation/extract-facts.sh delete mode 100644 czech-file-knife/.machine_readable/coaptation/grades.ncl delete mode 100644 czech-file-knife/.machine_readable/coaptation/receipts/latest.a2ml delete mode 100755 czech-file-knife/.machine_readable/coaptation/verify.sh delete mode 100644 czech-file-knife/.machine_readable/coaptation/witness-map.ncl delete mode 100644 czech-file-knife/.machine_readable/compliance/reuse/dep5 delete mode 100644 czech-file-knife/.machine_readable/compliance/rust/deny.toml delete mode 100644 czech-file-knife/.machine_readable/configs/README.adoc delete mode 100644 czech-file-knife/.machine_readable/configs/eclexiaiser.toml delete mode 100644 czech-file-knife/.machine_readable/configs/git-cliff/cliff.toml delete mode 100644 czech-file-knife/.machine_readable/configs/stapeln.toml delete mode 100644 czech-file-knife/.machine_readable/contractiles/INDEX.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/Justfile delete mode 100644 czech-file-knife/.machine_readable/contractiles/README.adoc delete mode 100644 czech-file-knife/.machine_readable/contractiles/_base.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/bust/bust.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/dust/dust.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/intend/intend.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/must/must.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml delete mode 100644 czech-file-knife/.machine_readable/contractiles/trust/trust.ncl delete mode 100644 czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/META.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/README.adoc delete mode 100644 czech-file-knife/.machine_readable/descriptiles/STATE.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml delete mode 100644 czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc delete mode 100644 czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml delete mode 100644 czech-file-knife/.machine_readable/integrations/groove.a2ml delete mode 100644 czech-file-knife/.machine_readable/integrations/proven.a2ml delete mode 100644 czech-file-knife/.machine_readable/integrations/verisimdb.a2ml delete mode 100644 czech-file-knife/.machine_readable/integrations/vexometer.a2ml delete mode 100644 czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore delete mode 100644 czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml delete mode 100644 czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml delete mode 100644 czech-file-knife/.machine_readable/policies/README.adoc delete mode 100644 czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml delete mode 100644 czech-file-knife/.machine_readable/root-allow.txt delete mode 100644 czech-file-knife/.machine_readable/rsr-profile.a2ml delete mode 100644 czech-file-knife/.machine_readable/scripts/forge/README.adoc delete mode 100755 czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh delete mode 100755 czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh delete mode 100644 czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc delete mode 100755 czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh delete mode 100644 czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh delete mode 100644 czech-file-knife/.machine_readable/scripts/verification/README.adoc delete mode 100644 czech-file-knife/.machine_readable/self-validating/README.adoc delete mode 100644 czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl delete mode 100644 czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl delete mode 100644 czech-file-knife/.mailmap delete mode 100644 czech-file-knife/.tool-versions delete mode 100644 czech-file-knife/.windsurfrules delete mode 100644 czech-file-knife/CHANGELOG.adoc delete mode 100644 czech-file-knife/CITATION.cff delete mode 100644 czech-file-knife/CLAUDE.md delete mode 100644 czech-file-knife/CONTRIBUTING.adoc delete mode 100644 czech-file-knife/Cargo.lock delete mode 100644 czech-file-knife/Cargo.toml delete mode 100644 czech-file-knife/GEMINI.md delete mode 100644 czech-file-knife/Justfile delete mode 100644 czech-file-knife/LICENSE delete mode 100644 czech-file-knife/LICENSES/CC-BY-SA-4.0.txt delete mode 100644 czech-file-knife/LICENSES/MPL-2.0.txt delete mode 100644 czech-file-knife/README.adoc delete mode 100644 czech-file-knife/benches/czech_file_knife_bench.rs delete mode 100755 czech-file-knife/benches/template_bench.sh delete mode 100644 czech-file-knife/build/container/.containerignore delete mode 100644 czech-file-knife/build/container/Containerfile delete mode 100644 czech-file-knife/build/container/README.adoc delete mode 100644 czech-file-knife/build/container/compose.example.yaml delete mode 100644 czech-file-knife/build/container/compose.yaml delete mode 100755 czech-file-knife/build/container/entrypoint.sh delete mode 100644 czech-file-knife/build/container/stapeln/.gatekeeper.yaml delete mode 100644 czech-file-knife/build/container/stapeln/compose.example.toml delete mode 100644 czech-file-knife/build/container/stapeln/compose.toml delete mode 100755 czech-file-knife/build/container/stapeln/ct-build.sh delete mode 100644 czech-file-knife/build/container/stapeln/deploy.k9.ncl delete mode 100644 czech-file-knife/build/container/stapeln/manifest.toml delete mode 100644 czech-file-knife/build/container/stapeln/rokur.toml delete mode 100644 czech-file-knife/build/container/stapeln/vordr.toml delete mode 100755 czech-file-knife/build/guix.scm delete mode 100644 czech-file-knife/build/just/assess.just delete mode 100644 czech-file-knife/build/just/container.just delete mode 100644 czech-file-knife/build/just/groove.just delete mode 100644 czech-file-knife/build/just/proofs.just delete mode 100644 czech-file-knife/build/just/repo-init.just delete mode 100644 czech-file-knife/build/just/validate.just delete mode 100644 czech-file-knife/build/packaging/arch/PKGBUILD delete mode 100644 czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec delete mode 100644 czech-file-knife/build/packaging/debian/control delete mode 100644 czech-file-knife/build/packaging/debian/rules delete mode 100644 czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml delete mode 100644 czech-file-knife/build/packaging/macports/Portfile delete mode 100644 czech-file-knife/build/packaging/rpm/czech-file-knife.spec delete mode 100644 czech-file-knife/build/packaging/scoop/czech-file-knife.json delete mode 100644 czech-file-knife/build/packaging/winget/czech-file-knife.yaml delete mode 100644 czech-file-knife/ci/.gitlab-ci.yml delete mode 100644 czech-file-knife/ci/.pre-commit-config.yaml delete mode 100644 czech-file-knife/ci/README.adoc delete mode 100644 czech-file-knife/coordination.k9.ncl delete mode 100644 czech-file-knife/czech-file-knife_chora.deed delete mode 100644 czech-file-knife/docs/AFFIRMATION.adoc delete mode 100644 czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc delete mode 100644 czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc delete mode 100644 czech-file-knife/docs/ARCHITECTURE.adoc delete mode 100644 czech-file-knife/docs/AUDIT.adoc delete mode 100644 czech-file-knife/docs/EXPLAINME.adoc delete mode 100644 czech-file-knife/docs/GOVERNANCE.adoc delete mode 100644 czech-file-knife/docs/MAINTAINERS.adoc delete mode 100644 czech-file-knife/docs/QUICKSTART.adoc delete mode 100644 czech-file-knife/docs/README.adoc delete mode 100644 czech-file-knife/docs/RSR-PHILOSOPHY.adoc delete mode 100644 czech-file-knife/docs/RSR_OUTLINE.adoc delete mode 100644 czech-file-knife/docs/STATE-VISUALIZER.adoc delete mode 100644 czech-file-knife/docs/architecture.adoc delete mode 100644 czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc delete mode 100644 czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc delete mode 100644 czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc delete mode 100644 czech-file-knife/docs/architecture/THREAT-MODEL.adoc delete mode 100644 czech-file-knife/docs/architecture/TOPOLOGY.adoc delete mode 100644 czech-file-knife/docs/attribution/CFK-CITATIONS.adoc delete mode 100644 czech-file-knife/docs/attribution/CITATIONS.adoc delete mode 100644 czech-file-knife/docs/attribution/CODEOWNERS.adoc delete mode 100644 czech-file-knife/docs/attribution/README.adoc delete mode 100644 czech-file-knife/docs/contributing.adoc delete mode 100644 czech-file-knife/docs/decisions/0000-template.adoc delete mode 100644 czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc delete mode 100644 czech-file-knife/docs/decisions/0001-template.adoc delete mode 100644 czech-file-knife/docs/decisions/0002-variant-contract.adoc delete mode 100644 czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc delete mode 100644 czech-file-knife/docs/decisions/README.adoc delete mode 100644 czech-file-knife/docs/developer/ABI-FFI-README.adoc delete mode 100644 czech-file-knife/docs/developer/IOS_INTEGRATION.adoc delete mode 100644 czech-file-knife/docs/developer/README.adoc delete mode 100644 czech-file-knife/docs/developer/invariant-path.adoc delete mode 100644 czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc delete mode 100644 czech-file-knife/docs/governance/CRG-CRITERIA.a2ml delete mode 100644 czech-file-knife/docs/governance/CRG-CRITERIA.adoc delete mode 100644 czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc delete mode 100644 czech-file-knife/docs/governance/README.adoc delete mode 100644 czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc delete mode 100644 czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc delete mode 100644 czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc delete mode 100644 czech-file-knife/docs/governance/TSDM.a2ml delete mode 100644 czech-file-knife/docs/governance/TSDM.adoc delete mode 100644 czech-file-knife/docs/governance/audit/README.adoc delete mode 100644 czech-file-knife/docs/governance/audit/compliance/README.adoc delete mode 100644 czech-file-knife/docs/governance/audit/effects/README.adoc delete mode 100644 czech-file-knife/docs/governance/audit/systems/README.adoc delete mode 100644 czech-file-knife/docs/governance/maintenance/README.adoc delete mode 100644 czech-file-knife/docs/governance/maintenance/adaptive/README.adoc delete mode 100644 czech-file-knife/docs/governance/maintenance/corrective/README.adoc delete mode 100644 czech-file-knife/docs/governance/maintenance/perfective/README.adoc delete mode 100644 czech-file-knife/docs/governance/planning/README.adoc delete mode 100644 czech-file-knife/docs/governance/planning/could/README.adoc delete mode 100644 czech-file-knife/docs/governance/planning/must/README.adoc delete mode 100644 czech-file-knife/docs/governance/planning/should/README.adoc delete mode 100644 czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt delete mode 100644 czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt delete mode 100644 czech-file-knife/docs/legal/PALIMPSEST.adoc delete mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc delete mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc delete mode 100644 czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc delete mode 100644 czech-file-knife/docs/onboarding/llm-warmup-dev.adoc delete mode 100644 czech-file-knife/docs/onboarding/llm-warmup-user.adoc delete mode 100644 czech-file-knife/docs/practice/AI-CONVENTIONS.adoc delete mode 100644 czech-file-knife/docs/practice/README.adoc delete mode 100644 czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc delete mode 100644 czech-file-knife/docs/practice/ci-cost-reduction.adoc delete mode 100644 czech-file-knife/docs/proposals/root-cleanup.adoc delete mode 100644 czech-file-knife/docs/standards/README.adoc delete mode 100644 czech-file-knife/docs/status/PROOF-NEEDS.adoc delete mode 100644 czech-file-knife/docs/status/PROOF-STATUS.adoc delete mode 100644 czech-file-knife/docs/status/READINESS.adoc delete mode 100644 czech-file-knife/docs/status/ROADMAP.adoc delete mode 100644 czech-file-knife/docs/status/TEST-NEEDS.adoc delete mode 100644 czech-file-knife/docs/theory/README.adoc delete mode 100644 czech-file-knife/docs/theory/computing/README.adoc delete mode 100644 czech-file-knife/docs/theory/formalisms/README.adoc delete mode 100644 czech-file-knife/docs/theory/mathematics/README.adoc delete mode 100644 czech-file-knife/docs/theory/ontologies/README.adoc delete mode 100644 czech-file-knife/docs/theory/other/README.adoc delete mode 100644 czech-file-knife/docs/theory/socio-technical/README.adoc delete mode 100644 czech-file-knife/docs/troubleshooting.adoc delete mode 100644 czech-file-knife/docs/usage.adoc delete mode 100644 czech-file-knife/docs/whitepapers/README.adoc delete mode 100644 czech-file-knife/docs/whitepapers/academic/README.adoc delete mode 100644 czech-file-knife/docs/whitepapers/industry/README.adoc delete mode 100644 czech-file-knife/docs/whitepapers/outreach/README.adoc delete mode 100644 czech-file-knife/docs/wikis/README.md delete mode 100644 czech-file-knife/examples/README.adoc delete mode 100644 czech-file-knife/examples/sample-manifest.ncl delete mode 100644 czech-file-knife/examples/web-project-deno.json delete mode 100644 czech-file-knife/features/README.adoc delete mode 100644 czech-file-knife/features/boj-server/README.adoc delete mode 100644 czech-file-knife/features/panic-attacker/README.adoc delete mode 100644 czech-file-knife/features/ssg/README.adoc delete mode 100755 czech-file-knife/features/ssg/ssg-bootstrap.sh delete mode 100644 czech-file-knife/mise.toml delete mode 100644 czech-file-knife/scripts/campaigns/www-bundle.campaign delete mode 100644 czech-file-knife/scripts/check-action-pinning.js delete mode 100755 czech-file-knife/scripts/check-adoc-renders.sh delete mode 100755 czech-file-knife/scripts/check-invisible-characters.sh delete mode 100755 czech-file-knife/scripts/check-lock-sync.sh delete mode 100644 czech-file-knife/scripts/check-no-md-in-docs.sh delete mode 100755 czech-file-knife/scripts/check-no-placeholders.sh delete mode 100755 czech-file-knife/scripts/check-no-vlang.sh delete mode 100755 czech-file-knife/scripts/check-proofs.sh delete mode 100755 czech-file-knife/scripts/check-root-shape.sh delete mode 100755 czech-file-knife/scripts/check-template-conformance.sh delete mode 100755 czech-file-knife/scripts/check-variant-drift.sh delete mode 100755 czech-file-knife/scripts/gen-repo-map.sh delete mode 100755 czech-file-knife/scripts/invariant-path.sh delete mode 100755 czech-file-knife/scripts/migrate-wellknown-to-www.sh delete mode 100644 czech-file-knife/scripts/prune-dependabot-ecosystems.rs delete mode 100644 czech-file-knife/scripts/rsr-campaign.sh delete mode 100644 czech-file-knife/scripts/rust-tool.sh delete mode 100755 czech-file-knife/scripts/scan-dangerous.sh delete mode 100644 czech-file-knife/scripts/strip-instruction-blocks.rs delete mode 100755 czech-file-knife/scripts/sweep-wellknown.sh delete mode 100644 czech-file-knife/scripts/validate-session-contracts.sh delete mode 100755 czech-file-knife/scripts/validate-template.sh delete mode 100644 czech-file-knife/session/README.adoc delete mode 100644 czech-file-knife/session/custom-checks.k9.ncl delete mode 100755 czech-file-knife/session/dispatch.sh delete mode 100755 czech-file-knife/session/local-hooks.sh delete mode 100644 czech-file-knife/sonar-project.properties delete mode 100644 czech-file-knife/src/README.adoc delete mode 100644 czech-file-knife/src/aspects/README.adoc delete mode 100644 czech-file-knife/src/aspects/integrity/README.adoc delete mode 100644 czech-file-knife/src/aspects/observability/README.adoc delete mode 100644 czech-file-knife/src/aspects/security/README.adoc delete mode 100644 czech-file-knife/src/bridges/.gitkeep delete mode 100644 czech-file-knife/src/cfk-cache/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-cache/src/blob_store.rs delete mode 100644 czech-file-knife/src/cfk-cache/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-cache/src/metadata_cache.rs delete mode 100644 czech-file-knife/src/cfk-cache/src/policy.rs delete mode 100644 czech-file-knife/src/cfk-cache/src/sled_backend.rs delete mode 100644 czech-file-knife/src/cfk-cli/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-cli/src/commands.rs delete mode 100644 czech-file-knife/src/cfk-cli/src/main.rs delete mode 100644 czech-file-knife/src/cfk-core/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-core/src/backend.rs delete mode 100644 czech-file-knife/src/cfk-core/src/entry.rs delete mode 100644 czech-file-knife/src/cfk-core/src/error.rs delete mode 100644 czech-file-knife/src/cfk-core/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-core/src/metadata.rs delete mode 100644 czech-file-knife/src/cfk-core/src/operations.rs delete mode 100644 czech-file-knife/src/cfk-core/src/path.rs delete mode 100644 czech-file-knife/src/cfk-core/src/platform.rs delete mode 100644 czech-file-knife/src/cfk-core/src/reversible.rs delete mode 100644 czech-file-knife/src/cfk-integrations/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-integrations/src/agrep.rs delete mode 100644 czech-file-knife/src/cfk-integrations/src/aria2.rs delete mode 100644 czech-file-knife/src/cfk-integrations/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-integrations/src/pandoc.rs delete mode 100644 czech-file-knife/src/cfk-ios/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-ios/src/domain.rs delete mode 100644 czech-file-knife/src/cfk-ios/src/error.rs delete mode 100644 czech-file-knife/src/cfk-ios/src/ffi.rs delete mode 100644 czech-file-knife/src/cfk-ios/src/item.rs delete mode 100644 czech-file-knife/src/cfk-ios/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-ios/src/provider.rs delete mode 100644 czech-file-knife/src/cfk-ios/swift/CfkBridge.h delete mode 100644 czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift delete mode 100644 czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift delete mode 100644 czech-file-knife/src/cfk-providers/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-providers/src/afs.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/box_com.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/ceph.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/dropbox.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/gdrive.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/ipfs.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/local.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/nfs.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/ninep.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/onedrive.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/protocols.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/s3.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/sftp.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/smb.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/syncthing.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/transport.rs delete mode 100644 czech-file-knife/src/cfk-providers/src/webdav.rs delete mode 100644 czech-file-knife/src/cfk-search/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-search/src/lib.rs delete mode 100644 czech-file-knife/src/cfk-tui/cfk_tui.gpr delete mode 100644 czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads delete mode 100644 czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb delete mode 100644 czech-file-knife/src/cfk-vfs/Cargo.toml delete mode 100644 czech-file-knife/src/cfk-vfs/src/lib.rs delete mode 100644 czech-file-knife/src/contracts/README.adoc delete mode 100644 czech-file-knife/src/core/.gitkeep delete mode 100644 czech-file-knife/src/definitions/README.adoc delete mode 100644 czech-file-knife/src/errors/README.adoc delete mode 100755 czech-file-knife/tests/aspect_tests.sh delete mode 100755 czech-file-knife/tests/e2e.sh delete mode 100644 czech-file-knife/tests/e2e/julia_mint_test.sh delete mode 100755 czech-file-knife/tests/e2e/template_instantiation_test.sh delete mode 100644 czech-file-knife/tests/fuzz/Cargo.toml delete mode 100644 czech-file-knife/tests/fuzz/README.adoc delete mode 100644 czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs delete mode 100755 czech-file-knife/tests/invisible-characters-test.sh delete mode 100755 czech-file-knife/tests/shape/check_root_shape_test.sh delete mode 100755 czech-file-knife/tests/shape/repo_map_determinism_test.sh delete mode 100755 czech-file-knife/tests/workflows/check_adoc_renders_test.sh delete mode 100755 czech-file-knife/tests/workflows/check_no_vlang_test.sh delete mode 100644 czech-file-knife/tests/workflows/k9_typecheck_test.sh delete mode 100644 czech-file-knife/tests/workflows/mint_cleanup_test.sh delete mode 100644 czech-file-knife/tests/workflows/session_contracts_test.sh delete mode 100755 czech-file-knife/tests/workflows/template_conformance_test.sh delete mode 100755 czech-file-knife/tests/workflows/validate_workflows_test.sh delete mode 100644 czech-file-knife/verification/README.adoc delete mode 100644 czech-file-knife/verification/benchmarks/README.adoc delete mode 100644 czech-file-knife/verification/coverage/README.adoc delete mode 100644 czech-file-knife/verification/fuzzing/README.adoc delete mode 100644 czech-file-knife/verification/proofs/README.adoc delete mode 100644 czech-file-knife/verification/proofs/agda/MANIFEST delete mode 100644 czech-file-knife/verification/proofs/agda/Properties.agda delete mode 100644 czech-file-knife/verification/proofs/coq/MANIFEST delete mode 100644 czech-file-knife/verification/proofs/coq/TypeSafety.v delete mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr delete mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr delete mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Layout.idr delete mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Platform.idr delete mode 100644 czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr delete mode 100644 czech-file-knife/verification/proofs/idris2/MANIFEST delete mode 100644 czech-file-knife/verification/proofs/idris2/Types.idr delete mode 100644 czech-file-knife/verification/proofs/lean4/ApiTypes.lean delete mode 100644 czech-file-knife/verification/proofs/lean4/MANIFEST delete mode 100644 czech-file-knife/verification/proofs/lean4/lean-toolchain delete mode 100644 czech-file-knife/verification/proofs/tlaplus/StateMachine.tla delete mode 100644 czech-file-knife/verification/safety_case/README.adoc delete mode 100644 czech-file-knife/verification/simulations/README.adoc delete mode 100644 czech-file-knife/verification/tests/README.adoc delete mode 100644 czech-file-knife/verification/traceability/README.adoc delete mode 100644 czech-file-knife/www/.well-known/ai.txt delete mode 100644 czech-file-knife/www/.well-known/aibdp.json delete mode 100644 czech-file-knife/www/.well-known/aibdp.json.license delete mode 100644 czech-file-knife/www/.well-known/humans.txt delete mode 100644 czech-file-knife/www/.well-known/security.txt delete mode 100644 czech-file-knife/www/README.adoc delete mode 100644 czech-file-knife/www/dns/bind9/README.adoc delete mode 100644 czech-file-knife/www/dns/bind9/named.conf.example delete mode 100644 czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc delete mode 100644 czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone delete mode 100644 czech-file-knife/www/dns/records/README.adoc delete mode 100644 czech-file-knife/www/dns/records/example.invalid.zone delete mode 100644 czech-file-knife/www/errors/403.html delete mode 100644 czech-file-knife/www/errors/404.html delete mode 100644 czech-file-knife/www/errors/429.html delete mode 100644 czech-file-knife/www/errors/500.html delete mode 100644 czech-file-knife/www/errors/502.html delete mode 100644 czech-file-knife/www/errors/503.html delete mode 100644 czech-file-knife/www/policies/acceptable-use.adoc delete mode 100644 czech-file-knife/www/policies/ai-use.adoc delete mode 100644 czech-file-knife/www/policies/privacy.adoc delete mode 100644 czech-file-knife/www/profiles/README.adoc delete mode 100644 czech-file-knife/www/profiles/authoritative-dns.toml delete mode 100644 czech-file-knife/www/profiles/baseline-site.toml delete mode 100644 czech-file-knife/www/profiles/consent-aware-web.toml delete mode 100644 czech-file-knife/www/profiles/full-expert.toml delete mode 100644 czech-file-knife/www/profiles/privacy-enhanced.toml delete mode 100644 czech-file-knife/www/public/index.html delete mode 100644 czech-file-knife/www/public/styles/site.css delete mode 100644 czech-file-knife/www/runbooks/cert-rotation.adoc delete mode 100644 czech-file-knife/www/runbooks/deploy.adoc delete mode 100644 czech-file-knife/www/runbooks/dns-change.adoc delete mode 100644 czech-file-knife/www/runbooks/rollback.adoc delete mode 100644 czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc delete mode 100644 czech-file-knife/www/schemas/aibdp-schema-v0.2.json delete mode 100644 czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license delete mode 100644 czech-file-knife/www/schemas/publishable-paths.txt delete mode 100644 czech-file-knife/www/security_headers/README.adoc delete mode 100644 czech-file-knife/www/security_headers/csp.conf delete mode 100755 czech-file-knife/www/tests/check-aibdp.sh delete mode 100755 czech-file-knife/www/tests/check-bind-safety.sh delete mode 100755 czech-file-knife/www/tests/check-migration.sh delete mode 100755 czech-file-knife/www/tests/check-profiles.sh delete mode 100755 czech-file-knife/www/tests/check-publication-boundary.sh delete mode 100755 czech-file-knife/www/tests/check-wellknown.sh delete mode 100644 czech-file-knife/www/tests/controls/aibdp-bad-status.control.json delete mode 100644 czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license delete mode 100644 czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json delete mode 100644 czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license delete mode 100644 czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf delete mode 100644 czech-file-knife/www/tests/controls/bad-deploy/index.html delete mode 100644 czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh delete mode 100644 czech-file-knife/www/tests/controls/caddy-serve-everything.control delete mode 100644 czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt delete mode 100644 czech-file-knife/www/tests/controls/good-deploy/errors/404.html delete mode 100644 czech-file-knife/www/tests/controls/good-deploy/index.html delete mode 100644 czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html delete mode 100644 czech-file-knife/www/tests/controls/named.conf.open-recursion.control delete mode 100644 czech-file-knife/www/tests/controls/profile-enforce-430.control.toml delete mode 100644 czech-file-knife/www/tests/controls/profile-hidden-start.control.toml delete mode 100755 czech-file-knife/www/tests/run-all.sh delete mode 100644 czech-file-knife/www/tls/POLICY.adoc delete mode 100644 czech-file-knife/www/webservers/README.adoc delete mode 100644 czech-file-knife/www/webservers/apache/vhost.conf.example delete mode 100644 czech-file-knife/www/webservers/caddy/Caddyfile.example delete mode 100644 czech-file-knife/www/webservers/nginx/site.conf.example diff --git a/.gitleaks.toml b/.gitleaks.toml index 271609429..406c8ab2b 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -41,10 +41,6 @@ paths = [ # named secret_value.toml — inputs to a manifest/invariant test. '''(^|/)shellstate/test/''', - # Documentation. The match is an illustrative connection string in a - # distributed-filesystem explainer. - '''(^|/)czech-file-knife/docs/''', - # A DETECTOR definition: the flagged line is the literal '-----BEGIN # PRIVATE KEY-----' inside a list of patterns this contract searches FOR. # The scanner matched the rule, not a key. diff --git a/.hypatia-baseline.json b/.hypatia-baseline.json index 32c3a354a..1522027b3 100644 --- a/.hypatia-baseline.json +++ b/.hypatia-baseline.json @@ -55,14 +55,6 @@ "note": "Translation target fixture: Python source used to demonstrate julianiser Python-to-Julia translation patterns. This file exists to be translated AWAY from Python, not as functional Python code.", "tracking_issue": "hyperpolymath/developer-ecosystem#111" }, - { - "severity": "high", - "rule_module": "cicd_rules", - "type": "banned_language_file", - "file_pattern": "czech-file-knife/cfk-ios/**", - "note": "cfk-ios is the iOS File Provider Extension for czech-file-knife; requires Swift for NSFileProviderReplicatedExtension (no Tauri/Dioxus equivalent for this specific iOS API).", - "tracking_issue": "hyperpolymath/developer-ecosystem#111" - }, { "severity": "high", "rule_module": "cicd_rules", diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9481df2b0..0a148241e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,14 @@ https://semver.org/spec/v2.0.0.html[Semantic Versioning]. === [Unreleased] +==== Removed + +* `czech-file-knife/` extracted to its own repository, + https://github.com/hyperpolymath/czech-file-knife[hyperpolymath/czech-file-knife] + (it was only held here). The standalone, RSR-template-compliant tree is the + `czech-file-knife/` prefix at commit `60ba3be3`; history is preserved + with `git subtree split --prefix=czech-file-knife`. + ==== Fixed * fix(licence): developer-ecosystem — clear scaffold-placeholder leak diff --git a/czech-file-knife/.cicd-hygiene-allow b/czech-file-knife/.cicd-hygiene-allow deleted file mode 100644 index 1513822b2..000000000 --- a/czech-file-knife/.cicd-hygiene-allow +++ /dev/null @@ -1,12 +0,0 @@ -# Code-hygiene gate allowlist (consumed by cicd-suite actions/code-hygiene-check). -# Patterns are git pathspec excludes, applied to both the debt-marker scan and -# the proof-circumvention scan. -# -# The two entries below are TEMPLATE SCAFFOLDS, not implementation debt: -# their TODOs are the instantiation seams every minted repo is meant to fill -# in (entrypoint command, e2e sections). Excluding them keeps the gate's -# debt-tracking signal clean for real source in instantiated repos, which -# inherit this file. If an instantiated repo later owns genuine debt in -# these paths, resolve or issue-link it there — do not widen this list. -build/container/entrypoint.sh -tests/e2e.sh diff --git a/czech-file-knife/.clinerules b/czech-file-knife/.clinerules deleted file mode 100644 index fb38d21d8..000000000 --- a/czech-file-knife/.clinerules +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# Authoritative source: docs/practice/AI-CONVENTIONS.adoc - -# STARTUP: Read the repo deed (*_chora.deed at the repo root) first, then .machine_readable/descriptiles/STATE.a2ml. - -# LICENSE -# All original code: MPL-2.0. -# Never AGPL-3.0. MPL-2.0 only as platform-required fallback. -# SPDX header required on every source file. -# Copyright: Jonathan D.A. Jewell (hyperpolymath) - -# STATE FILES (.machine_readable/ ONLY) -# Never create in repo root: STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, -# AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml. -# The .machine_readable/ directory is the single source of truth. - -# BANNED PATTERNS -# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO -# Haskell: unsafeCoerce, unsafePerformIO, undefined, error -# OCaml: Obj.magic, Obj.repr, Obj.obj -# Coq: Admitted -# Lean: sorry -# Rust: transmute (unless FFI with // SAFETY: comment) - -# JS/TS RUNTIMES — ordered preference, reach for the first that can do the job -# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) -# 1. Bun default for all new work; runs .ts directly, no build step -# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm -# 3. pnpm only where an upstream toolchain needs a node_modules layout -# 4. npm last resort; permitted, never preferred — a noted decision -# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is -# RETIRED: ReScript is no longer used in this estate, so that rule named a dead -# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. - -# BANNED LANGUAGES -# Go -> Rust -# Python -> Julia or Rust - -# CONTAINERS -# Runtime: Podman first. -# File: Containerfile (never Dockerfile). -# Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. - -# ABI/FFI -# This repo does not carry the estate Idris2/Zig ABI seam (not declared in -# .machine_readable/rsr-profile.a2ml). The only FFI is the C ABI in -# src/cfk-ios (Swift File Provider bridge). - -# BUILD: Use just (Justfile) for all tasks. -# STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/czech-file-knife/.clusterfuzzlite/Dockerfile b/czech-file-knife/.clusterfuzzlite/Dockerfile deleted file mode 100644 index bc3c04ea0..000000000 --- a/czech-file-knife/.clusterfuzzlite/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# ClusterFuzzLite build environment for czech-file-knife -FROM gcr.io/oss-fuzz-base/base-builder-rust@sha256:73c1d5648db54100639339d411a5d192cbc8bf413ee91e843a07cf6f0e319dc7 - -COPY . $SRC/czech-file-knife -WORKDIR $SRC/czech-file-knife - -COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/czech-file-knife/.clusterfuzzlite/build.sh b/czech-file-knife/.clusterfuzzlite/build.sh deleted file mode 100644 index 31b15f12e..000000000 --- a/czech-file-knife/.clusterfuzzlite/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -eu -# SPDX-License-Identifier: MPL-2.0 -# Build script for ClusterFuzzLite - -cd $SRC/czech-file-knife - -# Build fuzz targets using cargo-fuzz -cargo +nightly fuzz build --fuzz-dir tests/fuzz - -# Copy fuzz targets to $OUT -for target in $(cargo +nightly fuzz list --fuzz-dir tests/fuzz); do - cp ./target/x86_64-unknown-linux-gnu/release/$target $OUT/ -done diff --git a/czech-file-knife/.clusterfuzzlite/project.yaml b/czech-file-knife/.clusterfuzzlite/project.yaml deleted file mode 100644 index 77e9a1901..000000000 --- a/czech-file-knife/.clusterfuzzlite/project.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# ClusterFuzzLite configuration for czech-file-knife -language: rust diff --git a/czech-file-knife/.cursorrules b/czech-file-knife/.cursorrules deleted file mode 100644 index 82200daf6..000000000 --- a/czech-file-knife/.cursorrules +++ /dev/null @@ -1,53 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# Authoritative source: docs/practice/AI-CONVENTIONS.adoc - -# Read the repo deed (*_chora.deed in the repo root) FIRST: canonical file locations -# live in its (ply ...) canonical-locations clauses. - -# LICENSE -# All original code: MPL-2.0 (SPDX header required on every file). -# Copyright: Jonathan D.A. Jewell (hyperpolymath) - -# STATE FILES -# .deed metadata files go in .machine_readable/ ONLY. -# Never create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, or PLAYBOOK.a2ml. - -# BANNED PATTERNS -# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO -# Haskell: unsafeCoerce, unsafePerformIO, undefined, error -# OCaml: Obj.magic, Obj.repr, Obj.obj -# Coq: Admitted -# Lean: sorry -# Rust: transmute (unless FFI with // SAFETY: comment) - -# JS RUNTIMES — ordered preference, reach for the first that can do the job -# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) -# 1. Bun default for all new work; runs .ts directly, no build step -# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm -# 3. pnpm only where an upstream toolchain needs a node_modules layout -# 4. npm last resort; permitted, never preferred — a noted decision -# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is -# RETIRED: ReScript is no longer used in this estate, so that rule named a dead -# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. - -# BANNED LANGUAGES -# Go -> use Rust -# Python -> use Julia or Rust - -# CONTAINERS -# Runtime: Podman (never Docker) -# File: Containerfile (never Dockerfile) -# Base: cgr.dev/chainguard/wolfi-base:latest - -# ABI/FFI STANDARD -# No estate Idris2/Zig ABI seam in this repo (see rsr-profile.a2ml); -# the only FFI is the C ABI in src/cfk-ios (Swift bridge). - -# BUILD SYSTEM -# Use just (Justfile) for all build, test, lint, and format tasks. - -# CODE STYLE -# Use descriptive variable names. -# Annotate and document all files. -# Add SPDX-License-Identifier headers to every source file. diff --git a/czech-file-knife/.devcontainer/Containerfile b/czech-file-knife/.devcontainer/Containerfile deleted file mode 100644 index 2d7c5c7ff..000000000 --- a/czech-file-knife/.devcontainer/Containerfile +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Dev Container image for Czech File Knife -# Base: Chainguard Wolfi (minimal, supply-chain-secure) -# Build: podman build -t Czech File Knife-dev -f .devcontainer/Containerfile . -# `:latest` suits a dev container (rebuilt daily); pin by digest -# (...@sha256:) if you need a reproducible dev environment. - -FROM cgr.dev/chainguard/wolfi-base:latest - -# Install common development tools -RUN apk update && apk add --no-cache \ - bash \ - curl \ - git \ - openssh-client \ - ca-certificates \ - build-base \ - posix-libc-utils \ - shadow \ - && rm -rf /var/cache/apk/* - -# Create non-root dev user (matches devcontainer.json remoteUser) -RUN groupadd -g 1000 nonroot || true \ - && useradd -m -u 1000 -g 1000 -s /bin/bash nonroot || true - -# Set workspace directory -WORKDIR /workspaces/Czech File Knife - -# Default shell -ENV SHELL=/bin/bash - -USER nonroot diff --git a/czech-file-knife/.devcontainer/README.adoc b/czech-file-knife/.devcontainer/README.adoc deleted file mode 100644 index e23a08cfb..000000000 --- a/czech-file-knife/.devcontainer/README.adoc +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Dev Container Usage -:author: Jonathan D.A. Jewell - -== Overview - -This dev container uses `cgr.dev/chainguard/wolfi-base` with git, curl, bash, and just pre-installed. Dev container features add git, just, and nickel automatically. - -== VS Code (Local) - -. Install the *Dev Containers* extension (`ms-vscode-remote.remote-containers`). -. Set `dev.containers.dockerPath` to `podman` in VS Code settings. -. Open the repo folder, then choose **Reopen in Container** from the command palette. - -== GitHub Codespaces - -. From the repository on GitHub, click **Code > Codespaces > New codespace**. -. The container builds automatically from this configuration. - -== Gitpod - -. Prefix the repo URL with `https://gitpod.io/#` to launch a workspace. -. Gitpod reads `devcontainer.json` and builds the environment. - -== Customization - -Replace `Czech File Knife` placeholders in both `devcontainer.json` and `Containerfile` with your actual project name. Run `just deps` to verify the environment after first launch. diff --git a/czech-file-knife/.devcontainer/devcontainer.json b/czech-file-knife/.devcontainer/devcontainer.json deleted file mode 100644 index a50659a48..000000000 --- a/czech-file-knife/.devcontainer/devcontainer.json +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// Dev Container configuration for Czech File Knife -// Works with: VS Code Dev Containers, GitHub Codespaces, Gitpod -// Container runtime: Podman (recommended) or any OCI-compliant runtime -{ - "name": "Czech File Knife", - - "build": { - "dockerfile": "Containerfile", - "context": ".." - }, - - "features": { - "ghcr.io/devcontainers/features/git:1": { - "ppa": false, - "version": "latest" - }, - "ghcr.io/jdx/devcontainer-features/just:1": {}, - "ghcr.io/nickel-lang/devcontainer-feature:0": {} - }, - - "postCreateCommand": "just deps", - - "remoteUser": "nonroot", - - "containerEnv": { - "EDITOR": "code --wait", - "LANG": "C.UTF-8" - }, - - "customizations": { - "vscode": { - "extensions": [ - "EditorConfig.EditorConfig", - "eamodio.gitlens", - "streetsidesoftware.code-spell-checker", - "timonwong.shellcheck", - "tamasfe.even-better-toml", - "skellock.just", - "redhat.vscode-yaml", - "DavidAnson.vscode-markdownlint", - "asciidoctor.asciidoctor-vscode", - "usernamehw.errorlens" - ], - "settings": { - "editor.formatOnSave": true, - "editor.insertSpaces": true, - "editor.tabSize": 2, - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "[makefile]": { - "editor.insertSpaces": false - } - } - }, - "codespaces": { - "openFiles": [ - "README.adoc" - ] - } - }, - - "forwardPorts": [], - - "shutdownAction": "stopContainer" -} diff --git a/czech-file-knife/.editorconfig b/czech-file-knife/.editorconfig deleted file mode 100644 index a6faed49a..000000000 --- a/czech-file-knife/.editorconfig +++ /dev/null @@ -1,140 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Hyperpolymath estate canonical .editorconfig (standards#343 phase 1) -# Canon lives in czech-file-knife; do not add a per-repo name to this header. -# https://editorconfig.org - -root = true - -# --- Estate baseline ------------------------------------------------------- -[*] -charset = utf-8 -end_of_line = lf -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -# --- Prose: trailing whitespace is significant ----------------------------- -[*.md] -trim_trailing_whitespace = false - -[*.adoc] -trim_trailing_whitespace = false - -# --- 4-space languages ----------------------------------------------------- -[*.rs] -indent_size = 4 - -[*.zig] -indent_size = 4 - -[*.jl] -indent_size = 4 - -[*.c] -indent_size = 4 - -[*.h] -indent_size = 4 - -[*.php] -indent_size = 4 - -# --- 3-space languages (Ada / GNAT house style) ---------------------------- -[*.ada] -indent_size = 3 - -[*.adb] -indent_size = 3 - -[*.ads] -indent_size = 3 - -[*.gpr] -indent_size = 3 - -# --- 2-space languages (explicit; matches the [*] default) ----------------- -[*.hs] -indent_size = 2 - -[*.ex] -indent_size = 2 - -[*.exs] -indent_size = 2 - -[*.ncl] -indent_size = 2 - -[*.rkt] -indent_size = 2 - -[*.scm] -indent_size = 2 - -[*.idr] -indent_size = 2 - -[*.ipkg] -indent_size = 2 - -[*.k9] -indent_size = 2 - -[*.agda] -indent_size = 2 - -[*.lean] -indent_size = 2 - -[*.ebnf] -indent_size = 2 - -# --- Tab-mandatory formats ------------------------------------------------- - -# --- Task runners ---------------------------------------------------------- -[Justfile] -indent_style = space -indent_size = 4 - -[*.just] -indent_style = space -indent_size = 4 - -[Mustfile] -indent_style = space -indent_size = 4 - -# --- Platform-mandated line endings ---------------------------------------- -# Windows batch/PowerShell hosts require CRLF; keep in step with .gitattributes. -[*.bat] -end_of_line = crlf - -[*.cmd] -end_of_line = crlf - -[*.ps1] -end_of_line = crlf - -# --- Legacy / retired toolchains (retained for byte hygiene only) ---------- -# ReScript (LANGUAGE-POLICY 1.2) and Nix (retired 2026-06-01) are no longer -# adopted for new work. These sections are inert where the files are absent and -# keep surviving files from drifting. Removal is a per-repo judgement. - -[*.a2ml] -indent_size = 2 - -[*.go] -indent_style = tab - -[*.nix] -indent_size = 2 - -[*.res] -indent_size = 2 - -[*.resi] -indent_size = 2 - -[Makefile] -indent_style = tab diff --git a/czech-file-knife/.envrc b/czech-file-knife/.envrc deleted file mode 100644 index 6fe01c3d8..000000000 --- a/czech-file-knife/.envrc +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Activate development environment -# Install direnv: https://direnv.net/ - -# Load .tool-versions if asdf is available -if has mise; then - use mise -fi - -# Load Guix shell if build/guix.scm exists -if has guix && [ -f build/guix.scm ]; then - use guix -fi - -# Project environment variables -export PROJECT_NAME="Czech File Knife" -export RSR_TIER="infrastructure" -# Add non-secret project env vars above (e.g. DATABASE_URL, service endpoints). -# Real secrets belong in .env (gitignored) — never commit them to .envrc. - -# Source .env if it exists (gitignored) -dotenv_if_exists diff --git a/czech-file-knife/.gitattributes b/czech-file-knife/.gitattributes deleted file mode 100644 index 8578ef91b..000000000 --- a/czech-file-knife/.gitattributes +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Hyperpolymath estate canonical .gitattributes (standards#343 phase 1) -# Canon lives in czech-file-knife; do not add a per-repo name to this header. -# -# Only diff drivers that git actually ships are used here. MEASURED on git -# 2.47.3: diff=go and diff=zig are NOT drivers (git silently falls back to the -# default heuristic); the Go driver is named `golang`. - -* text=auto eol=lf - -# --- Source ---------------------------------------------------------------- -*.rs text eol=lf diff=rust -*.ex text eol=lf diff=elixir -*.exs text eol=lf diff=elixir -*.ada text eol=lf diff=ada -*.adb text eol=lf diff=ada -*.ads text eol=lf diff=ada -*.gpr text eol=lf diff=ada -*.go text eol=lf diff=golang -*.scm text eol=lf diff=scheme linguist-language=Scheme -*.rkt text eol=lf -*.jl text eol=lf -*.hs text eol=lf -*.zig text eol=lf -*.chpl text eol=lf -*.idr text eol=lf linguist-language=Idris -*.ipkg text eol=lf linguist-language=Idris -*.agda text eol=lf linguist-language=Agda -*.lagda text eol=lf linguist-language=Agda -*.lean text eol=lf -# `.v` is ambiguous (Coq / Verilog / V). This estate's `.v` files are Coq -# proofs; 49 repos currently mislabel them `linguist-language=V`. -*.v text eol=lf linguist-language=Coq -*.ncl text eol=lf -*.k9 text eol=lf linguist-language=Nickel -*.deed text eol=lf linguist-language=SCM -*.ebnf text eol=lf -*.js text eol=lf -*.sh text eol=lf diff=bash -*.bash text eol=lf diff=bash - -# --- Windows hosts require CRLF -------------------------------------------- -*.bat text eol=crlf -*.cmd text eol=crlf -*.ps1 text eol=crlf - -# --- Docs ------------------------------------------------------------------ -*.md text eol=lf diff=markdown -*.adoc text eol=lf -*.txt text eol=lf -*.tex text eol=lf diff=tex -*.bib text eol=lf diff=bibtex - -# --- Data / config --------------------------------------------------------- -*.json text eol=lf -*.jsonl text eol=lf -*.yaml text eol=lf -*.yml text eol=lf -*.toml text eol=lf -*.svg text eol=lf -*.csv text eol=lf -*.html text eol=lf diff=html -*.css text eol=lf diff=css - -# --- Repo control files ---------------------------------------------------- -.gitignore text eol=lf -.gitattributes text eol=lf -.editorconfig text eol=lf -.tool-versions text eol=lf -Justfile text eol=lf -*.just text eol=lf -Mustfile text eol=lf -Containerfile text eol=lf - -# --- Binary ---------------------------------------------------------------- -*.png binary -*.jpg binary -*.jpeg binary -*.gif binary -*.webp binary -*.ico binary -*.pdf binary -*.woff binary -*.woff2 binary -*.ttf binary -*.otf binary -*.eot binary -*.zip binary -*.tar binary -*.gz binary -*.xz binary -*.bz2 binary -*.so binary -*.dylib binary -*.dll binary -*.exe binary -*.wasm binary -*.rlib binary -*.beam binary - -# --- Generated lockfiles: no diff noise, not counted as source ------------- -Cargo.lock text eol=lf -diff linguist-generated=true -mix.lock text eol=lf -diff linguist-generated=true -bun.lock text eol=lf -diff linguist-generated=true -bun.lockb binary -diff linguist-generated=true -pnpm-lock.yaml text eol=lf -diff linguist-generated=true -package-lock.json text eol=lf -diff linguist-generated=true - -# --- Legacy / retired toolchains (byte hygiene only) ----------------------- -# ReScript is retired (LANGUAGE-POLICY 1.2); Nix was retired 2026-06-01. These -# lines keep surviving files normalised and keep retired tech out of the -# GitHub primary-language badge. Removal is a per-repo judgement, never a sweep. -*.res text eol=lf -*.resi text eol=lf -**/*.res linguist-detectable=false -*.nix text eol=lf -flake.lock text eol=lf -diff linguist-generated=true diff --git a/czech-file-knife/.github/CODEOWNERS b/czech-file-knife/.github/CODEOWNERS deleted file mode 100644 index 8d339b776..000000000 --- a/czech-file-knife/.github/CODEOWNERS +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# CODEOWNERS - Define code review assignments -# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# -# Replace hyperpolymath with your GitHub username or team - -# Default owners for everything -* @hyperpolymath - -# Security-sensitive files require explicit review -SECURITY.md @hyperpolymath -.github/workflows/ @hyperpolymath -Trustfile.a2ml @hyperpolymath -.machine_readable/ @hyperpolymath diff --git a/czech-file-knife/.github/CODE_OF_CONDUCT.md b/czech-file-knife/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 8082c512e..000000000 --- a/czech-file-knife/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,318 +0,0 @@ - -# Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in czech-file-knife a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -We recognise that a thriving open source community requires **psychological safety** — an environment where people can contribute, ask questions, make mistakes, and learn without fear of ridicule or retaliation. - ---- - -## Our Standards - -### Expected Behaviour - -The following behaviours contribute to a positive environment: - -**Communication** -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Giving and gracefully accepting constructive feedback -- Assuming good intent while addressing impact -- Communicating clearly and patiently, especially with newcomers - -**Collaboration** -- Focusing on what is best for the community -- Showing empathy and kindness toward other community members -- Being collaborative rather than competitive -- Mentoring and supporting less experienced contributors -- Celebrating others' contributions and successes - -**Professionalism** -- Accepting responsibility and apologising to those affected by our mistakes -- Learning from the experience and avoiding repetition -- Respecting others' time and attention -- Staying on topic in project spaces -- Following project guidelines and conventions - -**Accessibility** -- Using plain language and avoiding unnecessary jargon -- Providing alt text for images and transcripts for audio/video -- Being patient with those using assistive technologies -- Accommodating different communication styles and needs -- Recognising that not everyone communicates the same way - -### Unacceptable Behaviour - -The following behaviours are considered harassment and are unacceptable: - -**Harassment** -- The use of sexualised language or imagery, and sexual attention or advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Deliberate intimidation, stalking, or following (online or in-person) -- Unwelcome physical contact or simulated physical contact (e.g., emoji) -- Sustained disruption of talks, events, or online discussions - -**Discrimination** -- Discriminatory jokes and language -- Posting or threatening to post others' personally identifying information ("doxing") -- Advocating for, or encouraging, any of the above behaviour -- Microaggressions — subtle, often unintentional, discriminatory comments or actions - -**Professional Misconduct** -- Publishing others' private information without explicit permission -- Misrepresenting affiliation or contributions -- Plagiarism or claiming credit for others' work -- Retaliating against anyone who reports a Code of Conduct violation -- Other conduct which could reasonably be considered inappropriate in a professional setting - -### Grey Areas - -Some situations require judgement. When uncertain: - -- **Intent vs Impact**: Good intentions do not excuse harmful impact. Focus on making things right. -- **Power Dynamics**: Those with more power (maintainers, employers, experienced contributors) must be especially mindful of their impact. -- **Cultural Differences**: What's acceptable varies by culture. When in doubt, err on the side of caution and ask. -- **Humour**: Jokes at others' expense are rarely funny to everyone. Punch up, not down. - ---- - -## Scope - -This Code of Conduct applies within all community spaces, including: - -**Online Spaces** -- Repository discussions, issues, and pull/merge requests -- Project chat channels (Matrix, Discord, Slack, IRC) -- Mailing lists and forums -- Social media when representing the project -- Video calls and virtual meetings - -**In-Person Spaces** -- Conferences, meetups, and events -- Workshops and training sessions -- Any gathering where you represent the project - -**Representation** -This Code of Conduct also applies when an individual is officially representing the community in public spaces. Examples include: - -- Using an official project email address -- Posting via an official social media account -- Acting as an appointed representative at an event -- Speaking on behalf of the project - ---- - -## Enforcement - -### Reporting - -If you experience or witness unacceptable behaviour, or have any other concerns, please report it as soon as possible. - -**How to Report** - -| Method | Details | Best For | -|--------|---------|----------| -| **Email** | j.d.a.jewell@open.ac.uk | Detailed reports, sensitive matters | -| **Private Message** | Contact any maintainer directly | Quick questions, minor issues | - -**What to Include** - -- Your contact information (unless anonymous) -- Names/usernames of those involved -- Description of what happened -- When and where it occurred -- Any witnesses -- Any supporting evidence (screenshots, links) -- How you would like us to respond (if you have a preference) - -**What Happens Next** - -1. You will receive acknowledgment within **48 hours** -2. The Code of Conduct Committee will review the report -3. We may ask for additional information -4. We will determine appropriate action -5. We will inform you of the outcome (respecting others' privacy) - -### Confidentiality - -All reports will be handled with discretion: - -- Reporter identity is protected by default -- Details are shared only with those who need to know -- We will ask before naming you in any communication -- Anonymous reports are accepted and investigated - -### Conflicts of Interest - -If the Code of Conduct Committee are themselves involved in an incident: - -- They will recuse themselves from the process -- Another maintainer or external party will handle the report -- We will disclose any potential conflicts - ---- - -## Enforcement Guidelines - -The Code of Conduct Committee will follow these guidelines in determining consequences: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behaviour deemed unprofessional or unwelcome. - -**Consequence**: A private, written warning providing clarity around the nature of the violation and an explanation of why the behaviour was inappropriate. A public apology may be requested. - -**Duration**: Immediate - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of actions. - -**Consequence**: A warning with consequences for continued behaviour. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. - -**Duration**: 1-4 weeks - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including sustained inappropriate behaviour. - -**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. - -**Duration**: 1-6 months - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the community. - -**Duration**: Permanent (with appeal rights after 12 months) - -### Enforcement Across Perimeters - -This project uses the estate's **Tri-Perimeter Contribution Framework (TPCF)**, a -graduated trust model defined in `hyperpolymath/standards` -(`rhodium-standard-repositories/README.adoc`): - -- 🔒 **Perimeter 1 (Core)** — maintainers only; shell runtime, build systems -- 🧠 **Perimeter 2 (Expert)** — trusted contributors; protocol extensions, validators -- 🌱 **Perimeter 3 (Community)** — open to all; docs, tests, proposals - -For contributors with elevated access (Perimeter 2 or 1): - -| Level | Additional Consequence | -|-------|----------------------| -| Correction | Noted in contributor record | -| Warning | Access privileges may be temporarily reduced | -| Temporary Ban | Access reduced to Perimeter 3 for ban duration | -| Permanent Ban | All access revoked | - ---- - -## Appeals - -If you believe an enforcement decision was made in error: - -1. **Wait 7 days** after the decision (cooling-off period) -2. **Email** j.d.a.jewell@open.ac.uk with subject line "Appeal: [Original Report ID]" -3. **Explain** why you believe the decision should be reconsidered -4. **Provide** any new information not previously available - -**Appeals Process** - -- Appeals are reviewed by the Code of Conduct Committee, excluding anyone involved in the original -- You will receive a response within 14 days -- The appeals decision is final -- You may only appeal once per incident - -**Grounds for Appeal** - -- Procedural errors in the original investigation -- New evidence not previously available -- Disproportionate response to the violation -- Misunderstanding of facts - ---- - -## Supporting Those Who Report - -We are committed to supporting those who report violations: - -**We Will** -- Believe and take all reports seriously -- Respect your privacy and confidentiality preferences -- Keep you informed of progress (if you wish) -- Take steps to protect you from retaliation -- Provide resources if you need support - -**We Will Not** -- Require you to confront the person directly -- Dismiss reports without investigation -- Reveal your identity without consent -- Tolerate retaliation against reporters -- Rush you to make decisions - ---- - -## Prevention - -Beyond enforcement, we actively work to prevent issues: - -**Onboarding** -- All contributors are expected to read this Code of Conduct -- Perimeter 2 applicants must confirm they've read and understood it -- Maintainers receive additional training on enforcement - -**Culture** -- We model the behaviour we expect -- We intervene early when we see potential issues -- We thank people for positive contributions -- We create opportunities for diverse voices - -**Review** -- This Code of Conduct is reviewed annually -- Community feedback is welcomed -- Changes are communicated clearly - ---- - -## Acknowledgments - -This Code of Conduct is adapted from: - -- [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1 -- [Django Code of Conduct](https://www.djangoproject.com/conduct/) -- [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) -- [Python Community Code of Conduct](https://www.python.org/psf/conduct/) - -We thank these communities for their leadership in creating welcoming spaces. - ---- - -## Questions? - -If you have questions about this Code of Conduct: - -- Open a [Discussion](https://github.com/hyperpolymath/czech-file-knife/discussions) (for general questions) -- Email j.d.a.jewell@open.ac.uk (for private questions) -- Contact any maintainer directly - ---- - -## Summary - -**Be kind. Be respectful. Be collaborative.** - -We're all here because we care about this project. Let's make it a place where everyone can do their best work. - ---- - -Last updated: 2026 · Based on Contributor Covenant 2.1 diff --git a/czech-file-knife/.github/CONTRIBUTING.md b/czech-file-knife/.github/CONTRIBUTING.md deleted file mode 100644 index b1c101ba8..000000000 --- a/czech-file-knife/.github/CONTRIBUTING.md +++ /dev/null @@ -1,103 +0,0 @@ - -``` -# Clone the repository -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife - -# Using Guix (recommended for reproducibility) -guix shell -D -f build/guix.scm - -# Or using toolbox/distrobox -toolbox create czech-file-knife-dev -toolbox enter czech-file-knife-dev -# Install dependencies manually - -# Verify setup -just check # or: cargo check / mix compile / etc. -just test # Run test suite -``` - -### Repository Structure - -The authoritative map is **generated** from the tree and checked in CI, so it -cannot drift: -[`docs/architecture/REPOSITORY-MAP.adoc`](../docs/architecture/REPOSITORY-MAP.adoc). -Regenerate it with `just repo-map`. - -A hand-written tree used to live here. It described `lib/`, `extensions/`, -`plugins/` and `spec/` directories that this repository has never contained, -which is precisely why the map is now generated rather than typed. - ---- - -## How to Contribute - -### Reporting Bugs - -**Before reporting**: -1. Search existing issues -2. Check if it's already fixed in `main` -3. Determine which perimeter the bug affects - -**When reporting**: - -Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: - -- Clear, descriptive title -- Environment details (OS, versions, toolchain) -- Steps to reproduce -- Expected vs actual behaviour -- Logs, screenshots, or minimal reproduction - -### Suggesting Features - -**Before suggesting**: -1. Check the [roadmap](../docs/status/ROADMAP.adoc) if available -2. Search existing issues and discussions -3. Consider which perimeter the feature belongs to - -**When suggesting**: - -Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) and include: - -- Problem statement (what pain point does this solve?) -- Proposed solution -- Alternatives considered -- Which perimeter this affects - -### Your First Contribution - -Look for issues labelled: - -- [`good first issue`](https://github.com/hyperpolymath/czech-file-knife/labels/good%20first%20issue) — Simple Perimeter 3 tasks -- [`help wanted`](https://github.com/hyperpolymath/czech-file-knife/labels/help%20wanted) — Community help needed -- [`documentation`](https://github.com/hyperpolymath/czech-file-knife/labels/documentation) — Docs improvements -- [`perimeter-3`](https://github.com/hyperpolymath/czech-file-knife/labels/perimeter-3) — Community sandbox scope - ---- - -## Development Workflow - -### Branch Naming -``` -docs/short-description # Documentation (P3) -test/what-added # Test additions (P3) -feat/short-description # New features (P2) -fix/issue-number-description # Bug fixes (P2) -refactor/what-changed # Code improvements (P2) -security/what-fixed # Security fixes (P1-2) -``` - -### Commit Messages - -We follow [Conventional Commits](https://www.conventionalcommits.org/): -``` -(): - -[optional body] - -[optional footer] -``` diff --git a/czech-file-knife/.github/FUNDING.yml b/czech-file-knife/.github/FUNDING.yml deleted file mode 100644 index 557149be4..000000000 --- a/czech-file-knife/.github/FUNDING.yml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 for code -# SPDX-License-Identifier: CC-BY-SA-4.0 for documentation -# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell - -# These are supported funding model platforms - -buy_me_a_coffee: jonathan.jewell -community_bridge: jonathan-jewell -github: hyperpolymath -issuehunt: hyperpolymath -ko_fi: hyperpolymath -lfx_crowdfunding: hyperpolymath -liberapay: hyperpolymath -open_collective: jonathan-jewell -patreon: cc_studio -polar: hyperpolymath -thanks_dev: hyperpolymath diff --git a/czech-file-knife/.github/GOVERNANCE.md b/czech-file-knife/.github/GOVERNANCE.md deleted file mode 100644 index ec74fec6a..000000000 --- a/czech-file-knife/.github/GOVERNANCE.md +++ /dev/null @@ -1,160 +0,0 @@ - -# Project Governance - -This document describes the governance model for **Czech File Knife**. - ---- - -## Project Governance Model - -Czech File Knife follows a **Benevolent Dictator For Life (BDFL)** governance model. -This model is well-suited for solo maintainers and small project teams where rapid, -consistent decision-making is more valuable than formal consensus processes. - -The BDFL has final authority on all project decisions, including technical direction, -release schedules, contributor access, and community standards. - -> **Transition clause:** When the core team exceeds three active maintainers, this -> project should transition to a **consensus-based governance model** with documented -> voting procedures. That transition should itself be recorded as an Architecture -> Decision Record (ADR) in `docs/decisions/`. - ---- - -## Decision Making - -### Day-to-day decisions - -- The BDFL makes final decisions on all matters. -- Routine decisions (bug fixes, dependency updates, minor improvements) may be made - by any maintainer with commit access. -- Maintainers are expected to use good judgement and seek input on non-trivial changes. - -### Proposing changes - -- Contributors can propose changes by opening issues or pull requests. -- Significant changes (new features, breaking changes, architectural shifts) should - be discussed in an issue before implementation begins. -- The BDFL will provide a clear accept/reject decision with reasoning. - -### Architecture Decision Records (ADRs) - -- Significant technical decisions are documented as ADRs in `docs/decisions/`. -- ADR statuses: `proposed`, `accepted`, `deprecated`, `superseded`, `rejected`. -- ADRs provide a historical record of why decisions were made and what alternatives - were considered. -- See `.machine_readable/descriptiles/META.a2ml` for the machine-readable ADR index. - ---- - -## Roles - -### BDFL (Benevolent Dictator For Life) - -- The project creator and ultimate decision-maker. -- Sets the project's technical direction and long-term vision. -- Has final say on all matters, including maintainer appointments and removals. -- Responsible for ensuring the project adheres to RSR standards. - -### Maintainer - -- Has commit access to the repository. -- Reviews and merges pull requests. -- Triages issues and manages releases. -- Upholds code quality, security standards, and the Code of Conduct. -- Listed in [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc). - -### Contributor - -- Anyone who submits pull requests, opens issues, or participates in discussions. -- Does not have direct commit access. -- Contributions are reviewed by maintainers before merging. -- All contributors must follow the [Code of Conduct](CODE_OF_CONDUCT.md). - -### Bot - -- Automated agents managed via your bot orchestration system. -- Perform automated code review, security scanning, dependency updates, and - standards enforcement. -- Bot actions are subject to the same quality and review standards as human - contributions. -- Configure your bots in `.machine_readable/bot_directives/`. - ---- - -## Becoming a Maintainer - -A contributor may be nominated to become a maintainer when they demonstrate: - -1. **Sustained quality contributions** -- a track record of well-crafted pull requests - that follow project conventions and require minimal revision. -2. **Understanding of RSR standards** -- familiarity with the Repository Structure - Requirements, security policies, and CI/CD workflows used across the project. -3. **Constructive participation** -- helpful issue triage, thoughtful code review - comments, and mentoring of other contributors. -4. **Reliability** -- consistent engagement over a meaningful period (typically 3+ - months of active contribution). - -### Process - -1. An existing maintainer nominates the candidate by opening a private discussion - with the BDFL. -2. The BDFL reviews the candidate's contribution history and community interactions. -3. The BDFL approves or declines the nomination, with reasoning provided to the - nominator. -4. If approved, the new maintainer is added to [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) and - granted appropriate repository access. - ---- - -## Removing a Maintainer - -A maintainer may be removed under the following circumstances: - -- **Inactivity**: No meaningful contributions or reviews for 12 or more consecutive - months. The maintainer will be contacted before removal and offered the option to - move to emeritus status voluntarily. -- **Code of Conduct violation**: Behaviour that violates the - [Code of Conduct](CODE_OF_CONDUCT.md), as determined through the enforcement - process described therein. -- **BDFL discretion**: The BDFL may remove a maintainer for other reasons (e.g., - repeated disregard for project standards, loss of trust). Reasoning will be - documented privately. - -Removed maintainers are moved to the Emeritus section of -[MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) unless removal was due to a serious Code of Conduct -violation. - ---- - -## Code of Conduct - -All participants in this project are expected to follow the -[Code of Conduct](CODE_OF_CONDUCT.md). The Code of Conduct applies to all project -spaces, including issues, pull requests, discussions, and any forum where the project -is represented. - -Enforcement of the Code of Conduct is described in that document. The BDFL serves as -the final arbiter in conduct disputes. - ---- - -## Amendments - -This governance document may be amended by the BDFL at any time. All amendments will -be: - -1. Documented as an ADR in `docs/decisions/` explaining the rationale for the change. -2. Committed to the repository with a clear commit message. -3. Communicated to existing maintainers and contributors via the project's usual - channels. - -Substantive changes (e.g., changing the governance model itself) should be discussed -with the community before adoption, even though the BDFL retains final authority. - ---- - -Copyright (c) 2026 hyperpolymath. Licensed under MPL-2.0. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 41699b61b..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: CC-BY-SA-4.0 -# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -name: Bug report -description: Something behaves differently from what it claims to do. -labels: ["bug", "needs-triage"] -body: - - type: markdown - attributes: - value: | - Please report what you **observed**, not what you inferred. A command and - its actual output is worth more than a description of the problem. - - type: textarea - id: what-happened - attributes: - label: What happened - description: The observed behaviour, with the exact command and its output. - placeholder: | - $ just verify - error: ... - render: shell - validations: - required: true - - type: textarea - id: expected - attributes: - label: What you expected instead - validations: - required: true - - type: textarea - id: repro - attributes: - label: Minimal reproduction - description: The shortest sequence that reproduces it from a clean checkout. - validations: - required: true - - type: input - id: version - attributes: - label: Version / commit - description: Output of `git rev-parse --short HEAD`. - validations: - required: true - - type: textarea - id: environment - attributes: - label: Environment - description: OS, and the output of `just --version` and `mise current` if relevant. - validations: - required: false - - type: checkboxes - id: checks - attributes: - label: Before submitting - options: - - label: I have reported observed output rather than a summary of it. - required: true - - label: This is not a security vulnerability (those go via a private advisory). - required: true diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 29c723403..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: CC-BY-SA-4.0 -# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -# -# Issue chooser configuration. Blank issues stay enabled so that reports which -# fit neither form are not silently discouraged. -blank_issues_enabled: true -contact_links: - - name: Security vulnerability - url: https://github.com/hyperpolymath/czech-file-knife/security/advisories/new - about: Report privately via a security advisory. Do NOT open a public issue. - - name: Question or support request - url: https://github.com/hyperpolymath/czech-file-knife/discussions - about: Ask a question. See .github/SUPPORT.md for what to expect. diff --git a/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml b/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 068c5c2f6..000000000 --- a/czech-file-knife/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: CC-BY-SA-4.0 -# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -name: Feature request -description: Propose a capability this project does not yet have. -labels: ["enhancement", "needs-triage"] -body: - - type: textarea - id: problem - attributes: - label: The problem - description: What are you unable to do today? Describe the situation, not the solution. - validations: - required: true - - type: textarea - id: proposal - attributes: - label: Proposed change - validations: - required: true - - type: textarea - id: alternatives - attributes: - label: Alternatives considered - description: Including doing nothing — say why that is insufficient. - validations: - required: false - - type: dropdown - id: scope - attributes: - label: Scope - description: Would this change the template's shape, and therefore every repo minted from it? - options: - - Repo-local — affects only this project - - Template-wide — would propagate to minted repos - - Not sure - validations: - required: true diff --git a/czech-file-knife/.github/SECURITY.md b/czech-file-knife/.github/SECURITY.md deleted file mode 100644 index 34cbd3cb2..000000000 --- a/czech-file-knife/.github/SECURITY.md +++ /dev/null @@ -1,389 +0,0 @@ - -# Security Policy - - - -We take security seriously. We appreciate your efforts to responsibly disclose vulnerabilities and will make every effort to acknowledge your contributions. - -## Table of Contents - -- [Reporting a Vulnerability](#reporting-a-vulnerability) -- [What to Include](#what-to-include) -- [Response Timeline](#response-timeline) -- [Disclosure Policy](#disclosure-policy) -- [Scope](#scope) -- [Safe Harbour](#safe-harbour) -- [Recognition](#recognition) -- [Security Updates](#security-updates) -- [Security Best Practices](#security-best-practices) - ---- - -## Reporting a Vulnerability - -### Preferred Method: GitHub Security Advisories - -The preferred method for reporting security vulnerabilities is through GitHub's Security Advisory feature: - -1. Navigate to [Report a Vulnerability](https://github.com/hyperpolymath/czech-file-knife/security/advisories/new) -2. Click **"Report a vulnerability"** -3. Complete the form with as much detail as possible -4. Submit — we'll receive a private notification - -This method ensures: - -- End-to-end encryption of your report -- Private discussion space for collaboration -- Coordinated disclosure tooling -- Automatic credit when the advisory is published - -### Alternative: Email - -If you cannot use GitHub Security Advisories, you may email us directly: - -| | | -|---|---| -| **Email** | j.d.a.jewell@open.ac.uk | - -This mailbox is not encrypted. For anything sensitive enough to need -encryption, prefer GitHub Security Advisories above — the report stays private -to the maintainers until an advisory is published. - -> **⚠️ Important:** Do not report security vulnerabilities through public GitHub issues, pull requests, discussions, or social media. - ---- - -## What to Include - -A good vulnerability report helps us understand and reproduce the issue quickly. - -### Required Information - -- **Description**: Clear explanation of the vulnerability -- **Impact**: What an attacker could achieve (confidentiality, integrity, availability) -- **Affected versions**: Which versions/commits are affected -- **Reproduction steps**: Detailed steps to reproduce the issue - -### Helpful Additional Information - -- **Proof of concept**: Code, scripts, or screenshots demonstrating the vulnerability -- **Attack scenario**: Realistic attack scenario showing exploitability -- **CVSS score**: Your assessment of severity (use [CVSS 3.1 Calculator](https://www.first.org/cvss/calculator/3.1)) -- **CWE ID**: Common Weakness Enumeration identifier if known -- **Suggested fix**: If you have ideas for remediation -- **References**: Links to related vulnerabilities, research, or advisories - -### Example Report Structure - -```markdown -## Summary -[One-sentence description of the vulnerability] - -## Vulnerability Type -[e.g., SQL Injection, XSS, SSRF, Path Traversal, etc.] - -## Affected Component -[File path, function name, API endpoint, etc.] - -## Affected Versions -[Version range or specific commits] - -## Severity Assessment -- CVSS 3.1 Score: [X.X] -- CVSS Vector: [CVSS:3.1/AV:X/AC:X/PR:X/UI:X/S:X/C:X/I:X/A:X] - -## Description -[Detailed technical description] - -## Steps to Reproduce -1. [First step] -2. [Second step] -3. [...] - -## Proof of Concept -[Code, curl commands, screenshots, etc.] - -## Impact -[What can an attacker achieve?] - -## Suggested Remediation -[Optional: your ideas for fixing] - -## References -[Links to related issues, CVEs, research] -``` - ---- - -## Response Timeline - -We commit to the following response times: - -| Stage | Timeframe | Description | -|-------|-----------|-------------| -| **Initial Response** | 48 hours | We acknowledge receipt and confirm we're investigating | -| **Triage** | 7 days | We assess severity, confirm the vulnerability, and estimate timeline | -| **Status Update** | Every 7 days | Regular updates on remediation progress | -| **Resolution** | 90 days | Target for fix development and release (complex issues may take longer) | -| **Disclosure** | 90 days | Public disclosure after fix is available (coordinated with you) | - -> **Note:** These are targets, not guarantees. Complex vulnerabilities may require more time. We'll communicate openly about any delays. - ---- - -## Disclosure Policy - -We follow **coordinated disclosure** (also known as responsible disclosure): - -1. **You report** the vulnerability privately -2. **We acknowledge** and begin investigation -3. **We develop** a fix and prepare a release -4. **We coordinate** disclosure timing with you -5. **We publish** security advisory and fix simultaneously -6. **You may publish** your research after disclosure - -### Our Commitments - -- We will not take legal action against researchers who follow this policy -- We will work with you to understand and resolve the issue -- We will credit you in the security advisory (unless you prefer anonymity) -- We will notify you before public disclosure -- We will publish advisories with sufficient detail for users to assess risk - -### Your Commitments - -- Report vulnerabilities promptly after discovery -- Give us reasonable time to address the issue before disclosure -- Do not access, modify, or delete data beyond what's necessary to demonstrate the vulnerability -- Do not degrade service availability (no DoS testing on production) -- Do not share vulnerability details with others until coordinated disclosure - -### Disclosure Timeline - -``` -Day 0 You report vulnerability -Day 1-2 We acknowledge receipt -Day 7 We confirm vulnerability and share initial assessment -Day 7-90 We develop and test fix -Day 90 Coordinated public disclosure - (earlier if fix is ready; later by mutual agreement) -``` - -If we cannot reach agreement on disclosure timing, we default to 90 days from your initial report. - ---- - -## Scope - -### In Scope ✅ - -The following are within scope for security research: - -- This repository (`hyperpolymath/czech-file-knife`) and all its code -- Official releases and packages published from this repository -- Documentation that could lead to security issues -- Build and deployment configurations in this repository -- Dependencies (report here, we'll coordinate with upstream) - -### Out of Scope ❌ - -The following are **not** in scope: - -- Third-party services we integrate with (report directly to them) -- Social engineering attacks against maintainers -- Physical security -- Denial of service attacks against production infrastructure -- Spam, phishing, or other non-technical attacks -- Issues already reported or publicly known -- Theoretical vulnerabilities without proof of concept - -### Qualifying Vulnerabilities - -We're particularly interested in: - -- Remote code execution -- SQL injection, command injection, code injection -- Authentication/authorisation bypass -- Cross-site scripting (XSS) and cross-site request forgery (CSRF) -- Server-side request forgery (SSRF) -- Path traversal / local file inclusion -- Information disclosure (credentials, PII, secrets) -- Cryptographic weaknesses -- Deserialisation vulnerabilities -- Memory safety issues (buffer overflows, use-after-free, etc.) -- Supply chain vulnerabilities (dependency confusion, etc.) -- Significant logic flaws - -### Non-Qualifying Issues - -The following generally do not qualify as security vulnerabilities: - -- Missing security headers on non-sensitive pages -- Clickjacking on pages without sensitive actions -- Self-XSS (requires victim to paste code) -- Missing rate limiting (unless it enables a specific attack) -- Username/email enumeration (unless high-risk context) -- Missing cookie flags on non-sensitive cookies -- Software version disclosure -- Verbose error messages (unless exposing secrets) -- Best practice deviations without demonstrable impact - ---- - -## Safe Harbour - -We support security research conducted in good faith. - -### Our Promise - -If you conduct security research in accordance with this policy: - -- ✅ We will not initiate legal action against you -- ✅ We will not report your activity to law enforcement -- ✅ We will work with you in good faith to resolve issues -- ✅ We consider your research authorised under the Computer Fraud and Abuse Act (CFAA), UK Computer Misuse Act, and similar laws -- ✅ We waive any potential claim against you for circumvention of security controls - -### Good Faith Requirements - -To qualify for safe harbour, you must: - -- Comply with this security policy -- Report vulnerabilities promptly -- Avoid privacy violations (do not access others' data) -- Avoid service degradation (no destructive testing) -- Not exploit vulnerabilities beyond proof-of-concept -- Not use vulnerabilities for profit (beyond bug bounties where offered) - -> **⚠️ Important:** This safe harbour does not extend to third-party systems. Always check their policies before testing. - ---- - -## Recognition - -We believe in recognising security researchers who help us improve. - -### Hall of Fame - -Researchers who report valid vulnerabilities will be acknowledged in our [Security Acknowledgments](SECURITY-ACKNOWLEDGMENTS.md) (unless they prefer anonymity). - -Recognition includes: - -- Your name (or chosen alias) -- Link to your website/profile (optional) -- Brief description of the vulnerability class -- Date of report - -### What We Offer - -- ✅ Public credit in security advisories -- ✅ Acknowledgment in release notes -- ✅ Entry in our Hall of Fame -- ✅ Reference/recommendation letter upon request (for significant findings) - -### What We Don't Currently Offer - -- ❌ Monetary bug bounties -- ❌ Hardware or swag -- ❌ Paid security research contracts - -> **Note:** We're a community project with limited resources. Your contributions help everyone who uses this software. - ---- - -## Security Updates - -### Receiving Updates - -To stay informed about security updates: - -- **Watch this repository**: Click "Watch" → "Custom" → Select "Security alerts" -- **GitHub Security Advisories**: Published at [Security Advisories](https://github.com/hyperpolymath/czech-file-knife/security/advisories) -- **Release notes**: Security fixes noted in [CHANGELOG](../CHANGELOG.md) - -### Update Policy - -| Severity | Response | -|----------|----------| -| **Critical/High** | Patch release as soon as fix is ready | -| **Medium** | Included in next scheduled release (or earlier) | -| **Low** | Included in next scheduled release | - -### Supported Versions - - - -| Version | Supported | Notes | -|---------|-----------|-------| -| `main` branch | ✅ Yes | Latest development | -| Latest release | ✅ Yes | Current stable | -| Previous minor release | ✅ Yes | Security fixes backported | -| Older versions | ❌ No | Please upgrade | - ---- - -## Security Best Practices - -When using czech-file-knife, we recommend: - -### General - -- Keep dependencies up to date -- Use the latest stable release -- Subscribe to security notifications -- Review configuration against security documentation -- Follow principle of least privilege - -### For Contributors - -- Never commit secrets, credentials, or API keys -- Use signed commits (`git config commit.gpgsign true`) -- Review dependencies before adding them -- Run security linters locally before pushing -- Report any concerns about existing code - ---- - -## Additional Resources - -- [Security Advisories](https://github.com/hyperpolymath/czech-file-knife/security/advisories) -- [Changelog](../CHANGELOG.md) -- [Contributing Guidelines](CONTRIBUTING.md) -- [CVE Database](https://cve.mitre.org/) -- [CVSS Calculator](https://www.first.org/cvss/calculator/3.1) - ---- - -## Contact - -| Purpose | Contact | -|---------|---------| -| **Security issues** | [Report via GitHub](https://github.com/hyperpolymath/czech-file-knife/security/advisories/new) or j.d.a.jewell@open.ac.uk | -| **General questions** | [GitHub Discussions](https://github.com/hyperpolymath/czech-file-knife/discussions) | -| **Other enquiries** | See [README](../README.adoc) for contact information | - ---- - -## Policy Changes - -This security policy may be updated from time to time. Significant changes will be: - -- Committed to this repository with a clear commit message -- Noted in the changelog -- Announced via GitHub Discussions (for major changes) - ---- - -*Thank you for helping keep czech-file-knife and its users safe.* 🛡️ - ---- - -Last updated: 2026 · Policy version: 1.0.0 diff --git a/czech-file-knife/.github/SUPPORT.md b/czech-file-knife/.github/SUPPORT.md deleted file mode 100644 index 1bf7f22e2..000000000 --- a/czech-file-knife/.github/SUPPORT.md +++ /dev/null @@ -1,7 +0,0 @@ -# Support - -For questions, help, and community discussion: - -- GitHub Discussions: https://github.com/hyperpolymath/czech-file-knife/discussions -- GitHub Issues: https://github.com/hyperpolymath/czech-file-knife/issues -- Documentation: See README.adoc in the root directory. diff --git a/czech-file-knife/.github/copilot-instructions.md b/czech-file-knife/.github/copilot-instructions.md deleted file mode 100644 index 4564efa76..000000000 --- a/czech-file-knife/.github/copilot-instructions.md +++ /dev/null @@ -1,85 +0,0 @@ - - - - -# Copilot Instructions - -## Before Writing Code - -- Read the repo deed (`*_chora.deed` in the repo root) for canonical file locations - (its `(ply ...)` clauses carry the canonical-locations tables). -- State files (.a2ml) live in `.machine_readable/` ONLY, never the root. - -## License - -- SPDX: `MPL-2.0` on all new files. -- Never use AGPL-3.0. -- Copyright: `Jonathan D.A. Jewell (hyperpolymath) ` - -## Code Style - -- Use descriptive variable names. -- Annotate and document all files. -- Add SPDX header to every source file. -- Use `just` for build/test/lint commands. - -## Banned Patterns - -- Idris2: no `believe_me`, no `assert_total` -- Haskell: no `unsafeCoerce`, no `unsafePerformIO` -- OCaml: no `Obj.magic` -- Coq: no `Admitted` -- Lean: no `sorry` -- Rust: no `transmute` unless FFI with `// SAFETY:` comment - -## JavaScript / TypeScript runtimes - -Ordered preference (`standards/3-practice/LANGUAGE-POLICY.adoc` §1) — reach for the first -that can do the job: - -1. **Bun** — default for all new work. Runs compiled ESM/JS directly, no bundler - step. Uses an npm-compatible `package.json` plus `bun.lock`; both are - expected, not anti-patterns. -2. **pnpm** — only where an upstream toolchain requires `node_modules`. -3. **npm** — last resort. Permitted, never preferred; a deliberate, noted choice. - -**Deno is being removed**, not grandfathered. Owner ruling 2026-08-26: *"deno is -to go and bun is the way we are going, put it first everywhere unless not -possible and explain why if not."* Existing Deno projects migrate to Bun; where -Bun genuinely cannot be used, document the reason in the repo. - -**TypeScript is not the language for new application code — AffineScript is.** -`LANGUAGE-POLICY.adoc` §1.2, ruled 2026-08-25, separates two questions the older -text ran together: *runtime* is Bun (where `.ts` runs at all, Bun runs it), while -the *language* target is AffineScript. TypeScript is permitted only where -AffineScript cannot reach — the same narrow, transitional carve-out JavaScript -holds for MCP protocol glue and runtime APIs. ReScript remains banned; its -migration destination is AffineScript. - -## Banned Languages - -- No Go (use Rust) -- No Python (use Julia or Rust) -- No Nix (use Guix) -- No Deno for new work — being removed estate-wide; use Bun (owner ruling 2026-08-26) -- No ReScript (`LANGUAGE-POLICY.adoc` §3) — migrate to AffineScript - -## Containers - -- Use Podman, never Docker. -- Name the file `Containerfile`, never `Dockerfile`. -- Base image: `cgr.dev/chainguard/wolfi-base:latest`. - -## ABI/FFI - -- This repo does not carry the Idris2/Zig ABI seam; the only FFI is the C ABI in `src/cfk-ios`. -- Cargo workspace crates live in `src/cfk-*`; every destructive operation must go through `ReversibleBackend`. - -## State Files - -Never create these in the repo root: -STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml. -They belong in `.machine_readable/` only. diff --git a/czech-file-knife/.github/copilot/coding-agent.yml b/czech-file-knife/.github/copilot/coding-agent.yml deleted file mode 100644 index a719a773b..000000000 --- a/czech-file-knife/.github/copilot/coding-agent.yml +++ /dev/null @@ -1,6 +0,0 @@ -mcp_servers: - boj-server: - command: npx - args: ["-y", "@hyperpolymath/boj-server@latest"] - env: - BOJ_URL: http://localhost:7700 diff --git a/czech-file-knife/.github/dependabot.yml b/czech-file-knife/.github/dependabot.yml deleted file mode 100644 index 39fb47669..000000000 --- a/czech-file-knife/.github/dependabot.yml +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dependabot configuration for RSR-compliant repositories -# Covers common ecosystems - remove unused ones for your project - -version: 2 -updates: - # GitHub Actions - always include - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - actions: - patterns: - - "*" - open-pull-requests-limit: 2 - - # Rust/Cargo - # - # `open-pull-requests-limit: 0` suppresses routine version-update PRs - # (no weekly patch-bump noise) while leaving Dependabot SECURITY PRs - # flowing. Under GitHub's current Dependabot behaviour (2024+), using - # an `ignore:` rule with `version-update:semver-patch` would ALSO - # silence security PRs that happen to be patch-level — historically - # the cause of estate-wide vulns sitting un-PR'd for weeks. - # `open-pull-requests-limit: 0` is the GitHub-endorsed way to say - # "security only, not routine bumps". Pair with the - # dependabot-automerge.yml workflow for low-touch security - # maintenance. - ignore: - # HOLD: github/codeql-action at v4.38.0 (SHA-pinned). v4.38.1 fails - # GitHub workflow-startup validation estate-wide (nexia-list#100; - # SHA-form re-bump bypassed versions-scoped ignores - see - # nexia-list#101/#104). Hold until upstream clears 4.38.1 or a - # newer release verifies green; revisit deliberately. - - dependency-name: "github/codeql-action" - - - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 diff --git a/czech-file-knife/.github/hooks/install.sh b/czech-file-knife/.github/hooks/install.sh deleted file mode 100755 index b853bb423..000000000 --- a/czech-file-knife/.github/hooks/install.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Point this clone's git hooks at .github/hooks/ so the local Dogfood Gate runs -# on push. Idempotent; safe to re-run. -set -euo pipefail -cd "$(git rev-parse --show-toplevel)" -git config core.hooksPath .github/hooks -git config commit.template .gitmessage -chmod +x .github/hooks/pre-push .github/hooks/validate-deed.sh .github/hooks/validate-k9.sh 2>/dev/null || true -echo "Installed: core.hooksPath -> .github/hooks (pre-push DEED+K9 gate active), commit.template -> .gitmessage." diff --git a/czech-file-knife/.github/hooks/pre-push b/czech-file-knife/.github/hooks/pre-push deleted file mode 100755 index 7e006867b..000000000 --- a/czech-file-knife/.github/hooks/pre-push +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# pre-push — local Dogfood Gate. -# -# Runs the SAME DEED + K9 validators the CI Dogfood Gate runs, but here on -# your machine before the push leaves, so format drift is caught in seconds -# instead of surfacing as red CI minutes later. The vendored validators in -# this directory are byte-identical to the pinned CI action scripts and are -# refreshed by the estate `refresh-githooks` sweep. -# -# Enable once per clone: -# git config core.hooksPath .github/hooks -# (or run: .github/hooks/install.sh) -# -# Override for an emergency push: git push --no-verify -# -# The DEED/K9 validators are skipped gracefully if their tooling is absent. -# The SECRET gate is not: see .github/hooks/scan-secrets.sh for why it fails closed. - -set -euo pipefail - -HOOK_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(git rev-parse --show-toplevel)" -status=0 - -# git feeds pre-push one " " -# line per ref on stdin. Capture it HERE, before any validator runs, and hand it -# to the scanners in K9_PUSH_RANGES so they can scan exactly the commits being -# pushed rather than the whole history. Guarded on `! -t 0`: when the hook is -# run by hand from a terminal there are no ref lines, and an unguarded `cat` -# would block forever on the tty. -K9_PUSH_RANGES="" -if [ ! -t 0 ]; then K9_PUSH_RANGES="$(cat)"; fi -export K9_PUSH_RANGES - -run() { - local label="$1" script="$2" - if [ ! -f "$HOOK_DIR/$script" ]; then - echo "[pre-push] ($label) validator missing: $script — skipping" >&2 - return 0 - fi - echo "[pre-push] $label…" - if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then - status=1 - fi -} - -# Same as run(), but a MISSING script is a failure rather than a skip. Used for -# the secret gate only: "validator absent" and "no secrets found" produce the -# same silence, so a skip here is a gate that reports success having examined -# nothing. The DEED/K9 validators keep the lenient behaviour — a missing -# formatter costs a red CI run, a missing secret scanner costs a leaked key. -run_required() { - local label="$1" script="$2" - if [ ! -f "$HOOK_DIR/$script" ]; then - echo "[pre-push] ($label) BLOCKED: required validator missing: $script" >&2 - echo "[pre-push] Restore it from the template, or override: git push --no-verify" >&2 - status=1 - return 0 - fi - echo "[pre-push] $label…" - if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then - status=1 - fi -} - -run "DEED manifests" "validate-deed.sh" -run "K9 contracts" "validate-k9.sh" -run_required "Secrets" "scan-secrets.sh" - -if [ "$status" -ne 0 ]; then - echo "" >&2 - echo "[pre-push] BLOCKED: validation failed (see errors above)." >&2 - echo "[pre-push] Fix the files, or override with: git push --no-verify" >&2 - exit 1 -fi - -echo "[pre-push] Dogfood Gate passed." -exit 0 diff --git a/czech-file-knife/.github/hooks/scan-secrets.sh b/czech-file-knife/.github/hooks/scan-secrets.sh deleted file mode 100755 index b26dd40c1..000000000 --- a/czech-file-knife/.github/hooks/scan-secrets.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# scan-secrets.sh — local pre-push secret gate (TruffleHog). -# -# ── Why this is NOT duplicated work ───────────────────────────────────────── -# The estate runs a TWO-TIER secret defence: -# -# CI gitleaks, via the standards `secret-scanner-reusable.yml` -# (gitleaks + rust-secrets + shell-secrets). Catches what reaches the -# remote, on every pull_request and push to main. -# LOCAL TruffleHog — this script — before the push leaves the machine. -# -# Different detection engines at different checkpoints. The reusable's header -# retires TruffleHog "as redundant with gitleaks"; that judgement is scoped to -# CI, where running both cost twice over one population. It is not a reason to -# leave the local tier unguarded. DO NOT delete this script as duplication — -# see .github/workflows/README.adoc, "Secret scanning is single-sourced". -# -# ── Fail closed ───────────────────────────────────────────────────────────── -# A missing or non-functional scanner BLOCKS the push and prints an install -# hint. The sibling validators skip gracefully when their tooling is absent; a -# SECRET gate must not, because "skipped" and "clean" are indistinguishable in -# the output, and the machines least likely to carry the toolchain are exactly -# the ones whose pushes nobody has vetted. -# The escape hatch is unchanged and documented: git push --no-verify -# -# ── Two measured instrument traps this script is written around ───────────── -# 1. TRUFFLEHOG EXITS 0 WHEN IT FINDS SECRETS. `--fail` is mandatory, and with -# it the found-secrets status is 183 — NOT 1. Every test here is for -# non-zero; never compare against 1. -# 2. PIPING A SCANNER DESTROYS ITS EXIT CODE — a pipeline reports the LAST -# command's status. Measured: `gitleaks … | tail` returns rc=0 while -# printing "leaks found: 1". Nothing here pipes the scanner; output is -# captured to a file and printed after the status is read. -# -# Env overrides: -# TRUFFLEHOG explicit path to the binary (used by the tests) -# K9_SECRETS_MAX_DEPTH commits to scan for a brand-new branch (default 500) -# K9_SECRETS_VERIFY=1 enable live credential verification (see below) - -set -euo pipefail - -REPO_ROOT="${INPUT_PATH:-$(git rev-parse --show-toplevel)}" -MAX_DEPTH="${K9_SECRETS_MAX_DEPTH:-500}" -ZERO="0000000000000000000000000000000000000000" - -# Verification OFF by default. `--no-verification` keeps the hook offline and -# fast, and — the real reason — verification transmits candidate credentials to -# the issuing provider's API on every push. A detected-but-unverified secret -# must still block, so verification buys only false-positive reduction at the -# cost of egress. Opt in with K9_SECRETS_VERIFY=1. -VERIFY_ARGS=(--no-verification) -if [ "${K9_SECRETS_VERIFY:-0}" = "1" ]; then VERIFY_ARGS=(); fi - -# ── The SonarCloud detector must be excluded, and why ─────────────────────── -# TruffleHog's SonarCloud detector matches ANY bare 40-hex string. This estate -# SHA-pins every GitHub Action by doctrine, so every pin is a 40-hex string and -# every pin reads as a secret. MEASURED on a clean clone of this template: -# 24 findings, 0 verified, 100% SonarCloud, 12 distinct values, ALL exactly -# 40 hex, all in .github/workflows/ — and .github/workflows/actions.lock -# carries one of them verbatim as `commit: 'sha1-ede1191ef…'`. -# Left in, the gate blocks every push on this repo and every inheritor of the -# template: a gate that always fires is uninstallable, and an uninstallable gate -# gets deleted. With the detector excluded the same history scans rc=0 (5,103 -# chunks, 0 findings), and a planted AWS/Slack credential still returns 183. -# -# What this gives up, and what covers it: a genuine SonarCloud token is also -# 40-hex, so it is indistinguishable from a pin BY SHAPE — no path or context -# filter recovers it. CI's gitleaks tier catches it instead; measured, gitleaks -# under the estate baseline (`useDefault = true`) flags a SonarCloud token as -# `generic-api-key`, rc=1. The two tiers are complementary here by design. -DETECTOR_ARGS=(--exclude-detectors SonarCloud) - -# ── Resolve the binary by INVOKING it ─────────────────────────────────────── -# `command -v` is not enough: a mise shim with no pinned global version sits on -# PATH, answers `command -v` happily, and fails every actual invocation with -# "No version is set for shim". Installed is not invokable. Each candidate is -# therefore probed with `--version` and only accepted on exit 0. -probe() { [ -n "${1:-}" ] && [ -x "$1" ] && "$1" --version >/dev/null 2>&1; } - -TH="" -if probe "${TRUFFLEHOG:-}"; then - TH="$TRUFFLEHOG" -elif c="$(command -v trufflehog 2>/dev/null)" && probe "$c"; then - TH="$c" -else - # mise installs tree, newest version first. MISE_DATA_DIR when exported, - # else mise's documented default. No machine-specific path is hardcoded. - _md="${MISE_DATA_DIR:-$HOME/.local/share/mise}" - while IFS= read -r c; do - if probe "$c"; then TH="$c"; break; fi - done < <(find "$_md/installs" -mindepth 3 -maxdepth 3 -type f -name trufflehog \ - -path '*trufflehog*' 2>/dev/null | sort -rV) -fi - -if [ -z "$TH" ]; then - cat >&2 <<'HINT' -[scan-secrets] BLOCKED: TruffleHog is not installed, or is a dead shim. - - This gate fails closed on purpose — a secret scanner that skips silently is - indistinguishable from one that found nothing. - - Install (mise, as used by this estate): - mise use -g aqua:trufflesecurity/trufflehog@3.96.0 - - Or point the hook at an existing binary: - TRUFFLEHOG=/path/to/trufflehog git push - - Emergency override (you are asserting the push carries no secrets): - git push --no-verify -HINT - exit 1 -fi - -# ── Build the scan ranges from the pre-push stdin lines ───────────────────── -# pre-push receives " " per ref -# and forwards them in K9_PUSH_RANGES. Scanning only the commits actually being -# pushed is both the correct population for this gate and far faster than the -# whole history (which CI already covers). -# ── Findings and scanner errors BOTH block, but are NOT the same thing ─────── -# TruffleHog returns 183 for "secrets found" and other non-zero codes for "I -# could not scan" (a broken ref, an unreadable repo, a bad flag). Both must fail -# closed — an unscanned push is exactly as unvetted as an unchecked one — but -# they must be REPORTED apart. Measured here: a stale remote ref pointing at -# 0000…0 made every scan exit 1 with "upload-pack: not our ref", and a handler -# that said "Secrets detected" for any non-zero told the developer to go hunting -# for a credential that did not exist. A gate that misnames its own failure -# sends people looking in the wrong place, which is how gates get switched off. -status=0 -scanned=0 -found=0 -errored=0 -out="$(mktemp)" -trap 'rm -f "$out"' EXIT - -# Run TruffleHog against a git ref/range with the configured verify/detector args. -# Takes a human-readable label ($1) and any extra trufflehog arguments, captures -# output to a file (not piped, to preserve the exit code), and sets the shared -# found/errored/status variables based on the result: 0 = clean, 183 = secrets -# found, anything else = scanner error. -scan() { - local label="$1"; shift - local rc=0 - # NOT piped — see trap 2 in the header. - "$TH" git "file://$REPO_ROOT" "$@" "${VERIFY_ARGS[@]}" "${DETECTOR_ARGS[@]}" \ - --fail --no-update >"$out" 2>&1 || rc=$? - scanned=$((scanned + 1)) - if [ "$rc" -eq 183 ]; then - echo "[scan-secrets] FINDINGS while scanning $label:" >&2 - cat "$out" >&2 - found=1 - status=1 - elif [ "$rc" -ne 0 ]; then - echo "[scan-secrets] SCANNER ERROR (exit $rc) while scanning $label —" >&2 - echo "[scan-secrets] this is a failure to scan, NOT a detected secret:" >&2 - cat "$out" >&2 - errored=1 - status=1 - fi -} - -if [ -n "${K9_PUSH_RANGES:-}" ]; then - while read -r local_ref local_sha remote_ref remote_sha; do - [ -z "${local_sha:-}" ] && continue - [ "$local_sha" = "$ZERO" ] && continue # branch deletion - if [ "${remote_sha:-$ZERO}" = "$ZERO" ]; then - scan "new branch ${local_ref:-HEAD} (last $MAX_DEPTH commits)" \ - --branch "$local_sha" --max-depth "$MAX_DEPTH" - else - scan "${local_ref:-HEAD} since ${remote_sha:0:12}" \ - --branch "$local_sha" --since-commit "$remote_sha" - fi - done <<< "$K9_PUSH_RANGES" -fi - -# No ranges (hook invoked directly, or an empty push): scan the bounded tail of -# HEAD rather than reporting a pass over nothing. A gate that reports success -# having examined zero commits is the vacuity this estate keeps re-learning. -if [ "$scanned" -eq 0 ]; then - scan "HEAD (no push ranges; last $MAX_DEPTH commits)" --max-depth "$MAX_DEPTH" -fi - -if [ "$found" -ne 0 ]; then - echo "[scan-secrets] Secrets detected in the commits being pushed." >&2 - echo "[scan-secrets] Remove them and rewrite the offending commits." >&2 - echo "[scan-secrets] Override (only if these are false positives): git push --no-verify" >&2 -fi - -if [ "$errored" -ne 0 ]; then - echo "[scan-secrets] TruffleHog could not complete the scan (see above)." >&2 - echo "[scan-secrets] BLOCKED because an unscanned push is an unvetted push." >&2 - echo "[scan-secrets] Common cause: a broken local ref. Check with: git fsck" >&2 - echo "[scan-secrets] Override (you are asserting this push carries no secrets):" >&2 - echo "[scan-secrets] git push --no-verify" >&2 -fi - -if [ "$status" -ne 0 ]; then - exit 1 -fi - -echo "[scan-secrets] TruffleHog clean ($scanned range(s) scanned, $("$TH" --version 2>&1 | head -1))." -exit 0 diff --git a/czech-file-knife/.github/hooks/validate-deed.sh b/czech-file-knife/.github/hooks/validate-deed.sh deleted file mode 100755 index 3973abe03..000000000 --- a/czech-file-knife/.github/hooks/validate-deed.sh +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# validate-deed.sh — DEED manifest validation script -# -# Scans for .a2ml and .deed files (dual-accept per owner ruling R-H2) and validates: -# 1. Required fields: agent-id or pedigree name, version -# 2. SPDX-License-Identifier header presence -# 3. Attestation block structure (if present) -# 4. Section heading syntax ([section] or ## section) -# -# Environment variables: -# INPUT_PATH — Directory to scan (default: .) -# INPUT_STRICT — Promote warnings to errors (default: false) -# -# Exit codes: -# 0 — All files valid (or only warnings in non-strict mode) -# 1 — Validation errors found - -set -euo pipefail - -# --------------------------------------------------------------------------- -# Configuration -# --------------------------------------------------------------------------- - -SCAN_PATH="${INPUT_PATH:-.}" -STRICT="${INPUT_STRICT:-false}" -PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" -GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" - -# Parse paths-ignore: newline-separated fragments, blank lines and # comments -# stripped. Each fragment is a substring match against the file path. Pattern -# adopted from hyperpolymath/hypatia#243 — content-pattern validators must -# distinguish a target from a vendored / fixture file that legitimately -# contains the very pattern being checked. -PATHS_IGNORE=() -while IFS= read -r _frag; do - # Strip leading and trailing whitespace (canonical bash idiom). - _frag="${_frag#"${_frag%%[![:space:]]*}"}" - _frag="${_frag%"${_frag##*[![:space:]]}"}" - [[ -z "$_frag" || "$_frag" == \#* ]] && continue - PATHS_IGNORE+=("$_frag") -done <<< "$PATHS_IGNORE_RAW" - -# Returns 0 if path should be skipped (matches any ignore fragment) -path_ignored() { - local p="$1" frag - for frag in "${PATHS_IGNORE[@]}"; do - [[ "$p" == *"$frag"* ]] && return 0 - done - return 1 -} - -# Counters -FILES_SCANNED=0 -ERRORS=0 -WARNINGS=0 - -# --------------------------------------------------------------------------- -# Helper: emit GitHub annotation -# --------------------------------------------------------------------------- -# Usage: annotate -# level: error | warning | notice -annotate() { - local level="$1" file="$2" line="$3" message="$4" - echo "::${level} file=${file},line=${line}::${message}" -} - -# --------------------------------------------------------------------------- -# Helper: report issue (respects strict mode) -# --------------------------------------------------------------------------- -# Usage: report_issue -# severity: error | warning -report_issue() { - local severity="$1" file="$2" line="$3" message="$4" - - if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then - severity="error" - fi - - annotate "$severity" "$file" "$line" "$message" - - if [[ "$severity" == "error" ]]; then - ERRORS=$((ERRORS + 1)) - else - WARNINGS=$((WARNINGS + 1)) - fi -} - -# --------------------------------------------------------------------------- -# Validator: check a single .a2ml file -# --------------------------------------------------------------------------- -validate_deed() { - local file="$1" - FILES_SCANNED=$((FILES_SCANNED + 1)) - - # --- Check 1: SPDX header --- - # The SPDX-License-Identifier should appear in the first 10 lines - local has_spdx=false - local line_num=0 - while IFS= read -r line; do - line_num=$((line_num + 1)) - if [[ $line_num -gt 10 ]]; then - break - fi - if [[ "$line" == *"SPDX-License-Identifier"* ]]; then - has_spdx=true - break - fi - done < "$file" - - if [[ "$has_spdx" == "false" ]]; then - report_issue "warning" "$file" 1 \ - "Missing SPDX-License-Identifier in first 10 lines" - fi - - # --- Check 2: Required identity fields --- - # DEED files must contain either: - # - agent-id = "..." or agent_id = "..." - # - pedigree block with name field - # - name = "..." at top level (for AI manifests) - # - project = "..." (for STATE.a2ml) - local has_identity=false - local has_version=false - local first_form_seen=false - line_num=0 - - while IFS= read -r line; do - line_num=$((line_num + 1)) - - # Check for identity fields (various DEED patterns) - # TOML/kv form: `name = "..."`, `project = "..."`, `agent-id = "..."`. - # - # `archetype` is the identity key of the ARCHETYPE.a2ml shape — the - # `just repo-init` scaffolding descriptors under archetypes/. It names the - # archetype exactly as `name` names a manifest, and is the fourth - # dialect this recogniser accommodates alongside TOML, s-expression and - # brace-block. Without it, archetypes/julia-library/ARCHETYPE.a2ml - # failed with "Missing required identity field" — on main, in this repo - # and in every repo instantiated from it. - # - # Recognising the shape is the right fix rather than adding a redundant - # `name = "julia-library"` beside `archetype = "julia-library"`: that - # file's own header states an archetype "must not write fiction", and - # duplicating its identity to satisfy a grep is exactly that. - if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|archetype)[[:space:]]*= ]]; then - has_identity=true - fi - # S-expression form: `(name "...")`, `(project "...")`, - # `(agent-id "...")`. Some DEED dialects (audit registries, - # classification stores) use Lisp-style s-expressions for the - # metadata block instead of TOML. Identity carries the same - # semantics; only the syntax differs. Match at any indent so it - # also picks up entries nested under `(metadata ...)`. - if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(agent[-_]id|name|project)[[:space:]]+\" ]]; then - has_identity=true - fi - # Colon / brace-block form: `name: "..."`, `id: "..."`, `project: "..."`. - # YAML-ish and brace-block DEED dialects (e.g. `Trust { name: "..." }`, - # `id: "tsdm-standard"`) carry the same identity semantics; only the - # delimiter (`:` vs `=`) differs. `id` is the brace-block spelling of an - # identity key. - if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|id)[[:space:]]*: ]]; then - has_identity=true - fi - # DEED s-expression head form: `(estate-deed`, `(repo-deed`, - # `(estate-atlas-deed`, `(praxis-deed`. Per DEED-GRAMMAR-SPEC - # <>, a file whose first form is one of the four declared - # heads is a deed of that kind, and the head satisfies the structural - # half of identity. Only the FIRST form is eligible — checking every - # line would let a malformed file open with some other form and append - # a deed head lower down to buy identity. Ported from - # hyperpolymath/deed-ecosystem validate-action/validate-a2ml.sh so the - # local hook and the CI action agree on what a deed is. - if [[ "$first_form_seen" == "false" && "$line" =~ ^[[:space:]]*\( ]]; then - first_form_seen=true - if [[ "$line" =~ ^[[:space:]]*\((estate-deed|repo-deed|estate-atlas-deed|praxis-deed)([[:space:]]|$) ]]; then - has_identity=true - fi - fi - # DEED keyword identity form: `:canonical-name "..."` and the two other - # identity keywords the spec names. Leading colon: none of the forms - # above match it, because they test the bare words. - if [[ "$line" =~ ^[[:space:]]*:(canonical-name|estate-authority|agent-id)[[:space:]] ]]; then - has_identity=true - fi - # Check for version field — TOML form - if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*= ]]; then - has_version=true - fi - # Version field — s-expression form - if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(version|schema_version)[[:space:]]+\" ]]; then - has_version=true - fi - # Version field — colon / brace-block form - if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*: ]]; then - has_version=true - fi - # DEED keyword version form: `:schema-version "1.0.0"` — leading colon, - # hyphenated, REQUIRED on all four deed heads. The three patterns above - # spell it `schema_version` with no leading colon, so a conforming deed - # matched none of them. `:registry-version` is a distinct, optional - # atlas field and never satisfies the version requirement. - if [[ "$line" =~ ^[[:space:]]*:schema-version[[:space:]] ]]; then - has_version=true - fi - done < "$file" - - # AI manifest files (0-AI-MANIFEST.a2ml, 0.1-AI-MANIFEST.a2ml, etc.) - # use markdown-style headers and free text, so identity check is relaxed - local basename - basename="$(basename "$file")" - local is_manifest=false - if [[ "$basename" == *"AI-MANIFEST"* ]]; then - is_manifest=true - fi - # Canonical typed manifests under .machine_readable/descriptiles/ — identity comes - # from the enclosing directory + filename, not an in-file field. Sibling - # files in the same directory (ECOSYSTEM.a2ml, STATE.a2ml) DO carry their - # own $name/project and continue to be validated normally. - case "$basename" in - AGENTIC.a2ml|META.a2ml|NEUROSYM.a2ml|PLAYBOOK.a2ml|AI.a2ml) - # AI.a2ml = free-text "AI Assistant Instructions" manifest, the same - # doc type as 0-AI-MANIFEST.a2ml but with the bare name; identity is - # carried by the enclosing repo/plugin dir, not an in-file field. - is_manifest=true - ;; - # Dockerfile-style top-level typed manifests (Intentfile, Trustfile, …) - # use markdown-flavoured DEED; identity is carried by the parent repo. - *file.a2ml) - is_manifest=true - ;; - esac - - # Contractile-shape DEED files use `@directive:` syntax instead of - # TOML `key = value`. Trustfile.a2ml, Intentfile.a2ml, Mustfile.a2ml, - # Adjustfile.a2ml etc. are policy / trust / intent / abstract files - # whose identity is implicit in their @-prefixed directives - # (`@trust-level`, `@intent`, ...) rather than a TOML name/version - # pair. Treating them as manifest-shape produces 100% false positives — - # they're a different DEED doc type. Detected by the presence of any - # contractile directive in the file body. - local is_contractile_shape=false - if grep -qE '^@(abstract|trust-level|trust-boundary|trust-actions|trust-deny|intent|must|adjust|end)([[:space:]]*:|$)' "$file"; then - is_contractile_shape=true - fi - - # Canonical structured DEED tree. Everything under a `.machine_readable/` - # directory is a typed agent-readable doc (CLADE, ANCHOR, STATE, - # ECOSYSTEM, bot_directives/{debt,coverage,methodology}, ai/AI, - # policies/*, integrations/*, …). Per the RSR convention these carry - # identity structurally — owning repo + path + filename — not via an - # in-file `name`/`agent-id`. This generalises the `.machine_readable/descriptiles/` - # rationale above to the whole tree: czech-file-knife itself ships these - # files without an in-file identity key, so requiring one produces - # estate-wide false positives on every repo built from the canonical - # template. Files outside `.machine_readable/` are still validated. - local is_structural_identity=false - if [[ "$file" == *"/.machine_readable/"* || "$file" == "./.machine_readable/"* || "$file" == ".machine_readable/"* ]]; then - is_structural_identity=true - fi - - if [[ "$has_identity" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then - report_issue "error" "$file" 1 \ - "Missing required identity field (agent-id, name, or project)" - fi - - if [[ "$has_version" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then - report_issue "warning" "$file" 1 \ - "Missing version or schema_version field" - fi - - # --- Check 3: Attestation block structure --- - # If file contains [attestation] or ## ATTESTATION, validate it has - # required sub-fields: proof or signature - local in_attestation=false - local attestation_line=0 - local attestation_has_content=false - line_num=0 - - while IFS= read -r line; do - line_num=$((line_num + 1)) - - # Detect attestation section start - if [[ "$line" =~ ^\[attestation\] ]] || [[ "$line" =~ ^##[[:space:]]+[Aa]ttestation ]] || [[ "$line" =~ ^##[[:space:]]+ATTESTATION ]]; then - in_attestation=true - attestation_line=$line_num - continue - fi - - # Detect next section (ends attestation block) - if [[ "$in_attestation" == "true" ]]; then - if [[ "$line" =~ ^\[.+\] ]] || [[ "$line" =~ ^##[[:space:]] ]]; then - in_attestation=false - continue - fi - # Check for content in attestation block - if [[ "$line" =~ (proof|signature|verified|hash)[[:space:]]*= ]]; then - attestation_has_content=true - fi - fi - done < "$file" - - if [[ $attestation_line -gt 0 && "$attestation_has_content" == "false" ]]; then - report_issue "warning" "$file" "$attestation_line" \ - "Attestation block found but missing proof/signature/hash fields" - fi - - # --- Check 4: Section heading syntax --- - # Validate that [section] headings are well-formed (no unclosed brackets) - line_num=0 - while IFS= read -r line; do - line_num=$((line_num + 1)) - # Lines starting with [ should have a matching ] - if [[ "$line" =~ ^\[ && ! "$line" =~ ^\[.+\] ]]; then - # Exclude markdown-style links and multi-line values - if [[ ! "$line" =~ ^\[.*\]\( && ! "$line" =~ ^\[TODO && ! "$line" =~ ^\[YOUR ]]; then - report_issue "warning" "$file" "$line_num" \ - "Possibly malformed section heading: unclosed bracket" - fi - fi - done < "$file" -} - -# --------------------------------------------------------------------------- -# Main: discover and validate .a2ml files -# --------------------------------------------------------------------------- - -echo "::group::DEED Manifest Validation" -echo "Scanning ${SCAN_PATH} for .a2ml files..." -echo "" - -# Find all manifest files (.a2ml legacy + .deed — dual-accept; extension migration = standards #837, the DEED conversion campaign), excluding .git -mapfile -t deed_candidates < <(find "$SCAN_PATH" \( -name '*.a2ml' -o -name '*.deed' \) -not -path '*/.git/*' -type f | sort) - -# Apply paths-ignore filter -deed_files=() -SKIPPED=0 -for _f in "${deed_candidates[@]}"; do - if path_ignored "$_f"; then - SKIPPED=$((SKIPPED + 1)) - continue - fi - deed_files+=("$_f") -done - -if [[ $SKIPPED -gt 0 ]]; then - echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" -fi - -if [[ ${#deed_files[@]} -eq 0 ]]; then - echo "::notice::No .a2ml files found in ${SCAN_PATH}" - echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "::endgroup::" - exit 0 -fi - -echo "Found ${#deed_files[@]} .a2ml file(s)" -echo "" - -for file in "${deed_files[@]}"; do - echo " Validating: ${file}" - validate_deed "$file" -done - -echo "" -echo "────────────────────────────────────────" -echo "Files scanned: ${FILES_SCANNED}" -echo "Errors: ${ERRORS}" -echo "Warnings: ${WARNINGS}" -echo "Strict mode: ${STRICT}" -echo "────────────────────────────────────────" - -# Write outputs for GitHub Actions -{ - echo "files_scanned=${FILES_SCANNED}" - echo "errors=${ERRORS}" - echo "warnings=${WARNINGS}" -} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - -echo "::endgroup::" - -# Exit with failure if errors were found -if [[ $ERRORS -gt 0 ]]; then - echo "::error::DEED validation failed with ${ERRORS} error(s)" - exit 1 -fi - -echo "DEED validation passed." -exit 0 diff --git a/czech-file-knife/.github/hooks/validate-k9.sh b/czech-file-knife/.github/hooks/validate-k9.sh deleted file mode 100755 index 02845a4c2..000000000 --- a/czech-file-knife/.github/hooks/validate-k9.sh +++ /dev/null @@ -1,389 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# validate-k9.sh — K9 configuration file validation script -# -# Scans for .k9 and .k9.ncl files and validates: -# 1. K9! magic number on line 1 -# 2. Pedigree block presence with required fields (name, version) -# 3. Security level is one of: kennel, yard, hunt (case-insensitive) -# 4. Hunt-level files must have a signature or signature_required field -# 5. SPDX-License-Identifier header presence -# -# Environment variables: -# INPUT_PATH — Directory to scan (default: .) -# INPUT_STRICT — Promote warnings to errors (default: false) -# -# Exit codes: -# 0 — All files valid (or only warnings in non-strict mode) -# 1 — Validation errors found - -set -euo pipefail - -# --------------------------------------------------------------------------- -# Configuration -# --------------------------------------------------------------------------- - -SCAN_PATH="${INPUT_PATH:-.}" -STRICT="${INPUT_STRICT:-false}" -PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" -GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" - -# Parse paths-ignore: newline-separated fragments, blank lines and # comments -# stripped. Each fragment is a substring match against the file path. Pattern -# adopted from hyperpolymath/hypatia#243 — content-pattern validators must -# distinguish a target from a vendored / fixture file that legitimately -# contains the very pattern being checked. -PATHS_IGNORE=() -while IFS= read -r _frag; do - # Strip leading and trailing whitespace (canonical bash idiom). - _frag="${_frag#"${_frag%%[![:space:]]*}"}" - _frag="${_frag%"${_frag##*[![:space:]]}"}" - [[ -z "$_frag" || "$_frag" == \#* ]] && continue - PATHS_IGNORE+=("$_frag") -done <<< "$PATHS_IGNORE_RAW" - -# Returns 0 if path should be skipped (matches any ignore fragment) -path_ignored() { - local p="$1" frag - for frag in "${PATHS_IGNORE[@]}"; do - [[ "$p" == *"$frag"* ]] && return 0 - done - return 1 -} - -# Counters -FILES_SCANNED=0 -ERRORS=0 -WARNINGS=0 - -# Valid security levels (the leash metaphor) -VALID_LEVELS="kennel yard hunt" - -# --------------------------------------------------------------------------- -# Helper: emit GitHub annotation -# --------------------------------------------------------------------------- -annotate() { - local level="$1" file="$2" line="$3" message="$4" - echo "::${level} file=${file},line=${line}::${message}" -} - -# --------------------------------------------------------------------------- -# Helper: report issue (respects strict mode) -# --------------------------------------------------------------------------- -report_issue() { - local severity="$1" file="$2" line="$3" message="$4" - - if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then - severity="error" - fi - - annotate "$severity" "$file" "$line" "$message" - - if [[ "$severity" == "error" ]]; then - ERRORS=$((ERRORS + 1)) - else - WARNINGS=$((WARNINGS + 1)) - fi -} - -# --------------------------------------------------------------------------- -# Helper: normalise a security level string -# --------------------------------------------------------------------------- -# Strips quotes, leading/trailing whitespace, Nickel enum tick prefix -normalise_level() { - local raw="$1" - # Remove surrounding quotes, tick prefix ('Kennel -> Kennel), whitespace - raw="${raw#*=}" # Remove everything before = - raw="${raw//\"/}" # Remove double quotes - raw="${raw//\'/}" # Remove single quotes (Nickel tick) - raw="${raw//,/}" # Remove trailing commas - raw="${raw## }" # Trim leading space - raw="${raw%% }" # Trim trailing space - raw="${raw%%#*}" # Remove inline comments - raw="${raw## }" # Trim again - raw="${raw%% }" - echo "${raw,,}" # Lowercase -} - -# --------------------------------------------------------------------------- -# Validator: check a single K9 file -# --------------------------------------------------------------------------- -validate_k9() { - local file="$1" - FILES_SCANNED=$((FILES_SCANNED + 1)) - - # --- Check 1: K9! magic number on first non-empty line --- - local first_content_line="" - local first_content_line_num=0 - local line_num=0 - - while IFS= read -r line; do - line_num=$((line_num + 1)) - # Skip empty lines - if [[ -z "${line// /}" ]]; then - continue - fi - first_content_line="$line" - first_content_line_num=$line_num - break - done < "$file" - - if [[ "$first_content_line" != "K9!" ]]; then - report_issue "error" "$file" "$first_content_line_num" \ - "Missing K9! magic number. First non-empty line must be exactly 'K9!'" - fi - - # --- Check 2: SPDX header --- - local has_spdx=false - line_num=0 - while IFS= read -r line; do - line_num=$((line_num + 1)) - if [[ $line_num -gt 10 ]]; then - break - fi - if [[ "$line" == *"SPDX-License-Identifier"* ]]; then - has_spdx=true - break - fi - done < "$file" - - if [[ "$has_spdx" == "false" ]]; then - report_issue "warning" "$file" 1 \ - "Missing SPDX-License-Identifier in first 10 lines" - fi - - # --- Check 3: Pedigree block with required fields --- - local has_pedigree=false - local has_pedigree_name=false - local has_pedigree_version=false - local has_security_level=false - local security_level_value="" - local security_level_line=0 - local has_signature_field=false - local in_pedigree=false - local pedigree_depth=0 - - # Resolve a one-hop `let` indirection before scanning. - # - # Nickel lets you build the pedigree separately and attach it by name: - # - # let component_pedigree = { - # metadata = { name = "verisimdb-test-infra", ... }, - # } in - # { pedigree = component_pedigree, ... } - # - # The brace-depth scanner below only sees `pedigree = component_pedigree,` - # — a line with no braces — so depth never rises, the block appears empty, - # and `name` is invisible. The file is valid; the grep could not follow the - # reference. Observed on verisimdb/connectors/test-infra/deploy.k9.ncl, - # which does declare metadata.name at its line 23. - # - # If `pedigree = ` names a binding, treat `let = {` - # as the block opener too. One hop only: chased aliases would need a real - # Nickel evaluator, and `nickel typecheck` cannot be used here because the - # mandatory `K9!` magic line on line 1 is not valid Nickel. - local pedigree_alias="" - pedigree_alias=$(sed -nE 's/^[[:space:]]*pedigree[[:space:]]*=[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*,?[[:space:]]*$/\1/p' "$file" | head -1) - - line_num=0 - while IFS= read -r line; do - line_num=$((line_num + 1)) - - # Detect pedigree block start. Note: do NOT `continue` here — the - # `pedigree = {` line itself contains the opening brace that - # establishes the block. Falling through to the brace counter - # below makes depth start at 1, so a subsequent `security = {…},` - # closing brace correctly takes depth to 1 (not 0), keeping us - # inside the pedigree block when later fields (name/version/leash) - # are checked. Previously the `continue` skipped this opening - # brace, depth started at 0, and the first nested block's close - # prematurely terminated the validator's view of the pedigree — - # making `pedigree.metadata.name` invisible. - if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then - has_pedigree=true - in_pedigree=true - pedigree_depth=0 - # fall through - fi - - # `let = {` where is what `pedigree =` points at. - # See the pedigree_alias note above. - if [[ -n "$pedigree_alias" ]] && \ - [[ "$line" =~ ^[[:space:]]*let[[:space:]]+${pedigree_alias}[[:space:]]*= ]]; then - has_pedigree=true - in_pedigree=true - pedigree_depth=0 - # fall through — this line carries the opening brace - fi - - if [[ "$in_pedigree" == "true" ]]; then - # Track brace depth to know when pedigree block ends - local opens closes - opens="${line//[^\{]/}" - closes="${line//[^\}]/}" - pedigree_depth=$(( pedigree_depth + ${#opens} - ${#closes} )) - - if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" ]]; then - # Check this final line too before leaving - : - fi - - # Check for name field within pedigree.metadata or pedigree directly. - # Two patterns cover both multi-line and single-line pedigrees: - # 1. ^[[:space:]]+name[[:space:]]*= — the normal multi-line case where - # `name = "..."` appears on its own indented line. - # 2. [[:space:]]name[[:space:]]*= — inline within a single-line - # pedigree assignment such as: - # pedigree = component_pedigree & { name = "foo" } - # (root cause: developer-ecosystem@baab1534 — single-line form - # was missed entirely because the pedigree block opened and - # closed in one line, never reaching the ^[[:space:]]+ check on - # a subsequent iteration.) - if [[ "$line" =~ ^[[:space:]]+name[[:space:]]*= ]] || \ - [[ "$line" =~ [[:space:]]name[[:space:]]*= ]]; then - has_pedigree_name=true - fi - - # Check for version field - if [[ "$line" =~ ^[[:space:]]+(version|schema_version)[[:space:]]*= ]] || \ - [[ "$line" =~ [[:space:]](version|schema_version)[[:space:]]*= ]]; then - has_pedigree_version=true - fi - - # Check for security level (leash field) - if [[ "$line" =~ ^[[:space:]]+(leash|security_level)[[:space:]]*= ]]; then - has_security_level=true - security_level_value="$(normalise_level "$line")" - security_level_line=$line_num - fi - - # Check for signature fields - if [[ "$line" =~ ^[[:space:]]+(signature|signature_required)[[:space:]]*= ]]; then - has_signature_field=true - fi - - # End of pedigree block - if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" && "$line" == *"}"* ]]; then - in_pedigree=false - fi - fi - - # Also check for signature fields outside pedigree (top-level) - if [[ "$line" =~ ^[[:space:]]*(signature)[[:space:]]*= ]]; then - has_signature_field=true - fi - done < "$file" - - if [[ "$has_pedigree" == "false" ]]; then - report_issue "error" "$file" 1 \ - "Missing pedigree block. K9 files must contain a 'pedigree = { ... }' section" - else - if [[ "$has_pedigree_name" == "false" ]]; then - report_issue "error" "$file" 1 \ - "Pedigree block missing 'name' field (in pedigree.metadata.name or pedigree.name)" - fi - - if [[ "$has_pedigree_version" == "false" ]]; then - report_issue "warning" "$file" 1 \ - "Pedigree block missing 'version' or 'schema_version' field" - fi - fi - - # --- Check 4: Security level validation --- - if [[ "$has_security_level" == "true" ]]; then - local level_valid=false - for valid in $VALID_LEVELS; do - if [[ "$security_level_value" == "$valid" ]]; then - level_valid=true - break - fi - done - - if [[ "$level_valid" == "false" ]]; then - report_issue "error" "$file" "$security_level_line" \ - "Invalid security level '${security_level_value}'. Must be one of: kennel, yard, hunt" - fi - else - if [[ "$has_pedigree" == "true" ]]; then - report_issue "warning" "$file" 1 \ - "No security level (leash/security_level) found in pedigree block" - fi - fi - - # --- Check 5: Hunt-level signature requirement --- - if [[ "$security_level_value" == "hunt" && "$has_signature_field" == "false" ]]; then - report_issue "error" "$file" "$security_level_line" \ - "Hunt-level K9 file must include a 'signature' or 'signature_required' field" - fi -} - -# --------------------------------------------------------------------------- -# Main: discover and validate K9 files -# --------------------------------------------------------------------------- - -echo "::group::K9 Configuration Validation" -echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." -echo "" - -# Find all K9 files, excluding .git directory -mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) - -# Apply paths-ignore filter -k9_files=() -SKIPPED=0 -for _f in "${k9_candidates[@]}"; do - if path_ignored "$_f"; then - SKIPPED=$((SKIPPED + 1)) - continue - fi - k9_files+=("$_f") -done - -if [[ $SKIPPED -gt 0 ]]; then - echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" -fi - -if [[ ${#k9_files[@]} -eq 0 ]]; then - echo "::notice::No K9 files found in ${SCAN_PATH}" - echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - echo "::endgroup::" - exit 0 -fi - -echo "Found ${#k9_files[@]} K9 file(s)" -echo "" - -for file in "${k9_files[@]}"; do - echo " Validating: ${file}" - validate_k9 "$file" -done - -echo "" -echo "────────────────────────────────────────" -echo "Files scanned: ${FILES_SCANNED}" -echo "Errors: ${ERRORS}" -echo "Warnings: ${WARNINGS}" -echo "Strict mode: ${STRICT}" -echo "────────────────────────────────────────" - -# Write outputs for GitHub Actions -{ - echo "files_scanned=${FILES_SCANNED}" - echo "errors=${ERRORS}" - echo "warnings=${WARNINGS}" -} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true - -echo "::endgroup::" - -# Exit with failure if errors were found -if [[ $ERRORS -gt 0 ]]; then - echo "::error::K9 validation failed with ${ERRORS} error(s)" - exit 1 -fi - -echo "K9 validation passed." -exit 0 diff --git a/czech-file-knife/.github/label-classifier.json b/czech-file-knife/.github/label-classifier.json deleted file mode 100644 index d349eaad4..000000000 --- a/czech-file-knife/.github/label-classifier.json +++ /dev/null @@ -1,739 +0,0 @@ -{ - "_generated_from": ".github/label-classifier.yml + .github/labels.yml in hyperpolymath/.git-private-farm", - "_do_not_edit": "regenerate with scripts/gen-classifier-json.py", - "version": 1, - "prefix_split_on": "/", - "title_prefix": { - "docs": { - "type": "documentation" - }, - "ci": { - "type": "chore", - "areas": [ - "cicd" - ] - }, - "governance": { - "type": "chore", - "areas": [ - "governance" - ] - }, - "roadmap": { - "type": "enhancement", - "meta": "meta:roadmap" - }, - "chore": { - "type": "chore" - }, - "build": { - "type": "chore", - "areas": [ - "cicd" - ] - }, - "security": { - "type": "chore", - "areas": [ - "security" - ] - }, - "proof": { - "type": "chore", - "areas": [ - "proofs" - ] - }, - "proofs": { - "type": "chore", - "areas": [ - "proofs" - ] - }, - "proof-debt": { - "type": "tech-debt", - "areas": [ - "proofs" - ] - }, - "epic": { - "type": "enhancement", - "meta": "meta:umbrella" - }, - "umbrella": { - "type": "enhancement", - "meta": "meta:umbrella" - }, - "tracking": { - "type": "chore", - "meta": "meta:umbrella" - }, - "campaign": { - "type": "enhancement", - "meta": "meta:campaign" - }, - "hygiene": { - "type": "tech-debt" - }, - "audit": { - "type": "research" - }, - "estate": { - "type": "chore", - "scope": "scope:estate" - }, - "automation": { - "type": "enhancement", - "areas": [ - "automation" - ] - }, - "research": { - "type": "research" - }, - "refactor": { - "type": "refactor" - }, - "test": { - "type": "testing" - }, - "tests": { - "type": "testing" - }, - "feat": { - "type": "enhancement" - }, - "fix": { - "type": "bug" - }, - "bug": { - "type": "bug" - }, - "perf": { - "type": "enhancement", - "areas": [ - "performance" - ] - }, - "codegen": { - "type": "enhancement", - "areas": [ - "architecture" - ] - }, - "packaging": { - "type": "chore", - "areas": [ - "packaging" - ] - }, - "policy": { - "type": "chore", - "areas": [ - "governance" - ] - }, - "ops": { - "type": "chore", - "areas": [ - "automation" - ] - }, - "standard": { - "type": "chore", - "areas": [ - "governance" - ] - }, - "migration": { - "type": "refactor", - "areas": [ - "migration" - ] - }, - "drift": { - "type": "tech-debt" - }, - "corrective": { - "type": "bug" - }, - "adaptive": { - "type": "enhancement" - }, - "perfective": { - "type": "enhancement" - }, - "preventive": { - "type": "tech-debt" - }, - "machine-readable": { - "type": "tech-debt" - }, - "parser": { - "type": "bug" - }, - "lang": { - "type": "bug" - }, - "clippy": { - "type": "tech-debt" - }, - "release": { - "type": "chore" - }, - "upstream": { - "type": "chore" - }, - "hardening": { - "type": "chore", - "areas": [ - "security" - ] - }, - "deps": { - "type": "chore" - }, - "rustsec": { - "type": "chore", - "areas": [ - "security" - ] - }, - "track": { - "type": "chore", - "meta": "meta:umbrella" - }, - "tracker": { - "type": "chore", - "meta": "meta:umbrella" - }, - "wiki": { - "type": "documentation" - }, - "reclassify": { - "type": "refactor" - }, - "backlog": { - "type": "chore" - }, - "core": { - "type": "enhancement", - "areas": [ - "design" - ] - }, - "evidence": { - "type": "enhancement", - "areas": [ - "design" - ] - }, - "manifest": { - "type": "enhancement", - "areas": [ - "design" - ] - }, - "backends": { - "type": "enhancement", - "areas": [ - "design" - ] - } - }, - "bracket_tag": { - "campaign": { - "meta": "meta:campaign" - }, - "umbrella": { - "meta": "meta:umbrella" - }, - "gov": { - "areas": [ - "governance" - ] - }, - "proofs/a": { - "areas": [ - "proofs" - ] - }, - "proofs/b": { - "areas": [ - "proofs" - ] - }, - "proofs/c": { - "areas": [ - "proofs" - ] - }, - "estate": { - "scope": "scope:estate" - }, - "repo": { - "scope": "scope:repo" - }, - "feature": { - "type": "enhancement" - }, - "integration": { - "areas": [ - "conformance" - ] - }, - "reference": { - "type": "documentation" - }, - "register": { - "type": "documentation" - }, - "p0": { - "priority": "priority:p0" - }, - "p1": { - "priority": "priority:p1" - }, - "p2": { - "priority": "priority:p2" - }, - "et-l2": { - "areas": [ - "conformance" - ] - }, - "et-l4": { - "areas": [ - "conformance" - ] - } - }, - "keyword_area": { - "proofs": [ - "agda", - "coq", - "rocq", - "idris", - "lean", - "isabelle", - "hol", - "mizar", - "why3", - "tla", - "alloy", - "dafny", - "acl2", - "pvs", - "metamath", - "z3", - "smt", - "prover", - "provers", - "theorem", - "theorems", - "axiom", - "axioms", - "postulate", - "postulates", - "believe_me", - "sorry", - "proof obligation", - "proof obligations", - "proof hole", - "proof holes", - "proof suite", - "proof-pipeline", - "proof debt", - "proof-debt", - "metatheory", - "mechanize", - "qed" - ], - "cicd": [ - "workflow", - "github action", - "actions.lock", - "lockfile", - "runner", - "startup_failure", - "dependabot", - "check run", - "required context", - "scorecard", - "codeql", - "ci/cd" - ], - "licensing": [ - "spdx", - "licence", - "license", - "reuse", - "copyright", - "attribution", - "agpl", - "mpl" - ], - "security": [ - "gitleaks", - "secret", - "vulnerabilit", - "advisory", - "supply chain", - "cve" - ], - "bindings": [ - "abi", - "ffi", - "wasm", - "jni", - "c api", - "interop", - "extern \"c\"", - "nif", - "snif" - ], - "packaging": [ - "guix", - "nix", - "container", - "containerfile", - "docker", - "flatpak", - "oci image" - ], - "scaffolding": [ - "rsr", - "scaffold", - "template", - "repo-init", - "instantiat", - "placeholder" - ], - "governance": [ - "ruleset", - "policy", - "compliance", - "governance", - "branch protection", - "codeowners", - "code of conduct" - ], - "migration": [ - "rescript", - "to-affinescript", - "\u2192 affinescript", - "port", - "deno", - "bun" - ], - "automation": [ - "bot", - "gitbot", - "hypatia", - "sustainabot", - "oikosbot", - "fan-out", - "fanout", - "dispatch", - "self-heal" - ], - "performance": [ - "latency", - "throughput", - "binary size", - "memory", - "hot path", - "regression" - ] - }, - "keyword_type": { - "tech-debt": [ - "debt", - "drift", - "hygiene", - "stale", - "cleanup", - "follow-up", - "clean up", - "left over", - "leftover", - "anti-pattern", - "inconsistency", - "inconsistent", - "placeholder", - "placeholders", - "tbd", - "todo", - "todos", - "unfilled" - ], - "documentation": [ - "document", - "docs", - "readme", - "adoc", - "prose", - "docs/", - "changelog", - "explainme", - "quickstart", - "wiki", - "docstring", - "doc tree" - ], - "testing": [ - "test", - "tests", - "fuzz", - "bench", - "coverage", - "crash-consistency", - "linearizability", - "equivalence", - "property-correspondence", - "property-based", - "test suite", - "proptest" - ], - "bug": [ - "broken", - "fails", - "failing", - "crash", - "oom", - "regression", - "incorrect", - "does not", - "panic", - "panics", - "unreachable", - "mangled", - "never run", - "never ran", - "never succeeded", - "never fires", - "cannot fail", - "deadlock", - "hangs" - ], - "refactor": [ - "refactor", - "restructure", - "consolidate", - "consolidation", - "reconcile", - "reconciliation", - "unify", - "dedupe", - "re-point", - "repoint", - "extract", - "retire", - "retire duplicate", - "deduplicate", - "reclassify", - "migrate" - ], - "research": [ - "investigat", - "explore", - "spike", - "work out", - "triage", - "assess", - "survey", - "gap analysis", - "self-audit", - "inventory", - "weakness list", - "theory", - "synthesis", - "prioritised weakness", - "feasibility" - ], - "decision": [ - "ruling", - "decide", - "decision", - "adjudicat", - "which of" - ], - "enhancement": [ - "add", - "implement", - "support", - "introduce", - "enable", - "expand", - "expansion", - "extend", - "wire", - "complete", - "build", - "create", - "port" - ] - }, - "meta_signal": { - "meta:umbrella": [ - "umbrella", - "epic", - "master issue", - "parent issue", - "sub-issues", - "child issues" - ], - "meta:campaign": [ - "campaign" - ], - "meta:roadmap": [ - "roadmap", - "capability-expansion", - "future work" - ], - "meta:recurring": [ - "recurring", - "recurrence", - "standing", - "every run", - "each week" - ] - }, - "status_signal": { - "status:blocked": [ - "blocked on", - "blocked:", - "(blocked", - "is blocked", - "gated on", - "waiting on upstream", - "needs upstream" - ], - "status:needs-owner": [ - "unassigned", - "needs an owner", - "no owner" - ], - "status:needs-ruling": [ - "needs a ruling", - "awaiting ruling", - "owner decision needed" - ] - }, - "scope_signal": { - "scope:estate": [ - "estate-wide", - "estate wide", - "across the estate", - "all repos", - "fleet-wide" - ] - }, - "tier_of": { - "bug": "type", - "enhancement": "type", - "documentation": "type", - "refactor": "type", - "tech-debt": "type", - "testing": "type", - "chore": "type", - "research": "type", - "decision": "type", - "question": "type", - "cicd": "area", - "security": "area", - "proofs": "area", - "governance": "area", - "design": "area", - "architecture": "area", - "performance": "area", - "bindings": "area", - "migration": "area", - "packaging": "area", - "licensing": "area", - "automation": "area", - "scaffolding": "area", - "conformance": "area", - "priority:p0": "priority", - "priority:p1": "priority", - "priority:p2": "priority", - "priority:p3": "priority", - "status:blocked": "status", - "status:ready": "status", - "status:needs-owner": "status", - "status:needs-ruling": "status", - "status:do-not-automate": "status", - "meta:umbrella": "meta", - "meta:campaign": "meta", - "meta:roadmap": "meta", - "meta:recurring": "meta", - "scope:estate": "scope", - "scope:repo": "scope" - }, - "tier_max": { - "type": 1, - "area": null, - "priority": 1, - "status": 1, - "meta": 1, - "scope": 1 - }, - "types": [ - "bug", - "enhancement", - "documentation", - "refactor", - "tech-debt", - "testing", - "chore", - "research", - "decision", - "question" - ], - "frozen": [ - "dependencies", - "duplicate", - "elixir", - "gitar-approved", - "github_actions", - "good first issue", - "help wanted", - "invalid", - "javascript", - "never-stale", - "nix", - "pinned", - "python", - "rust", - "security", - "stale", - "wontfix" - ], - "precedence": { - "meta:campaign": 0, - "meta:umbrella": 1, - "meta:recurring": 2, - "meta:roadmap": 3, - "priority:p0": 0, - "priority:p1": 1, - "priority:p2": 2, - "priority:p3": 3, - "status:blocked": 0, - "status:needs-ruling": 1, - "status:needs-owner": 2, - "status:do-not-automate": 3, - "status:ready": 4, - "scope:estate": 0, - "scope:repo": 1, - "bug": 0, - "decision": 1, - "tech-debt": 2, - "testing": 3, - "documentation": 4, - "refactor": 5, - "research": 6, - "enhancement": 7, - "chore": 8, - "question": 9 - } -} diff --git a/czech-file-knife/.github/labels.json b/czech-file-knife/.github/labels.json deleted file mode 100644 index 78786d4e1..000000000 --- a/czech-file-knife/.github/labels.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "_generated_from": ".github/labels.yml in hyperpolymath/.git-private-farm", - "_do_not_edit": "regenerate with scripts/gen-labels-json.py", - "version": 1, - "labels": [ - { - "name": "bug", - "color": "d73a4a", - "description": "Something is broken or behaves incorrectly", - "tier": "type" - }, - { - "name": "enhancement", - "color": "a2eeef", - "description": "New capability or improvement to existing behaviour", - "tier": "type" - }, - { - "name": "documentation", - "color": "0075ca", - "description": "Docs, prose, diagrams, READMEs, ADRs", - "tier": "type" - }, - { - "name": "refactor", - "color": "c5def5", - "description": "Restructuring that preserves observable behaviour", - "tier": "type" - }, - { - "name": "tech-debt", - "color": "fbca04", - "description": "Known shortcut, drift, or hygiene owed - includes cleanup", - "tier": "type" - }, - { - "name": "testing", - "color": "bfd4f2", - "description": "Tests, benchmarks, fuzzing, property checks, coverage", - "tier": "type" - }, - { - "name": "chore", - "color": "ededed", - "description": "Routine maintenance with no behaviour change", - "tier": "type" - }, - { - "name": "research", - "color": "d4c5f9", - "description": "Open investigation; the outcome is knowledge, not code", - "tier": "type" - }, - { - "name": "decision", - "color": "8b5cf6", - "description": "A ruling is required before work can proceed", - "tier": "type" - }, - { - "name": "question", - "color": "d876e3", - "description": "Further information is requested", - "tier": "type" - }, - { - "name": "cicd", - "color": "006b75", - "description": "CI/CD: workflows, actions, lockfiles, pins, runners, release gates", - "tier": "area" - }, - { - "name": "security", - "color": "006b75", - "description": "Security posture, secrets, scanning, advisories, supply chain", - "tier": "area" - }, - { - "name": "proofs", - "color": "006b75", - "description": "Formal verification: Agda, Coq, Idris, Lean, Z3/SMT, axiom debt", - "tier": "area" - }, - { - "name": "governance", - "color": "006b75", - "description": "Policy, rulesets, standards, compliance, and their enforcement", - "tier": "area" - }, - { - "name": "design", - "color": "006b75", - "description": "Design of an interface, protocol, grammar, or type theory", - "tier": "area" - }, - { - "name": "architecture", - "color": "006b75", - "description": "Structural/system-level shape and runtime behaviour", - "tier": "area" - }, - { - "name": "performance", - "color": "006b75", - "description": "Throughput, latency, memory, binary size", - "tier": "area" - }, - { - "name": "bindings", - "color": "006b75", - "description": "ABI, FFI, WASM, and cross-language interop surfaces", - "tier": "area" - }, - { - "name": "migration", - "color": "006b75", - "description": "Porting between languages or toolchains (e.g. -> AffineScript)", - "tier": "area" - }, - { - "name": "packaging", - "color": "006b75", - "description": "Guix, Nix, containers, distribution artefacts", - "tier": "area" - }, - { - "name": "licensing", - "color": "006b75", - "description": "Licences, SPDX headers, REUSE compliance, attribution", - "tier": "area" - }, - { - "name": "automation", - "color": "006b75", - "description": "Bots, schedulers, dispatch, self-healing, fan-out", - "tier": "area" - }, - { - "name": "scaffolding", - "color": "006b75", - "description": "RSR templates, repo init, instantiation, project skeletons", - "tier": "area" - }, - { - "name": "conformance", - "color": "006b75", - "description": "Conformance to an external or internal specification", - "tier": "area" - }, - { - "name": "priority:p0", - "color": "b60205", - "description": "Critical - drop other work", - "tier": "priority" - }, - { - "name": "priority:p1", - "color": "d93f0b", - "description": "High - schedule next", - "tier": "priority" - }, - { - "name": "priority:p2", - "color": "e99695", - "description": "Normal - queue it", - "tier": "priority" - }, - { - "name": "priority:p3", - "color": "f9d0c4", - "description": "Low - nice to have", - "tier": "priority" - }, - { - "name": "status:blocked", - "color": "fbca04", - "description": "Cannot proceed until a dependency clears", - "tier": "status" - }, - { - "name": "status:ready", - "color": "fbca04", - "description": "Fully specified and ready to be picked up", - "tier": "status" - }, - { - "name": "status:needs-owner", - "color": "fbca04", - "description": "Unassigned and needs someone to take it", - "tier": "status" - }, - { - "name": "status:needs-ruling", - "color": "fbca04", - "description": "Awaiting an owner decision", - "tier": "status" - }, - { - "name": "status:do-not-automate", - "color": "fbca04", - "description": "Bots and sweeps must not touch this issue", - "tier": "status" - }, - { - "name": "meta:umbrella", - "color": "5319e7", - "description": "Parent issue aggregating child issues", - "tier": "meta" - }, - { - "name": "meta:campaign", - "color": "5319e7", - "description": "Coordinated multi-repo push with a defined end state", - "tier": "meta" - }, - { - "name": "meta:roadmap", - "color": "5319e7", - "description": "Forward planning; not yet actionable work", - "tier": "meta" - }, - { - "name": "meta:recurring", - "color": "5319e7", - "description": "Recurs on a schedule or by trigger; never finally closed", - "tier": "meta" - }, - { - "name": "scope:estate", - "color": "bfdadc", - "description": "Affects many or all repos across the estate", - "tier": "scope" - }, - { - "name": "scope:repo", - "color": "bfdadc", - "description": "Confined to this repository", - "tier": "scope" - } - ], - "frozen": [ - "dependencies", - "duplicate", - "elixir", - "gitar-approved", - "github_actions", - "good first issue", - "help wanted", - "invalid", - "javascript", - "never-stale", - "nix", - "pinned", - "python", - "rust", - "security", - "stale", - "wontfix" - ] -} diff --git a/czech-file-knife/.github/pull_request_template.md b/czech-file-knife/.github/pull_request_template.md deleted file mode 100644 index 2cca1e1f5..000000000 --- a/czech-file-knife/.github/pull_request_template.md +++ /dev/null @@ -1,47 +0,0 @@ - -## Summary - - - -## Changes - - - -- - -## RSR Quality Checklist - - - -### Required - -- [ ] Tests pass (`just test` or equivalent) -- [ ] Code is formatted (`just fmt` or equivalent) -- [ ] Linter is clean (no new warnings or errors) -- [ ] No banned language patterns (no , no npm/bun, no Go/Python) -- [ ] No `unsafe` blocks without `// SAFETY:` comments -- [ ] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) -- [ ] SPDX license headers present on all new/modified source files -- [ ] No secrets, credentials, or `.env` files included - -### As Applicable - -- [ ] `.machine_readable/descriptiles/STATE.a2ml` updated (if project state changed) -- [ ] `.machine_readable/descriptiles/ECOSYSTEM.a2ml` updated (if integrations changed) -- [ ] `.machine_readable/descriptiles/META.a2ml` updated (if architectural decisions changed) -- [ ] Documentation updated for user-facing changes -- [ ] `TOPOLOGY.md` updated (if architecture changed) -- [ ] `CHANGELOG` or release notes updated -- [ ] New dependencies reviewed for license compatibility (MPL-2.0 / MPL-2.0) -- [ ] ABI/FFI changes validated (`src/interface/abi/` and `src/interface/ffi/` consistent) - -## Testing - - - -## Screenshots - - diff --git a/czech-file-knife/.github/rulesets/Immutable-Tags.json b/czech-file-knife/.github/rulesets/Immutable-Tags.json deleted file mode 100644 index 75fe73a05..000000000 --- a/czech-file-knife/.github/rulesets/Immutable-Tags.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "Immutable-Tags", - "target": "tag", - "enforcement": "active", - "conditions": { - "ref_name": { - "include": [ - "~ALL" - ], - "exclude": [] - } - }, - "bypass_actors": [], - "rules": [ - { - "type": "deletion" - }, - { - "type": "non_fast_forward" - }, - { - "type": "update" - }, - { - "type": "required_signatures" - } - ] -} diff --git a/czech-file-knife/.github/rulesets/README.adoc b/czech-file-knife/.github/rulesets/README.adoc deleted file mode 100644 index 17d45efd6..000000000 --- a/czech-file-knife/.github/rulesets/README.adoc +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= Repository rulesets - -[IMPORTANT] -==== -**GitHub does not read this directory.** Unlike `.github/workflows/`, -`.github/dependabot.yml` or `.github/settings.yml`, ruleset JSON in a repository -path is *not* auto-applied by any GitHub feature. These files are -**documentation of intent plus a ready-to-POST payload** — nothing more. - -A ruleset only takes effect once someone applies it via the REST API or imports -it in the web UI. Until then the protection described here **is not in force**. -==== - -== Applying a ruleset - -Per ADR-0003 the *Configure* stage is an operator stage: identity, visibility and -branch/tag protection are set out of band. Automation is future work and must not -be described as existing. - -[source,bash] ----- -# Apply (create) a ruleset on the current repo: -gh api --method POST \ - -H "Accept: application/vnd.github+json" \ - "/repos/{owner}/{repo}/rulesets" \ - --input .github/rulesets/base.json - -# List what is actually in force — the only authoritative answer: -gh api "/repos/{owner}/{repo}/rulesets" --jq '.[] | "\(.id)\t\(.name)\t\(.enforcement)"' - -# Update an existing ruleset in place (needs its numeric id from the list above): -gh api --method PUT \ - "/repos/{owner}/{repo}/rulesets/" \ - --input .github/rulesets/base.json ----- - -== Files - -`base.json`:: Canonical branch protection ruleset for the default branch (`~DEFAULT_BRANCH`). -Enforces deleted-branch protection, linear history, squash merge, signatures and required -status checks, without unsatisfiable coverage or deadlocking approval requirements. -Sourced verbatim from `hyperpolymath/standards` `config/rulesets/base.json` (§7.3). - -`branch-floor.json`:: The irreducible branch floor for the default branch: deletion and -non-fast-forward protection only, with no bypass actors. This is the ruleset that keeps a -branch recoverable when the richer `Base` ruleset is unavailable or has been removed. -Sourced verbatim from `hyperpolymath/standards` `config/rulesets/branch-floor.json`. - -`Immutable-Tags.json`:: Makes release tags immutable — blocks tag deletion, -non-fast-forward updates, and overwrites, and requires signed tags. - -[NOTE] -==== -Files here are named to match the *live ruleset name*, which is not always the -upstream filename. Upstream ships `branch-floor.json` and `immutable-tags.json` -(lowercase, with a matching lowercase `name` inside the JSON); this repository's -`Immutable-Tags.json` declares `"name": "Immutable-Tags"`. Renaming a file here -changes nothing on GitHub, but it does change which upstream file it is diffed -against — keep that in mind when syncing. -==== - -== What is actually enforced on this repository - -Files are intent; this table is fact. Regenerate it with: - -[source,bash] ----- -gh api "/repos/{owner}/{repo}/rulesets" --jq '.[] | "\(.id)\t\(.name)\t\(.enforcement)\t\(.target)"' ----- - -|=== -| Live ruleset | Enforcement | Target | Covers - -| `Base` | active | branch | deletion, required signatures, code scanning (CodeQL/Hypatia/Scorecard), Copilot review, pull request -| `Branch-Floor` | active | branch | deletion, non-fast-forward -| `Immutable-Tags` | active | tag | creation, deletion, non-fast-forward, update, required signatures -|=== - -=== Known drift between these files and what is live - -The files below are *not* a transcript of the live rulesets. This is the -divergence as of 2026-09-28, recorded rather than silently reconciled because -`base.json` is sourced verbatim from upstream and must not be forked here. - -*Live `Base` rule that no file describes* :: - -`code_scanning` (with CodeQL, Hypatia and Scorecard tools) and -`copilot_code_review` are enforced live but appear in neither `base.json` nor any -upstream file synced into this directory. Upstream's nearest equivalent, -`config/rulesets/Optimus-Extras.json`, declares `code_scanning` with an *empty* -`code_scanning_tools` list, so it does not describe what is live here either. - -*`base.json` rules that are not in force* :: - -`required_linear_history` is in `base.json` but is not enforced by any live -ruleset. `required_status_checks` (an empty list, so a no-op) and -`non_fast_forward` are likewise absent from live `Base` — though -`non_fast_forward` *is* covered, by `Branch-Floor`. - -*`Immutable-Tags.json` lags the live ruleset* :: - -The live tag ruleset includes a `creation` rule; this file does not. Upstream -`immutable-tags.json` also populates `bypass_actors` (repository-role 5 and an -integration) with `bypass_mode: always`, and this file declares none. - -[WARNING] -==== -`bypass_actors` cannot be diffed from a low-privilege token: the REST API returns -`null` for that field unless the caller has admin on the repository. Absence of -`bypass_actors` in the output above is *not* evidence that none are configured. -==== - -== Verifying - -Do not infer that a ruleset is active because the JSON is present. Check with the -`gh api ... /rulesets` list above. A ruleset that exists as a file but was never -POSTed is the protection equivalent of a check that cannot fail. - -Conversely, do not infer that the estate is protected because a ruleset exists. -A ruleset with `enforcement: disabled` is inert, and a `pull_request` rule with -`allowed_merge_methods: []` can never be satisfied at all. diff --git a/czech-file-knife/.github/rulesets/base.json b/czech-file-knife/.github/rulesets/base.json deleted file mode 100644 index 136c0a0d9..000000000 --- a/czech-file-knife/.github/rulesets/base.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "name": "Base", - "target": "branch", - "enforcement": "active", - "conditions": { - "ref_name": { - "include": [ - "~DEFAULT_BRANCH" - ], - "exclude": [] - } - }, - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "pull_request" - }, - { - "actor_id": 1236702, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 29110, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 15368, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 347564, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 46505, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 1143301, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 1144995, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 12526, - "actor_type": "Integration", - "bypass_mode": "pull_request" - }, - { - "actor_id": 2538504, - "actor_type": "Integration", - "bypass_mode": "pull_request" - } - ], - "rules": [ - { - "type": "deletion" - }, - { - "type": "non_fast_forward" - }, - { - "type": "required_signatures" - }, - { - "type": "required_linear_history" - }, - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 0, - "dismiss_stale_reviews_on_push": true, - "require_code_owner_review": false, - "require_last_push_approval": false, - "required_review_thread_resolution": true, - "require_extra_approval_for_unattributed_changes": false, - "required_reviewers": [], - "allowed_merge_methods": [ - "squash" - ] - } - }, - { - "type": "required_status_checks", - "parameters": { - "strict_required_status_checks_policy": true, - "do_not_enforce_on_create": false, - "required_status_checks": [] - } - } - ] -} diff --git a/czech-file-knife/.github/rulesets/branch-floor.json b/czech-file-knife/.github/rulesets/branch-floor.json deleted file mode 100644 index 465908ac8..000000000 --- a/czech-file-knife/.github/rulesets/branch-floor.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "Branch-Floor", - "target": "branch", - "enforcement": "active", - "bypass_actors": [], - "conditions": { - "ref_name": { - "include": ["~DEFAULT_BRANCH"], - "exclude": [] - } - }, - "rules": [ - { "type": "deletion" }, - { "type": "non_fast_forward" } - ] -} diff --git a/czech-file-knife/.github/scripts/classify-issue.jq b/czech-file-knife/.github/scripts/classify-issue.jq deleted file mode 100644 index 6467c74ec..000000000 --- a/czech-file-knife/.github/scripts/classify-issue.jq +++ /dev/null @@ -1,164 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Classify one issue title against the estate label taxonomy. -# -# jq -r --arg title "docs: fix the README" \ -# --argjson have '[]' \ -# -f .github/scripts/classify-issue.jq .github/label-classifier.json -# -# Prints one label per line, or NOTHING when it cannot place the issue -# confidently. Nothing printed means "leave it for a human" -- a correct -# outcome, not a failure. -# -# WHY jq AND NOT PYTHON -# -# Python is fully banned estate-wide: the `governance / Language / package -# anti-pattern policy` gate runs `git ls-files '*.py'` and fails the PR -# ("Python is fully banned -- use AffineScript/Rust/SPARK/Julia"). This file -# is dispatched into every repo in the estate, so shipping it as .py would -# mean shipping an exemption into every repo too -- normalising the policy -# away by sweep. jq is preinstalled on every GitHub runner, is not on the -# banned list, needs no action (so no actions.lock entry can drift), and the -# rules are already JSON. -# -# The canonical implementation remains scripts/label-classify.py in the hub, -# which never runs in CI. tests/test-classifier-parity.py asserts this file -# agrees with it on every title in the corpus. -# -# `$have` lists labels the issue already carries. Anything already present is -# never re-suggested, and the classifier stays out of any max-1 tier the issue -# already has a label in, so a human's classification is never overridden. - -# Escape every non-alphanumeric so a keyword is matched literally. Escaping -# punctuation that needs no escape is harmless in Oniguruma. -def reesc: gsub("(?[^A-Za-z0-9 _])"; "\\\(.c)"); - -def norm: (. // "") | ascii_downcase - | sub("^[[:space:]]+"; "") | sub("[[:space:]]+$"; ""); - -# Asymmetric boundary: STRICT on the left, inflection-tolerant on the right. -# -# Measured over the issue corpus, the two error directions are not symmetric: -# * every false positive is a LEFT-side prefix -- `lean` in "clean up", -# `abi` in "capability", `mpl` in "Implement", `ffi` in "AffineScript", -# `smt` in "wasmtime". The left boundary must stay strict. -# * every real miss is a RIGHT-side inflection -- `test` vs "tests", -# `theorem` vs "theorems", `todo` vs "TODOs", `scaffold` vs "scaffolding". -# -# The right side therefore admits a CLOSED set of inflections. Closed, not open -# (`.*`), because an open right side re-admits the prefix false positives. -# -# `ion`/`ation` are excluded from the base set: they mint unrelated words -# (`port` + `ion` = "portion", and `port` is a live keyword). They are enabled -# only for shapes that are unambiguously truncated stems -- `-at` -# (instantiat, investigat, adjudicat) and `-ment` (document, implement). -def kwrx($kw): - ( "s|es|ed|d|ing|er|ers|y|ies" - + (if ($kw | endswith("at")) then "|ion|ions|e" - elif ($kw | endswith("ment")) then "|ation|ations" - else "" end) - ) as $suf - # Boundaries are conditional: a keyword not starting alphanumeric has no left - # boundary to enforce, and one not ending alphanumeric takes no suffix. - | (if ($kw | test("^[A-Za-z0-9]")) then "(?[^\\]]{1,25})\\]")) // null) as $m - | if $m == null then {rule: null, rest: $t} - else (($m.tag | norm | split("#")[0]) | norm) as $tag - | { rule: ($R.bracket_tag[$tag] // null), - rest: ($t | sub("^[[:space:]]*\\[[^\\]]{1,25}\\]"; "")) } - end; - -# Leading `word:` / `word(scope):` conventional-commit prefix. -def prefixrule($R; $t): - (($t | capture("^[[:space:]]*(?[A-Za-z][A-Za-z0-9_./-]{1,24})(?:[[:space:]]*\\([^)]*\\))?[[:space:]]*:")) // null) as $m - | if $m == null then null - else ($m.w | norm) as $k - # Compound prefixes such as "adaptive/must:" carry their meaning in the - # ISO 14764 category only; the modality does not label. - | (if ($R.prefix_split_on // "") != "" and ($k | contains($R.prefix_split_on)) - then ($k | split($R.prefix_split_on) | .[0]) else $k end) as $key - | ($R.title_prefix[$key] // null) - end; - -def signals($R; $tl; $sec): - [ ($R[$sec] // {}) | to_entries[] - | select(.value | any(. as $k | kwhit($k; $tl))) - | .key ]; - -# The HIGHEST-PRECEDENCE matching type, not merely the first in key order. -def kwtype($R; $tl): - [ $R.keyword_type | to_entries[] - | select(.value | any(. as $k | kwhit($k; $tl))) - | .key ] - | if length == 0 then null - else min_by([($R.precedence[.] // 99), .]) end; - -# Drop violations of each tier's `max`, keeping the highest-precedence member. -def enforce($R; $labels): - ($labels | unique) - | group_by($R.tier_of[.] // "?") - | map( ($R.tier_of[.[0]] // "?") as $tier - | ($R.tier_max[$tier] // null) as $mx - | if $mx == null or (length <= $mx) then . - else (sort_by([($R.precedence[.] // 99), .]))[0:$mx] end ) - | flatten; - -def classify($R; $title; $have0): - ($title // "") as $t0 - | ($t0 | norm) as $tl - | ($have0 | map(select(. != null and . != "")) - | unique) as $have - | ($R.tier_of | keys) as $canon - | $R.types as $types - | bracket($R; $t0) as $b - | (if $b.rule != null then ($b.rule | rulelabels) else [] end) as $l1 - | prefixrule($R; $b.rest) as $pr - | (if $pr != null then ($pr | rulelabels) else [] end) as $l2 - | (($b.rule != null) or ($pr != null)) as $matched0 - # 3. keyword areas are additive and never contribute a type - | ($l1 + $l2 + signals($R; $tl; "keyword_area")) as $acc - # 4. a type only if neither the rules nor the issue already supplied one - | (if (($acc + $have) | any(. as $x | $types | index($x))) - then null else kwtype($R; $tl) end) as $ty - | ($acc + (if $ty != null then [$ty] else [] end)) as $acc - | ($matched0 or ($ty != null)) as $matched - | ( $acc - + signals($R; $tl; "status_signal") - + signals($R; $tl; "meta_signal") - + signals($R; $tl; "scope_signal") ) as $acc - # NOTE: `frozen` is deliberately NOT subtracted. Frozen means "never rename or - # delete this label" -- `security` is frozen because triage.yml pins it in - # exempt-issue-labels. APPLYING it to an issue is correct; only the - # definition is protected. - | ($acc | map(select(. as $x | $canon | index($x))) | unique) as $acc - | enforce($R; $acc + ($have | map(select(. as $x | $canon | index($x))))) as $acc - | ($acc - $have) as $out - # Stay out of any max-1 tier the issue ALREADY has a label in -- a human's, - # or one an ISSUE_TEMPLATE applied. A prefix rule fires unconditionally, so - # "fix: ..." on an issue already labelled `enhancement` would otherwise add - # `bug` beside it. This covers every max-1 tier (type, priority, status, - # meta, scope), not just type. - | ( [ $R.tier_max | to_entries[] | select(.value == 1) | .key ] - | map(. as $t | select($have | any(($R.tier_of[.] // "?") == $t))) - ) as $lockedtiers - | ($out | map(select(($R.tier_of[.] // "?") as $t | ($lockedtiers | index($t)) | not))) as $out - # A rule must actually have FIRED: keyword-area hits alone are not enough. - | if ($matched | not) then [] - # a type is mandatory - elif ((($out + $have) | any(. as $x | $types | index($x))) | not) then [] - else ($out | sort) end; - -classify(.; $title; $have) | .[] diff --git a/czech-file-knife/.github/settings.yml b/czech-file-knife/.github/settings.yml deleted file mode 100644 index b9baefd76..000000000 --- a/czech-file-knife/.github/settings.yml +++ /dev/null @@ -1,160 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Repository settings for probot/settings GitHub App. -# https://github.com/probot/settings -# -# This file defines repository-level configuration that is automatically -# applied by the probot/settings app when changes are pushed to the default -# branch. Install the app at: https://github.com/apps/settings -# -# ─── THIS FILE MUST NEVER DECLARE REPOSITORY IDENTITY ───────────────────────── -# -# It carries NO `name`, `description`, `homepage` or `private` key, and it must -# never gain one. The reason is a real incident, not a hypothetical: -# -# This file previously read `name: "czech-file-knife"`. probot/settings applies it on -# every push to the default branch, so it submitted the literal string -# `czech-file-knife` as the repository name. GitHub sanitises an invalid name by -# collapsing each run of illegal characters to a dash — `czech-file-knife` became -# `-REPO-`. The template renamed itself on every push, its old URL 404'd, and -# it was mistaken for a deleted repository. `description` was likewise left -# reading the literal `Canonical RSR (Rhodium Standard Repository) template — governance, CI/CD, machine-readable metadata, ABI/FFI seam and formal-verification scaffolding that hyperpolymath projects are instantiated from.` on the live repo. -# -# Two properties make identity keys unsafe here specifically: -# -# 1. This is a TEMPLATE. `just repo-init` fills placeholders in repos minted by the -# scaffolder — but GitHub's "Use this template" button copies the default -# branch verbatim and never runs `just repo-init`. Any placeholder left in a -# probot-managed file therefore reaches children unrendered. -# 2. Identity is not shareable. The template must be public while children -# default private; a child cannot inherit either `name` or `private` from -# its parent without being wrong. -# -# Repository identity and visibility are therefore set OUT OF BAND: once per -# repo, at creation time, by the operator (the Configure stage of ADR-0003). -# `just repo-init` deliberately runs NO `gh` commands — it prints the exact -# `gh repo edit` commands as next steps instead. Fail-closed default: repos -# stay private unless the owner flips visibility deliberately; the template's -# own name and visibility are set deliberately by the owner. -# -# Everything below is safe to inherit: it is true of every RSR repo regardless -# of that repo's name, purpose or visibility. -# -# Enforced by `scripts/check-no-placeholders.sh`, which fails if this file -# contains a `{{` token or declares any of the four identity keys. - -# ─── Repository Settings ─────────────────────────────────────────────────────── - -repository: - has_issues: true - has_projects: true - has_wiki: false - has_downloads: true - default_branch: main - allow_squash_merge: true - allow_merge_commit: true - allow_rebase_merge: true - delete_branch_on_merge: true - enable_automated_security_fixes: true - enable_vulnerability_alerts: true - -# ─── Labels ──────────────────────────────────────────────────────────────────── - -labels: - - name: "bug" - color: "d73a4a" - description: "Something isn't working" - - - name: "enhancement" - color: "a2eeef" - description: "New feature or request" - - - name: "documentation" - color: "0075ca" - description: "Improvements or additions to documentation" - - - name: "security" - color: "e4e669" - description: "Security-related issue or vulnerability" - - - name: "good first issue" - color: "7057ff" - description: "Good for newcomers" - - - name: "help wanted" - color: "008672" - description: "Extra attention is needed" - - - name: "question" - color: "d876e3" - description: "Further information is requested" - - - name: "duplicate" - color: "cfd3d7" - description: "This issue or pull request already exists" - - - name: "invalid" - color: "e4e669" - description: "This doesn't seem right" - - - name: "wontfix" - color: "ffffff" - description: "This will not be worked on" - - - name: "dependencies" - color: "0366d6" - description: "Pull requests that update a dependency file" - - - name: "ci/cd" - color: "fbca04" - description: "Continuous integration and deployment" - - - name: "rsr" - color: "006b75" - description: "Rhodium Standard Repository compliance" - - - name: "hypatia" - color: "5319e7" - description: "Hypatia neurosymbolic scanner finding" - - - name: "bot" - color: "b4a8d1" - description: "Automated action by gitbot-fleet" - - - name: "breaking-change" - color: "b60205" - description: "Introduces a breaking change" - - - name: "performance" - color: "f9d0c4" - description: "Performance improvement" - - - name: "refactor" - color: "c5def5" - description: "Code refactoring with no functional change" - -# ─── Branch Protection ───────────────────────────────────────────────────────── - -# A required context must name a check that is actually EMITTED, or the branch -# deadlocks: the check never reports, so it stays permanently pending, and with -# enforce_admins even the owner cannot merge or push. This block previously -# required three contexts, two of which no repo has ever emitted — -# -# "codeql" is emitted as `analyze (actions, none)` (job id + matrix) -# "hypatia-scan" is emitted as `scan / Hypatia Neurosymbolic Analysis` -# (a reusable-workflow call always reports `caller / called`) -# -# — while "openssf-compliance" resolved only because its job *id* is literally -# `openssf-compliance` and it declares no `name:` and no matrix. That is the -# rule: pin the job **id** to the context string. Note this file is applied by -# probot on every push to the default branch, so a wrong context here does not -# merely describe protection, it re-imposes the deadlock on every push. -# -# `contexts` is deliberately EMPTY rather than aspirational. Requiring a check -# that cannot pass is the same defect as requiring one that cannot report, and -# on this repo no check can currently run at all: GitHub Actions is billing- -# blocked for PRIVATE repositories on this account ("The job was not started -# because recent account payments have failed or your spending limit needs to be -# increased"), which is why every workflow here fails with zero steps while the -# estate's public repos run normally. Repopulate this list — one context at a -# time, each pinned to a job id, each verified green — once Actions can run. diff --git a/czech-file-knife/.github/workflows/README.adoc b/czech-file-knife/.github/workflows/README.adoc deleted file mode 100644 index c7a232815..000000000 --- a/czech-file-knife/.github/workflows/README.adoc +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -= GitHub Workflows — what runs, and why some look duplicated - -This directory holds the repo's CI/CD. A few workflows *look* like they overlap -but are deliberately distinct — documented here so the apparent duplication is -not "tidied away" into a real coverage gap. - -== Hypatia runs in two places (on purpose) - -* `hypatia-scan.yml` — the **standalone security scan** (push / PR / weekly). - Independent; this is the repo's own Hypatia coverage. -* `static-analysis-gate.yml` -> `hypatia-scan` job — runs Hypatia as part of the - **gate**, then the `deposit-findings` job hands the results to the - **gitbot-fleet learning** pipeline. This job is also a *required status check*. - -Same tool, two different consumers (security scan vs fleet learning). Removing -either loses real function — keep both. - -== Secret scanning is single-sourced *in CI* — and two-tier overall - -`secret-scanner.yml` calls the standards reusable (gitleaks + a Rust-secrets -check). An inline TruffleHog job was removed: the reusable deliberately retired -TruffleHog as redundant with gitleaks, so re-adding it was duplicated work, not -extra coverage. - -**That retirement is scoped to CI.** The estate runs a deliberate *two-tier* -secret defence, and the second tier is not in this directory: - -[cols="1,2,3"] -|=== -| Tier | Engine | Where - -| CI -| gitleaks (+ rust-secrets, shell-secrets) -| `secret-scanner.yml` -> standards reusable. Runs on pull_request and on push - to main — i.e. on what has *already reached* the remote. - -| Local -| TruffleHog -| `.github/hooks/scan-secrets.sh`, called by `.github/hooks/pre-push`. Runs *before* the - push leaves the machine, on exactly the commits being pushed. -|=== - -Different engines at different checkpoints — the local tier is the only one that -can stop a key before it exists on a server, where revocation, not deletion, is -the only real remedy. Ratified by the owner, 2026-09-14: *"we [run] gitleaks on -github regularly but [run] trufflehog prior to pushes using the hooks system."* - -*Do not delete `.github/hooks/scan-secrets.sh` as duplication of `secret-scanner.yml`.* -It is the other half of this design, not a second copy of this half. The two -tiers also cover for each other's blind spots: the hook excludes TruffleHog's -SonarCloud detector (it matches any 40-hex string, so every SHA-pinned action -reads as a secret — measured, 24 findings on a clean clone, all of them action -pins), and gitleaks in CI catches a genuine SonarCloud token as `generic-api-key`. - -== SAST tools are complementary, not redundant - -* `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows). -* `sonarqube.yml` — SonarCloud (shell / JS surface; see `sonar-project.properties`). -* `static-analysis-gate.yml` — panic-attack + Hypatia gate (see above). - -Each targets a different language/surface; together they cover what no single -analyser does. diff --git a/czech-file-knife/.github/workflows/actions.lock b/czech-file-knife/.github/workflows/actions.lock deleted file mode 100644 index 929a39d7e..000000000 --- a/czech-file-knife/.github/workflows/actions.lock +++ /dev/null @@ -1,296 +0,0 @@ -# This file is machine-generated by `gh actions-lock`. -# Do not edit by hand; run `gh actions-lock` to update. -# Docs: https://gh.io/actions-lockfile -version: 'v0.0.2' -workflows: - '.github/workflows/build-notification.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/cflite_batch.yml': - - 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1' - '.github/workflows/cflite_pr.yml': - - 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1' - '.github/workflows/codeql.yml': - - 'actions/checkout@v7.0.1' - - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63' - '.github/workflows/deed-validate.yml': - - 'actions/checkout@v7.0.1' - - 'hyperpolymath/deed-ecosystem@main' - '.github/workflows/dependabot-automerge.yml': - - 'dependabot/fetch-metadata@v3.1.0' - '.github/workflows/docker-build.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/dogfood-gate.yml': - - 'actions/checkout@v7.0.1' - - 'erlef/setup-beam@v1.24.1' - - 'hyperpolymath/deed-ecosystem@main' - - 'hyperpolymath/k9-ecosystem@main' - '.github/workflows/dogfood-summary.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/dot-wellknown-enforcement.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/eclexiaiser-validate.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/empty-linter.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/estate-rules.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/governance.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/groove-check.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/guix-policy.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/hypatia-scan.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/k9-validate.yml': - - 'actions/checkout@v7.0.1' - - 'hyperpolymath/k9-ecosystem@main' - '.github/workflows/label-triage.yml': [] - '.github/workflows/labels.yml': [] - '.github/workflows/lock-sync-gate.yml': [] - '.github/workflows/main-estate-audit.yml': - - 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d' - '.github/workflows/mirror.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/ossf-best-practices.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/pages.yml': - - 'actions/cache@v6.1.0' - - 'actions/checkout@v7.0.1' - - 'actions/configure-pages@v6.0.0' - - 'actions/deploy-pages@v5.0.1' - - 'actions/upload-pages-artifact@v5.0.0' - - 'haskell-actions/setup@v2.12.0' - '.github/workflows/push-email-notify.yml': - - 'hyperpolymath/smtp-notify-action@v0.3.0' - '.github/workflows/quality.yml': - - 'actions/checkout@v7.0.1' - - 'editorconfig-checker/action-editorconfig-checker@v3.0.0' - '.github/workflows/release.yml': - - 'actions/attest-build-provenance@v4.2.2' - - 'actions/checkout@v7.0.1' - - 'actions/upload-artifact@v7.0.1' - - 'softprops/action-gh-release@v3.0.3' - '.github/workflows/rsr-compliance-canary.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/runtime-policy.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/rust-ci.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/scorecard.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/secret-scanner.yml': - - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' - '.github/workflows/security-policy.yml': - - 'actions/checkout@v7.0.1' - '.github/workflows/sonarqube.yml': - - 'actions/checkout@v7.0.1' - - 'sonarsource/sonarqube-scan-action@v8.2.2' - '.github/workflows/static-analysis-gate.yml': - - 'actions/checkout@v7.0.1' - - 'actions/download-artifact@v8.0.1' - - 'actions/upload-artifact@v7.0.1' - - 'erlef/setup-beam@v1.24.1' - '.github/workflows/workflow-linter.yml': - - 'actions/checkout@v7.0.1' - - 'oven-sh/setup-bun@v2.2.0' -dependencies: - 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': - ref: '6323deb102c322ba6fcbdcafc7e3dddab59af2b6' - commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' - owner_id: 580492 - repo_id: 298565987 - 'actions/attest-build-provenance@v4.2.2': - ref: 'v4.2.2' - commit: 'sha1-4d101475d8b20a2381f78447822ac1eab6504dd8' - owner_id: 44036562 - repo_id: 760702757 - uses: - - 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d' - 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d': - ref: 'v4.2.1' - commit: 'sha1-508db95dd578ae2727ebd6217d5ba78e4fbda05d' - owner_id: 44036562 - repo_id: 760701061 - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': - ref: '55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - owner_id: 44036562 - repo_id: 215566462 - 'actions/cache@v6.1.0': - ref: 'v6.1.0' - commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - owner_id: 44036562 - repo_id: 215566462 - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1': - ref: '3d3c42e5aac5ba805825da76410c181273ba90b1' - commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' - owner_id: 44036562 - repo_id: 197814629 - 'actions/checkout@v7.0.1': - ref: 'v7.0.1' - commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' - owner_id: 44036562 - repo_id: 197814629 - 'actions/configure-pages@v6.0.0': - ref: 'v6.0.0' - commit: 'sha1-45bfe0192ca1faeb007ade9deae92b16b8254a0d' - owner_id: 44036562 - repo_id: 513659658 - 'actions/deploy-pages@v5.0.1': - ref: 'v5.0.1' - commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' - owner_id: 44036562 - repo_id: 438112499 - 'actions/download-artifact@v8.0.1': - ref: 'v8.0.1' - commit: 'sha1-3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' - owner_id: 44036562 - repo_id: 192626254 - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a': - ref: '043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' - commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' - owner_id: 44036562 - repo_id: 192625955 - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': - ref: 'v7.0.0' - commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' - owner_id: 44036562 - repo_id: 192625955 - 'actions/upload-artifact@v7.0.1': - ref: 'v7.0.1' - commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' - owner_id: 44036562 - repo_id: 192625955 - 'actions/upload-pages-artifact@v5.0.0': - ref: 'v5.0.0' - commit: 'sha1-fc324d3547104276b827a68afc52ff2a11cc49c9' - owner_id: 44036562 - repo_id: 496012378 - uses: - - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' - 'dependabot/fetch-metadata@v3.1.0': - ref: 'v3.1.0' - commit: 'sha1-25dd0e34f4fe68f24cc83900b1fe3fe149efef98' - owner_id: 27347476 - repo_id: 371068214 - 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': - ref: '6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' - commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' - owner_id: 1940490 - repo_id: 260749683 - 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393': - ref: '51f63319f592f97930c73d9c46184d20bd206393' - commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' - owner_id: 26415196 - repo_id: 297874902 - 'editorconfig-checker/action-editorconfig-checker@v3.0.0': - ref: 'v3.0.0' - commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' - owner_id: 26415196 - repo_id: 297874902 - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': - ref: '54075bcc5e249e4758d363f27d099f55d843f124' - commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' - owner_id: 47606891 - repo_id: 331103973 - 'erlef/setup-beam@v1.24.1': - ref: 'v1.24.1' - commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' - owner_id: 47606891 - repo_id: 331103973 - 'google/clusterfuzzlite@884713a6c30a92e5e8544c39945cd7cb630abcd1': - ref: 'v1' - commit: 'sha1-884713a6c30a92e5e8544c39945cd7cb630abcd1' - owner_id: 1342004 - repo_id: 400046858 - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63': - ref: 'v4.38.0' - commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' - owner_id: 9919 - repo_id: 259445878 - 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938': - ref: 'cdf488f595d80d6e07e03d4674febd5ab45fa938' - commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' - owner_id: 9919 - repo_id: 259445878 - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406': - ref: 'abea47f85e598557f500fa1fd2ab7464fcb39406' - commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' - owner_id: 1006268 - repo_id: 212984112 - 'haskell-actions/setup@v2.12.0': - ref: 'v2.12.0' - commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' - owner_id: 75048950 - repo_id: 623796603 - 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5': - ref: '0da816c05ae7486671d863e07935f93981c0c2d5' - commit: 'sha1-0da816c05ae7486671d863e07935f93981c0c2d5' - owner_id: 6759885 - repo_id: 1326697643 - 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d': - ref: '55556cf5ba71ba744695861503c13148d3915a5d' - commit: 'sha1-55556cf5ba71ba744695861503c13148d3915a5d' - owner_id: 6759885 - repo_id: 1326697643 - uses: - - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' - - 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5' - 'hyperpolymath/deed-ecosystem@main': - ref: 'main' - commit: 'sha1-44f9c9fc42901a55e0f489a40eabf4173e550d1d' - owner_id: 6759885 - repo_id: 1275649586 - 'hyperpolymath/k9-ecosystem@main': - ref: 'main' - commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' - owner_id: 6759885 - repo_id: 1275650185 - 'hyperpolymath/smtp-notify-action@v0.3.0': - ref: 'v0.3.0' - commit: 'sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be' - owner_id: 6759885 - repo_id: 1352485172 - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540': - ref: 'da2c748aad55c1a1dcba00b60fe4a35017bc6540' - commit: 'sha1-da2c748aad55c1a1dcba00b60fe4a35017bc6540' - owner_id: 6759885 - repo_id: 1116521501 - uses: - - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' - - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' - - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' - - 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' - - 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393' - - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' - - 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938' - - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406' - - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' - - 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' - - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': - ref: '2d1146689b8cda280b9bc96326124645441f03bc' - commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' - owner_id: 67707773 - repo_id: 421101922 - 'oven-sh/setup-bun@v2.2.0': - ref: 'v2.2.0' - commit: 'sha1-0c5077e51419868618aeaa5fe8019c62421857d6' - owner_id: 108928776 - repo_id: 512644635 - 'softprops/action-gh-release@v3.0.3': - ref: 'v3.0.3' - commit: 'sha1-efb35369e0ad2afab669f228072c1b0d510eae64' - owner_id: 2242 - repo_id: 204253808 - 'sonarsource/sonarqube-scan-action@v8.2.2': - ref: 'v8.2.2' - commit: 'sha1-ba9859eae8dd6bd29e412f25ddbbef3d032000f4' - owner_id: 545988 - repo_id: 366408409 - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555': - ref: 'e83874834305fe9a4a2997156cb26c5de65a8555' - commit: 'sha1-e83874834305fe9a4a2997156cb26c5de65a8555' - owner_id: 135788 - repo_id: 208510314 diff --git a/czech-file-knife/.github/workflows/build-notification.yml b/czech-file-knife/.github/workflows/build-notification.yml deleted file mode 100644 index e43da5b7f..000000000 --- a/czech-file-knife/.github/workflows/build-notification.yml +++ /dev/null @@ -1,50 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# -# OPTIONAL: BoJ Server Build Trigger -# This workflow notifies a BoJ Server instance when code is pushed. -# It is a no-op if BOJ_SERVER_URL is not set or the server is unreachable. -# To enable: set BOJ_SERVER_URL as a repository variable (secrets cannot gate -# a job-level if:). A BOJ_SERVER_URL secret is still honoured at run time. -# To disable: delete this file or leave BOJ_SERVER_URL unset. -name: BoJ Server Build Trigger -on: - push: - branches: [main, master] - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - trigger-boj: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: ${{ vars.BOJ_SERVER_URL != '' }} - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - name: Trigger BoJ Server (Casket/ssg-mcp) - env: - BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }} - REPO_NAME: ${{ github.repository }} - BRANCH_NAME: ${{ github.ref_name }} - run: | - set -euo pipefail - - if [ -z "$BOJ_URL" ]; then - echo "BOJ_SERVER_URL not configured - skipping" - exit 0 - fi - - payload="$(jq -cn \ - --arg repo "$REPO_NAME" \ - --arg branch "$BRANCH_NAME" \ - --arg engine "casket" \ - '{repo:$repo, branch:$branch, engine:$engine}')" - - curl -sf -X POST "${BOJ_URL}/cartridges/ssg-mcp/invoke" \ - -H "Content-Type: application/json" \ - --data "$payload" \ - || echo "BoJ server unreachable - skipping (non-fatal)" diff --git a/czech-file-knife/.github/workflows/cflite_batch.yml b/czech-file-knife/.github/workflows/cflite_batch.yml deleted file mode 100644 index e11e36e5e..000000000 --- a/czech-file-knife/.github/workflows/cflite_batch.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: ClusterFuzzLite batch fuzzing -on: - schedule: - - cron: '0 3 * * 0' # Weekly on Sunday at 3am UTC - workflow_dispatch: -permissions: read-all -jobs: - BatchFuzzing: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - sanitizer: [address] - steps: - - name: Build Fuzzers (${{ matrix.sanitizer }}) - id: build - uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 - with: - language: rust - sanitizer: ${{ matrix.sanitizer }} - - name: Run Fuzzers (${{ matrix.sanitizer }}) - id: run - uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - fuzz-seconds: 1800 - mode: batch - sanitizer: ${{ matrix.sanitizer }} diff --git a/czech-file-knife/.github/workflows/cflite_pr.yml b/czech-file-knife/.github/workflows/cflite_pr.yml deleted file mode 100644 index edb9391a7..000000000 --- a/czech-file-knife/.github/workflows/cflite_pr.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: ClusterFuzzLite PR fuzzing -on: - pull_request: - branches: [main] -permissions: read-all -jobs: - PR: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - sanitizer: [address] - steps: - - name: Build Fuzzers (${{ matrix.sanitizer }}) - id: build - uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 - with: - language: rust - sanitizer: ${{ matrix.sanitizer }} - - name: Run Fuzzers (${{ matrix.sanitizer }}) - id: run - uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - fuzz-seconds: 300 - mode: code-change - sanitizer: ${{ matrix.sanitizer }} diff --git a/czech-file-knife/.github/workflows/codeql.yml b/czech-file-knife/.github/workflows/codeql.yml deleted file mode 100644 index 517077e75..000000000 --- a/czech-file-knife/.github/workflows/codeql.yml +++ /dev/null @@ -1,47 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: CodeQL Security Analysis -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - schedule: - - cron: '0 6 1 * *' # monthly 1st 06:00 UTC (Actions burn cut, standards#288) -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - analyze: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - include: - # Default to `actions` — scaffolded repos rarely have JS/TS - # failures on every CodeQL run. The `actions` extractor scans - # workflow files which every repo has. Override per-repo if - # the scaffolded project actually contains JS/TS code. - # Per hypatia rule `codeql_language_matrix_mismatch`. - - language: actions - build-mode: none - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - with: - persist-credentials: false - - name: Initialize CodeQL - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) - with: - category: "/language:${{ matrix.language }}" diff --git a/czech-file-knife/.github/workflows/deed-validate.yml b/czech-file-knife/.github/workflows/deed-validate.yml deleted file mode 100644 index f05a5ddc5..000000000 --- a/czech-file-knife/.github/workflows/deed-validate.yml +++ /dev/null @@ -1,59 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "🟡 CHECK: DEED Manifest Validation" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - deed-validate: - name: Validate DEED manifests - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for A2ML files - id: detect - run: | - COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) - echo "count=$COUNT" >> "$GITHUB_OUTPUT" - if [ "$COUNT" -eq 0 ]; then - echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" - fi - - - name: "🟡 CHECK: Validate DEED manifests" - if: steps.detect.outputs.count > 0 - uses: hyperpolymath/deed-ecosystem/validate-action@main - with: - path: '.' - strict: 'false' - - - name: Write summary - run: | - MANIFEST_COUNT="${{ steps.detect.outputs.count }}" - if [ "$MANIFEST_COUNT" -eq 0 ]; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## DEED Manifest Validation - - :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. - - Copy it from [czech-file-knife](https://github.com/hyperpolymath/czech-file-knife). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. - EOF - else - echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/czech-file-knife/.github/workflows/dependabot-automerge.yml b/czech-file-knife/.github/workflows/dependabot-automerge.yml deleted file mode 100644 index 31a7b6aa7..000000000 --- a/czech-file-knife/.github/workflows/dependabot-automerge.yml +++ /dev/null @@ -1,137 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# -# dependabot-automerge.yml — enable GitHub's native auto-merge on -# Dependabot pull requests that match a declared severity / ecosystem -# policy. Pairs with `.github/dependabot.yml`'s -# `open-pull-requests-limit: 0` + security-only pattern (see the -# cargo block there). -# -# What this does: -# - Triggers on every Dependabot PR. -# - Reads the PR's update-type metadata via the dependabot/fetch-metadata -# action (no free-text parsing). -# - Requires CI to be green before merge (GitHub's auto-merge enforces -# required status checks). -# - Gates merge behind a severity+ecosystem policy table. Default is -# low+medium security updates only. -# -# Why auto-merge on GitHub (not via a bot like rhodibot) is the right -# layer: GitHub enforces branch protection + required checks natively, -# and the PR author is already `dependabot[bot]`. Rhodibot doesn't need -# to know anything about ecosystems — GitHub handles the merge mechanics -# once we approve. -# -# Threat model: -# - A compromised upstream package with a bogus security advisory -# could propose a malicious version bump. Mitigation: require at -# least one non-automated reviewer for HIGH+CRITICAL severity -# (done below — we explicitly refuse to auto-approve those). -# - A compromised Dependabot itself is an Akerlof claim-grounder -# problem. Not in scope here; track under -# `project_claim_grounders_dual_use_akerlof.md`. -# -# Dogfooding: this workflow template is itself subject to the same -# Dependabot config via the github-actions ecosystem block, so SHA -# bumps for dependabot/fetch-metadata flow through the same path. - -name: Dependabot Auto-Merge -on: - pull_request: - types: [opened, reopened, synchronize] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false -permissions: - contents: read - pull-requests: write # needed to approve - # NB: keep narrow — do NOT add secrets: read or id-token: write here. - # The write scope auto-merge needs is elevated PER JOB below, not here: a - # job-level permissions: block REPLACES this one, so the job restates both. -jobs: - automerge: - # Only run for PRs actually authored by Dependabot. - if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' - permissions: - contents: write # needed to enable auto-merge (gh pr merge --auto) - pull-requests: write # needed to approve - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Fetch Dependabot metadata - id: meta - uses: dependabot/fetch-metadata@v3.1.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - # --- Policy gate ------------------------------------------------------- - # Outputs from fetch-metadata we care about: - # update-type → version-update:semver-{patch,minor,major} - # dependency-type → direct:{development,production} | indirect - # alert-state → AUTO_DISMISSED | DISMISSED | FIXED | OPEN - # ghsa-id → GHSA-... if this is a security PR - # --- Policy ------------------------------------------------------------- - # AUTO-APPROVE + AUTO-MERGE when: - # 1. This is a SECURITY update (ghsa-id present), AND - # 2. Update is patch or minor, AND - # 3. Severity ≤ moderate (Dependabot doesn't expose severity - # directly in fetch-metadata; infer from the absence of - # HIGH/CRITICAL labels added by Dependabot). - # Otherwise: do nothing. Human reviews HIGH+CRITICAL security - # updates and all non-security bumps. - - name: Decide policy outcome - id: policy - env: - GHSA_ID: ${{ steps.meta.outputs.ghsa-id }} - UPDATE_TYPE: ${{ steps.meta.outputs.update-type }} - PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} - run: | - set -euo pipefail - - is_security=false - [ -n "$GHSA_ID" ] && is_security=true - - # Owner policy (deliberate: velocity over caution). Auto-merge EVERY - # Dependabot update — patch, minor AND major, security or routine. - # Safe because GitHub's auto-merge only COMPLETES once the required - # checks (secret-scanner, codeql, hypatia-scan, openssf-compliance, - # build/test) are green: a bump that breaks fails CI and the PR stays - # OPEN with an email ("oi, it broke") instead of landing on a red - # main; a bump that passes lands within minutes with no chasing. - # This is preferred over making Dependabot a ruleset BYPASS actor, - # which would let bumps skip those very checks (no gate, no signal). - echo "action=automerge" >> "$GITHUB_OUTPUT" - echo "security=$is_security" >> "$GITHUB_OUTPUT" - echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" - echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT" - - name: Approve PR (if policy allows) - if: steps.policy.outputs.action == 'automerge' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_URL: ${{ github.event.pull_request.html_url }} - run: | - gh pr review --approve "$PR_URL" \ - --body "Auto-approving Dependabot security update (${{ steps.policy.outputs.ghsa }}, ${{ steps.policy.outputs.update_type }}). Policy: low/moderate security patches/minors only." - - name: Enable auto-merge (if policy allows) - if: steps.policy.outputs.action == 'automerge' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_URL: ${{ github.event.pull_request.html_url }} - run: | - gh pr merge --auto --squash "$PR_URL" - - name: Write decision to step summary - env: - ACTION: ${{ steps.policy.outputs.action }} - IS_SECURITY: ${{ steps.policy.outputs.security }} - UPDATE_TYPE: ${{ steps.policy.outputs.update_type }} - GHSA: ${{ steps.policy.outputs.ghsa }} - run: | - { - echo "## Dependabot Auto-Merge Decision" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| Policy action | \`$ACTION\` |" - echo "| Security update | \`$IS_SECURITY\` |" - echo "| Update type | \`$UPDATE_TYPE\` |" - echo "| GHSA ID | \`${GHSA:-n/a}\` |" - } >> "$GITHUB_STEP_SUMMARY" diff --git a/czech-file-knife/.github/workflows/docker-build.yml b/czech-file-knife/.github/workflows/docker-build.yml deleted file mode 100644 index f795d2eee..000000000 --- a/czech-file-knife/.github/workflows/docker-build.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: container build -on: - pull_request: - paths: - - 'build/container/**' - - 'build/just/container.just' - - '.github/workflows/container-build.yml' - push: - tags: ['v*'] - workflow_dispatch: - -# Scope + cancel superseded runs (estate guardrail). -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - container: - # OFF by default — costs nothing in derived repos. A repo opts in by setting - # the repository/organisation variable CONTAINER_CI=true. When enabled it - # runs on OWNED self-hosted runners (no metered GitHub Actions minutes); - # override the labels with the CONTAINER_RUNNER variable (a JSON array). - if: vars.CONTAINER_CI == 'true' - runs-on: ${{ fromJSON(vars.CONTAINER_RUNNER || '["self-hosted","owned","container"]') }} - timeout-minutes: 30 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - - name: Tooling check - run: | - command -v just >/dev/null 2>&1 || { echo "::error::just not found on this runner"; exit 1; } - # The container recipes auto-detect the engine (podman | nerdctl | docker). - for e in podman nerdctl docker; do command -v "$e" >/dev/null 2>&1 && { echo "engine: $e"; break; }; done - - - name: Build image - run: just container-build - - - name: Verify compose configuration - run: just container-verify - - - name: Scan image (trivy, best-effort) - run: | - if command -v trivy >/dev/null 2>&1; then - trivy image --severity HIGH,CRITICAL --exit-code 0 "${{ github.event.repository.name }}:latest" || true - else - echo "trivy not installed on this runner — skipping image scan" - fi - - - name: Sign & verify .ctp bundle (tags only) - if: startsWith(github.ref, 'refs/tags/v') - run: just container-sign # cerro-torre: build + pack + Ed25519 sign + verify diff --git a/czech-file-knife/.github/workflows/dogfood-gate.yml b/czech-file-knife/.github/workflows/dogfood-gate.yml deleted file mode 100644 index 6274422af..000000000 --- a/czech-file-knife/.github/workflows/dogfood-gate.yml +++ /dev/null @@ -1,632 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# This workflow is managed by gh actions-lock. -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate -# Validates that the repo uses hyperpolymath's own formats and tools. -# Companion to static-analysis-gate.yml (security) — this is for format compliance. -name: Dogfood Gate - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read - -jobs: - # --------------------------------------------------------------------------- - # Job 1: DEED manifest validation - # --------------------------------------------------------------------------- - deed-validate: - name: Validate DEED manifests - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for manifest files (.a2ml/.deed) - id: detect - run: | - COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) - echo "count=$COUNT" >> "$GITHUB_OUTPUT" - if [ "$COUNT" -eq 0 ]; then - echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" - fi - - - name: Validate DEED manifests - if: steps.detect.outputs.count > 0 - uses: hyperpolymath/deed-ecosystem/validate-action@main - with: - path: '.' - strict: 'false' - - - name: Write summary - run: | - MANIFEST_COUNT="${{ steps.detect.outputs.count }}" - if [ "$MANIFEST_COUNT" -eq 0 ]; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## DEED Manifest Validation - - :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. - - Copy it from [czech-file-knife](https://github.com/hyperpolymath/czech-file-knife). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. - EOF - else - echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" - fi - - # --------------------------------------------------------------------------- - # Job 2: K9 contract validation - # --------------------------------------------------------------------------- - k9-validate: - name: Validate K9 contracts - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for K9 files - id: detect - run: | - COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) - CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ - -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ - -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) - echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" - echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" - if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then - echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." - fi - - - name: Validate K9 contracts - if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-ecosystem/validate-action@main - with: - path: '.' - strict: 'false' - - - name: Write summary - run: | - K9_COUNT="${{ steps.detect.outputs.k9_count }}" - CFG_COUNT="${{ steps.detect.outputs.config_count }}" - if [ "$K9_COUNT" -eq 0 ]; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## K9 Contract Validation - - :warning: **No K9 contract files found.** Run `k9iser` to generate contracts. - - Generate contracts with: `k9iser generate .` - EOF - else - echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" - fi - - # --------------------------------------------------------------------------- - # Job 3: Empty-linter — invisible character detection - # --------------------------------------------------------------------------- - empty-lint: - name: "🔴 GATE: Empty-linter (invisible characters)" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Scan for invisible characters - id: lint - run: | - RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" - BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" - if ! scripts/check-invisible-characters.sh \ - "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then - echo "::error::Invisible-character scanner failed; refusing a partial pass" - exit 2 - fi - - FINDINGS=0 - while IFS= read -r -d '' filepath; do - FINDINGS=$((FINDINGS + 1)) - REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" - SAFE_PATH="${REL_PATH//'%'/'%25'}" - SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" - SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" - SAFE_PATH="${SAFE_PATH//':'/'%3A'}" - SAFE_PATH="${SAFE_PATH//','/'%2C'}" - echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" - done < "$RESULTS_FILE" - - BLOCKING=0 - while IFS= read -r -d '' filepath; do - BLOCKING=$((BLOCKING + 1)) - REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" - SAFE_PATH="${REL_PATH//'%'/'%25'}" - SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" - SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" - SAFE_PATH="${SAFE_PATH//':'/'%3A'}" - SAFE_PATH="${SAFE_PATH//','/'%2C'}" - echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" - done < "$BLOCKING_FILE" - - echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" - echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" - echo "ready=true" >> "$GITHUB_OUTPUT" - - if [ "$BLOCKING" -gt 0 ]; then - echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - - name: Write summary - run: | - if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then - FINDINGS="${{ steps.lint.outputs.findings }}" - if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then - echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" - else - echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" - fi - else - echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" - fi - - # --------------------------------------------------------------------------- - # Job 4: Groove manifest check (for repos that should expose services) - # --------------------------------------------------------------------------- - groove-check: - name: "🟡 CHECK: Groove manifest check" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for Groove manifest - id: groove - run: | - # Check for static or dynamic Groove endpoints - HAS_MANIFEST="false" - HAS_GROOVE_CODE="false" - - # Canonical manifest location is www/.well-known/groove/ (issue #53); - # the repository-root path is accepted, with a warning, during the - # migration window. - MANIFEST="" - if [ -f "www/.well-known/groove/manifest.json" ]; then - MANIFEST="www/.well-known/groove/manifest.json" - elif [ -f ".well-known/groove/manifest.json" ]; then - MANIFEST=".well-known/groove/manifest.json" - echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" - fi - - if [ -n "$MANIFEST" ]; then - HAS_MANIFEST="true" - # Validate the manifest JSON - if ! jq empty "$MANIFEST" 2>/dev/null; then - # Gate, don't annotate: an unparseable manifest is a real error, - # not a warning — same behaviour as the standalone - # groove-check.yml (this job had drifted to annotation-only, - # the class of "check that cannot fail" from nexia-list#49). - echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" - exit 1 - else - SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") - echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" - fi - fi - - # Check for Groove endpoint code (Rust, Elixir, Zig, V) - if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then - HAS_GROOVE_CODE="true" - fi - - # Check if this repo likely serves HTTP in production. Key on - # production HTTP-server framework markers only. A bare `TcpListener` - # is deliberately NOT a signal: it is dominated by test/utility use - # (e.g. a #[cfg(test)] loopback fake), so matching it produced a - # spurious groove nudge on client-only apps. A real hand-rolled Rust - # server still pairs the listener with `axum::serve`/`hyper::Server`, - # which are matched here. - HAS_SERVER="false" - if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then - HAS_SERVER="true" - fi - - echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" - echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" - echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" - - if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then - echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." - fi - - - name: Write summary - run: | - echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" - - # --------------------------------------------------------------------------- - # Job 5: eclexiaiser manifest validation - # --------------------------------------------------------------------------- - eclexiaiser-validate: - name: Validate eclexiaiser manifest - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check and validate eclexiaiser manifest - id: eclex - run: | - if [ ! -f "eclexiaiser.toml" ]; then - # Check if repo has a Containerfile — if so, recommend eclexiaiser - if [ -f "Containerfile" ]; then - echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." - fi - echo "has_manifest=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "has_manifest=true" >> "$GITHUB_OUTPUT" - - # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). - # Structural presence checks only — deep schema validation is eclexiaiser's own job. - err=0 - grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } - grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } - grep -qE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } - if [ "$err" -ne 0 ]; then - exit 1 - fi - fns=$(grep -cE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml) - echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" - - - name: Write summary - run: | - if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then - echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" - else - echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" - fi - - # --------------------------------------------------------------------------- - # Job 6: Canon lockstep — does this template still implement the canon? - # - # The canon<->spine binding. hyperpolymath/standards publishes canon.lock: the - # released identity of the law, with a sha256 per law artefact. This repo - # declares the SAME hashes in .machine_readable/rsr-profile.a2ml [canon]. - # - # Before this job, the declaration was two free-text strings - # (`spec = "rsr-criteria-v2"`, `declares-against = "2.0.0-draft"`) and nothing - # failed when the canon changed. This makes it a comparison. - # - # Deliberately a bare `run:` step with NO `uses:`. Adding an action reference - # would have required an actions.lock row, and this repo's lock records - # FLOATING refs ('actions/checkout@v7.0.1') where the canon's records SHAs - - # so a pinned `uses:` here would mismatch the lock and fail at LOAD time with - # `jobs=0` and no annotation. Read-only over the public internet needs no auth. - # --------------------------------------------------------------------------- - canon-lockstep: - name: Canon lockstep - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read - steps: - - name: Fetch the canon's released identity - id: canon - run: | - set -euo pipefail - LOCK="$RUNNER_TEMP/canon.lock" - URL="https://raw.githubusercontent.com/hyperpolymath/standards/main/canon.lock" - if ! curl -fsSL --retry 3 --max-time 30 "$URL" -o "$LOCK"; then - echo "::error::could not fetch canon.lock from $URL" - echo "The canon does not publish a released identity, so this repo" - echo "cannot be shown to implement it. Fix at source: add canon.lock." - exit 1 - fi - # Read (not compute) the criteria hash canon.lock asserts. - # - # awk only JOINS the record — the artefact entries are inline tables - # that span lines — then grep -oE extracts the hash. Deliberately the - # same two-step the canon's own check-canon-lockstep.sh uses, so - # there is one technique across both repos. - # - # Two traps this avoids, both found by running it rather than reading - # it. (1) An earlier version used `gsub(/.*"|"/,"",s)` to strip the - # quotes; the `.*"` is GREEDY, so it ate the entire string and the - # hash came out EMPTY. (2) awk interval expressions are not portable - # across mawk/gawk builds; grep -oE is. - rec="$(awk ' - /^[[:space:]]*#/ { next } - /^criteria[[:space:]]*=/ { on=1 } - on { - line=$0; sub(/#.*/,"",line); rec = rec " " line - if (line ~ /}/) { on=0 } - } - END { print rec }' "$LOCK")" - want="$(printf '%s\n' "$rec" \ - | grep -oE 'sha256[[:space:]]*=[[:space:]]*"[0-9a-f]{64}"' \ - | grep -oE '[0-9a-f]{64}' | head -1)" - if [ -z "$want" ]; then - echo "::error::canon.lock carries no criteria sha256 — malformed, or" - echo "the criteria record no longer starts a line with 'criteria ='." - exit 1 - fi - echo "want=$want" >> "$GITHUB_OUTPUT" - - - name: Compare this repo's declared pin - env: - WANT: ${{ steps.canon.outputs.want }} - # This job has NO `uses:` steps, deliberately: any added action would - # have to be pinned in actions.lock, and `uses ⊆ actions.lock` is already - # failing on main, so this job must not make it worse. That means no - # actions/checkout — and therefore NO WORKING TREE. Which is what - # broke this job: it read "machine-readable/rsr-profile.a2ml" from a - # directory that was never populated, so it failed 100% of the time - # with "no rsr-profile.a2ml", and would have whatever the pin said. - # - # Fetch the profile by SHA instead, exactly as canon.lock is fetched - # above. Deliberately NOT github.sha: on pull_request that is the - # ephemeral MERGE commit, which is not on the remote and would 404. - # The event's head sha is the commit that actually exists there. - HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - run: | - set -euo pipefail - PROFILE="$RUNNER_TEMP/rsr-profile.a2ml" - fetched="" - for p in ".machine_readable/rsr-profile.a2ml" "machine-readable/rsr-profile.a2ml"; do - URL="https://raw.githubusercontent.com/hyperpolymath/czech-file-knife/${HEAD_SHA}/${p}" - if curl -fsSL --retry 3 --max-time 30 "$URL" -o "$PROFILE" 2>/dev/null; then - fetched="$p"; break - fi - done - if [ -z "$fetched" ]; then - echo "::error::no rsr-profile.a2ml at ${HEAD_SHA:0:7} — this repo cannot declare a canon pin" - echo "Looked for .machine_readable/rsr-profile.a2ml and machine-readable/rsr-profile.a2ml." - exit 1 - fi - echo "profile: $fetched @ ${HEAD_SHA:0:7}" - - # ALL THREE PINS, BOTH SIDES. - # - # This step used to compare only criteria_sha256, while this repo's own - # rsr-profile.a2ml stated that "these values ... equal canon.lock - # [canon.artifacts]" — plural. The claim was wider than the check, so - # `version` and `gates_sha256` could sit at stale values and nothing - # reported it. They did: canon 2.0.1 changed gates_sha256, and this repo - # kept asserting 2.0.0 and the superseded hash. A pin nothing verifies - # is not a binding. See hyperpolymath/standards docs/AUDIT.adoc F8. - # - # Both readers below are the technique the canon's own - # check-canon-lockstep.sh uses, so there is one idiom across both repos. - canon_key() { - awk -v key="$1" ' - /^[[:space:]]*#/ { next } - /^\[/ { f = ($0 == "[canon]") ? 1 : 0; next } - f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { - sub(/^[^=]*=[[:space:]]*/, ""); gsub(/^["[:space:]]+|["[:space:]]+$/, ""); print; exit - }' "$2" - } - # The artefact records are inline tables spanning lines, so awk JOINS the - # record and grep extracts the hash. Deliberately not anchored to the - # sha256 being on any particular line of the record. - artefact_hash() { - awk -v key="$1" ' - $0 ~ "^" key "[[:space:]]*=" { f = 1 } - f { buf = buf " " $0 } - f && /sha256[[:space:]]*=/ { print buf; exit } - ' "$2" | grep -oE '[0-9a-f]{64}' | head -1 - } - - want_crit="$WANT" - want_gates="$(artefact_hash gates "$RUNNER_TEMP/canon.lock")" - want_ver="$(canon_key version "$RUNNER_TEMP/canon.lock")" - got_crit="$(canon_key criteria_sha256 "$PROFILE")" - got_gates="$(canon_key gates_sha256 "$PROFILE")" - got_ver="$(canon_key version "$PROFILE")" - - # An unreadable pin must not pass. Hashing or parsing nothing yields an - # empty string, and `"" = ""` is a green build with nothing behind it — - # the same class of bug as this job reading a file that was never there. - for pair in "want_crit:$want_crit" "want_gates:$want_gates" "want_ver:$want_ver" \ - "got_crit:$got_crit" "got_gates:$got_gates" "got_ver:$got_ver"; do - if [ -z "${pair#*:}" ]; then - echo "::error::could not read ${pair%%:*} — refusing to compare an empty value" - exit 1 - fi - done - - ok=1 - row() { - mark=":x:" - if [ "$2" = "$3" ]; then mark=":white_check_mark:"; else ok=0; fi - printf '| `%s` | `%s` | `%s` | %s |\n' "$1" "${2:0:16}" "${3:0:16}" "$mark" - } - { - echo "## Canon lockstep" - echo "" - echo "| pin | canon.lock asserts | this repo declares | |" - echo "|---|---|---|---|" - row version "$want_ver" "$got_ver" - row criteria_sha256 "$want_crit" "$got_crit" - row gates_sha256 "$want_gates" "$got_gates" - } >> "$GITHUB_STEP_SUMMARY" - if [ "$ok" = "1" ]; then - echo ":white_check_mark: This template implements the canon at HEAD." - exit 0 - fi - echo "::error::canon lockstep broken — the template does not implement the canon at HEAD" - { - echo "" - echo ":x: **Lockstep broken.**" - echo "" - echo "The canon's law changed and this template has not adopted it." - echo "Order matters (\`canon.lock [canon.lockstep].order\` is" - echo "\`spine-adopts-then-canon-releases\`): re-pin \`[canon]\` in" - echo "\`.machine_readable/rsr-profile.a2ml\` to the new hashes and fix any" - echo "criteria this repo now fails, BEFORE the canon release lands." - } >> "$GITHUB_STEP_SUMMARY" - exit 1 - # --------------------------------------------------------------------------- - # Job 7: Dogfooding summary - # --------------------------------------------------------------------------- - dogfood-summary: - name: "ℹ️ ADVISORY: Dogfooding compliance summary (non-gating)" - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: [deed-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate, canon-lockstep] - if: always() - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Generate dogfooding scorecard - run: | - SCORE=0 - MAX=6 - - # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) - if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - DEED_STATUS=":white_check_mark:" - else - DEED_STATUS=":x:" - fi - - # K9 contracts present? - if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - K9_STATUS=":white_check_mark:" - else - K9_STATUS=":x:" - fi - - # .editorconfig present? - if [ -f ".editorconfig" ]; then - SCORE=$((SCORE + 1)) - EC_STATUS=":white_check_mark:" - else - EC_STATUS=":x:" - fi - - # Groove manifest or code? - if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - GROOVE_STATUS=":white_check_mark:" - else - GROOVE_STATUS=":ballot_box_with_check:" - fi - - # VeriSimDB integration? - if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - VSDB_STATUS=":white_check_mark:" - else - VSDB_STATUS=":ballot_box_with_check:" - fi - - # eclexiaiser energy tracking? - if [ -f "eclexiaiser.toml" ]; then - SCORE=$((SCORE + 1)) - ECLEX_STATUS=":white_check_mark:" - else - ECLEX_STATUS=":ballot_box_with_check:" - fi - - cat <> "$GITHUB_STEP_SUMMARY" - ## Dogfooding Scorecard - - **Score: ${SCORE}/${MAX}** - - | Tool/Format | Status | Notes | - |-------------|--------|-------| - | DEED repo deed (`_chora.deed`) | ${DEED_STATUS} | Required for all RSR repos | - | K9 contracts | ${K9_STATUS} | Required for repos with config files | - | .editorconfig | ${EC_STATUS} | Required for all repos | - | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | - | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | - | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | - - --- - *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/czech-file-knife) workflow.* - *Dogfooding is guinea pig fooding — we test our tools on ourselves.* - EOF - - rsr-score-self: - name: RSR oracle — dogfood this repo - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 - - uses: actions/checkout@v7.0.1 - with: - repository: hyperpolymath/standards - path: _standards - - uses: actions/checkout@v7.0.1 - with: - repository: hyperpolymath/hypatia - path: _hypatia - - name: Setup Elixir for the oracle - uses: erlef/setup-beam@v1.24.1 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - name: Compile the oracle - working-directory: _hypatia - run: mix deps.get && mix compile - - name: Score this repo against the canon - working-directory: _hypatia - run: | - # --ssot is explicit until the hypatia M-3 fix lands (the - # --standards default still points at the pre-reorg path). - mix hypatia.rsr_score .. \ - --ssot ../_standards/0-canon/rsr/rsr-criteria-v2.a2ml \ - --write - - name: Show the scorecard - if: always() - run: | - if [ -f .machine_readable/descriptiles/SCORECARD.a2ml ]; then - cat .machine_readable/descriptiles/SCORECARD.a2ml - else - echo "::warning::no scorecard written — the oracle failed; see the step log" - fi - # Deliberately NO --fail-under: the point is to publish the honest - # scorecard (provisional=true until the coverage worklist is triaged), - # not to gate the spine on itself. Tightens in the PR that lands the - # first coverage tranche. diff --git a/czech-file-knife/.github/workflows/dogfood-summary.yml b/czech-file-knife/.github/workflows/dogfood-summary.yml deleted file mode 100644 index 2df5c3dd3..000000000 --- a/czech-file-knife/.github/workflows/dogfood-summary.yml +++ /dev/null @@ -1,99 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "ℹ️ ADVISORY: Dogfooding Compliance Scorecard" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - dogfood-summary: - name: "Dogfooding compliance summary" - runs-on: ubuntu-latest - timeout-minutes: 15 - if: always() - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Generate dogfooding scorecard - run: | - SCORE=0 - MAX=6 - - # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) - if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - DEED_STATUS=":white_check_mark:" - else - DEED_STATUS=":x:" - fi - - # K9 contracts present? - if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - K9_STATUS=":white_check_mark:" - else - K9_STATUS=":x:" - fi - - # .editorconfig present? - if [ -f ".editorconfig" ]; then - SCORE=$((SCORE + 1)) - EC_STATUS=":white_check_mark:" - else - EC_STATUS=":x:" - fi - - # Groove manifest or code? - if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - GROOVE_STATUS=":white_check_mark:" - else - GROOVE_STATUS=":ballot_box_with_check:" - fi - - # VeriSimDB integration? - if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then - SCORE=$((SCORE + 1)) - VSDB_STATUS=":white_check_mark:" - else - VSDB_STATUS=":ballot_box_with_check:" - fi - - # eclexiaiser energy tracking? - if [ -f "eclexiaiser.toml" ]; then - SCORE=$((SCORE + 1)) - ECLEX_STATUS=":white_check_mark:" - else - ECLEX_STATUS=":ballot_box_with_check:" - fi - - cat <> "$GITHUB_STEP_SUMMARY" - ## Dogfooding Scorecard - - **Score: ${SCORE}/${MAX}** - - | Tool/Format | Status | Notes | - |-------------|--------|-------| - | DEED manifest (repo deed `*_chora.deed`) | ${DEED_STATUS} | Required for all RSR repos | - | K9 contracts | ${K9_STATUS} | Required for repos with config files | - | .editorconfig | ${EC_STATUS} | Required for all repos | - | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | - | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | - | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | - - --- - *Generated by the Dogfooding Compliance workflow.* - *Dogfooding is guinea pig fooding — we test our tools on ourselves.* - EOF diff --git a/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml b/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml deleted file mode 100644 index 7ce166edf..000000000 --- a/czech-file-knife/.github/workflows/dot-wellknown-enforcement.yml +++ /dev/null @@ -1,157 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Well-Known Standards (RFC 9116 + RSR) -on: - push: - branches: [main, master] - paths: - - 'www/.well-known/**' - - 'www/tests/**' - - 'www/schemas/**' - - '.well-known/**' # legacy location — migration window (issue #53) - - 'security.txt' - pull_request: - paths: - - 'www/.well-known/**' - - 'www/tests/**' - - 'www/schemas/**' - - '.well-known/**' # legacy location — migration window (issue #53) - schedule: - # Weekly expiry check (Mondays 09:00 UTC) - - cron: '0 9 * * 1' - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - validate: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: RFC 9116 security.txt validation - run: | - SECTXT="" - if [ -f "www/.well-known/security.txt" ]; then - SECTXT="www/.well-known/security.txt" - elif [ -f ".well-known/security.txt" ]; then - SECTXT=".well-known/security.txt" - echo "::warning::security.txt at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" - elif [ -f "security.txt" ]; then - SECTXT="security.txt" - echo "::warning::security.txt at repository root — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" - fi - - if [ -z "$SECTXT" ]; then - echo "::error::No security.txt found — required for OpenSSF Best Practices. See https://github.com/hyperpolymath/well-known-ecosystem" - exit 1 - fi - - # Required: Contact - grep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; } - - # Required: Expires - if ! grep -q "^Expires:" "$SECTXT"; then - echo "::error::Missing Expires field" - exit 1 - fi - - # Check expiry - EXPIRES=$(grep "^Expires:" "$SECTXT" | cut -d: -f2- | tr -d ' ' | head -1) - if date -d "$EXPIRES" > /dev/null 2>&1; then - DAYS=$(( ($(date -d "$EXPIRES" +%s) - $(date +%s)) / 86400 )) - if [ $DAYS -lt 0 ]; then - echo "::error::security.txt EXPIRED" - exit 1 - elif [ $DAYS -lt 30 ]; then - echo "::warning::security.txt expires in $DAYS days" - else - echo "✅ security.txt valid ($DAYS days)" - fi - fi - - name: RSR well-known compliance - run: | - MISSING="" - LEGACY="" - for f in security.txt ai.txt humans.txt; do - if [ -f "www/.well-known/$f" ]; then - : - elif [ -f ".well-known/$f" ] || { [ "$f" = security.txt ] && [ -f security.txt ]; }; then - LEGACY="$LEGACY $f" - else - MISSING="$MISSING $f" - fi - done - if [ -n "$LEGACY" ]; then - echo "::warning::legacy .well-known location (canonical is www/.well-known/):$LEGACY — run scripts/migrate-wellknown-to-www.sh" - fi - - if [ -n "$MISSING" ]; then - echo "::warning::Missing RSR recommended files:$MISSING" - echo "Reference: https://github.com/hyperpolymath/well-known-ecosystem/.well-known/" - else - echo "✅ RSR well-known compliant" - fi - # Stage 5 (#119) sweeps ~270 repositories. #106 claimed CI "drives the - # migrator"; until now it only printed advice, so nothing verified that a - # repo would actually survive the migration. This step runs the real - # migrator and turns every conflict into an annotation. - # - # --dry-run because this job holds contents: read and cannot push: the - # point is to surface divergence early in the migration window, not to - # mutate a checkout that will be thrown away. The sweep driver - # (scripts/sweep-wellknown.sh) is what performs the real migration. - - name: Legacy root .well-known/ migration report - run: | - if [ ! -d .well-known ]; then - echo "No root .well-known/ — nothing to migrate." - exit 0 - fi - if [ ! -f scripts/migrate-wellknown-to-www.sh ]; then - echo "::warning::root .well-known/ present but no migrator — template is behind #53 stage 5." - exit 0 - fi - bash scripts/migrate-wellknown-to-www.sh --dry-run --report wellknown-migration.tsv - echo "--- planned migration ---" - cat wellknown-migration.tsv - # Conflicts are warnings, not errors, for the duration of the - # migration window (#106): a repo must not go red for a condition - # the sweep exists to fix. It must not be silent either. - while IFS=$'\t' read -r action path detail; do - case "$action" in - quarantined|conflict|left) - echo "::warning title=well-known migration::$action $path — $detail" - ;; - esac - done < <(tail -n +2 wellknown-migration.tsv) - - name: www bundle self-test - run: | - if [ -d www/tests ]; then - bash www/tests/run-all.sh - else - echo "www/tests absent — site-operations bundle not present; skipping." - fi - - name: Mixed content check - run: | - MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com' | head -5 || true) - if [ -n "$MIXED" ]; then - echo "::error::Mixed content (HTTP in HTML)" - echo "$MIXED" - exit 1 - fi - echo "✅ No mixed content" - - name: DNS security records check - if: hashFiles('CNAME') != '' - run: | - DOMAIN=$(cat CNAME 2>/dev/null | tr -d '\n') - if [ -n "$DOMAIN" ]; then - echo "Checking DNS for $DOMAIN..." - # CAA record - dig +short CAA "$DOMAIN" | grep -q "issue" && echo "✅ CAA record" || echo "::warning::No CAA record" - # DNSSEC - dig +dnssec +short "$DOMAIN" | grep -q "RRSIG" && echo "✅ DNSSEC" || echo "::warning::No DNSSEC" - fi diff --git a/czech-file-knife/.github/workflows/e2e.yml.template b/czech-file-knife/.github/workflows/e2e.yml.template deleted file mode 100644 index 984245107..000000000 --- a/czech-file-knife/.github/workflows/e2e.yml.template +++ /dev/null @@ -1,224 +0,0 @@ -# ⚠ THIS FILE IS A SCAFFOLD, NOT A WORKFLOW. -# -# It is named .yml.template deliberately: GitHub Actions reads only *.yml and -# *.yaml under .github/workflows/, so this file is ignored until someone -# instantiates it. -# -# It used to be called e2e.yml, and because every job block below is commented -# out, `jobs:` parsed as null — an invalid workflow. It therefore FAILED on -# every run, in every repository minted from this template, without ever -# executing a single check. 29 repos inherited that. -# -# A scaffold that is invalid until edited WILL be merged unedited. Naming it -# so the platform ignores it removes the failure without removing the guidance. -# -# TO USE: copy to .github/workflows/e2e.yml, uncomment the block matching your -# stack, delete the rest, and confirm it can FAIL before treating it as a gate -# (standards docs/language-testing-standards.md R10). -# -# SPDX-License-Identifier: MPL-2.0 -# This workflow is managed by gh actions-lock. -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# RSR Standard E2E + Aspect + Benchmark Workflow Template -# -# Covers ALL merge requirement test categories: -# - E2E (end-to-end pipeline tests) -# - Aspect (cross-cutting concern validation) -# - Benchmarks (performance regression detection) -# - Readiness (Component Readiness Grade: D/C/B) -# -# INSTRUCTIONS: Uncomment and customise the section matching your stack. -# Delete sections that don't apply. See examples in each job. - -name: E2E + Aspect + Bench -on: - push: - branches: [main, master, develop] - paths: - - 'src/**' - - 'src/interface/ffi/**' - - 'tests/**' - - '.github/workflows/e2e.yml' - pull_request: - branches: [main, master] - paths: - - 'src/**' - - 'src/interface/ffi/**' - - 'tests/**' - workflow_dispatch: -permissions: read-all -concurrency: - group: e2e-${{ github.ref }} - cancel-in-progress: true -jobs: - # ─── Default: template smoke (always runs — not a silent no-op) ───── - # Runs the template's own E2E harness (tests/e2e.sh). On the bare template - # this is a clean pass (no stack-specific checks enabled yet); downstream - # repos either extend tests/e2e.sh or uncomment a stack block below. - e2e: - name: E2E — template smoke - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - name: Run E2E harness - run: | - if [ -f tests/e2e.sh ]; then - bash tests/e2e.sh - else - echo "::notice::no tests/e2e.sh present — nothing to run" - fi - -# ─── End-to-End Tests (downstream stack-specific examples) ───────── -# Uncomment ONE of the following e2e job blocks matching your stack. - -## === RUST E2E === -# e2e: -# name: E2E — Full Pipeline -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable -# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 -# - run: cargo build --release -# - run: bash tests/e2e.sh -# # OR: cargo test --test end_to_end -- --nocapture - -## === ZIG FFI E2E === -# e2e: -# name: E2E — FFI Pipeline -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 -# with: -# version: 0.15.1 -# - run: cd ffi/zig && zig build test -# - run: bash tests/e2e.sh - -## === ELIXIR E2E === -# e2e: -# name: E2E — Full Pipeline -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 -# with: -# otp-version: '27.0' -# elixir-version: '1.17' -# - run: mix deps.get && mix compile --warnings-as-errors -# - run: mix test test/integration/e2e_test.exs --trace - -## === DENO/ E2E === -# e2e: -# name: E2E — Full Pipeline -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 -# with: -# deno-version: v2.x -# - run: deno install --node-modules-dir=auto -# - run: deno task res:build # ReScript compile -# - run: deno test tests/e2e/ - -## === PLAYWRIGHT (Browser E2E) === -# e2e-playwright: -# name: Playwright — ${{ matrix.project }} -# runs-on: ubuntu-latest -# timeout-minutes: 20 -# strategy: -# fail-fast: false -# matrix: -# project: [chromium-1080p, firefox-1080p, webkit-1080p] -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 -# with: -# deno-version: v2.x -# - run: deno install --node-modules-dir=auto -# - run: npx playwright install --with-deps -# - run: npx playwright test --project=${{ matrix.project }} -# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 -# if: failure() -# with: -# name: playwright-traces-${{ matrix.project }} -# path: test-results/**/trace.zip -# retention-days: 7 - -## === HASKELL E2E === -# e2e: -# name: E2E — Full Pipeline -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0 -# with: -# ghc-version: '9.6' -# cabal-version: '3.10' -# - run: cabal build all -# - run: bash tests/integration-test.sh - -# ─── Aspect Tests ────────────────────────────────────────────────── -# Cross-cutting concerns: thread safety, ABI contracts, SPDX, dangerous patterns -# Uncomment and customise: - -# aspect-tests: -# name: Aspect — Architectural Invariants -# runs-on: ubuntu-latest -# timeout-minutes: 10 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - run: bash tests/aspect_tests.sh - -# ─── Benchmarks ──────────────────────────────────────────────────── -# Performance regression detection. Uncomment matching stack: - -## === RUST BENCH === -# benchmarks: -# name: Bench — Performance Regression -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable -# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 -# - run: cargo bench 2>&1 | tee /tmp/bench-results.txt -# - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 -# if: always() -# with: -# name: benchmark-results -# path: /tmp/bench-results.txt -# retention-days: 30 - -## === ZIG BENCH === -# benchmarks: -# name: Bench — Performance Regression -# runs-on: ubuntu-latest -# timeout-minutes: 15 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 -# with: -# version: 0.15.1 -# - run: cd ffi/zig && zig build bench - -# ─── Readiness (CRG) ────────────────────────────────────────────── -# Component Readiness Grade: D (runs) → C (correct) → B (edge cases) - -# readiness: -# name: Readiness — Grade D/C/B -# runs-on: ubuntu-latest -# timeout-minutes: 10 -# steps: -# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable -# - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 -# - run: cargo test --test readiness -- --nocapture diff --git a/czech-file-knife/.github/workflows/eclexiaiser-validate.yml b/czech-file-knife/.github/workflows/eclexiaiser-validate.yml deleted file mode 100644 index aae748161..000000000 --- a/czech-file-knife/.github/workflows/eclexiaiser-validate.yml +++ /dev/null @@ -1,64 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "🟡 CHECK: Eclexiaiser Manifest Validation" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - eclexiaiser-validate: - name: "🟡 CHECK: Validate eclexiaiser manifest" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check and validate eclexiaiser manifest - id: eclex - run: | - if [ ! -f "eclexiaiser.toml" ]; then - # Check if repo has a Containerfile — if so, recommend eclexiaiser - if [ -f "Containerfile" ]; then - echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." - fi - echo "has_manifest=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "has_manifest=true" >> "$GITHUB_OUTPUT" - - # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). - # Structural presence checks only — deep schema validation is eclexiaiser's own job. - err=0 - grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } - grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } - grep -qE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } - if [ "$err" -ne 0 ]; then - exit 1 - fi - fns=$(grep -cE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml) - echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" - - - name: "Write summary" - run: | - if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then - echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" - else - echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/czech-file-knife/.github/workflows/empty-linter.yml b/czech-file-knife/.github/workflows/empty-linter.yml deleted file mode 100644 index 7bdf23715..000000000 --- a/czech-file-knife/.github/workflows/empty-linter.yml +++ /dev/null @@ -1,89 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "🔴 GATE: Invisible Character Detection" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - empty-lint: - name: "🔴 GATE: Empty-linter (invisible characters)" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Scan for invisible characters - id: lint - run: | - RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" - BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" - if ! scripts/check-invisible-characters.sh \ - "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then - echo "::error::Invisible-character scanner failed; refusing a partial pass" - exit 2 - fi - - FINDINGS=0 - while IFS= read -r -d '' filepath; do - FINDINGS=$((FINDINGS + 1)) - REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" - SAFE_PATH="${REL_PATH//'%'/'%25'}" - SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" - SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" - SAFE_PATH="${SAFE_PATH//':'/'%3A'}" - SAFE_PATH="${SAFE_PATH//','/'%2C'}" - echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" - done < "$RESULTS_FILE" - - BLOCKING=0 - while IFS= read -r -d '' filepath; do - BLOCKING=$((BLOCKING + 1)) - REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" - SAFE_PATH="${REL_PATH//'%'/'%25'}" - SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" - SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" - SAFE_PATH="${SAFE_PATH//':'/'%3A'}" - SAFE_PATH="${SAFE_PATH//','/'%2C'}" - echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" - done < "$BLOCKING_FILE" - - echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" - echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" - echo "ready=true" >> "$GITHUB_OUTPUT" - - if [ "$BLOCKING" -gt 0 ]; then - echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - - name: "Write summary" - run: | - if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then - FINDINGS="${{ steps.lint.outputs.findings }}" - if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then - echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" - else - echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" - fi - else - echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/czech-file-knife/.github/workflows/estate-rules.yml b/czech-file-knife/.github/workflows/estate-rules.yml deleted file mode 100644 index 2cd65682c..000000000 --- a/czech-file-knife/.github/workflows/estate-rules.yml +++ /dev/null @@ -1,97 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Estate Rules — enforces hyperpolymath estate-wide conventions: -# * root shape (allowlist of permitted root entries) -# * AsciiDoc-by-default (no .md files under docs/) -# * AsciiDoc that renders (every tracked .adoc parses with no diagnostics) -# * V-lang is banned (no V-lang scaffolding or references; Zig is the FFI default) -# -# Each rule is enforced by an executable check script under scripts/. Failures -# are surfaced as workflow errors so the rule can't silently regress. - -name: Estate Rules -on: - push: - branches: [main] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - estate-rules: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - # Prove the instrument works before trusting its reading. This gate was - # one-directional for months, which is indistinguishable from a working - # gate until you try to make it fail. - - name: Root-shape gate self-test (must fail on drift) - run: bash tests/shape/check_root_shape_test.sh - - name: Root shape allowlist - run: bash scripts/check-root-shape.sh . - # The map is generated. Five hand-written predecessors all rotted because - # nothing diffed them. - # The freshness check below is only meaningful if the generator is - # environment-independent. It wasn't: sort is locale-dependent, so the - # map generated differently in CI than locally. Vary the locale first. - - name: Repository map generates identically across locales - run: bash tests/shape/repo_map_determinism_test.sh - - name: Repository map is not stale - run: | - cp docs/architecture/REPOSITORY-MAP.adoc /tmp/map.before - bash scripts/gen-repo-map.sh . - diff -u /tmp/map.before docs/architecture/REPOSITORY-MAP.adoc \ - || { echo "::error::REPOSITORY-MAP.adoc is stale - run 'just repo-map'"; exit 1; } - - name: AsciiDoc by default (no .md under docs/) - run: bash scripts/check-no-md-in-docs.sh . - # The rule above checks the file EXTENSION only, so a Markdown body - # renamed to .adoc passes it. This one checks the file actually parses. - # asciidoctor's --failure-level defaults to FATAL, so a document emitting - # WARNING or ERROR still exits 0; the version is pinned because the - # verdict rests on a stderr comparison. - - name: Install asciidoctor - run: sudo gem install asciidoctor -v 2.0.26 --no-document - - name: AsciiDoc render gate self-test (must catch what bare asciidoctor misses) - run: bash tests/workflows/check_adoc_renders_test.sh - - name: Every tracked .adoc renders clean - run: bash scripts/check-adoc-renders.sh . - - name: No V-language references - run: bash scripts/check-no-vlang.sh . - # Was written but wired to nothing, so it had never run in CI. - - name: V-language gate self-test - run: bash tests/workflows/check_no_vlang_test.sh - - name: Install verified Nickel 1.17.0 - run: | - mkdir -p "$RUNNER_TEMP/nickel-bin" - curl --proto '=https' --proto-redir '=https' --fail --silent --show-error --location \ - https://github.com/nickel-lang/nickel/releases/download/1.17.0/nickel-x86_64-linux \ - --output "$RUNNER_TEMP/nickel-bin/nickel" - echo "afcdfa6e0fff31760cf229e85997456c02c00b8b3b84ff38f897ac7b3f39ae34 $RUNNER_TEMP/nickel-bin/nickel" | sha256sum --check --strict - chmod +x "$RUNNER_TEMP/nickel-bin/nickel" - echo "$RUNNER_TEMP/nickel-bin" >> "$GITHUB_PATH" - - name: Evaluate session contracts - run: bash scripts/validate-session-contracts.sh - - name: Check Nickel typecheck controls - run: bash tests/workflows/k9_typecheck_test.sh - - name: Verify session envelope controls - run: bash tests/workflows/session_contracts_test.sh - - name: Verify mint cleanup controls - run: bash tests/workflows/mint_cleanup_test.sh - # The CHECK half of the template contract. repo-init has always written an - # answer-file (.machine_readable/PROVENANCE.a2ml); nothing ever read it. - # This is the missing reader, and it covers the #200/#201/#203 class: - # self-parenting identity, unresolved pins, and leaked template branches. - # - # The template itself self-skips (archetypes/ present), so the real check - # runs in minted children. What THIS proves is that the instrument can - # fail — the only property that matters for a gate, and the one that was - # missing in #49, #64 and the conformance gate that "reported them - # conforming" upstream. - - name: Template conformance gate self-test (must fail on drift) - run: bash tests/workflows/template_conformance_test.sh diff --git a/czech-file-knife/.github/workflows/governance.yml b/czech-file-knife/.github/workflows/governance.yml deleted file mode 100644 index 73366eae5..000000000 --- a/czech-file-knife/.github/workflows/governance.yml +++ /dev/null @@ -1,21 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Governance - -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - actions: read # required by the reusable workflow (staleness check reads workflow runs) - contents: read - -jobs: - governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/groove-check.yml b/czech-file-knife/.github/workflows/groove-check.yml deleted file mode 100644 index f0e8785bb..000000000 --- a/czech-file-knife/.github/workflows/groove-check.yml +++ /dev/null @@ -1,91 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "🟡 CHECK: Groove Protocol Compliance" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - groove-check: - name: "🟡 CHECK: Groove manifest check" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for Groove manifest - id: groove - run: | - # Check for static or dynamic Groove endpoints - HAS_MANIFEST="false" - HAS_GROOVE_CODE="false" - - # Canonical manifest location is www/.well-known/groove/ (issue #53); - # the repository-root path is accepted, with a warning, during the - # migration window. - MANIFEST="" - if [ -f "www/.well-known/groove/manifest.json" ]; then - MANIFEST="www/.well-known/groove/manifest.json" - elif [ -f ".well-known/groove/manifest.json" ]; then - MANIFEST=".well-known/groove/manifest.json" - echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" - fi - - if [ -n "$MANIFEST" ]; then - HAS_MANIFEST="true" - # Validate the manifest JSON - if ! jq empty "$MANIFEST" 2>/dev/null; then - echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" - exit 1 - else - SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") - echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" - fi - fi - - # Check for Groove endpoint code (Rust, Elixir, Zig, V) - if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then - HAS_GROOVE_CODE="true" - fi - - # Check if this repo likely serves HTTP in production. Key on - # production HTTP-server framework markers only. A bare `TcpListener` - # is deliberately NOT a signal: it is dominated by test/utility use - # (e.g. a #[cfg(test)] loopback fake), so matching it produced a - # spurious groove nudge on client-only apps. A real hand-rolled Rust - # server still pairs the listener with `axum::serve`/`hyper::Server`, - # which are matched here. - HAS_SERVER="false" - if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then - HAS_SERVER="true" - fi - - echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" - echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" - echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" - - if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then - echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." - fi - - - name: "Write summary" - run: | - echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" diff --git a/czech-file-knife/.github/workflows/guix-policy.yml b/czech-file-knife/.github/workflows/guix-policy.yml deleted file mode 100644 index 6363018b0..000000000 --- a/czech-file-knife/.github/workflows/guix-policy.yml +++ /dev/null @@ -1,49 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Guix Package Policy -on: - push: - branches: [main, master] - pull_request: - -# Estate guardrail: scope push to default branches so a PR fires once (not -# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: Enforce Guix-only package policy - run: | - # Guix is the sole package manager estate-wide. Nix is BANNED. - HAS_GUIX=$(find . -path ./.git -prune -o \( -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" \) -print 2>/dev/null | head -1) - HAS_NIX=$(find . -path ./.git -prune -o -name "*.nix" -print 2>/dev/null | head -1) - - # Hard-fail on any Nix file — Nix is banned, migrate to Guix. - if [ -n "$HAS_NIX" ]; then - echo "::error::Nix is banned estate-wide (Guix only). Remove flake.nix/*.nix and use guix.scm: $HAS_NIX" - exit 1 - fi - - # Warn on non-reproducible lock files; prefer Guix manifests. - NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) - if [ -n "$NEW_LOCKS" ]; then - echo "⚠️ Lock files detected. Prefer Guix manifests for reproducibility." - fi - - if [ -n "$HAS_GUIX" ]; then - echo "✅ Guix package management detected" - else - echo "ℹ️ Consider adding guix.scm / .guix-channel for reproducible builds" - fi - - echo "✅ Guix package policy check passed" diff --git a/czech-file-knife/.github/workflows/hypatia-scan.yml b/czech-file-knife/.github/workflows/hypatia-scan.yml deleted file mode 100644 index 997e008b8..000000000 --- a/czech-file-knife/.github/workflows/hypatia-scan.yml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# -# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate -# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to -# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this -# workflow is the repo's independent security scan. Same tool, two consumers. -# See .github/workflows/README.adoc. -name: Hypatia Security Scan - -on: - push: - branches: [main, master, develop] - pull_request: - branches: [main, master] - schedule: - - cron: '0 0 * * 0' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - actions: read # required by the reusable workflow (staleness check reads workflow runs) - contents: read - security-events: write - -jobs: - hypatia: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/k9-validate.yml b/czech-file-knife/.github/workflows/k9-validate.yml deleted file mode 100644 index 548c3bbfb..000000000 --- a/czech-file-knife/.github/workflows/k9-validate.yml +++ /dev/null @@ -1,64 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: "🟡 CHECK: K9 Contract Validation" - -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - k9-validate: - name: "🟡 CHECK: Validate K9 contracts" - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - - name: Check for K9 files - id: detect - run: | - COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) - CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ - -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ - -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) - echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" - echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" - if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then - echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." - fi - - - name: "🟡 CHECK: Validate K9 contracts" - if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-ecosystem/validate-action@main - with: - path: '.' - strict: 'false' - - - name: Write summary - run: | - K9_COUNT="${{ steps.detect.outputs.k9_count }}" - CFG_COUNT="${{ steps.detect.outputs.config_count }}" - if [ "$K9_COUNT" -eq 0 ]; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## K9 Contract Validation - - :warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts. - - Generate contracts with: `k9iser generate .` - EOF - else - echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/czech-file-knife/.github/workflows/label-triage.yml b/czech-file-knife/.github/workflows/label-triage.yml deleted file mode 100644 index b7522268c..000000000 --- a/czech-file-knife/.github/workflows/label-triage.yml +++ /dev/null @@ -1,118 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Label Triage - -# Classify newly-filed issues against the estate label taxonomy. -# -# The sweep that established the taxonomy is a one-off; this is what stops it -# decaying. Without it every new issue arrives unlabelled and the 55%-unlabelled -# state rebuilds itself. -# -# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces -# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow -# the lock does not list is rejected before any step runs (startup_failure, and -# therefore no check run at all). A dispatched workflow lands in repos whose -# lock has not been regenerated, so it must not depend on any action. -# -# ⚠ THE CLASSIFIER IS jq, NOT PYTHON. Python is fully banned estate-wide -- the -# `governance / Language / package anti-pattern policy` gate runs -# `git ls-files '*.py'` and fails the PR. Shipping a .py into 416 repos would -# mean shipping an exemption into 416 repos. jq is preinstalled on every GitHub -# runner, is not banned, and needs no action. -# -# Deliberately conservative: -# - ADDITIVE ONLY. It never removes a label and never overrides a human's -# classification: anything already on the issue is passed in via `have` and -# is never re-suggested, and the classifier stays out of any max-1 tier the -# issue already carries a label in. -# - SILENT WHEN UNSURE. Nothing is printed unless a prefix, bracket or type -# rule actually fired. Roughly 70% of the historical corpus classified this -# way; the rest is meant to reach a human. -# - NEVER FAILS THE ISSUE. Every step is best-effort; a missing payload or an -# API hiccup exits 0 rather than leaving a red mark on someone's bug report. - -on: - issues: - types: [opened, reopened] - workflow_dispatch: - inputs: - issue: - description: "Issue number to (re)classify" - required: true - -permissions: - issues: write - contents: read - -jobs: - triage: - runs-on: ubuntu-latest - timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #68) - steps: - - name: Classify and label - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NUM: ${{ github.event.issue.number || inputs.issue }} - run: | - set -uo pipefail - work=$(mktemp -d); RULES=$work/rules.json; SCRIPT=$work/classify.jq - - # fetch instead of checking out -- no action means no lock entry to drift - gh api "repos/$GITHUB_REPOSITORY/contents/.github/label-classifier.json?ref=$GITHUB_SHA" \ - --jq '.content' 2>/dev/null | base64 -d > "$RULES" || true - gh api "repos/$GITHUB_REPOSITORY/contents/.github/scripts/classify-issue.jq?ref=$GITHUB_SHA" \ - --jq '.content' 2>/dev/null | base64 -d > "$SCRIPT" || true - if [[ ! -s "$RULES" || ! -s "$SCRIPT" ]]; then - echo "no classifier payload in this repo - nothing to do" - exit 0 - fi - - TITLE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" --json title --jq .title) || exit 0 - echo "issue #$NUM: $TITLE" - - # Labels this repo actually defines. --limit 1000 is GitHub's real - # per-repo ceiling; the default of 30 would silently hide most of the - # taxonomy. Fetched BEFORE the label read below so that read stays as - # close to the write as possible. - mapfile -t DEFINED < <(gh label list -R "$GITHUB_REPOSITORY" --limit 1000 \ - --json name --jq '.[].name' 2>/dev/null) - - # Labels already present; a human's work is never overridden. Read - # HERE rather than earlier: every API call between this read and the - # edit below widens a window in which someone could add a type label - # and get a second one back from us. Only the local jq call is inside it. - HAVE=$(gh issue view "$NUM" -R "$GITHUB_REPOSITORY" \ - --json labels --jq '[.labels[].name]' 2>/dev/null) || HAVE='[]' - [[ -n "$HAVE" ]] || HAVE='[]' - echo "already has: $HAVE" - - mapfile -t ADD < <(jq -r --arg title "$TITLE" --argjson have "$HAVE" \ - -f "$SCRIPT" "$RULES" 2>/dev/null) - if [[ ${#ADD[@]} -eq 0 || -z "${ADD[0]:-}" ]]; then - echo "no confident classification - leaving for a human" - exit 0 - fi - - apply=() - for want in "${ADD[@]}"; do - for def in "${DEFINED[@]}"; do - if [[ "$want" == "$def" ]]; then apply+=("$want"); break; fi - done - done - if [[ ${#apply[@]} -eq 0 ]]; then - echo "classified as ${ADD[*]} but this repo defines none of them - run the label sync" - exit 0 - fi - - printf 'applying: %s\n' "${apply[*]}" - # Build the arguments as an ARRAY. The previous form was an unquoted - # command substitution, so the shell re-split its output on spaces and - # a label name containing whitespace would arrive as several broken - # arguments. No canonical label contains a space today, which is - # exactly why this would have failed quietly the first time one did. - # (Also clears actionlint SC2046.) - edit_args=() - for lab in "${apply[@]}"; do edit_args+=(--add-label "$lab"); done - gh issue edit "$NUM" -R "$GITHUB_REPOSITORY" "${edit_args[@]}" \ - || echo "label apply failed - not failing the run" - exit 0 diff --git a/czech-file-knife/.github/workflows/labels.yml b/czech-file-knife/.github/workflows/labels.yml deleted file mode 100644 index 31618e4b7..000000000 --- a/czech-file-knife/.github/workflows/labels.yml +++ /dev/null @@ -1,107 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Labels - -# Applies the canonical estate label set from .github/labels.json. -# -# Additive and idempotent by design: it CREATES missing labels and UPDATES -# colour/description drift. It never deletes, and it never touches a label in -# the `frozen` list -- those are applied by Dependabot / PR automation, or are -# wired into triage.yml's exempt-issue-labels, and renaming them breaks things. -# -# jq is preinstalled on GitHub runners; PyYAML is not, which is why the payload -# is JSON rather than YAML. -# -# ⚠ NO `uses:` ANYWHERE, DELIBERATELY. The estate enforces -# .github/workflows/actions.lock, which is keyed BY WORKFLOW PATH: a workflow -# the lock does not list is rejected before any step runs (startup_failure, and -# therefore no check run at all). A dispatched workflow lands in repos whose -# lock has not been regenerated, so it must not depend on any action. - -on: - workflow_dispatch: - push: - paths: - - '.github/labels.json' - schedule: - - cron: "23 4 1 * *" # monthly drift repair - -permissions: - issues: write - contents: read - -jobs: - sync: - runs-on: ubuntu-latest - timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #69) - steps: - - name: Apply canonical labels - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # ⚠ LOAD-BEARING. This workflow deliberately does not check the repo - # out (no `uses:`, so no actions.lock entry can drift), which means - # `gh label create` / `gh label edit` have no git remote to infer a - # target from. Without GH_REPO every mutation fails, and because the - # errors used to be discarded the step still exited 0 reporting - # "created=0 updated=0" -- a silent, estate-wide no-op. - GH_REPO: ${{ github.repository }} - run: | - set -uo pipefail - work=$(mktemp -d); PAYLOAD=$work/labels.json - - # fetch instead of checking out -- no action means no lock entry to drift - gh api "repos/$GITHUB_REPOSITORY/contents/.github/labels.json?ref=$GITHUB_SHA" \ - --jq '.content' 2>/dev/null | base64 -d > "$PAYLOAD" || true - [ -s "$PAYLOAD" ] || { echo "no .github/labels.json - nothing to do"; exit 0; } - - mapfile -t FROZEN < <(jq -r '.frozen[]' "$PAYLOAD") - created=0; updated=0; skipped=0; failed=0 - - existing=$(gh api "repos/$GITHUB_REPOSITORY/labels" --paginate \ - --jq '.[] | [.name, .color, (.description // "")] | @tsv') - - while IFS=$'\t' read -r name color desc; do - [ -z "$name" ] && continue - frozen=0 - for f in "${FROZEN[@]}"; do [ "$f" = "$name" ] && frozen=1 && break; done - - cur=$(printf '%s\n' "$existing" | awk -F'\t' -v n="$name" '$1==n{print;exit}') - if [ -z "$cur" ]; then - # A MISSING label is created even when frozen. "Frozen" protects a - # label's DEFINITION from being renamed or recoloured -- it was - # never meant to stop the label existing. Skipping creation broke - # `security`, the one canonical label that is also frozen: it was - # absent from 10 of 12 sampled repos, and label-triage drops any - # label the repo does not define, so every `security` finding was - # silently discarded estate-wide. - if err=$(gh label create "$name" --color "$color" \ - --description "$desc" 2>&1 >/dev/null); then - created=$((created+1)); sleep 0.4 - else - echo " create failed: $name -- ${err:-unknown}"; failed=$((failed+1)) - fi - else - # Present AND frozen: leave it exactly as it is. - if [ "$frozen" -eq 1 ]; then skipped=$((skipped+1)); continue; fi - ccol=$(cut -f2 <<<"$cur"); cdesc=$(cut -f3- <<<"$cur") - if [ "${ccol,,}" != "${color,,}" ] || [ "$cdesc" != "$desc" ]; then - if err=$(gh label edit "$name" --color "$color" \ - --description "$desc" 2>&1 >/dev/null); then - updated=$((updated+1)); sleep 0.4 - else - echo " edit failed: $name -- ${err:-unknown}"; failed=$((failed+1)) - fi - fi - fi - done < <(jq -r '.labels[] | [.name, .color, .description] | @tsv' "$PAYLOAD") - - echo "created=$created updated=$updated frozen-skipped=$skipped failed=$failed" - - # Fail ONLY on the misconfiguration shape: work was attempted, every - # attempt failed. That is the silent-no-op signature. A single flaky - # label must not turn the whole estate's CI red. - if [ "$failed" -gt 0 ] && [ "$((created + updated))" -eq 0 ]; then - echo "every label mutation failed - the sync did nothing. Check GH_REPO and token scope." - exit 1 - fi - exit 0 diff --git a/czech-file-knife/.github/workflows/lock-sync-gate.yml b/czech-file-knife/.github/workflows/lock-sync-gate.yml deleted file mode 100644 index db046cb76..000000000 --- a/czech-file-knife/.github/workflows/lock-sync-gate.yml +++ /dev/null @@ -1,64 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Lock Sync Gate - -# Fails any pull request whose .github/workflows/actions.lock has drifted from -# the workflow YAML. That drift is not cosmetic: GitHub refuses such a run at -# startup, creating ZERO jobs, and reports only "This run likely failed because -# of a workflow file issue." A single grouped Dependabot bump can take out most -# of a repository's CI that way, because Dependabot rewrites `uses:` refs in the -# YAML and cannot touch the lockfile. Measured across 200 repositories on -# 2026-09-22: 39 had silently dead CI from exactly this cause. -# See hyperpolymath/standards#968. -# -# This workflow deliberately carries NO `uses:` of its own. It checks out by -# calling git in a `run:` step instead of using actions/checkout, so it has no -# lockfile entry to go stale and is structurally immune to the very failure it -# detects. Do not add a `uses:` to this file. -# -# There is also no `paths:` filter, on purpose: a filtered workflow never -# reports on pull requests that miss the filter, which deadlocks any branch -# ruleset that requires this check. - -on: - workflow_dispatch: - pull_request: - push: - branches: [main] - -permissions: - contents: read - -concurrency: - group: lock-sync-gate-${{ github.ref }} - cancel-in-progress: true - -jobs: - lock-sync: - name: actions.lock is in sync with the workflow YAML - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Check out without actions/checkout - env: - REPO: ${{ github.repository }} - SHA: ${{ github.event.pull_request.head.sha || github.sha }} - TOKEN: ${{ github.token }} - run: | - set -euo pipefail - # Authenticate the fetch. An anonymous clone works only for public - # repositories; this gate must also run on private ones. The header - # form is used rather than a token in the remote URL so the - # credential is never written into .git/config. - AUTH="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${TOKEN}" | base64 -w0)" - git init -q . - git remote add origin "https://github.com/${REPO}.git" - git -c http.extraheader="${AUTH}" fetch -q --depth 1 origin "${SHA}" - git checkout -q FETCH_HEAD - echo "checked out ${SHA}" - - - name: Verify lockfile synchronisation - run: | - set -euo pipefail - test -x scripts/check-lock-sync.sh \ - || { echo "::error::scripts/check-lock-sync.sh missing or not executable"; exit 1; } - ./scripts/check-lock-sync.sh diff --git a/czech-file-knife/.github/workflows/main-estate-audit.yml b/czech-file-knife/.github/workflows/main-estate-audit.yml deleted file mode 100644 index c856ee3f9..000000000 --- a/czech-file-knife/.github/workflows/main-estate-audit.yml +++ /dev/null @@ -1,20 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# This workflow is managed by gh actions-lock. -name: Central Estate CI/CD Audit - -# The called reusable declares `permissions: contents: read`; a caller must grant -# at least what the reusable declares, and this grants exactly that — no more. -permissions: - contents: read - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - workflow_call: - -jobs: - call-estate-audit: - uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@55556cf5ba71ba744695861503c13148d3915a5d # cicd-suite MAIN (branch-reachable): remote-form callee — 27 full-SHA refs, de-onboarded; witnessed cross-repo in rsr runs 35282081912 + 35283168495 (jobs spawn; gates audit THIS repo). Prior f9e173a pin (via #137) = deleted PR head: pull-ref-only reachability -> 0-job creation rejection; its onboarded @main/tag form was separately proven startup_failure cross-repo (35280642055). Repin on callee updates; never to PR-head commits. diff --git a/czech-file-knife/.github/workflows/mirror.yml b/czech-file-knife/.github/workflows/mirror.yml deleted file mode 100644 index 35c568381..000000000 --- a/czech-file-knife/.github/workflows/mirror.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Mirror to Git Forges -on: - push: - branches: [main] - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false -permissions: - actions: read # required by the reusable workflow (staleness check reads workflow runs) - contents: read -jobs: - mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 - # Explicit secrets map — no `secrets: inherit` (Hypatia WH008, alert #131). - # All seven are the callee's complete optional contract (standards - # mirror-reusable.yml@da2c748); behaviour is unchanged, future secrets - # are no longer shared implicitly. - secrets: - GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} - BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }} - CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }} - SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }} - DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }} - GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }} - RADICLE_KEY: ${{ secrets.RADICLE_KEY }} diff --git a/czech-file-knife/.github/workflows/ossf-best-practices.yml b/czech-file-knife/.github/workflows/ossf-best-practices.yml deleted file mode 100644 index b3aa90eeb..000000000 --- a/czech-file-knife/.github/workflows/ossf-best-practices.yml +++ /dev/null @@ -1,97 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# OpenSSF Best Practices compliance gate — blocks PRs and pushes that lack -# required files or still contain unfilled placeholder tokens. -name: OpenSSF Compliance -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - openssf-compliance: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - with: - persist-credentials: false - - name: Check SECURITY.md exists and has substance - run: | - SECFILE="" - [ -f "SECURITY.md" ] && SECFILE="SECURITY.md" - [ -f "SECURITY.adoc" ] && SECFILE="SECURITY.adoc" - [ -f ".github/SECURITY.md" ] && SECFILE=".github/SECURITY.md" - - if [ -z "$SECFILE" ]; then - echo "::error::SECURITY.md (or SECURITY.adoc) is required for OpenSSF Best Practices" - exit 1 - fi - - LINES=$(wc -l < "$SECFILE") - if [ "$LINES" -lt 10 ]; then - echo "::error::$SECFILE has only $LINES lines — must have >10 lines of substantive content" - exit 1 - fi - echo "SECURITY file: OK ($SECFILE, $LINES lines)" - - name: Check LICENSE exists - run: | - if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ]; then - echo "::error::LICENSE file is required for OpenSSF Best Practices" - exit 1 - fi - echo "LICENSE: OK" - - name: Check CONTRIBUTING exists - run: | - if [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] \ - && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ]; then - echo "::error::CONTRIBUTING file is required for OpenSSF Best Practices" - exit 1 - fi - echo "CONTRIBUTING: OK" - - name: Check README exists - run: | - if [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && [ ! -f "README.rst" ] && [ ! -f "README.txt" ] && [ ! -f "README" ]; then - echo "::error::README file is required for OpenSSF Best Practices" - exit 1 - fi - echo "README: OK" - - name: Check .machine_readable directory and STATE.a2ml - run: | - if [ ! -d ".machine_readable" ]; then - echo "::error::.machine_readable/ directory is required" - exit 1 - fi - - if [ ! -f ".machine_readable/descriptiles/STATE.a2ml" ]; then - echo "::error::.machine_readable/descriptiles/STATE.a2ml is required" - exit 1 - fi - echo ".machine_readable/descriptiles/STATE.a2ml: OK" - - name: Check CHANGELOG exists - run: | - if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG.adoc" ] && [ ! -f "CHANGES.md" ]; then - echo "::error::CHANGELOG.md is required for OpenSSF Best Practices" - exit 1 - fi - echo "CHANGELOG: OK" - - name: Check no unfilled placeholder tokens - # Delegates to the same script tests/e2e/template_instantiation_test.sh - # runs, so the gate and the test can never disagree about the rule. - # This step used to check a hand-maintained list of required files that - # omitted .github/settings.yml and ANCHOR.a2ml — the two places the - # leaks actually were. The script scans everything and allow-lists the - # legitimate carriers instead. - run: bash scripts/check-no-placeholders.sh . - - name: Summary - run: | - echo "=== OpenSSF Best Practices Compliance: PASS ===" - echo "All required files present and placeholder-free." diff --git a/czech-file-knife/.github/workflows/pages.yml b/czech-file-knife/.github/workflows/pages.yml deleted file mode 100644 index a176cb208..000000000 --- a/czech-file-knife/.github/workflows/pages.yml +++ /dev/null @@ -1,91 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# GitHub Pages via casket-ssg (hyperpolymath's pure-Haskell static site generator). -# Replaces the orphan one-off Pages deployment with a reproducible build. -name: GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -# Serialise Pages deploys; never cancel an in-flight deploy. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - timeout-minutes: 20 - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - - name: Checkout casket-ssg - uses: actions/checkout@v7.0.1 - with: - repository: hyperpolymath/casket-ssg - path: .casket-ssg - - - name: Setup GHCup - uses: haskell-actions/setup@v2.12.1 - with: - ghc-version: '9.8.2' - cabal-version: '3.10' - - - name: Cache Cabal - uses: actions/cache@v6.1.0 - with: - path: | - ~/.cabal/packages - ~/.cabal/store - .casket-ssg/dist-newstyle - key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} - - - name: Build casket-ssg - working-directory: .casket-ssg - run: cabal build - - - name: Build site - run: | - mkdir -p site _site - # Seed site/index.md from README if the author hasn't provided one. - if [ ! -f site/index.md ]; then - { - echo "---" - echo "title: $(basename "$PWD")" - echo "date: $(date +%Y-%m-%d)" - echo "---" - if [ -f README.adoc ]; then cat README.adoc - elif [ -f README.md ]; then cat README.md - else printf '\n# %s\n\nDocumentation coming soon.\n' "$(basename "$PWD")" - fi - } > site/index.md - fi - cd .casket-ssg && cabal run casket-ssg -- build ../site ../_site - - - name: Setup Pages - uses: actions/configure-pages@v6.0.0 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v5.0.0 - with: - path: '_site' - - deploy: - timeout-minutes: 20 - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5.0.1 diff --git a/czech-file-knife/.github/workflows/push-email-notify.yml b/czech-file-knife/.github/workflows/push-email-notify.yml deleted file mode 100644 index 6414b39e7..000000000 --- a/czech-file-knife/.github/workflows/push-email-notify.yml +++ /dev/null @@ -1,58 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Dormant push-email notification. ARMED by setting the repo variable -# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; -# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by -# new repos from the template; placed on existing repos by the farm sweep. -# -# Re-landed after the 2026-07-20 notification-storm freeze (removed in -# 09f94c5), now on hyperpolymath/smtp-notify-action: Node-free, the SMTP -# session is Idris2-specified and machine-checked, the binary is Zig-built, -# byte-reproducible, and SHA-256-pinned inside the action itself. -name: Push email notification -on: - push: - # Branch pushes only; the job condition separately excludes branch deletions. - branches: ['**'] -concurrency: - # Deliberately per-RUN, so no run is ever queued behind another and none is - # ever cancelled. Do NOT "tidy" this into a shared group such as - # ${{ github.workflow }}-${{ github.ref }}. GitHub's workflow-syntax docs: - # "By default, any existing pending job or workflow in the same concurrency - # group will be canceled and the new queued job or workflow will take its - # place." That happens regardless of cancel-in-progress, which governs only - # the RUNNING job. On this workflow it silently loses a notification email, - # with no error anywhere. Every run here reports a DISTINCT commit, so there - # is no redundant work for a concurrency limit to remove. - # The docs also offer `queue: max` (up to 100 pending); not used, because 100 - # is still a cap whereas a per-run group needs none. - # Verified with zizmor 1.30.0: deleting this block raises concurrency-limits; - # this form silences it exactly as a shared group would. - group: push-email-${{ github.run_id }}-${{ github.run_attempt }} - cancel-in-progress: false -permissions: {} -jobs: - notify: - name: Email on push - if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' && github.event.deleted != true }} - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Send push notification email - uses: hyperpolymath/smtp-notify-action@v0.3.0 # NOSONAR — pin authority is actions.lock (sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be) - with: - server_address: ${{ secrets.SMTP_HOST }} - server_port: ${{ secrets.SMTP_PORT }} - # Standard submission uses mandatory STARTTLS; other ports retain implicit TLS. - secure: ${{ secrets.SMTP_PORT == '587' && 'starttls' || 'implicit' }} - username: ${{ secrets.SMTP_USER }} - password: ${{ secrets.SMTP_PASS }} - from: "GitHub Push <${{ secrets.SMTP_USER }}>" - to: "jonathan.jewell@gmail.com j.d.a.jewell@open.ac.uk" - subject: "[${{ github.repository }}] push to ${{ github.ref_name }} by ${{ github.actor }}" - body: | - Repository: ${{ github.repository }} - Branch: ${{ github.ref_name }} - Pusher: ${{ github.actor }} - Compare: ${{ github.event.compare }} - Head msg: ${{ github.event.head_commit.message }} diff --git a/czech-file-knife/.github/workflows/quality.yml b/czech-file-knife/.github/workflows/quality.yml deleted file mode 100644 index c9c638c7f..000000000 --- a/czech-file-knife/.github/workflows/quality.yml +++ /dev/null @@ -1,79 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Code Quality -on: - push: - branches: [main, master] - pull_request: - -# Estate guardrail: scope push to default branches so a PR fires once (not -# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - -permissions: - contents: read -jobs: - lint: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: Check file permissions - run: | - find . -type f -perm /111 -name "*.sh" | head -10 || true - - name: ShellCheck (error severity) - run: | - # Error severity only: parse failures, unterminated find -exec, shebang - # not on line 1. Warnings are deliberately not gated — a gate that fires - # on style gets switched off, and these are the findings that break - # scripts at runtime. - # - # Vendored/upstream trees are excluded: patching a third party's test - # fixtures creates permanent divergence and conflicts on every sync. - fail=0 - while IFS= read -r f; do - case "/$f" in - */node_modules/*|*/vendor/*|*/third_party/*|*/rescript-ecosystem/*) continue ;; - esac - shellcheck -S error "$f" || fail=1 - done < <(git ls-files '*.sh' '*.bash') - if [ "$fail" -ne 0 ]; then - echo "::error::shellcheck reported error-severity findings (see above)" - exit 1 - fi - echo "✅ shellcheck: no error-severity findings" - - - name: Check TODO/FIXME - run: | - echo "=== TODOs ===" - grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.res" --include="*.py" --include="*.ex" . | head -20 || echo "None found" - - name: Check for large files - run: | - find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" - - name: EditorConfig check - uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 - continue-on-error: true - docs: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: Check documentation - run: | - MISSING="" - [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && MISSING="$MISSING README" - [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ] && MISSING="$MISSING LICENSE" - [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ] && MISSING="$MISSING CONTRIBUTING" - - if [ -n "$MISSING" ]; then - echo "::warning::Missing docs:$MISSING" - else - echo "✅ Core documentation present" - fi diff --git a/czech-file-knife/.github/workflows/release.yml b/czech-file-knife/.github/workflows/release.yml deleted file mode 100644 index b85b40874..000000000 --- a/czech-file-knife/.github/workflows/release.yml +++ /dev/null @@ -1,159 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Release workflow — triggered by version tags (v*). -# Builds artifacts, generates changelog via git-cliff, creates a GitHub Release, -# and produces GitHub native build-provenance attestations (OIDC + Sigstore). -name: Release -on: - push: - tags: - - 'v*' -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false -permissions: - contents: read -jobs: - build: - name: Build Artifacts - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: Detect project type and build - id: build - run: | - # Auto-detect build system from project files. - # Order matters: more specific markers checked first. - if [ -f "mix.exs" ]; then - echo "::notice::Detected Elixir/Gleam project (mix.exs)" - echo "build_type=mix" >> "$GITHUB_OUTPUT" - mix local.hex --force --if-missing - mix local.rebar --force --if-missing - mix deps.get --only prod - MIX_ENV=prod mix release - elif [ -f "Cargo.toml" ]; then - echo "::notice::Detected Rust project (Cargo.toml)" - echo "build_type=cargo" >> "$GITHUB_OUTPUT" - cargo build --release - elif [ -f "build.zig" ]; then - echo "::notice::Detected Zig project (build.zig)" - echo "build_type=zig" >> "$GITHUB_OUTPUT" - zig build -Doptimize=ReleaseSafe - elif [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then - echo "::notice::Detected Deno project (deno.json)" - echo "build_type=deno" >> "$GITHUB_OUTPUT" - deno task build - elif [ -f "gossamer.conf.json" ]; then - echo "::notice::Detected Gossamer project (gossamer.conf.json)" - echo "build_type=gossamer" >> "$GITHUB_OUTPUT" - gossamer build - elif [ -f "gleam.toml" ]; then - echo "::notice::Detected Gleam project (gleam.toml)" - echo "build_type=gleam" >> "$GITHUB_OUTPUT" - gleam build - elif [ -f "rebar.config" ]; then - echo "::notice::Detected Erlang/Rebar project (rebar.config)" - echo "build_type=rebar" >> "$GITHUB_OUTPUT" - rebar3 as prod release - elif [ -f "Justfile" ] || [ -f "justfile" ]; then - echo "::notice::Detected Justfile — running 'just build'" - echo "build_type=just" >> "$GITHUB_OUTPUT" - just build - else - echo "::error::No recognised build system found." - echo "Expected one of: mix.exs, Cargo.toml, build.zig, deno.json, gossamer.conf.json, gleam.toml, rebar.config, Justfile" - exit 1 - fi - # TODO: Upload build artifacts if needed (pin actions/upload-artifact - # to a full commit SHA when enabling, per the SHA-pin policy): - # - uses: actions/upload-artifact@ # vX.Y.Z - # with: - # name: release-artifacts - # path: target/release/ - changelog: - name: Generate Changelog - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - outputs: - changelog: ${{ steps.cliff.outputs.content }} - version: ${{ steps.version.outputs.version }} - steps: - - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 - - name: Extract version from tag - id: version - run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - - name: Install git-cliff - run: | - curl -sSfL https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-$(uname -m)-unknown-linux-gnu.tar.gz \ - | tar -xz --strip-components=1 -C /usr/local/bin/ git-cliff-*/git-cliff - - name: Generate changelog for this release - id: cliff - run: | - # Generate changelog for the current tag only - CHANGELOG=$(git cliff --latest --strip header) - # Write to output using delimiter to handle multiline - { - echo "content<> "$GITHUB_OUTPUT" - - name: Update full CHANGELOG.md - run: | - git cliff --output CHANGELOG.md - - name: Upload updated CHANGELOG.md - uses: actions/upload-artifact@v7.0.1 - with: - name: changelog - path: CHANGELOG.md - retention-days: 5 - release: - name: Create GitHub Release - needs: [build, changelog] - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: write - id-token: write # mint the OIDC token attestation provenance is signed with - attestations: write # write the build-provenance attestation (the "claim") - steps: - - uses: actions/checkout@v7.0.1 - # TODO: Download build artifacts if uploading to the release (pin - # actions/download-artifact to a full commit SHA when enabling): - # - uses: actions/download-artifact@ # vX.Y.Z - # with: - # name: release-artifacts - # path: artifacts/ - - name: Create GitHub Release - uses: softprops/action-gh-release@v3.0.3 - with: - body: ${{ needs.changelog.outputs.changelog }} - draft: false - prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }} - generate_release_notes: false - # TODO: Add artifact files to the release - # files: | - # artifacts/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # GitHub native artifact attestation (build provenance). Generates a - # signed, verifiable claim binding each released artifact to this build - # (commit, workflow, runner) via OIDC + Sigstore — verify with - # `gh attest verify --repo ${{ github.repository }}`. - # Replaces the older SLSA-generator job; native attestations need no - # separate isolated workflow. - # TODO: point subject-path at the artifacts this release actually ships - # (must match the `files:` uploaded above, e.g. artifacts/*). - - name: Attest build provenance - if: ${{ hashFiles('artifacts/*') != '' }} # skip until real artifacts are wired - uses: actions/attest-build-provenance@v4.2.2 - with: - subject-path: 'artifacts/*' diff --git a/czech-file-knife/.github/workflows/rsr-compliance-canary.yml b/czech-file-knife/.github/workflows/rsr-compliance-canary.yml deleted file mode 100644 index 13e0a55a1..000000000 --- a/czech-file-knife/.github/workflows/rsr-compliance-canary.yml +++ /dev/null @@ -1,95 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# rhodibot.yml — RSR compliance CANARY (report-only) -# -# Rhodibot does NOT mutate this repository. It never deletes, renames, -# rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS -# what an auto-fixer would have changed and reports it. -# -# Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it -# detects something it considers non-compliant — that is itself a MAJOR -# WARNING. Either the repo has drifted, OR rhodibot's own rules have -# diverged from the normative style it is meant to enforce. Both warrant -# a human look, so the canary FAILS the run when it finds would-mutate -# drift. Dangerous-pattern hits are advisory warnings only. -# -# Licence note: SPDX/licence drift is reported for MANUAL, owner-only -# correction. Rhodibot must never edit a licence header (estate directive). - -name: "\U0001F916 Rhodibot — RSR Compliance Canary" -on: - schedule: - - cron: '0 6 * * 1' # Every Monday at 06:00 UTC - workflow_dispatch: # Manual trigger - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read -jobs: - canary: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 1 - - name: Rhodibot — detect drift (no mutations) - run: | - set -uo pipefail - DRIFT=0 - warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); } - note() { echo "::warning title=Rhodibot advisory::$*"; } - - echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY" - - # --- would-DELETE: banned files --- - for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do - [ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)" - done - # would-DELETE: stale snapshots - for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do - [ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)" - done - # would-RENAME: legacy manifest name (a repo deed at root is the - # deed-era equivalent — standards#837) - if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ] && ! ls *_chora.deed >/dev/null 2>&1; then - warn "AI.a2ml present without 0-AI-MANIFEST.a2ml or a repo deed (would be renamed)" - fi - # would-DELETE: duplicate community files - [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)" - if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then - warn "stub README.md alongside README.adoc (would be removed)" - fi - # SPDX drift — MANUAL owner-only fix, never auto-edited - for dotfile in .gitignore .gitattributes .editorconfig; do - if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then - warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)" - fi - done - # would-CREATE: missing required files - [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)" - [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)" - - # --- unfixable compliance gaps (also drift) --- - [ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || ls *_chora.deed >/dev/null 2>&1 || warn "missing AI manifest (0-AI-MANIFEST.a2ml or repo deed *_chora.deed)" - [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file" - [ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README" - - # --- advisory only: dangerous verification-bypass patterns --- - for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do - count=$(grep -rl "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || true) - [ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)" - done - - echo "" >> "$GITHUB_STEP_SUMMARY" - if [ "$DRIFT" -gt 0 ]; then - echo "🔴 **Canary tripped: $DRIFT would-mutate finding(s).** Either the repo drifted or rhodibot's rules diverged from the norm — investigate (no edits were made)." >> "$GITHUB_STEP_SUMMARY" - echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed." - exit 1 - fi - echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY" - echo "✅ Rhodibot canary clean — no drift, no mutations." diff --git a/czech-file-knife/.github/workflows/runtime-policy.yml b/czech-file-knife/.github/workflows/runtime-policy.yml deleted file mode 100644 index 51ec04fa6..000000000 --- a/czech-file-knife/.github/workflows/runtime-policy.yml +++ /dev/null @@ -1,72 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Runtime and package-manager policy check. -# -# Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc §1. -# Ordering: Bun (1st) > Deno (2nd) > pnpm (3rd) > npm (last resort). -# -# REPLACES npm-bun-blocker.yml, which failed any build carrying `bun.lockb` with -# the message "npm/bun artifacts detected. Use Deno instead." That gate blocked -# what is now the FIRST-choice runtime and mandated the second. It was present in -# 55 repositories. -# -# What this fails on, deliberately: -# MIXED TOOLCHAINS -- two different package managers' lockfiles in one repo. -# That is real, actionable drift: two dependency graphs that can disagree. -# What it does NOT fail on: -# Using bun, pnpm or npm. Deno is RETIRED (2026-08-26). npm is LAST but PERMITTED; the check reports -# the tier in use so drift is visible without blocking legitimate work. -name: Runtime Policy -on: - push: - branches: [main, master] - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - runtime-policy: - name: Runtime Policy - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - - name: Report runtime tier and reject mixed toolchains - run: | - set -euo pipefail - - bun=0; deno=0; pnpm=0; npm=0 - [ -f bun.lockb ] || [ -f bun.lock ] && bun=1 || true - [ -f deno.lock ] || [ -f deno.json ] || [ -f deno.jsonc ] && deno=1 || true - [ -f pnpm-lock.yaml ] && pnpm=1 || true - [ -f package-lock.json ] && npm=1 || true - - total=$((bun + deno + pnpm + npm)) - - if [ "$total" -eq 0 ]; then - echo "::notice::No JS/TS package manager in use — nothing to check." - exit 0 - fi - - # Report the tier actually in use (LANGUAGE-POLICY.adoc §1). - [ "$bun" -eq 1 ] && echo "Bun — tier 1 (preferred)" - [ "$deno" -eq 1 ] && echo "::warning::Deno lockfile/config present. Deno is BEING REMOVED, not grandfathered (owner ruling 2026-08-26, canon standards#655). Migrate to Bun — package.json + bun.lock. This is a warning rather than an error only while the estate migration is in flight; it escalates to an error when that completes." - [ "$pnpm" -eq 1 ] && echo "pnpm — tier 3" - [ "$npm" -eq 1 ] && echo "::warning::npm lockfile present. npm is tier 4, the last resort — permitted, never preferred. See LANGUAGE-POLICY.adoc §1." - - if [ "$total" -gt 1 ]; then - echo "::error::Mixed toolchains: $total package managers have lockfiles in this repository." - echo "Two dependency graphs that can disagree is real drift. Pick one — preferring the" - echo "highest tier present — and delete the others' lockfiles." - exit 1 - fi - - echo "✅ Single package manager in use." diff --git a/czech-file-knife/.github/workflows/rust-ci.yml b/czech-file-knife/.github/workflows/rust-ci.yml deleted file mode 100644 index 05369dd9b..000000000 --- a/czech-file-knife/.github/workflows/rust-ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Rust CI — thin wrapper calling the shared estate reusable in -# hyperpolymath/standards. Configure once, propagate everywhere. -# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. -name: Rust CI -on: - push: - branches: [main, master] - pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - actions: read - contents: read -jobs: - rust-ci: - uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/scorecard.yml b/czech-file-knife/.github/workflows/scorecard.yml deleted file mode 100644 index f2ce70367..000000000 --- a/czech-file-knife/.github/workflows/scorecard.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: OSSF Scorecard - -on: - schedule: - - cron: '0 4 * * *' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - actions: read # required by the reusable workflow (staleness check reads workflow runs) - contents: read - -jobs: - scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 - # Reusable called-workflow permissions are CAPPED by the caller's grants; - # without security-events: write here the scorecard SARIF upload fails with - # startup_failure (hypatia WF018). id-token: write enables OIDC publish. - permissions: - contents: read - security-events: write - id-token: write diff --git a/czech-file-knife/.github/workflows/secret-scanner.yml b/czech-file-knife/.github/workflows/secret-scanner.yml deleted file mode 100644 index 9a7b13693..000000000 --- a/czech-file-knife/.github/workflows/secret-scanner.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Secret Scanner -on: - pull_request: - push: - branches: [main] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - actions: read - contents: read -# Single secret scanner. The standards reusable runs gitleaks (+ a Rust-secrets -# check). An inline TruffleHog job previously lived here, but the reusable -# DELIBERATELY retired TruffleHog as redundant (gitleaks gives sufficient -# coverage at lower cost — see the reusable's header). Re-adding it was -# duplicated work, not extra coverage, so it has been removed. See -# .github/workflows/README.adoc. -jobs: - scan: - # The reusable installs and executes a pinned gitleaks binary directly. - # Since standards#500 it neither comments on PRs nor reads workflow-run - # metadata, so contents: read is the complete permission contract. Keep - # this job-level block explicit: it replaces the workflow-level grant and - # is therefore the cap GitHub applies to the called workflow. - permissions: - contents: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/czech-file-knife/.github/workflows/security-policy.yml b/czech-file-knife/.github/workflows/security-policy.yml deleted file mode 100644 index 3c64369d5..000000000 --- a/czech-file-knife/.github/workflows/security-policy.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -name: Security Policy -on: - push: - branches: [main, master] - pull_request: - -# Estate guardrail: scope push to default branches so a PR fires once (not -# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - name: Security checks - run: | - FAILED=false - - # Block MD5/SHA1 for security (allow for checksums/caching) - WEAK_CRYPTO=$(grep -rE 'md5\(|sha1\(' --include="*.py" --include="*.rb" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" . 2>/dev/null | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) - if [ -n "$WEAK_CRYPTO" ]; then - echo "⚠️ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" - echo "$WEAK_CRYPTO" - fi - - # Block HTTP URLs (except localhost) - HTTP_URLS=$(grep -rE 'http://[^l][^o][^c]' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) - if [ -n "$HTTP_URLS" ]; then - echo "⚠️ HTTP URLs found. Use HTTPS:" - echo "$HTTP_URLS" - fi - - # Block hardcoded secrets patterns - SECRETS=$(grep -rEi '(api_key|apikey|secret_key|password)\s*[=:]\s*["\x27][A-Za-z0-9+/=]{20,}' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.env" . 2>/dev/null | grep -v 'example\|sample\|test\|mock\|placeholder' | head -3 || true) - if [ -n "$SECRETS" ]; then - echo "❌ Potential hardcoded secrets detected!" - FAILED=true - fi - - if [ "$FAILED" = true ]; then - exit 1 - fi - - echo "✅ Security policy check passed" diff --git a/czech-file-knife/.github/workflows/sonarqube.yml b/czech-file-knife/.github/workflows/sonarqube.yml deleted file mode 100644 index 0b735fd82..000000000 --- a/czech-file-knife/.github/workflows/sonarqube.yml +++ /dev/null @@ -1,69 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# SonarQube Cloud (SonarCloud) static analysis. Analysis scope + exclusions live -# in sonar-project.properties. Requires the SONAR_TOKEN repository secret -# (Settings -> Secrets and variables -> Actions) and a SonarCloud project: -# https://sonarcloud.io/project/overview?id=hyperpolymath_czech-file-knife -# Mirrors the boj-server arrangement. -# -# WHY THE TOKEN GUARD (added 2026-07-28) -# -------------------------------------- -# This repo has NO Actions secrets configured at all, so `secrets.SONAR_TOKEN` -# expanded to the empty string and the scanner failed every run with -# "Not authorized or project not found" -- a permanent red that blocked merges -# through the ruleset's code_quality rule. It was never a code-quality signal: -# the scan never ran. -# -# SonarCloud's AUTOMATIC analysis is separately enabled here and does report -# (check name "SonarCloud Code Analysis"), so no coverage is lost by skipping. -# -# The guard makes the state honest rather than red: with no token the job -# reports that it is unconfigured and exits 0; the moment a SONAR_TOKEN secret -# is added it runs for real, with no further edit needed. Deleting the workflow -# would have discarded a correct configuration over a missing secret. -name: SonarQube -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - sonarqube: - name: SonarQube - runs-on: ubuntu-latest - timeout-minutes: 15 - # Secrets cannot be referenced from a job-level `if:`, so the presence test - # is carried through an env var set at job scope and read in a first step. - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - steps: - - name: Report configuration state - id: cfg - run: | - if [ -z "${SONAR_TOKEN}" ]; then - echo "configured=false" >> "$GITHUB_OUTPUT" - echo "::notice::SONAR_TOKEN is not configured for this repository - skipping the CI-based scan." - echo "SonarCloud automatic analysis (check: 'SonarCloud Code Analysis') is unaffected." - echo "To enable this scan: Settings > Secrets and variables > Actions > new secret SONAR_TOKEN." - else - echo "configured=true" >> "$GITHUB_OUTPUT" - echo "SONAR_TOKEN present - running the CI-based scan." - fi - - - name: Checkout - if: steps.cfg.outputs.configured == 'true' - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 # full history for accurate new-code detection - - - name: SonarQube Scan - if: steps.cfg.outputs.configured == 'true' - uses: SonarSource/sonarqube-scan-action@v8.2.2 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/czech-file-knife/.github/workflows/static-analysis-gate.yml b/czech-file-knife/.github/workflows/static-analysis-gate.yml deleted file mode 100644 index 62134f39c..000000000 --- a/czech-file-knife/.github/workflows/static-analysis-gate.yml +++ /dev/null @@ -1,454 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Static Analysis Gate — Required by branch protection rules. -# Runs panic-attack and hypatia, deposits findings for gitbot-fleet learning. -name: Static Analysis Gate -on: - pull_request: - branches: ['**'] - push: - branches: [main, master] -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read -jobs: - # --------------------------------------------------------------------------- - # Job 1: panic-attack assail - # --------------------------------------------------------------------------- - panic-attack-assail: - name: panic-attack assail - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 - - name: Install panic-attack (if available) - id: install - run: | - # Try to fetch the latest release binary from the org - PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" - if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then - curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" - chmod +x /usr/local/bin/panic-attack - echo "installed=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::panic-attack binary not available — skipping assail" - echo "installed=false" >> "$GITHUB_OUTPUT" - fi - - name: Run panic-attack assail - id: assail - if: steps.install.outputs.installed == 'true' - run: | - set +e - panic-attack assail --format json . > panic-attack-findings.json - PA_EXIT=$? - set -e - - # Same defect class as the Hypatia job below: `2>&1` folded the - # scanner's stderr into the JSON payload, so every jq parse failed, - # every count silently became 0 via `|| echo 0`, and "Fail on critical - # findings" could never fire on any input. Keep stderr on the log. - if [ ! -s panic-attack-findings.json ]; then - echo "[]" > panic-attack-findings.json - fi - - # Deliberately a WARNING, not a failure. panic-attack is a downloaded - # release binary whose exit-code and output contract are not verified - # here, and it has no confirmed --exit-zero equivalent, so we surface a - # malformed payload in the log rather than block on an unverified tool. - # Promote to `exit 1` (as the Hypatia job does) once that contract is - # confirmed -- see the follow-up issue linked from this PR. - if ! jq -e 'type == "array"' panic-attack-findings.json >/dev/null 2>&1; then - echo "::warning::panic-attack output is not a JSON array (exit ${PA_EXIT}); counts below are unreliable" - fi - - # Parse finding counts - TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0) - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' panic-attack-findings.json 2>/dev/null || echo 0) - MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' panic-attack-findings.json 2>/dev/null || echo 0) - LOW=$(jq '[.[] | select(.severity == "low")] | length' panic-attack-findings.json 2>/dev/null || echo 0) - - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" - echo "high=$HIGH" >> "$GITHUB_OUTPUT" - echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" - echo "low=$LOW" >> "$GITHUB_OUTPUT" - echo "exit_code=$PA_EXIT" >> "$GITHUB_OUTPUT" - - name: Emit check annotations - if: steps.install.outputs.installed == 'true' - run: | - # Convert JSON findings into GitHub Actions annotations - # Findings carry no `.message` (keys: action,file,line,reason,rule_module, - # severity,type), so every annotation read "null". `.file` is an absolute - # runner path, which GitHub cannot anchor to the diff, so it is made - # workspace-relative here. - jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | - (.file | ltrimstr($ws + "/")) as $f | - (.reason // .message // .type // "finding") as $m | - if .severity == "critical" then - "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" - elif .severity == "high" then - "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" - else - "::warning file=\($f),line=\(.line // 1)::[panic-attack] \($m)" - end - ' panic-attack-findings.json || true - - name: Write step summary - if: steps.install.outputs.installed == 'true' - run: | - cat <> "$GITHUB_STEP_SUMMARY" - ## panic-attack assail Results - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.assail.outputs.critical }} | - | High | ${{ steps.assail.outputs.high }} | - | Medium | ${{ steps.assail.outputs.medium }} | - | Low | ${{ steps.assail.outputs.low }} | - | **Total**| ${{ steps.assail.outputs.total }} | - EOF - - name: Create stub findings (when panic-attack unavailable) - if: steps.install.outputs.installed != 'true' - run: | - echo "[]" > panic-attack-findings.json - echo "## panic-attack assail" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload panic-attack findings - uses: actions/upload-artifact@v7.0.1 - with: - name: panic-attack-findings - path: panic-attack-findings.json - retention-days: 90 - - name: Fail on critical findings - if: steps.install.outputs.installed == 'true' && steps.assail.outputs.critical > 0 - run: | - echo "::error::panic-attack found ${{ steps.assail.outputs.critical }} critical issue(s) — blocking merge" - exit 1 - # --------------------------------------------------------------------------- - # Job 2: hypatia-scan - # - # NOTE — this is NOT a duplicate of the standalone `hypatia-scan.yml`. - # * THIS job runs Hypatia inside the gate and hands its findings to the - # `deposit-findings` job below, which feeds the gitbot-fleet LEARNING - # pipeline (artifact -> fleet). It is a REQUIRED status check. - # * `hypatia-scan.yml` runs Hypatia standalone as the repo's security scan, - # independent of the fleet-learning deposit. - # Same tool, two different consumers — keep both. See .github/workflows/README.adoc. - # --------------------------------------------------------------------------- - hypatia-scan: - name: Hypatia neurosymbolic scan - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 - - name: Setup Elixir for Hypatia scanner - id: beam - continue-on-error: true - uses: erlef/setup-beam@v1.24.1 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - name: Clone and build Hypatia - id: build - continue-on-error: true - run: | - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" 2>/dev/null || true - if [ -f "$HOME/hypatia/mix.exs" ]; then - cd "$HOME/hypatia" - # Build escript if neither hypatia nor hypatia-v2 exists - if [ ! -f hypatia ] && [ ! -f hypatia-v2 ]; then - mix deps.get - mix escript.build - fi - echo "ready=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::Hypatia scanner not available — skipping scan" - echo "ready=false" >> "$GITHUB_OUTPUT" - fi - - name: Run Hypatia scan - id: scan - if: steps.build.outputs.ready == 'true' - run: | - set +e - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json - HYP_EXIT=$? - set -e - - # --exit-zero is Hypatia's own documented CI recipe (lib/hypatia/cli.ex), - # for exactly this case: "use in CI when a downstream step gates on - # severity counts". Findings go to stdout, the one-line summary to - # stderr, and the process exits 0 unless the SCANNER itself failed. - # - # Do NOT redirect stderr into the payload with `2>&1`: that folds the - # summary line into the JSON, so every parse fails, the old `[]` - # fallback substituted a clean result, CRITICAL was always 0, and the - # gate below could never fire on any input. Keep stderr on the log. - if [ "$HYP_EXIT" -ne 0 ]; then - echo "::error::Hypatia scanner execution failed with exit ${HYP_EXIT}" - exit "$HYP_EXIT" - fi - # `jq empty` is NOT sufficient -- it succeeds on any valid JSON, - # including a bare string, object or null. Assert the array. - if [ ! -s hypatia-findings.json ] || ! jq -e 'type == "array"' hypatia-findings.json >/dev/null; then - echo "::error::Hypatia did not produce a valid JSON findings array" - exit 1 - fi - - TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json 2>/dev/null || echo 0) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json 2>/dev/null || echo 0) - MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' hypatia-findings.json 2>/dev/null || echo 0) - LOW=$(jq '[.[] | select(.severity == "low")] | length' hypatia-findings.json 2>/dev/null || echo 0) - - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" - echo "high=$HIGH" >> "$GITHUB_OUTPUT" - echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" - echo "low=$LOW" >> "$GITHUB_OUTPUT" - - name: Emit check annotations - if: steps.build.outputs.ready == 'true' - run: | - # Findings carry no `.message` (keys: action,file,line,reason,rule_module, - # severity,type), so every annotation read "null". `.file` is an absolute - # runner path, which GitHub cannot anchor to the diff, so it is made - # workspace-relative here. - jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | - (.file | ltrimstr($ws + "/")) as $f | - (.reason // .message // .type // "finding") as $m | - if .severity == "critical" then - "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" - elif .severity == "high" then - "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" - else - "::warning file=\($f),line=\(.line // 1)::[hypatia] \($m)" - end - ' hypatia-findings.json || true - - name: Write step summary - if: steps.build.outputs.ready == 'true' - run: | - cat <> "$GITHUB_STEP_SUMMARY" - ## Hypatia Scan Results - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.scan.outputs.critical }} | - | High | ${{ steps.scan.outputs.high }} | - | Medium | ${{ steps.scan.outputs.medium }} | - | Low | ${{ steps.scan.outputs.low }} | - | **Total**| ${{ steps.scan.outputs.total }} | - EOF - - name: Create stub findings (when Hypatia unavailable) - if: steps.build.outputs.ready != 'true' - run: | - echo "[]" > hypatia-findings.json - echo "## Hypatia Scan" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: Hypatia scanner not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload hypatia findings - uses: actions/upload-artifact@v7.0.1 - with: - name: hypatia-findings - path: hypatia-findings.json - retention-days: 90 - - name: Fail on critical security findings - if: steps.build.outputs.ready == 'true' && steps.scan.outputs.critical > 0 - run: | - echo "::error::Hypatia found ${{ steps.scan.outputs.critical }} critical security issue(s) — blocking merge" - exit 1 - # --------------------------------------------------------------------------- - # Job 3: patch-bridge triage (CVE contextual assessment) - # --------------------------------------------------------------------------- - patch-bridge-triage: - name: Patch Bridge CVE triage - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - with: - fetch-depth: 0 - - name: Install panic-attack (if available) - id: install - run: | - PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" - if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then - curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" - chmod +x /usr/local/bin/panic-attack - echo "installed=true" >> "$GITHUB_OUTPUT" - else - echo "::notice::panic-attack binary not available — skipping Patch Bridge" - echo "installed=false" >> "$GITHUB_OUTPUT" - fi - - name: Run Patch Bridge triage - id: triage - if: steps.install.outputs.installed == 'true' - run: | - set +e - panic-attack bridge triage --format json . > bridge-report.json 2>&1 - PB_EXIT=$? - set -e - - if [ ! -s bridge-report.json ] || ! jq empty bridge-report.json 2>/dev/null; then - echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json - fi - - UNMITIGABLE=$(jq '.unmitigable // 0' bridge-report.json) - MITIGATED=$(jq '.mitigated // 0' bridge-report.json) - CONCATENATIVE=$(jq '.concatenative // 0' bridge-report.json) - INFORMATIONAL=$(jq '.informational // 0' bridge-report.json) - - echo "unmitigable=$UNMITIGABLE" >> "$GITHUB_OUTPUT" - echo "mitigated=$MITIGATED" >> "$GITHUB_OUTPUT" - echo "concatenative=$CONCATENATIVE" >> "$GITHUB_OUTPUT" - echo "informational=$INFORMATIONAL" >> "$GITHUB_OUTPUT" - - name: Write step summary - if: steps.install.outputs.installed == 'true' - run: | - cat <> "$GITHUB_STEP_SUMMARY" - ## Patch Bridge CVE Triage - - | Classification | Count | - |----------------|-------| - | Unmitigable | ${{ steps.triage.outputs.unmitigable }} | - | Mitigated | ${{ steps.triage.outputs.mitigated }} | - | Concatenative | ${{ steps.triage.outputs.concatenative }} | - | Informational | ${{ steps.triage.outputs.informational }} | - - Unmitigable CVEs require dependency replacement or rearchitecture. - Mitigated CVEs have active controls with soundness proofs. - Concatenative risks are CVE combinations that multiply severity. - EOF - - name: Create stub report (when unavailable) - if: steps.install.outputs.installed != 'true' - run: | - echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json - echo "## Patch Bridge CVE Triage" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Skipped: panic-attack not available in this environment." >> "$GITHUB_STEP_SUMMARY" - - name: Upload bridge report - uses: actions/upload-artifact@v7.0.1 - with: - name: bridge-report - path: bridge-report.json - retention-days: 90 - - name: Fail on unmitigable CVEs in critical paths - if: steps.install.outputs.installed == 'true' && steps.triage.outputs.unmitigable > 0 - run: | - echo "::warning::Patch Bridge found ${{ steps.triage.outputs.unmitigable }} unmitigable CVE(s) — review required" - # Warning only, not blocking. Unmitigable means the developer needs - # to make an architectural decision, not that the PR is wrong. - # --------------------------------------------------------------------------- - # Job 4: deposit-findings (combines + archives for gitbot-fleet) - # --------------------------------------------------------------------------- - deposit-findings: - name: Deposit findings for gitbot-fleet - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: [panic-attack-assail, hypatia-scan, patch-bridge-triage] - if: always() - steps: - - name: Download panic-attack findings - uses: actions/download-artifact@v8.0.1 - with: - name: panic-attack-findings - path: findings/ - - name: Download hypatia findings - uses: actions/download-artifact@v8.0.1 - with: - name: hypatia-findings - path: findings/ - - name: Download bridge report - uses: actions/download-artifact@v8.0.1 - with: - name: bridge-report - path: findings/ - - name: Combine findings into unified report - id: combine - run: | - PA_FILE="findings/panic-attack-findings.json" - HYP_FILE="findings/hypatia-findings.json" - - # Ensure both files exist and are valid JSON arrays - for f in "$PA_FILE" "$HYP_FILE"; do - if [ ! -s "$f" ] || ! jq empty "$f" 2>/dev/null; then - echo "[]" > "$f" - fi - done - - # Tag each finding with its source scanner - jq '[.[] | . + {"scanner": "panic-attack"}]' "$PA_FILE" > /tmp/pa-tagged.json - jq '[.[] | . + {"scanner": "hypatia"}]' "$HYP_FILE" > /tmp/hyp-tagged.json - - # Read bridge report (CVE triage, not findings array) - BRIDGE_FILE="findings/bridge-report.json" - if [ ! -s "$BRIDGE_FILE" ] || ! jq empty "$BRIDGE_FILE" 2>/dev/null; then - echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > "$BRIDGE_FILE" - fi - - # Build unified report envelope - jq -n \ - --arg repo "${{ github.repository }}" \ - --arg sha "${{ github.sha }}" \ - --arg ref "${{ github.ref }}" \ - --arg run_id "${{ github.run_id }}" \ - --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --slurpfile pa /tmp/pa-tagged.json \ - --slurpfile hyp /tmp/hyp-tagged.json \ - --slurpfile bridge "$BRIDGE_FILE" \ - '{ - schema_version: "1.1.0", - repository: $repo, - commit_sha: $sha, - ref: $ref, - run_id: $run_id, - timestamp: $ts, - findings: ($pa[0] + $hyp[0]), - patch_bridge: $bridge[0] - }' > findings/unified-findings.json - - TOTAL=$(jq '.findings | length' findings/unified-findings.json) - CRITICAL=$(jq '[.findings[] | select(.severity == "critical")] | length' findings/unified-findings.json) - HIGH=$(jq '[.findings[] | select(.severity == "high")] | length' findings/unified-findings.json) - MEDIUM=$(jq '[.findings[] | select(.severity == "medium" or .severity == "warn")] | length' findings/unified-findings.json) - LOW=$(jq '[.findings[] | select(.severity == "low")] | length' findings/unified-findings.json) - - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" - echo "high=$HIGH" >> "$GITHUB_OUTPUT" - echo "medium=$MEDIUM" >> "$GITHUB_OUTPUT" - echo "low=$LOW" >> "$GITHUB_OUTPUT" - - name: Upload unified findings (fleet scanner picks these up) - uses: actions/upload-artifact@v7.0.1 - with: - name: unified-findings - path: findings/unified-findings.json - retention-days: 90 - - name: Write deposit summary - run: | - cat <> "$GITHUB_STEP_SUMMARY" - ## Unified Findings Deposit - - **Repository:** ${{ github.repository }} - **Commit:** \`${{ github.sha }}\` - **Deposited at:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.combine.outputs.critical }} | - | High | ${{ steps.combine.outputs.high }} | - | Medium | ${{ steps.combine.outputs.medium }} | - | Low | ${{ steps.combine.outputs.low }} | - | **Total**| ${{ steps.combine.outputs.total }} | - - Findings saved as \`unified-findings\` artifact. - The gitbot-fleet scanner will ingest these on its next pass. - EOF diff --git a/czech-file-knife/.github/workflows/workflow-linter.yml b/czech-file-knife/.github/workflows/workflow-linter.yml deleted file mode 100644 index 35dfd9d5a..000000000 --- a/czech-file-knife/.github/workflows/workflow-linter.yml +++ /dev/null @@ -1,178 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# workflow-linter.yml - Validates GitHub workflows against RSR security standards -# This workflow can be copied to other repos for consistent enforcement -name: Workflow Security Linter - -on: - push: - paths: - - '.github/workflows/**' - pull_request: - paths: - - '.github/workflows/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -permissions: - contents: read - -jobs: - lint-workflows: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@v7.0.1 - - - name: Check SPDX Headers - run: | - echo "=== Checking SPDX License Headers ===" - failed=0 - for file in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$file" ] || continue - # actions-lock may prepend its own comment. Require the licence - # in the leading comment block, before the workflow body. - if ! awk '/^# SPDX-License-Identifier:/ { found=1 } /^[^#[:space:]]/ { exit } END { exit !found }' "$file"; then - echo "ERROR: $file missing SPDX header" - failed=1 - fi - done - if [ $failed -eq 1 ]; then - echo "Add '# SPDX-License-Identifier: MPL-2.0' to the leading comment block" - exit 1 - fi - echo "All workflows have SPDX headers" - - - name: Check Permissions Declaration - run: | - echo "=== Checking Permissions ===" - failed=0 - for file in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$file" ] || continue - if ! grep -q "^permissions:" "$file"; then - echo "ERROR: $file missing top-level 'permissions:' declaration" - failed=1 - fi - done - if [ $failed -eq 1 ]; then - echo "Add a top-level 'permissions:' block (e.g. contents: read)" - exit 1 - fi - echo "All workflows have permissions declared" - - # Bun is the estate's first-choice runtime (LANGUAGE-POLICY.adoc §1: - # Bun > Deno > pnpm > npm) and executes .ts directly (§1.2). Tag form - # matches every other ref in this repo and is resolved by actions.lock. - - name: Set up Bun - uses: oven-sh/setup-bun@v2.2.0 - with: - bun-version: latest - - - name: Check SHA-Pinned Actions - run: | - echo "=== Checking Action Pinning ===" - # Find any uses: lines that don't have @SHA format - # Pattern: uses: owner/repo@<40-char-hex> - # Delegated to scripts/check-action-pinning.js. The rule it enforces is - # unchanged in spirit — every action ref must resolve to an immutable - # commit — but "pinned" now includes refs the workflow lockfile - # resolves, which is how this repo pins them. Kept as a script rather - # than inline because the inline form needs a heredoc inside a YAML - # block scalar, and that is a well-known way to ship a gate that - # silently does nothing. - if ! bun scripts/check-action-pinning.js; then - unpinned="see above" - else - unpinned="" - fi - - if [ -n "$unpinned" ]; then - echo "ERROR: Found unpinned actions:" - echo "" - echo "Replace version tags with SHA pins, e.g.:" - echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" - exit 1 - fi - echo "All actions are SHA-pinned" - - - name: Check for Duplicate Workflows - run: | - echo "=== Checking for Duplicates ===" - # Known duplicate patterns - if [ -f .github/workflows/codeql.yml ] && [ -f .github/workflows/codeql-analysis.yml ]; then - echo "ERROR: Duplicate CodeQL workflows found" - echo "Delete codeql-analysis.yml (keep codeql.yml)" - exit 1 - fi - if [ -f .github/workflows/rust.yml ] && [ -f .github/workflows/rust-ci.yml ]; then - echo "WARNING: Potential duplicate Rust workflows" - echo "Consider consolidating rust.yml and rust-ci.yml" - fi - echo "No critical duplicates found" - - - name: Check CodeQL Language Matrix - run: | - echo "=== Checking CodeQL Configuration ===" - if [ ! -f .github/workflows/codeql.yml ]; then - echo "No CodeQL workflow found (optional)" - exit 0 - fi - - # Detect repo languages - has_js=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) - has_py=$(find . -name "*.py" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) - has_go=$(find . -name "*.go" -path "*/src/*" -o -path "*/cmd/*" -o -path "*/pkg/*" 2>/dev/null | head -1) - has_rs=$(find . -name "*.rs" -path "*/src/*" 2>/dev/null | head -1) - has_java=$(find . -name "*.java" -path "*/src/*" 2>/dev/null | head -1) - has_rb=$(find . -name "*.rb" -path "*/lib/*" -o -path "*/app/*" 2>/dev/null | head -1) - - echo "Detected languages:" - [ -n "$has_py" ] && echo " - python" - [ -n "$has_go" ] && echo " - go" - [ -n "$has_rs" ] && echo " - rust (note: CodeQL rust is limited)" - [ -n "$has_java" ] && echo " - java-kotlin" - [ -n "$has_rb" ] && echo " - ruby" - - # Check for over-reach - if grep -q "language:.*'go'" .github/workflows/codeql.yml && [ -z "$has_go" ]; then - echo "WARNING: CodeQL configured for Go but no Go files found" - fi - if grep -q "language:.*'python'" .github/workflows/codeql.yml && [ -z "$has_py" ]; then - echo "WARNING: CodeQL configured for Python but no Python files found" - fi - if grep -q "language:.*'java'" .github/workflows/codeql.yml && [ -z "$has_java" ]; then - echo "WARNING: CodeQL configured for Java but no Java files found" - fi - if grep -q "language:.*'ruby'" .github/workflows/codeql.yml && [ -z "$has_rb" ]; then - echo "WARNING: CodeQL configured for Ruby but no Ruby files found" - fi - - echo "CodeQL check complete" - - - name: Check Secrets Guards - run: | - echo "=== Checking Secrets Usage ===" - # Look for secrets without conditional guards in mirror workflows - if [ -f .github/workflows/mirror.yml ]; then - if grep -q "secrets\." .github/workflows/mirror.yml; then - if ! grep -q "if:.*vars\." .github/workflows/mirror.yml; then - echo "WARNING: mirror.yml uses secrets without vars guard" - echo "Add 'if: vars.FEATURE_ENABLED == true' to jobs" - fi - fi - fi - echo "Secrets check complete" - - - name: Summary - run: | - echo "" - echo "=== Workflow Linter Summary ===" - echo "All critical checks passed." - echo "" - echo "For more info, see: robot-repo-bot/ERROR-CATALOG.scm" diff --git a/czech-file-knife/.gitignore b/czech-file-knife/.gitignore deleted file mode 100644 index afbe8516a..000000000 --- a/czech-file-knife/.gitignore +++ /dev/null @@ -1,146 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# RSR-compliant .gitignore - -# OS & Editor -.DS_Store -Thumbs.db -*.swp -*.swo -*~ -.idea/ -.vscode/ -.direnv/ - -# Agent / local session artifacts (Claude Code worktrees, scratch) — never commit -.claude/ - -# Build -target/ -_build/ -/dist/ -/out/ - -# Dependencies -node_modules/ -/vendor/ -/deps/ -/.elixir_ls/ - -# Rust -# Cargo.lock # Keep for binaries - -# Elixir -/cover/ -/doc/ -*.ez -erl_crash.dump - -# Julia -*.jl.cov -*.jl.mem -/Manifest.toml - -# -/lib/bs/ -/.bsb.lock - -# Ada/SPARK -*.ali -/obj/ -/bin/ - -# Haskell -/.stack-work/ -/dist-newstyle/ - -# Chapel -*.chpl.tmp.* - -# Secrets -.env -.env.* -*.pem -*.key -secrets/ - -# Test/Coverage -/coverage/ -htmlcov/ - -# Logs -*.log -/logs/ - -# Maintenance local artifacts -.maintenance-perms-state.tsv -docs/reports/maintenance/*.json - -# Machine-readable locks -.machine_readable/.locks/ - -# Temp -/tmp/ -*.tmp -*.bak - -# Crash recovery artifacts -ai-cli-crash-capture/ - -# KDE metadata -.directory - -# Sync artifacts -sync_report*.txt - -# Hypatia scan cache (local-only) -.hypatia/ -.zig-cache/ -target/ -node_modules/ -_build/ -deps/ -.elixir_ls/ -.cache/ -build/ -dist/ - -# /build/ is a tracked config directory (build orchestration: contractile.just, -# guix.scm, just/*.just, etc.) introduced in chore/root-cleanup. Whitelist -# root-level /build/ so its contents are tracked. The blanket `build/` rule -# above still ignores any nested `build/` directories (e.g. Rust crate -# target/build/) — only the root-level path is exempt. -!/build/ -!/build/** - -# ...but never track Idris2 typecheck output. `idris2 --typecheck src/interface/abi.ipkg` -# writes compiled .ttc/.ttm under build/ttc/; these are generated artifacts. -/build/ttc/ - -# Arrival-pack build artifact (regenerated from deed) -.machine_readable/arrival-pack/claude-md-data.json - -# Coaptation atomiser artifacts (regenerated from contractiles + descriptiles); -# the receipt itself (receipts/latest.a2ml) IS committed as the drift baseline. -.machine_readable/coaptation/clauses.json -.machine_readable/coaptation/facts.json - -# Coaptation Idris2 core typecheck artifacts -.machine_readable/coaptation/core/build/ - -# Coaptation re-anchor basis (occasional, generated on --reanchor when red; not a baseline) -.machine_readable/coaptation/receipts/reanchor-basis.a2ml - -# Generated by `just cookbook` (has timestamp; non-deterministic) -docs/just-cookbook.adoc - -# Generated man pages (`just man`) -docs/man/*.1 -# Coq compiled proof artifacts -verification/proofs/coq/*.vo -verification/proofs/coq/*.vok -verification/proofs/coq/*.vos -verification/proofs/coq/*.glob -verification/proofs/coq/.*.aux -# Agda compiled proof artifacts -verification/proofs/agda/*.agdai - diff --git a/czech-file-knife/.gitleaksignore b/czech-file-knife/.gitleaksignore deleted file mode 100644 index 76765a36b..000000000 --- a/czech-file-knife/.gitleaksignore +++ /dev/null @@ -1,4 +0,0 @@ -874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CHORA.deed.in:generic-api-key:21 -8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CHORA.deed.in:generic-api-key:21 -874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CLADE.a2ml.in:generic-api-key:21 -8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CLADE.a2ml.in:generic-api-key:21 diff --git a/czech-file-knife/.gitmessage b/czech-file-knife/.gitmessage deleted file mode 100644 index ef6021d43..000000000 --- a/czech-file-knife/.gitmessage +++ /dev/null @@ -1,18 +0,0 @@ -# (): (Max 50 chars) -# |<------------------------------------------------>| - -# Explain WHY this change is being made (Max 72 chars per line) -# |<---------------------------------------------------------------------->| - -# Explain HOW this change was implemented (if not obvious) - -# [ ] Tests added/updated -# [ ] Documentation updated -# [ ] ABI/FFI boundaries verified (if applicable) - -# Issue tracking: -# Resolves: # -# See also: # -# -# --- -# Allowed Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert diff --git a/czech-file-knife/.hypatia-ignore b/czech-file-knife/.hypatia-ignore deleted file mode 100644 index c3df862a9..000000000 --- a/czech-file-knife/.hypatia-ignore +++ /dev/null @@ -1,60 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# .hypatia-ignore — scoped, documented exemptions for the Hypatia scanner. -# -# Format (one entry per line): -# /: -# /*: (any type in module) -# (any rule, any module) -# -# Lines starting with `#` are comments. A path-fragment is a substring match -# against the repo-relative file path. Built-in defaults live in Hypatia's -# lib/hypatia/scanner_suppression.ex — do not duplicate them here. Each entry -# below carries a stated reason. Prefer fix-the-code or an inline directive -# over an entry here; this file is for whole-file/directory exemptions. - -# ─── build/docs-seed/ describes DOWNSTREAM repos, not this one ───────────── -# -# `build/docs-seed/` is the canonical template of documentation that gets -# copied into scaffolded repos. Its example paths (e.g. `src/middleware/…`, -# `src/main.rs`) describe a hypothetical generated project, so they -# legitimately do not resolve against this template's own tree. SD022 -# (stale `src//` reference) is therefore a structural false positive -# for everything under this directory — mirrors the built-in CHANGELOG.md -# and third_party/ carve-outs. Scaffolded repos inherit this file, so the -# exemption travels with the template. -structural_drift/SD022:build/docs-seed/ - -# ─── Research-extension workflow findings ─────────────────────────────────────── -# -# RE001 is an advisory recommendation to add harden-runner whenever a workflow -# references a secret. These workflows deliberately use repository-locked -# actions and least-privilege permissions while retaining the outbound access -# needed for GitHub, SMTP, BoJ and SonarQube. Keep the exemptions file-scoped so -# the advisory still applies to every new workflow. -research_extensions/RE001:.github/workflows/push-email-notify.yml -research_extensions/RE001:.github/workflows/release.yml -research_extensions/RE001:.github/workflows/build-notification.yml -research_extensions/RE001:.github/workflows/dependabot-automerge.yml -research_extensions/RE001:.github/workflows/label-triage.yml -research_extensions/RE001:.github/workflows/labels.yml -research_extensions/RE001:.github/workflows/sonarqube.yml - -# RE005's matches are non-masking uses: empty grep/find results and annotation -# rendering are allowed to be non-fatal, while each workflow retains a separate -# explicit failure path for the condition it gates. Scope each exemption to the -# reviewed workflow rather than disabling RE005 repository-wide. -research_extensions/RE005:.github/workflows/security-policy.yml -research_extensions/RE005:.github/workflows/quality.yml -research_extensions/RE005:.github/workflows/dot-wellknown-enforcement.yml -research_extensions/RE005:.github/workflows/static-analysis-gate.yml - -# RE008 assumes the actor-name comparison is used with pull_request_target. The -# Dependabot workflow uses the unprivileged pull_request event and also verifies -# github.event.pull_request.user.login, so that threat model does not apply. -research_extensions/RE008:.github/workflows/dependabot-automerge.yml - -# iOS File Provider Extension: NSFileProviderReplicatedExtension is a Swift-only -# Apple API with no Rust/Tauri equivalent. Carried over from -# developer-ecosystem/.hypatia-baseline.json (tracking: developer-ecosystem#111). -cicd_rules/banned_language_file:src/cfk-ios/swift/ diff --git a/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml b/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml deleted file mode 100644 index 082ebab39..000000000 --- a/czech-file-knife/.machine_readable/ENSAID_CONFIG.a2ml +++ /dev/null @@ -1,96 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# ENSAID_CONFIG.a2ml — eNSAID Environment Configuration -# Per-repo configuration for PanLL and eNSAID-compatible tools. -# -# Canonical location: .machine_readable/ENSAID_CONFIG.a2ml -# Spec: https://github.com/hyperpolymath/standards/tree/main/ensaid-config -# -# Naming convention: -# - UPPERCASE + underscore = non-executable machine-readable file -# - Lives in .machine_readable/ alongside STATE.a2ml, META.a2ml, etc. - -# ───────────────────────────────────────────────────────────────── -# [ensaid] — Core eNSAID identity and version -# ───────────────────────────────────────────────────────────────── -[ensaid] -version = "1.0.0" -tool = "panll" - -# ───────────────────────────────────────────────────────────────── -# [workspace] — Workspace mode, protection, and execution policy -# ───────────────────────────────────────────────────────────────── -[workspace] -mode = "rhodium" # rhodium | gold | silver | bronze -protection = "open" # open | guarded | locked -execution = "live" # live | dry-run | approval-required - -# ───────────────────────────────────────────────────────────────── -# [preferences] — User/repo-level display and behaviour preferences -# ───────────────────────────────────────────────────────────────── -[preferences] -humidity = "medium" # high | medium | low (drift aura intensity) -default-arrangement = "default-3-panel" # workspace arrangement ID -auto-connect = true # auto-connect panels to backends on load - -# ───────────────────────────────────────────────────────────────── -# [panels] — Panel visibility, enablement, and isolation overrides -# ───────────────────────────────────────────────────────────────── -[panels] -version = "1.0.0" - -# By default, all panels are available. Uncomment to restrict: -# [[panels.enabled]] -# id = "valence-shell" -# isolation = "native" -# auto-connect = true -# -# [[panels.enabled]] -# id = "editor-bridge" -# isolation = "native" -# auto-connect = true - -# Panels to hide for this repo context: -# [panels.disabled] -# ids = [] - -# ───────────────────────────────────────────────────────────────── -# [workflows] — Automation Router event-driven cross-panel rules -# ───────────────────────────────────────────────────────────────── -[workflows] -version = "1.0.0" - -# Example: rebuild on file save -# [[workflows.rule]] -# name = "build-on-save" -# trigger = { event = "file-changed", pattern = "src/**/*.res" } -# condition = { panel = "build-dashboard", field = "watchMode", equals = true } -# action = { panel = "build-dashboard", message = "TriggerBuild", args = { target = "game" } } -# approval = "auto-fire" # auto-fire | require-approval | approve-once | dry-run-first - -# ───────────────────────────────────────────────────────────────── -# [clades] — Panel clade trait and capability overrides -# ───────────────────────────────────────────────────────────────── -[clades] -version = "1.0.0" - -# Example: add a custom capability to a panel clade -# [[clades.override]] -# id = "build-dashboard" -# traits = { has-work-items = true } -# capabilities-add = ["CustomCheck"] - -# ───────────────────────────────────────────────────────────────── -# [portfolios] — Custom panel bundles for this repo's workflow -# ───────────────────────────────────────────────────────────────── -[portfolios] -version = "1.0.0" - -# Example: a custom portfolio for this project -# [[portfolios.custom]] -# id = "czech_file_knife-dev" -# name = "Czech File Knife Development" -# description = "Panels for Czech File Knife development" -# panels = ["valence-shell", "editor-bridge", "build-dashboard"] -# default-isolation = "native" diff --git a/czech-file-knife/.machine_readable/PROVENANCE.a2ml b/czech-file-knife/.machine_readable/PROVENANCE.a2ml deleted file mode 100644 index 1bffb57ac..000000000 --- a/czech-file-knife/.machine_readable/PROVENANCE.a2ml +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# PROVENANCE.a2ml — what this repo was minted FROM. -# Written once, at mint, by build/just/repo-init.just. Not a hand-edited -# file: tools that want "what is this repo" read STATE.a2ml; tools that -# want "where did this repo come from" read this. - -[provenance] -minted_at = "2026-09-28" - -template_repo = "hyperpolymath/rsr-template-repo" -template_branch = "master" -template_commit = "933507582ef2467d77541dbf2837d278d321db1b" -template_tree = "42883b5a9d3e4dcf1b2cdc69ecf820425b4f39b7" -# Contract: branches this repo is EXPECTED to carry beyond the default. -# Empty means default-only. A mint must never inherit template work -# branches (coderabbit/, chore/, bot-task or stale branches) — that is -# how knot-knot received a byte-identical copy of a template work branch -# with no common ancestor (#203). Enforced by -# scripts/check-template-conformance.sh. -extra_branches = [] - -canon_version = "2.1.1" -criteria_sha256 = "6a5aa8857bd0d0d58ef48827938ca17c251b6b854dacf59d306388d61694d82a" -gates_sha256 = "e70efd2f53c9445e30da4baf770366f04a4a84ffd844a01426e587e565b53e6a" - -archetype = "" -# Which minting path actually ran. Hardcoded as "hand" before this, so -# every scripted mint claimed to be hand-minted — which destroyed the -# one signal that would have told #201/#203 which path to trust. -minted_by = "repo-init" -mint_mode = "interactive" - -[substitutions] -# ADR-0003 records the old state honestly: "renders 34 substitutions and -# derives identity, but leaves 12 tokens UNASSIGNED". Listing them here -# converts that from a known defect into a QUERYABLE one: a non-empty -# list means the mint did not fully render, and the validation step -# below already fails on a leftover token. -rendered = 34 -unassigned = [] diff --git a/czech-file-knife/.machine_readable/README.adoc b/czech-file-knife/.machine_readable/README.adoc deleted file mode 100644 index fd6b71971..000000000 --- a/czech-file-knife/.machine_readable/README.adoc +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= machine-readable Pillar -:toc: - -This pillar holds the repository's machine-readable metadata: the records -that let tools and agents read this project's state, boundaries and -obligations without parsing prose. - -[IMPORTANT] -==== -*Single normative source.* The grammar for the record family in this pillar -is `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in -https://github.com/hyperpolymath/standards[hyperpolymath/standards]. That -document is the only place the syntax, semantics and typechecking rules are -defined. Every other mention of the format across the estate — including -every README in this tree — is a *pointer* to it, never a restatement. - -The format formerly called A2ML is now *DEED* (`.deed`). Files here still -carry `.a2ml` pending a single atomic estate-wide rename; do not -hand-convert them. -==== - -== Contents - -`descriptiles/`:: What this repository *is* and what state it is *in* — -`META`, `STATE`, `ECOSYSTEM`, `PLAYBOOK`, `AGENTIC`, `NEUROSYM`, plus the -anchors and the facet record. See `descriptiles/README.adoc`. - -`contractiles/`:: What this repository *ought* to do — the Must, Trust, -Adjust, Intend, Bust and Dust verb sets, each with its declaration, its -Nickel runner and its k9 service-automation component. See -`contractiles/README.adoc`. - -`arrival-pack/`:: The entry pack for agents visiting this repository. - -`policies/`:: Maintenance axes and related policy records. - -`rsr-profile.a2ml`:: This repository's declared RSR v2.0 capabilities, -which gate which conformance criteria apply to it. diff --git a/czech-file-knife/.machine_readable/ai/AI.a2ml b/czech-file-knife/.machine_readable/ai/AI.a2ml deleted file mode 100644 index d728f6027..000000000 --- a/czech-file-knife/.machine_readable/ai/AI.a2ml +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 - -# AI Assistant Instructions - -## Repository Focus -- `czech-file-knife` is treated as a Rhodium Standard Repository; obey the Rhodium policies and keep `.machine_readable/` authoritative. -- All machine-readable content lives under `.machine_readable/` — state files (a2ml), bot directives, and contractiles. -- Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. - -## Workflow -1. Inspect `.machine_readable/descriptiles/STATE.a2ml` for blockers and next actions. -2. Respect any constraints listed inside `.machine_readable/descriptiles/AGENTIC.a2ml` when tooling changes are requested. -3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. - -## Delivery Promises -- Mention in summaries whether STATE, `.machine_readable/contractiles/`, or `.machine_readable/bot_directives/` changed. -- Keep this file in sync with the repository's status; update it if the governance changes. - -[foundational-integrations] -# These tools form the architectural floor for all hyperpolymath repos. -# Integrate them incrementally as the project matures. - -proven = "Formally verified safety library — replace raw string/JSON/URL/crypto ops" -panic-attacker = "Static analysis — run `just assail` before every commit" -feedback-o-tron = "Autonomous bug filing — auto-report upstream dependency failures" -verisimdb = "Cross-repo analytics — emit scan results and build metrics" -vexometer = "Irritation surface analysis — measure friction in CLI/UI tools" -hypatia = "Neurosymbolic CI/CD — safety-triangle routing for automated fixes" -boj-server = "MCP gateway — all external service integrations route through BoJ" - -[floor-raise-checklist] -step-1 = "Ensure the repo deed (_chora.deed) exists at repo root" -step-2 = "Ensure Mustfile.a2ml + Trustfile.a2ml + Dustfile.a2ml in .machine_readable/contractiles/" -step-3 = "Add `assail` recipe to Justfile and wire into pre-commit hook" -step-4 = "Add proven integration reference in .machine_readable/integrations/" -step-5 = "Add verisimdb feed configuration" -step-6 = "Add feedback-o-tron integration for upstream reporting" -step-7 = "Add vexometer hooks for friction measurement (CLI/UI repos)" diff --git a/czech-file-knife/.machine_readable/ai/README.adoc b/czech-file-knife/.machine_readable/ai/README.adoc deleted file mode 100644 index a5dec5991..000000000 --- a/czech-file-knife/.machine_readable/ai/README.adoc +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AI Guidance Directory - -Put AI-facing instructions in this folder. - -Examples: - -* `CLAUDE.md` -* `COPILOT.md` -* `GEMINI.md` -* `AI.a2ml` -* `AI.djot` - -Avoid scattering agent instruction files around the repo root. - -Recommended machine read order: - -* `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` -* `.machine_readable/policies/MAINTENANCE-AXES.a2ml` -* `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` -* `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -* `.machine_readable/descriptiles/STATE.a2ml` -* `.machine_readable/descriptiles/META.a2ml` diff --git a/czech-file-knife/.machine_readable/arrival-pack/README.adoc b/czech-file-knife/.machine_readable/arrival-pack/README.adoc deleted file mode 100644 index a48f9fd9f..000000000 --- a/czech-file-knife/.machine_readable/arrival-pack/README.adoc +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Arrival Pack — the CLAUDE.md compiler - -`CLAUDE.md` is the one file an agent auto-loads on arrival, so it is this repo's -*boundary document*. It is compiled, not hand-written, so it cannot drift from -the repo's own machine-readable truth. - -== How it works - -[source] ----- -a2ml (descriptiles) extract.sh arrival-pack.ncl CLAUDE.md -CLADE/ECOSYSTEM/ ──▶ (a2ml → JSON, ──▶ (Nickel projection ──▶ region between -AGENTIC/STATE/ANCHOR the thin reader) = the engine) BEGIN/END markers ----- - -Two halves of the generated region: - -* *Estate-common* — identical in every repo; the Manifesto doctrine + format-family - orientation + canon pointers + language policy. Pinned to a Manifesto SHA once - the wording is ratified (today: `DRAFT-unratified`). -* *Repo-specific* — projected from this repo's a2ml: identity, IS / IS-NOT, - position, constraints, golden path, state. - -The a2ml files remain the single source of truth. CLAUDE.md is a *view*. Content -outside the `ARRIVAL-PACK:BEGIN/END` markers is hand-authorable and preserved. - -== Files - -[cols="1,3"] -|=== -| `extract.sh` | a2ml reader → deterministic `claude-md-data.json` (build artifact, git-ignored) -| `arrival-pack.ncl` | Nickel projection engine → the region string (`nickel export --format raw`) -| `generate.sh` | orchestrator: extract → render → splice (Hunt-tier, writes CLAUDE.md) -| `verify.sh` | drift check: regenerate + byte-compare committed region (Yard-tier) -| `claude-md.k9.ncl` | k9 contract for the drift check -|=== - -== Usage - -[source,console] ----- -just claude-md # (re)generate CLAUDE.md from a2ml -just validate-claude-md # fail if the committed region drifted / was hand-edited ----- - -== Status / TODO - -* Estate-common doctrine is a *DRAFT* in the agent's words — owner to ratify into - his voice, then pin to a `hyperpolymath/manifesto` commit SHA. -* `ECOSYSTEM.what-this-is-not` is empty here, so IS-NOT renders as "not yet - declared". Populating it is the boundary-erosion fix (the `lith` lesson). -* `ANCHOR.a2ml` currently encodes semantic-authority + golden-path; the - recognised-drift / re-anchor-ledger aspect is a pending standards change. diff --git a/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl b/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl deleted file mode 100644 index 032499051..000000000 --- a/czech-file-knife/.machine_readable/arrival-pack/arrival-pack.ncl +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# arrival-pack.ncl — the projection ENGINE for the CLAUDE.md arrival pack. -# -# Pure Nickel. Imports the deterministic JSON produced by extract.sh (the a2ml -# reader) and renders the generated CLAUDE.md region as a single string. -# Emit with: nickel export --format raw --file arrival-pack.ncl -# -# Two halves (see ARRIVAL-PACK-DESIGN): -# A. estate-common — identical everywhere; DRAFT doctrine, pinned to a -# Manifesto SHA once ratified (today: DRAFT-unratified). -# B. repo-specific — projected from this repo's a2ml (data.*). -# -# The a2ml files remain the single source of truth; this is a VIEW. Hand-editing -# the emitted region is a flagged error (see claude-md.k9.ncl drift check). -let data = import "claude-md-data.json" in - -# Pin to a Manifesto commit once the doctrine wording is ratified into the -# owner's voice. Until then the estate-common block is explicitly DRAFT. -let manifesto_pin = "DRAFT-unratified" in - -let provenance = "CLADE@%{data.h_clade} ECOSYSTEM@%{data.h_eco} AGENTIC@%{data.h_agentic} STATE@%{data.h_state} ANCHOR@%{data.h_anchor}" -in - -m%" - - -# You are in the hyperpolymath estate — orient before acting - -If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. - -## Doctrine (the rules here) -1. **Holes before anything else** — fix soundness holes before features/perf/docs. -2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. -3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. -4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. -5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. -6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. -7. **No deletion by access-recency** — cold ≠ disposable. -8. **Wire first** — unwired is not done. -9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). -10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). -11. **Stop-first** when an action is costly to undo or outward-facing. -12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. -13. **Equivalence as identity** — the estate's intellectual through-line. -14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. -15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. - -## The machine-readable substrate (read in this order on arrival) -**CLADE** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** - -The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. - -| File | Answers | -|---|---| -| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | -| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | -| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | -| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | -| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | -| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | -| `PLAYBOOK.a2ml` | *How* permitted actions run. | -| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | -| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | -| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | - -## Canon pointers -- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. -- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** - -## Estate language policy (overridable per-repo via AGENTIC) -Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) -JavaScript tooling order: **Bun** (default) > Deno (grandfathered) > pnpm > npm (last resort, permitted). -Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. - ---- - -# This repo: `%{data.canonical_name}` · clade `%{data.prefixed_name}` - -- **Identity** — uuid `%{data.uuid}`; clade `%{data.clade_primary}` (secondary `%{data.clade_secondary}`); born %{data.born}; forge `%{data.forge_gh}`. -- **IS** — %{data.purpose} -- **IS-NOT** — %{data.isnot} -- **Where it sits** — pipeline position **%{data.pipeline_pos}**; chain `%{data.chain}`; coordination = `%{data.coordination}`. -- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. -- **Golden path** (ANCHOR) — `%{data.golden_smoke}` → %{data.golden_crit}. -- **State** — phase %{data.phase}; maturity %{data.maturity}; %{data.completion}% complete; status %{data.status}. - - -"% diff --git a/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl b/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl deleted file mode 100644 index d3fbb1cff..000000000 --- a/czech-file-knife/.machine_readable/arrival-pack/claude-md.k9.ncl +++ /dev/null @@ -1,59 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# claude-md.k9.ncl — k9 contract for the CLAUDE.md arrival-pack drift check. -# -# Yard-tier (pure evaluation / comparison; no exec, no network, no FS write): -# the check regenerates the arrival-pack region from this repo's a2ml and asserts -# the committed CLAUDE.md region byte-matches it. The runnable side is verify.sh; -# writing (Hunt-tier) is generate.sh. This record documents and validates the -# contract shape. -{ - pedigree = { - schema_version = "1.0.0", - contractile_verb = "trust", # provenance: the view must equal its source - semantics = "projection-fidelity", - security = { - leash = 'Yard, - trust_level = "read-only comparison", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = true, # runs extract.sh + nickel to reproduce the view - }, - metadata = { - name = "claude-md-arrival-pack", - version = "1.0.0", - description = "CLAUDE.md arrival pack is a faithful projection of this repo's a2ml.", - paired_runner = "verify.sh", - writer = "generate.sh", - author = "Jonathan D.A. Jewell ", - }, - }, - - # Inputs the projection reads (single source of truth). - inputs = { - descriptiles = [ - ".machine_readable/descriptiles/CLADE.a2ml", - ".machine_readable/descriptiles/ECOSYSTEM.a2ml", - ".machine_readable/descriptiles/AGENTIC.a2ml", - ".machine_readable/descriptiles/STATE.a2ml", - ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", - ], - estate_common = "hyperpolymath/manifesto@", # DRAFT-unratified today - target = "CLAUDE.md", - markers = { begin = "" - echo "" - echo "" - echo - cat "$AP/.region.tmp" - } > "$TARGET" -fi - -rm -f "$AP/.region.tmp" -echo "claude-md: wrote $TARGET" diff --git a/czech-file-knife/.machine_readable/arrival-pack/verify.sh b/czech-file-knife/.machine_readable/arrival-pack/verify.sh deleted file mode 100755 index 1d90acf32..000000000 --- a/czech-file-knife/.machine_readable/arrival-pack/verify.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# verify.sh — drift check for the CLAUDE.md arrival pack (the runnable side of -# claude-md.k9.ncl). Regenerates the region from a2ml and byte-compares it to the -# committed region. Non-zero exit on drift or hand-edit. Wire into CI/pre-commit. -set -euo pipefail - -ROOT="$(git rev-parse --show-toplevel)" -AP="$ROOT/.machine_readable/arrival-pack" -TARGET="$ROOT/CLAUDE.md" - -[ -f "$TARGET" ] || { echo "DRIFT: CLAUDE.md missing — run \`just claude-md\`"; exit 1; } - -bash "$AP/extract.sh" "$ROOT/.machine_readable/descriptiles" > "$AP/claude-md-data.json" -fresh="$(nickel export --format raw "$AP/arrival-pack.ncl")" -committed="$(awk '/\n -""" -# Template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ -{%- macro remote_url() -%} - https://github.com/hyperpolymath/czech-file-knife -{%- endmacro -%} - -{% if version -%} - ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} -{% else -%} - ## [Unreleased] -{% endif -%} - -{% for group, commits in commits | group_by(attribute="group") %} - ### {{ group | striptags | trim }} - {% for commit in commits %} - - {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\ - {% if commit.breaking %}[**BREAKING**] {% endif %}\ - {{ commit.message | upper_first }}\ - {%- if commit.links %} \ - ({% for link in commit.links %}[{{ link.text }}]({{ link.href }}){% endfor %}){% endif -%} - {% endfor %} -{% endfor %} - -{%- if github -%} -{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} - ### New Contributors -{%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} - * @{{ contributor.username }} made their first contribution - {%- if contributor.pr_number %} in \ - [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) - {%- endif %} -{%- endfor %} -{% endif -%} -{% endif -%} - -""" -# Template for the changelog footer -footer = """ -{%- macro remote_url() -%} - https://github.com/hyperpolymath/czech-file-knife -{%- endmacro -%} - -{% for release in releases -%} - {% if release.version -%} - {% if release.previous.version -%} - [{{ release.version | trim_start_matches(pat="v") }}]: \ - {{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }} - {% endif -%} - {% else -%} - {% if release.previous.version -%} - [Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD - {% endif -%} - {% endif -%} -{% endfor %} - -""" -# Remove leading and trailing whitespace from templates -trim = true - -[git] -# Parse conventional commits -# https://www.conventionalcommits.org -conventional_commits = true -# Filter out unconventional commits -filter_unconventional = true -# Process each line of a commit as an individual commit -split_commits = false -# Regex for commit preprocessing -commit_preprocessors = [ - # Remove issue numbers from commit messages - { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, -] -# Regex for parsing and grouping commits -commit_parsers = [ - { message = "^feat", group = "Features" }, - { message = "^fix", group = "Bug Fixes" }, - { message = "^security", group = "Security" }, - { message = "^perf", group = "Performance" }, - { message = "^refactor", group = "Refactoring" }, - { message = "^docs", group = "Documentation" }, - { message = "^style", group = "Styling" }, - { message = "^test", group = "Testing" }, - { message = "^ci", group = "CI/CD" }, - { message = "^chore\\(release\\)", skip = true }, - { message = "^chore\\(deps.*\\)", skip = true }, - { message = "^chore\\(pr\\)", skip = true }, - { message = "^chore", group = "Miscellaneous" }, - { body = ".*security", group = "Security" }, -] -# Protect breaking changes from being skipped by a commit parser -protect_breaking_commits = false -# Filter out merge commits -filter_merge_commits = true -# Filter out commits by tag pattern (skip pre-releases) -# tag_pattern = "v[0-9].*" -# Regex for skipping tags -# skip_tags = "beta|alpha" -# Sort commits within each group by oldest first -sort_commits = "oldest" diff --git a/czech-file-knife/.machine_readable/configs/stapeln.toml b/czech-file-knife/.machine_readable/configs/stapeln.toml deleted file mode 100644 index 73ed2bdcc..000000000 --- a/czech-file-knife/.machine_readable/configs/stapeln.toml +++ /dev/null @@ -1,87 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# stapeln.toml — Layer-based container build for czech-file-knife -# -# stapeln builds containers as composable layers (German: "to stack"). -# Each layer is independently cacheable, verifiable, and signable. - -[metadata] -name = "czech-file-knife" -version = "0.1.0" -description = "czech-file-knife container service" -author = "Jonathan D.A. Jewell " -license = "MPL-2.0" -registry = "ghcr.io/hyperpolymath" - -[build] -containerfile = "Containerfile" -context = "." -runtime = "podman" - -# ── Layer Definitions ────────────────────────────────────────── - -[layers.base] -description = "Chainguard Wolfi minimal base" -from = "cgr.dev/chainguard/wolfi-base:latest" -cache = true -verify = true - -[layers.toolchain] -description = "Build tools" -extends = "base" -packages = [] -cache = true - -[layers.build] -description = "czech-file-knife build" -extends = "toolchain" -commands = [] - -[layers.runtime] -description = "Minimal runtime" -from = "cgr.dev/chainguard/wolfi-base:latest" -packages = ["ca-certificates", "curl"] -copy-from = [ - { layer = "build", src = "/app/", dst = "/app/" }, -] -entrypoint = ["/app/czech-file-knife"] -user = "nonroot" - -# ── Security ─────────────────────────────────────────────────── - -[security] -non-root = true -read-only-root = false -no-new-privileges = true -cap-drop = ["ALL"] -seccomp-profile = "default" - -[security.signing] -algorithm = "ML-DSA-87" -provider = "cerro-torre" - -[security.sbom] -format = "spdx-json" -output = "sbom.spdx.json" -include-deps = true - -# ── Verification ─────────────────────────────────────────────── - -[verify] -vordr = true -svalinn = true -scan-on-build = true -fail-on = ["critical", "high"] - -# ── Targets ──────────────────────────────────────────────────── - -[targets.development] -layers = ["base", "chainguard-toolchain", "build"] -env = { LOG_LEVEL = "debug" } - -[targets.production] -layers = ["runtime"] -env = { LOG_LEVEL = "info" } - -[targets.test] -layers = ["base", "chainguard-toolchain", "build"] -env = { LOG_LEVEL = "debug" } diff --git a/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml b/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml deleted file mode 100644 index 148ecfcb0..000000000 --- a/czech-file-knife/.machine_readable/contractiles/INDEX.a2ml +++ /dev/null @@ -1,133 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# INDEX.a2ml — Contractile Registry -# Author: Jonathan D.A. Jewell -# -# Machine-readable catalogue of all contractile verbs in this template set. -# Consumers (CI scripts, the contractile CLI, Hypatia rules) SHOULD read this -# file to discover available verbs rather than hard-coding the list. -# -# See: docs/CONTRACTILE-SPEC.adoc §Registry - ---- -id = "contractiles-registry" -version = "2.0.0" # 2.0.0 (2026-04-18): all 6 verbs on trident shape; verb set complete. -spec = "https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc" -last_updated = "2026-04-18" -base_schema = ".machine_readable/contractiles/_base.ncl" -meta_schema_status = "pending — see CONTRACTILE-SPEC §validator-meta-schema" - -## Verbs - -[[verbs]] -name = "adjust" -semantics = "drift tolerances + corrective actions" -trident = [ - "adjust/Adjustfile.a2ml", - "adjust/adjust.ncl", - "adjust/adjust.k9.ncl", -] -manifest = "adjust/adjust.manifest.a2ml" -status = "active" -tier = "Yard" -authority = "advisory" -gating = "advisory (continue-with-warnings)" -cardinality = "one per repo" -notes = "Fifth trident instance (2026-04-18). First (Yard, advisory) authority pattern. Specialises in cumulative-drift catchment — tolerance bands + trend tracking across sessions. auto_fix_when_available applies deterministic patches; advisory otherwise." - -[[verbs]] -name = "bust" -semantics = "hard-stop / expiry / must-not-run declarations" -trident = [ - "bust/Bustfile.a2ml", - "bust/bust.ncl", - "bust/bust.k9.ncl", -] -manifest = "bust/bust.manifest.a2ml" -status = "active" -tier = "Hunt-read-only" -authority = "blocking" -gating = "hard (exit-nonzero)" -cardinality = "one per repo" -notes = "Fourth trident instance (2026-04-18). Completes the blocking-authority triple (must + trust + bust). Specialises in deprecated-path-reintroduction catchment. Injects failures via declared probes and verifies recovery paths." - -[[verbs]] -name = "dust" -semantics = "rollback / recovery / deprecation / audit-trail preservation" -trident = [ - "dust/Dustfile.a2ml", - "dust/dust.ncl", - "dust/dust.k9.ncl", -] -manifest = "dust/dust.manifest.a2ml" -status = "active" -tier = "Yard" -authority = "advisory" -gating = "advisory (continue-with-warnings)" -cardinality = "one per repo" -notes = "Sixth and FINAL trident instance (2026-04-18) — completes the full verb set. Specialises in audit-trail preservation + rollback-path verification. Destructive actions gated behind --apply flag + per-item approval; dry-run default." - -[[verbs]] -name = "intend" -semantics = "north-star (commitments + aspirations)" -trident = [ - "intend/Intentfile.a2ml", - "intend/intend.ncl", - "intend/intend.k9.ncl", -] -manifest = "intend/intend.manifest.a2ml" -status = "active" -tier = "Hunt" -authority = "reporting" -gating = "non-gating (continue)" -cardinality = "one per repo" -notes = "First trident instance in the estate (2026-04-18). Reports progress toward committed next-actions AND lists horizon aspirations. Absorbed the deprecated `lust` verb 2026-04-18. Never blocks. Remaining 5 verbs still on file_pair shape until tridents are built." - -[[verbs]] -name = "k9" -semantics = "trust-tier templates (EXCEPTION to one-verbfile rule)" -file_pair = [ - "k9/template-hunt.k9.ncl", - "k9/template-kennel.k9.ncl", - "k9/template-yard.k9.ncl", -] -status = "exception" -gating = "not applicable" -notes = "k9 is service-automation meta-infrastructure, not a verb contractile. Three trust-tier templates (Kennel/Yard/Hunt). Does not have a Verbfile.a2ml. See CONTRACTILE-SPEC §k9-exception." - -# [[verbs]] lust REMOVED 2026-04-18 — name had unwanted associations; -# the horizon/aspiration semantics were always meant to live inside `intend` -# (the north-star verb). The [[wishes]] schema was absorbed into -# intend/Intentfile.a2ml. Any `lust/` dir found in an estate repo is drift -# and should be deleted. - -[[verbs]] -name = "must" -semantics = "invariant assertion — release-blocking" -trident = [ - "must/Mustfile.a2ml", - "must/must.ncl", - "must/must.k9.ncl", -] -manifest = "must/must.manifest.a2ml" -status = "active" -tier = "Hunt-read-only" -authority = "blocking" -gating = "hard (exit-nonzero)" -cardinality = "one per repo" -notes = "Third trident instance (2026-04-18). Completes the blocking-authority pair with trust: must = concrete + persistent invariants; trust = concrete + ephemeral transactions. Specialises in subtle invariant-erosion (tracking per-session trend; flagging silent regression). Single failure blocks merge. Simplest and most commonly populated verb." - -[[verbs]] -name = "trust" -semantics = "security + provenance + safe-hacking" -trident = [ - "trust/Trustfile.a2ml", - "trust/trust.ncl", - "trust/trust.k9.ncl", -] -manifest = "trust/trust.manifest.a2ml" -status = "active" -tier = "Hunt" -authority = "blocking" -gating = "hard (exit-nonzero)" -cardinality = "one per repo" -notes = "Second trident instance (2026-04-18). First (Hunt, blocking) verb — hard gate. Primary defense against threat-model misclassification (B1) and 'turn off the firewall' capability-collapse (C2). Inherits on_open negotiation+accountability+translation from intend.k9.ncl v2.0.0; adds threat_model_foregrounding + block_session_close_on_critical_drift." diff --git a/czech-file-knife/.machine_readable/contractiles/Justfile b/czech-file-knife/.machine_readable/contractiles/Justfile deleted file mode 100644 index 1dd3994b2..000000000 --- a/czech-file-knife/.machine_readable/contractiles/Justfile +++ /dev/null @@ -1,720 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# RSR Standard Justfile Template -# https://just.systems/man/en/ -# -# Copy this file to new projects and customize the placeholder values. -# -# Run `just` to see all available recipes -# Run `just cookbook` to generate docs/just-cookbook.adoc -# Run `just combinations` to see matrix recipe options - -set shell := ["bash", "-uc"] -set dotenv-load := true -set positional-arguments := true - -# Import auto-generated contractile recipes (must-check, trust-verify, etc.) -# Re-generate with: contractile gen-just -import? "build/contractile.just" - -# Project metadata — customize these -project := "czech-file-knife" -OWNER := "hyperpolymath" -REPO := "czech-file-knife" -version := "0.1.0" -tier := "infrastructure" # 1 | 2 | infrastructure - -# ═══════════════════════════════════════════════════════════════════════════════ -# DEFAULT & HELP -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show all available recipes with descriptions -default: - @just --list --unsorted - -# Show detailed help for a specific recipe -help recipe="": - #!/usr/bin/env bash - if [ -z "{{recipe}}" ]; then - just --list --unsorted - echo "" - echo "Usage: just help " - echo " just cookbook # Generate full documentation" - echo " just combinations # Show matrix recipes" - else - just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found" - fi - -# Show this project's info -info: - @echo "Project: czech_file_knife" - @echo "Version: {{version}}" - @echo "RSR Tier: {{tier}}" - @echo "Recipes: $(just --summary | wc -w)" - @[ -f ".machine_readable/descriptiles/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/descriptiles/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true - -# Run Invariant Path overlay tools for this repository -invariant-path *ARGS: - ./scripts/invariant-path.sh {{ARGS}} - -# ═══════════════════════════════════════════════════════════════════════════════ -# INIT — see build/just/repo-init.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/repo-init.just" - -# >>> container-module (three-tier: OCI · portable engine · stapeln) >>> -# Self-contained. Remove the entire block — this and the import — with `just no-container`. -import? "build/just/container.just" -# <<< container-module <<< - -# ═══════════════════════════════════════════════════════════════════════════════ -# GROOVE PROTOCOL — see build/just/groove.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/groove.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE — see build/just/assess.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/assess.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# BUILD & COMPILE -# ═══════════════════════════════════════════════════════════════════════════════ - -# Build the project (debug mode) -build *args: - @echo "Building czech_file_knife (debug)..." - # TODO: Replace with your build command - # Examples: - # cargo build {{args}} # Rust - # mix compile {{args}} # Elixir - # zig build {{args}} # Zig - # deno task build {{args}} # Deno/ - @echo "Build complete" - -# Build in release mode with optimizations -build-release *args: - @echo "Building czech_file_knife (release)..." - # TODO: Replace with your release build command - # Examples: - # cargo build --release {{args}} - # MIX_ENV=prod mix compile {{args}} - # zig build -Doptimize=ReleaseFast {{args}} - @echo "Release build complete" - -# Build and watch for changes (requires entr or similar) -build-watch: - @echo "Watching for changes..." - # TODO: Customize file patterns for your language - # Examples: - # find src -name '*.rs' | entr -c just build - # mix compile --force --warnings-as-errors - # deno task dev - -# Clean build artifacts [reversible: rebuild with `just build`] -clean: - @echo "Cleaning..." - # TODO: Customize for your build system - # - # `build/` is DELIBERATELY ABSENT from this list. It is not an artifact - # directory in an RSR repo: it holds 11 tracked files, including - # build/just/repo-init.just, which the root Justfile imports at line 65. - # Deleting it destroys `just repo-init`, `just verify` and the proof gates. - rm -rf target/ _build/ dist/ out/ obj/ bin/ - -# Deep clean including caches [reversible: rebuild] -clean-all: clean - rm -rf .cache .tmp - -# ═══════════════════════════════════════════════════════════════════════════════ -# TEST & QUALITY -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run all tests -test *args: - #!/usr/bin/env bash - # A check that cannot fail is not a check. This recipe MUST be replaced at - # mint with the project's real test command; until then it fails loudly - # rather than printing "Tests passed!" over an empty run. - # - # Replace this whole body with one of: - # cargo test --workspace {{args}} - # mix test {{args}} - # zig build test {{args}} - # deno test {{args}} - echo "FAIL: \`just test\` has not been wired to a real test command yet." >&2 - echo " Edit the 'test' recipe in the Justfile before relying on this gate." >&2 - exit 1 - -# Run tests with verbose output -test-verbose: - @echo "Running tests (verbose)..." - # TODO: Replace with verbose test command - -# Smoke test -test-smoke: - @echo "Smoke test..." - # TODO: Add basic sanity checks - -# Run end-to-end tests (full pipeline: build → run → verify) -e2e: - @echo "Running E2E tests..." - # TODO: Replace with your E2E test command. Examples: - # bash tests/e2e.sh # Shell-based E2E - # npx playwright test # Browser E2E - # mix test test/integration/e2e_test.exs # Elixir E2E - # cargo test --test end_to_end # Rust E2E - @echo "E2E tests passed!" - -# Run aspect tests (cross-cutting concern validation) -aspect: - @echo "Running aspect tests..." - # TODO: Replace with your aspect test command. Examples: - # bash tests/aspect_tests.sh # Shell-based aspect tests - # cargo test --test aspects # Rust aspect tests - # Aspect tests validate architectural invariants: - # - Thread safety (mutex in FFI modules) - # - ABI/FFI contract (declarations match exports) - # - SPDX compliance (all files have license headers) - # - No dangerous patterns (believe_me, assert_total, etc.) - @echo "Aspect tests passed!" - -# Run benchmarks (performance regression detection) -bench: - @echo "Running benchmarks..." - # TODO: Replace with your benchmark command. Examples: - # cargo bench # Rust criterion - # zig build bench # Zig benchmarks - # mix run bench/benchmarks.exs # Elixir benchee - # deno bench # Deno bench - @echo "Benchmarks complete!" - -# Run readiness tests (Component Readiness Grade: D/C/B) -readiness: - @echo "Running readiness tests..." - # TODO: Replace with your readiness test command. Examples: - # cargo test --test readiness -- --nocapture - @echo "Readiness tests complete!" - -# Print the current CRG grade (reads from READINESS.md '**Current Grade:** X' line) -crg-grade: - @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ - [ -z "$$grade" ] && grade="X"; \ - echo "$$grade" - -# Print a shields.io CRG badge for embedding in README files -# Looks for '**Current Grade:** X' in READINESS.md; falls back to X -crg-badge: - @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' READINESS.md 2>/dev/null | head -1); \ - [ -z "$$grade" ] && grade="X"; \ - case "$$grade" in \ - A) color="brightgreen" ;; \ - B) color="green" ;; \ - C) color="yellow" ;; \ - D) color="orange" ;; \ - E) color="red" ;; \ - F) color="critical" ;; \ - *) color="lightgrey" ;; \ - esac; \ - echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" - -# Run the full merge-requirement test suite (ALL categories) -# Per STANDING rule: P2P + E2E + aspect + execution + lifecycle + bench -test-all: test e2e aspect bench readiness - @echo "All test categories passed — safe to merge!" - -# Run all quality checks -quality: fmt-check lint test - @echo "All quality checks passed!" - -# Fix all auto-fixable issues [reversible: git checkout] -fix: fmt - @echo "Fixed all auto-fixable issues" - -# ═══════════════════════════════════════════════════════════════════════════════ -# LINT & FORMAT -# ═══════════════════════════════════════════════════════════════════════════════ - -# Format all source files [reversible: git checkout] -fmt: - @echo "Formatting source files..." - # TODO: Replace with your formatter - # Examples: - # cargo fmt - # mix format - # gleam format - # deno fmt - -# Check formatting without changes -fmt-check: - @echo "Checking formatting..." - # TODO: Replace with your format check - # Examples: - # cargo fmt --check - # mix format --check-formatted - # gleam format --check - -# Run linter -lint: - @echo "Linting source files..." - # TODO: Replace with your linter - # Examples: - # cargo clippy -- -D warnings - # mix credo --strict - # gleam check - -# ═══════════════════════════════════════════════════════════════════════════════ -# RUN & EXECUTE -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run the application -run *args: build - # TODO: Replace with your run command - echo "Run not configured yet" - -# Run with verbose output -run-verbose *args: build - # TODO: Replace with verbose run command - echo "Run not configured yet" - -# Install to user path -install: build-release - @echo "Installing czech_file_knife..." - # TODO: Replace with your install command - -# ═══════════════════════════════════════════════════════════════════════════════ -# DEPENDENCIES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Install/check all dependencies -deps: - @echo "Checking dependencies..." - # TODO: Replace with your dependency check - # Examples: - # cargo check - # mix deps.get - # gleam deps download - @echo "All dependencies satisfied" - -# Audit dependencies for vulnerabilities -deps-audit: - @echo "Auditing for vulnerabilities..." - # TODO: Replace with your audit command - # Examples: - # cargo audit - # mix audit - @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true - @echo "Audit complete" - -# ═══════════════════════════════════════════════════════════════════════════════ -# ARRIVAL PACK — agent-facing CLAUDE.md, compiled from a2ml -# ═══════════════════════════════════════════════════════════════════════════════ - -# Compile CLAUDE.md (the agent arrival pack) from this repo's a2ml -claude-md: - @bash .machine_readable/arrival-pack/generate.sh - -# Regenerate the single authoritative repository map -repo-map: - @bash scripts/gen-repo-map.sh . - -# Fail if the repository map is stale (the map is generated; CI diffs it) -validate-repo-map: - #!/usr/bin/env bash - set -euo pipefail - cd "{{justfile_directory()}}" - before=$(mktemp); cp docs/architecture/REPOSITORY-MAP.adoc "$before" 2>/dev/null || true - bash scripts/gen-repo-map.sh . >/dev/null - if ! diff -q "$before" docs/architecture/REPOSITORY-MAP.adoc >/dev/null 2>&1; then - echo "FAIL: docs/architecture/REPOSITORY-MAP.adoc is stale. Run: just repo-map" >&2 - diff -u "$before" docs/architecture/REPOSITORY-MAP.adoc | head -40 >&2 || true - cp "$before" docs/architecture/REPOSITORY-MAP.adoc - rm -f "$before"; exit 1 - fi - rm -f "$before" - echo "repository map: up to date" - -# Fail if CLAUDE.md's generated region drifted from a2ml or was hand-edited -validate-claude-md: - @bash .machine_readable/arrival-pack/verify.sh - -# ═══════════════════════════════════════════════════════════════════════════════ -# COAPTATION — typed descriptile↔contractile face-off (homeostasis reading) -# ═══════════════════════════════════════════════════════════════════════════════ - -# Emit the coaptation receipt: how the descriptiles coapt with the contractiles (SITREP) -coapt: - @bash .machine_readable/coaptation/coapt.sh --report - -# Assemble a re-anchor basis IF the band is red (the drop itself is a human act) -coapt-reanchor: - @bash .machine_readable/coaptation/coapt.sh --reanchor - -# Fail if the committed coaptation receipt drifted from the contractiles/descriptiles -validate-coapt: - @bash .machine_readable/coaptation/verify.sh - -# ═══════════════════════════════════════════════════════════════════════════════ -# DOCUMENTATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Generate all documentation -docs: - @mkdir -p docs/generated docs/man - just cookbook - just man - @echo "Documentation generated in docs/" - -# Generate justfile cookbook documentation -cookbook: - #!/usr/bin/env bash - mkdir -p docs - OUTPUT="docs/just-cookbook.adoc" - echo "= czech_file_knife Justfile Cookbook" > "$OUTPUT" - echo ":toc: left" >> "$OUTPUT" - echo ":toclevels: 3" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "Generated: $(date -Iseconds)" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "== Recipes" >> "$OUTPUT" - echo "" >> "$OUTPUT" - just --list --unsorted | while read -r line; do - if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then - recipe="${BASH_REMATCH[1]}" - echo "=== $recipe" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "[source,bash]" >> "$OUTPUT" - echo "----" >> "$OUTPUT" - echo "just $recipe" >> "$OUTPUT" - echo "----" >> "$OUTPUT" - echo "" >> "$OUTPUT" - fi - done - echo "Generated: $OUTPUT" - -# Generate man page -man: - #!/usr/bin/env bash - mkdir -p docs/man - cat > docs/man/czech_file_knife.1 << EOF - .TH czech_file_knife 1 "$(date +%Y-%m-%d)" "{{version}}" "czech_file_knife Manual" - .SH NAME - czech_file_knife \- RSR-compliant project - .SH SYNOPSIS - .B just - [recipe] [args...] - .SH DESCRIPTION - RSR (Rhodium Standard Repository) project managed with just. - .SH AUTHOR - $(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")> - EOF - echo "Generated: docs/man/czech_file_knife.1" - -# ═══════════════════════════════════════════════════════════════════════════════ -# CI & AUTOMATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run full CI pipeline locally -# proof-check-all is FATAL if any prover toolchain is absent (idris2/lean/agda/coqc): -# the full CI gate must not pass on a machine that cannot verify the proofs. -ci: deps quality proof-check-all - @echo "CI pipeline complete!" - -# Install git hooks -install-hooks: - @mkdir -p .git/hooks - @cat > .git/hooks/pre-commit << 'HOOKEOF' - #!/bin/bash - just fmt-check || exit 1 - just lint || exit 1 - just assail || exit 1 - HOOKEOF - @chmod +x .git/hooks/pre-commit - @echo "Git hooks installed" - -# ═══════════════════════════════════════════════════════════════════════════════ -# SECURITY -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run security audit -security: deps-audit - @echo "=== Security Audit ===" - @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true - @echo "Security audit complete" - -# Generate SBOM -sbom: - @mkdir -p docs/security - @command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found" - -# ═══════════════════════════════════════════════════════════════════════════════ -# VALIDATION & COMPLIANCE — see build/just/validate.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/validate.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# STATE MANAGEMENT -# ═══════════════════════════════════════════════════════════════════════════════ - -# Update STATE.a2ml timestamp -state-touch: - @if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ - sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/descriptiles/STATE.a2ml && \ - echo "STATE.a2ml timestamp updated"; \ - fi - -# Show current phase from STATE.a2ml -state-phase: - @sed -n 's/^[[:space:]]*phase[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .machine_readable/descriptiles/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" - -# ═══════════════════════════════════════════════════════════════════════════════ -# GUIX -# ═══════════════════════════════════════════════════════════════════════════════ - -# Enter Guix development shell (primary) -guix-shell: - guix shell -D -f build/guix.scm - -# Build with Guix -guix-build: - guix build -f build/guix.scm - -# ═══════════════════════════════════════════════════════════════════════════════ -# HYBRID AUTOMATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run local automation tasks -automate task="all": - #!/usr/bin/env bash - case "{{task}}" in - all) just fmt && just lint && just test && just docs && just state-touch ;; - cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;; - update) just deps && just validate ;; - *) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;; - esac - -# ═══════════════════════════════════════════════════════════════════════════════ -# COMBINATORIC MATRIX RECIPES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Build matrix: [debug|release] x [target] x [features] -build-matrix mode="debug" target="" features="": - @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" - -# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel] -test-matrix suite="unit" verbosity="normal" parallel="true": - @echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}" - -# CI matrix: [lint|test|build|security|all] x [quick|full] -ci-matrix stage="all" depth="quick": - @echo "CI matrix: stage={{stage}} depth={{depth}}" - -# Show all matrix combinations -combinations: - @echo "=== Combinatoric Matrix Recipes ===" - @echo "" - @echo "Build Matrix: just build-matrix [debug|release] [target] [features]" - @echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]" - @echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag] (needs container module)" - @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" - -# ═══════════════════════════════════════════════════════════════════════════════ -# VERSION CONTROL -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show git status -status: - @git status --short - -# Show recent commits -log count="20": - @git log --oneline -{{count}} - -# Generate CHANGELOG.adoc with git-cliff -changelog: - @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } - # AsciiDoc, not .md: CHANGELOG.adoc is what root-allow.txt permits, so a - # .md here would fail check-root-shape AND the estate's no-.md rule the - # moment anyone ran this recipe. - git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.adoc - @echo "Generated CHANGELOG.adoc" - -# Preview changelog for unreleased commits (does not write) -changelog-preview: - @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } - git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header - -# Tag a new release (usage: just release-tag 1.2.3) -release-tag version: - #!/usr/bin/env bash - TAG="v{{version}}" - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists" - exit 1 - fi - just changelog - git add CHANGELOG.md - git commit -m "chore(release): prepare $TAG" - git tag -a "$TAG" -m "Release $TAG" - echo "Created tag $TAG — push with: git push origin main --tags" - -# ═══════════════════════════════════════════════════════════════════════════════ -# UTILITIES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Count lines of code -loc: - @find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0" - -# Show TODO comments -todos: - @grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs" - -# Open in editor -edit: - ${EDITOR:-code} . - -# Run high-rigor security assault using panic-attacker -maint-assault: - @./.machine_readable/scripts/maintenance/maint-assault.sh - -# Run panic-attacker pre-commit scan (foundational floor-raise requirement) -assail: - @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "WARN: panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" - - -# Self-diagnostic — checks dependencies, permissions, paths -doctor: - @echo "Running diagnostics for czech-file-knife..." - @echo "Checking required tools..." - @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" - @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" - @echo "Checking for hardcoded paths..." - @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" - @echo "Diagnostics complete." - -# Guided tour of key features -tour: - @echo "=== czech-file-knife Tour ===" - @echo "" - @echo "1. Project structure:" - @ls -la - @echo "" - @echo "2. Available commands: just --list" - @echo "" - @echo "3. Read README.adoc for full overview" - @echo "4. Read EXPLAINME.adoc for architecture decisions" - @echo "5. Run 'just doctor' to check your setup" - @echo "" - @echo "Tour complete! Try 'just --list' to see all available commands." - -# Open feedback channel with diagnostic context -help-me: - @echo "=== czech-file-knife Help ===" - @echo "Platform: $(uname -s) $(uname -m)" - @echo "Shell: $SHELL" - @echo "" - @echo "To report an issue:" - @echo " https://github.com/hyperpolymath/czech-file-knife/issues/new" - @echo "" - @echo "Include the output of 'just doctor' in your report." - -# ═══════════════════════════════════════════════════════════════════════════════ -# FORMAL VERIFICATION (PROOFS) — see build/just/proofs.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/proofs.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# SESSION MANAGEMENT (THIN BINDINGS TO CENTRAL STANDARDS) -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show canonical session-management command model -session-help: - @echo "Canonical command model:" - @echo " intake repo " - @echo " checkpoint change " - @echo " verify maintenance " - @echo " verify substantial " - @echo " verify release " - @echo " close planned " - @echo " close urgent " - @echo " recover repo " - @echo " handover full " - @echo " handover split " - @echo " handover model " - @echo " handover human " - @echo "" - @echo "Use Just aliases below (thin wrappers around ./session/dispatch.sh)." - -# Canonical aliases (friendly recipe names that map to canonical commands) -intake-repo path=".": - @./session/dispatch.sh intake repo "{{path}}" - -checkpoint-change path=".": - @./session/dispatch.sh checkpoint change "{{path}}" - -verify-maintenance path=".": - @./session/dispatch.sh verify maintenance "{{path}}" - -verify-substantial path=".": - @./session/dispatch.sh verify substantial "{{path}}" - -verify-release path=".": - @./session/dispatch.sh verify release "{{path}}" - -close-planned path=".": - @./session/dispatch.sh close planned "{{path}}" - -close-urgent path=".": - @./session/dispatch.sh close urgent "{{path}}" - -recover-repo path=".": - @./session/dispatch.sh recover repo "{{path}}" - -handover-full path=".": - @./session/dispatch.sh handover full "{{path}}" - -handover-split path=".": - @./session/dispatch.sh handover split "{{path}}" - -handover-model path=".": - @./session/dispatch.sh handover model "{{path}}" - -handover-human path=".": - @./session/dispatch.sh handover human "{{path}}" - -secret-scan-trufflehog: - @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true - -# ═══════════════════════════════════════════════════════════════════════════════ -# WINDOWS RGONOMICS (CLOAKING) -# ═══════════════════════════════════════════════════════════════════════════════╓ -# Hide all dotfiles and dot-folders from Windows Explorer. On POSIX systems, -# leading-dot names are already hidden by convention, so these recipes are -# intentionally harmless no-ops. -cloak: - #!/usr/bin/env bash - set -euo pipefail - if command -v powershell.exe >/dev/null 2>&1; then - powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -bor [System.IO.FileAttributes]::Hidden }" - echo "Cloak engaged." - else - echo "Dotfiles are natively cloaked on this OS. No action required." - fi - -# Reveal dotfiles in Windows Explorer; on POSIX, explain the native mechanism. -uncloak: - #!/usr/bin/env bash - set -euo pipefail - if command -v powershell.exe >/dev/null 2>&1; then - powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -band -bnot [System.IO.FileAttributes]::Hidden }" - echo "Cloak lifted." - else - echo "Use 'ls -a' to view dotfiles on this OS." - fi diff --git a/czech-file-knife/.machine_readable/contractiles/README.adoc b/czech-file-knife/.machine_readable/contractiles/README.adoc deleted file mode 100644 index fc0cb742d..000000000 --- a/czech-file-knife/.machine_readable/contractiles/README.adoc +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Contractiles Template Set -:toc: -:sectnums: - -This directory contains the canonical contractile templates for the -hyperpolymath estate. `czech-file-knife` mirrors the standards master -structure; copy `.machine_readable/contractiles/` into a new repo to -establish a consistent operational, validation, trust, recovery, -aspiration, and service-automation framework. - -[IMPORTANT] -==== -The normative grammar for the record family used throughout this directory -is `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in -https://github.com/hyperpolymath/standards[hyperpolymath/standards]. The -format formerly called A2ML is now *DEED* (`.deed`); files here still carry -`.a2ml` pending a single atomic estate-wide rename, so do not hand-convert -them. Verb-file naming is tracked separately. - -The "contractile CLI" has never been built -- enforcement currently rides on -the CI gates, not on that tool. -==== - -Each verb directory holds a *trident* of files: - -* `file.a2ml` — the project-specific declaration (data) -* `.ncl` — the paired Nickel runner (pedigree + schema + run policy) -* `.k9.ncl` — the k9 service-automation component (trust-tiered) - -A per-verb `.manifest.a2ml` asserts that exactly these three files -constitute the trident, pins their content-hashes, and requires the -cross-references to round-trip — no partial publication is permitted. - -Anything else in a verb directory is human-only notes or archive; machines -ignore it. Filenames use lowercase verb in the `.ncl` name and noun-form -PascalCase in the A2ML (e.g. `intend.ncl` + `Intentfile.a2ml`, -`must.ncl` + `Mustfile.a2ml`). - -All verb runners import `_base.ncl` (shared pedigree + run-defaults + -probe-schema). The `INDEX.a2ml` registry catalogues every verb; consumers -(CI scripts, the contractile CLI, Hypatia rules) SHOULD read it to discover -available verbs rather than hard-coding the list. See -https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc[the normative specification]. - -== Verbs (6 + k9 exception) - -[cols="1,2,3", options="header"] -|=== -| Verb | A2ML file | Role - -| `must` -| `must/Mustfile.a2ml` -| Release-blocking invariants that must hold. Gating — fails block. - -| `trust` -| `trust/Trustfile.a2ml` -| Trust boundary, allowed actions, integrity checks. Gating. - -| `adjust` -| `adjust/Adjustfile.a2ml` -| Controlled corrective actions — bounded drift tolerances and responses. - -| `dust` -| `dust/Dustfile.a2ml` -| Rollback, recovery, and deprecation semantics. Report + act on undo. - -| `bust` -| `bust/Bustfile.a2ml` -| Breakage, expiry, and hard-stop conditions. Gating — declares "this is - broken" rather than "this must stay healthy". - -| `intend` -| `intend/Intentfile.a2ml` -| North-star: committed next-actions (\[[intents]] with probes) AND horizon - aspirations (\[[wishes]] grouped near/mid/far). Non-gating (report only). - Absorbed the deprecated `lust` verb 2026-04-18. -|=== - -NOTE: The `lust/` verb was deprecated 2026-04-18 (name had unwanted -associations). Its \[[wishes]] semantics live inside `intend/Intentfile.a2ml` -as a second section alongside \[[intents]]. Any `lust/` dir encountered in -an estate repo is drift and should be removed. - -== k9 — Service-Automation Layer (EXCEPTION to the one-verbfile rule) - -IMPORTANT: `k9/` is **not a contractile verb** and does NOT follow the -`file.a2ml` + `.ncl` pattern. This is an intentional, documented -exception. Do not apply the naming rule to k9. - -=== Why k9 is different - -The six verb contractiles each declare *one concern per repo* in a single -xfile. k9 is not a concern; it is the *graded automation surface* that -enforces or validates concern declarations. k9 provides three trust-tier -*templates* that repos copy and instantiate: - -[cols="1,1,3", options="header"] -|=== -| File | Trust tier | Description - -| `k9/template-kennel.k9.ncl` -| Kennel -| Pure data. No subprocess, no filesystem write, no network. Safe for - metadata and declarative settings. - -| `k9/template-yard.k9.ncl` -| Yard -| Nickel evaluation with contracts and validation. No side effects. - -| `k9/template-hunt.k9.ncl` -| Hunt -| Full execution surface. Must declare side effects, support dry-run, and - be signed before the estate treats it as trustworthy automation. -|=== - -Each verb's `.k9.ncl` instantiates one of these tiers (the runners in -this set import `../k9/template-hunt.k9.ncl`). - -=== Why the naming rule does not apply - -The one-verb-one-Verbfile rule exists to enforce clean concern separation. -k9 is meta-infrastructure: it does not have a `K9file.a2ml` because it is -not a declarative xfile — it is a template set that instantiates into -specific repos. Applying the rule would produce a meaningless `K9file.a2ml` -with nothing to declare. - -=== Audit rule - -If a repo claims `k9` enforcement, each k9 component in that repo MUST -declare a `paired_xfile` pointing to a specific contractile xfile (e.g. -`../must/Mustfile.a2ml`). Floating k9 components with no paired xfile are -non-conformant. - -See https://github.com/hyperpolymath/standards/blob/main/docs/CONTRACTILE-SPEC.adoc#k9-exception[the normative statement]. - -== Fill-In Instructions - -When copying this set into a new repo: - -1. Replace every template file's placeholders with project-specific content. -2. `Mustfile` — encode real invariants (schema versions, ports, required - checks), not generic samples. -3. `Trustfile` — point at actual keys, policies, and authority boundaries. -4. `Adjustfile` — define the drift tolerances and corrective actions the - repo commits to. -5. `Dustfile` — describe how this repo actually rolls back or retires - behaviour while preserving the audit trail. -6. `Bustfile` — declare real breakage / expiry / hard-stop conditions. -7. `Intentfile` — list tracked next-actions with observable probes - (\[[intents]] section) AND horizon aspirations (\[[wishes]] section). -8. Pair any `k9/*.k9.ncl` with a specific contractile via `paired_xfile`. - -== Intentfile: Commitments vs Aspirations — Two Sections, One File - -Since 2026-04-18 both axes live inside `intend/Intentfile.a2ml`: - -* `\[[intents]]` is the **commitment axis**. Items here are tracked - next-actions with probes. Status progresses - declared → in_progress → done/deferred/retired. -* `\[[wishes]]` is the **aspiration axis**. Items here are horizon goals - grouped near/mid/far. Status progresses - declared → in_progress → achieved/abandoned. - -If something is concrete enough to have a probe, it belongs in `\[[intents]]`. -If it is a horizon-level desire that might never be acted on, it belongs -in `\[[wishes]]`. A wish can graduate to an intent when a concrete plan -materialises. diff --git a/czech-file-knife/.machine_readable/contractiles/_base.ncl b/czech-file-knife/.machine_readable/contractiles/_base.ncl deleted file mode 100644 index b30f0aa51..000000000 --- a/czech-file-knife/.machine_readable/contractiles/_base.ncl +++ /dev/null @@ -1,140 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# _base.ncl — Shared contractile base -# -# Provides four named schema fragments imported by every verb runner: -# -# pedigree_schema — canonical pedigree block shape -# status_core_doc — documentation of the shared status trio (String list) -# probe_schema — target structured probe form (spec only; verb files -# still use probe | String with TODO comments) -# run_defaults — default runner behaviour -# -# Usage in a verb runner: -# -# let base = import "../_base.ncl" in -# { -# pedigree = base.pedigree_schema & { -# contractile_verb = "must", -# semantics = "invariant", -# security = { -# leash = 'Kennel, -# trust_level = "read-only verification", -# allow_network = false, -# allow_filesystem_write = false, -# allow_subprocess = true, -# }, -# metadata = { -# name = "must-runner", -# version = "1.0.0", -# description = "...", -# paired_xfile = "Mustfile.a2ml", -# author = "Jonathan D.A. Jewell ", -# }, -# }, -# schema = { ... }, -# run = base.run_defaults & { on_any_fail = "exit-nonzero" }, -# } -# -# See: docs/CONTRACTILE-SPEC.adoc §Shared Base -{ - # ------------------------------------------------------------------------- - # pedigree_schema - # - # The canonical shape of the `pedigree` block required in every verb runner. - # Verb runners merge this with their verb-specific values using Nickel's `&` - # (right-priority merge). Override contractile_verb, semantics, security.*, - # and metadata.* in each verb. - # ------------------------------------------------------------------------- - pedigree_schema = { - schema_version | String | default = "1.0.0", - contractile_verb | String | default = "UNSET", # MUST override in verb - semantics | String | default = "UNSET", # MUST override in verb - security = { - leash | [| 'Kennel, 'Yard, 'Hunt |] | default = 'Kennel, - trust_level | String | default = "UNSET", # MUST override in verb - allow_network | Bool | default = false, - allow_filesystem_write | Bool | default = false, - allow_subprocess | Bool | default = true, - # verb-specific additional security fields go in the verb's merge override: - # e.g. authorised_probes_only (trust), injection_scope (bust), - # destructive_mode_requires_flag (dust) - }, - metadata = { - name | String | default = "UNSET", # MUST override in verb - version | String | default = "1.0.0", - description | String | default = "UNSET", # MUST override in verb - paired_xfile | String | default = "UNSET", # MUST override in verb - author | String | default = "Jonathan D.A. Jewell ", - }, - }, - - # ------------------------------------------------------------------------- - # status_core_doc - # - # Documents the minimum shared status values present in every verb's status - # enum: declared, verified, failing. - # - # Nickel does not support structural enum extension, so verb files reproduce - # their full enum verbatim in `schema`. This field serves as documentation - # and for tooling that introspects the base. - # - # Verbs that extend status_core (i.e. all except must + trust): - # adjust: + 'partial - # bust: + 'drilled - # dust: 'declared, 'proposed, 'approved, 'removed (non-standard) - # intend: intents: 'declared, 'in_progress, 'done, 'deferred, 'retired - # wishes: 'declared, 'in_progress, 'achieved, 'abandoned - # (the wishes schema was absorbed from the deprecated `lust` - # verb 2026-04-18; lust/ dir removed estate-wide) - # - # See: docs/CONTRACTILE-SPEC.adoc §Per-Verb Extension - # ------------------------------------------------------------------------- - status_core_doc = "status_core values: declared | verified | failing — extended per verb", - - # ------------------------------------------------------------------------- - # probe_schema - # - # The TARGET structured probe form. See: docs/CONTRACTILE-SPEC.adoc §Probe - # - # IMPORTANT: This is a spec-only definition. Existing verb runner files still - # use `probe | String` with a `# TODO: migrate to probe_schema` comment. - # This is a breaking change; migration happens when the CLI supports both - # forms. - # - # Adopters writing new xfiles should prefer the structured form: - # probe = { - # command = "test -f my-file", - # timeout_seconds = 60, - # allowed_exit_codes = [0], - # permission_class = 'read_only, - # } - # ------------------------------------------------------------------------- - probe_schema = { - command | String, - timeout_seconds | Number | default = 300, - allowed_exit_codes | Array Number | default = [0], - permission_class - | [| 'read_only, 'filesystem_write, 'subprocess, 'network |] - | default - = 'read_only, - }, - - # ------------------------------------------------------------------------- - # run_defaults - # - # Default runner behaviour. Verb runners merge this with verb-specific - # overrides using Nickel's `&` (right-priority merge). - # - # Most verbs override on_any_fail: - # "exit-nonzero" : hard gate (must, trust, bust, adjust-gating) - # "continue-with-warnings": advisory (dust, adjust) - # "continue" : never gate (intend — covers both intents and wishes) - # ------------------------------------------------------------------------- - run_defaults = { - on_pass = "continue", - on_any_fail = "exit-nonzero", - report_format = "a2ml", - emit_summary = true, - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml deleted file mode 100644 index 952d923bc..000000000 --- a/czech-file-knife/.machine_readable/contractiles/adjust/Adjustfile.a2ml +++ /dev/null @@ -1,72 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Adjustfile — Drift-tolerance contract for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# Cumulative-drift catchment: tolerance bands + corrective actions. -# Authority: advisory (Yard) — continue-with-warnings; auto_fix where deterministic. -# Run with: adjust check -# Fix with: adjust fix (applies deterministic patches; advisory otherwise) - -@abstract: -Drift tolerances and corrective actions for czech-file-knife. Unlike -MUST (hard gate), ADJUST tracks cumulative drift against tolerance bands -and proposes corrective actions. Advisory — it warns and trends, it does -not block. -@end - -## Template Drift - -### placeholder-drift -- description: Template placeholders should be replaced when copied -- tolerance: 0 placeholder markers in copied repos -- corrective: Search and replace all {{PLACEHOLDER}} markers -- severity: advisory -- notes: This check only applies to repos that copied from this template - -### template-version-drift -- description: Template version should match RSR spec version -- tolerance: Template version matches current RSR spec -- corrective: Update template to match latest RSR spec -- severity: advisory - -## Documentation Drift - -### readme-completeness -- description: README should document all template features -- tolerance: README covers all contractiles and directory structure -- corrective: Update README.adoc with missing sections -- severity: advisory - -### example-accuracy -- description: Examples in documentation should match actual template content -- tolerance: All code examples in docs are accurate -- corrective: Audit and fix examples in documentation -- severity: advisory - -## Structural Drift - -### contractile-sync -- description: All contractiles should have matching a2ml and ncl implementations -- tolerance: Every .a2ml has a corresponding .ncl -- corrective: Generate missing .ncl files from .a2ml -- severity: advisory - -### no-broken-symlinks -- description: No broken symbolic links in template structure -- tolerance: 0 broken symlinks -- corrective: Run symlink-check script -- severity: advisory - -## Accessibility Drift - -### adoc-not-md -- description: Template docs should prefer AsciiDoc -- tolerance: New prose docs are *.adoc -- corrective: Convert any new *.md to *.adoc -- severity: advisory - -### spdx-header-consistency -- description: All template files have correct SPDX headers -- tolerance: 0 files missing SPDX-License-Identifier -- corrective: Add SPDX headers to files that need them -- severity: advisory diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl deleted file mode 100644 index 4974ba7f8..000000000 --- a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.k9.ncl +++ /dev/null @@ -1,167 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# adjust.k9.ncl — K9 trust-tier component of the adjust trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Adjustfile.a2ml (declaration) + adjust.ncl (runner). -# -# Verb: adjust (drift tolerances + corrective actions) -# Tier: Yard (validation with subprocess for measurement; -# no mutation beyond auto-fix where declared) -# Authority: advisory (continue-with-warnings; not blocking) -# -# adjust is the tolerance-band verb. Where must says "this MUST hold" -# and trust says "this MUST verify clean" (both binary), adjust says -# "drift ≤ X is acceptable; drift > X triggers action Y". Between them, -# adjust handles the subtle-drift territory that binary verbs can't. -# -# Cardinality: ONE adjust trident per repo. -# -# Failure-mode focus: adjust catches cumulative-small-drift patterns -# (E2 cosmetic churn accumulating into real regression, F2 context -# erosion causing gradual parameter drift). Where must flags "broken -# now", adjust flags "drifting toward broken". - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "adjust", - paired_xfile = "../adjust/Adjustfile.a2ml", - paired_runner = "../adjust/adjust.ncl", - - tier = 'Yard, - authority = 'advisory, - - metadata = { - name = "adjust-k9", - version = "1.0.0", - description = "Drift-tolerance + corrective-action runner. Fifth trident instance. First (Yard, advisory) authority pattern.", - paired_xfile = "Adjustfile.a2ml", - paired_runner = "adjust.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Yard, - trust_level = "tolerance measurement + declared auto-fix", - allow_network = false, - allow_filesystem_write_conditional = true, # auto_fix_when_available may edit - allow_subprocess = true, - probe_scope = 'measurement_plus_declared_fix, - }, - }, - - variance_schema = { - entry_id | String, - reason | String, - approved_by | String, - scope | String, - expires | String, - review_notes | String | optional, - # adjust-specific: which tolerance band the variance widens - tolerance_band_widened | String, - widened_to_value | String, - }, - - execution = { - triggers = [ 'session_close, 'on_demand, 'pre_push ], - - per_tolerance = { - measure_drift = true, - record_outcome = true, - respect_variance = true, - # adjust-specific authority: tolerance exceeded → warn, try - # auto-fix if declared, then continue. Never blocks. - on_exceeded = 'warn_and_attempt_fix, - on_auto_fix_applied = 'record_and_continue, - on_auto_fix_unavailable = 'record_as_advisory_drift, - # Cumulative-drift detection (adjust's specialty) - track_drift_trend_over_sessions = true, - flag_accelerating_drift = true, - }, - - evidence_sinks = [ - { kind = 'verisimdb, table = "contractile_executions", - schema = "contractile_execution_v1", - aux_tables = [ "adjust_drift_history" ] }, - { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true }, - ], - - on_close = { - re_measure_all_tolerances = true, - diff_against_last_ratification = true, - emit_drift_entries_for_tolerance_exceeded = true, - surface_expired_variances = true, - surface_accelerating_drift = true, - # adjust is advisory — does NOT block session close. - block_session_close_on_any_drift = false, - }, - - on_open = { - render_summary = 'plain_language, - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - include_tolerance_trend_summary = true, - - negotiation = { - required = true, - ai_required_inputs = [ - 'timeline_realism, - 'industry_standards, - 'audience_feasibility, - 'resulting_invariants, - 'ecosystem_dependencies, - ], - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - }, - }, - - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the tolerance bands and corrective actions. I accept accountability for reviewing drift warnings rather than muting them, and for re-tuning tolerances via amendment when the intended operating envelope changes.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will surface tolerance breaches and accelerating-drift patterns at session close; I will propose corrective actions rather than widening tolerances silently; I will require amendment for legitimate tolerance re-tuning, not quiet band-widening.", - signature_required = true, - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - includes_tolerance_bands_snapshot = true, - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, - }, - }, - }, - - failure_mode_defenses = [ - 'A1_enthusiasm_capture, - 'C3_helpfulness_inflation, # helpful additions surfaced if they widen tolerances silently - 'C4_modernization_drift, - 'E2_cosmetic_churn, # adjust tracks cumulative churn - 'F2_context_window_erosion, # parameter drift across sessions detected - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml deleted file mode 100644 index 0e108b64b..000000000 --- a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.manifest.a2ml +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# adjust.manifest.a2ml — Trident coherence manifest for the adjust verb. -# Author: Jonathan D.A. Jewell -# -# Fifth trident instance. First (Yard, advisory) authority pattern — -# complements the (Hunt, blocking) triple (must + trust + bust) and -# the (Hunt, reporting) north-star (intend). - ---- -trident_version = "1.0.0" -verb = "adjust" -semantics = "drift tolerances + corrective actions" -cardinality = "one per repo" -authority = "advisory (continue-with-warnings)" - -[[files]] -role = "declaration" -path = "Adjustfile.a2ml" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "runner" -path = "adjust.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "k9_component" -path = "adjust.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[cross_refs] -runner_paired_xfile = "Adjustfile.a2ml" -k9_paired_xfile = "../adjust/Adjustfile.a2ml" -k9_paired_runner = "../adjust/adjust.ncl" - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "adjust trident — canonical template in czech-file-knife. (Yard, advisory) authority pattern. Specialises in cumulative-drift catchment — tolerance bands + trend tracking + auto-fix-where-declared. Advisory (continue-with-warnings). Copy this trident into a new repo and define its drift tolerances and corrective actions." - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "Adjustfile.a2ml and adjust.ncl pre-existed. This manifest + adjust.k9.ncl complete the trident. Exercises the Yard tier + advisory authority for the first time; on_exceeded = 'warn_and_attempt_fix rather than 'fail. adjust-specific track_drift_trend_over_sessions + flag_accelerating_drift." diff --git a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl b/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl deleted file mode 100644 index f0f074323..000000000 --- a/czech-file-knife/.machine_readable/contractiles/adjust/adjust.ncl +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Adjust — accessibility runner -# -# Pairs with: Adjustfile.a2ml (same directory) -# Verb: adjust -# Semantics: accessibility compliance (WCAG 2.1 AA baseline). Gating where -# a deterministic fix exists; advisory where human review needed. -# CLI: `contractile adjust check` → run all probes, list violations -# `contractile adjust fix` → apply deterministic fixes where defined -# -# Anything else in this directory is human-only notes/archive; machines ignore. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "adjust", - semantics = "accessibility compliance", - security = { - leash = 'Kennel, - trust_level = "fixes allowed where deterministic", - allow_network = false, - allow_filesystem_write = true, # `adjust fix` may write (deterministic patches only) - allow_subprocess = true, - }, - metadata = { - name = "adjust-runner", - version = "1.0.0", - description = "Evaluates accessibility requirements from Adjustfile.a2ml. Fixes deterministic items; flags the rest for human review.", - paired_xfile = "Adjustfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - schema = { - requirements - | Array { - id | String, - description | String, - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String, - # status_core values: 'declared, 'verified, 'failing; adjust adds 'partial - status | [| 'declared, 'partial, 'verified, 'failing |] | default = 'declared, - compliance | String | optional, # e.g. "WCAG 2.1 AA" - notes | String | optional, - fix | String | optional, # deterministic fix command (optional) - }, - }, - - # Runner behaviour — inherits from base.run_defaults. - # adjust is advisory (continue-with-warnings) not a hard gate. - # auto_fix_when_available is adjust-specific. - run = - base.run_defaults - & { - on_any_fail = "continue-with-warnings", # accessibility is progress-tracked, not a hard gate by default - report_format = "a2ml", - emit_summary = true, - auto_fix_when_available = true, - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml deleted file mode 100644 index 8da753f6c..000000000 --- a/czech-file-knife/.machine_readable/contractiles/bust/Bustfile.a2ml +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Bustfile — failure mode contractile for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# Paired runner: bust.ncl -# Verb: bust -# Semantics: Every declared failure mode must have a working recovery path -# that has been exercised. Status moves: -# declared → drilled (probe run) → verified (recovery confirmed) -# or → failing (recovery broken) -# -# CLI: -# contractile bust check → list failure modes + recovery status -# contractile bust drill → inject failures, verify recovery paths -# -# This repository: czech-file-knife is the canonical template for RSR compliance. -# Failure modes here relate to template distribution and substitution. - -@abstract: -Bustfile for czech-file-knife. Lists failure modes specific to the template -repository itself, particularly around template distribution, substitution, -and synchronization across the hyperpolymath estate. -@end - -## Failure Modes - -### template-substitution-failure -- class: template_processing -- description: Template substitution fails when initializing a new repo from this template -- injection_probe: "cp -r czech-file-knife test-repo && cd test-repo && sed -i 's/czech-file-knife/TEST/g' .machine_readable/contractiles/Intentfile.a2ml && grep -q 'TEST' .machine_readable/contractiles/Intentfile.a2ml" -- recovery_probe: "git -C test-repo diff --quiet .machine_readable/contractiles/Intentfile.a2ml" -- expected_recovery_time_seconds: 10 -- status: declared -- notes: Verify that substitution scripts handle all placeholder replacements correctly - -### sync-drift-between-repos -- class: synchronization -- description: Drift occurs between czech-file-knife and other repos after template updates -- injection_probe: "echo 'template_updated' > /tmp/test_drift_marker" -- recovery_probe: "test -f /tmp/test_drift_marker && rm /tmp/test_drift_marker" -- expected_recovery_time_seconds: 60 -- status: declared -- notes: The estate-wide sync scripts (see scripts/) should prevent this; verify with scripts/verify-sync.sh - -### contractile-parse-error -- class: contractile_format -- description: A contractile file fails to parse due to syntax errors -- injection_probe: "echo 'invalid syntax' >> czech-file-knife/.machine_readable/contractiles/Intentfile.a2ml" -- recovery_probe: "git checkout czech-file-knife/.machine_readable/contractiles/Intentfile.a2ml" -- expected_recovery_time_seconds: 5 -- status: declared -- notes: All .a2ml files should be valid A2ML; use a2ml-validate runner diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl deleted file mode 100644 index a5e5e73d9..000000000 --- a/czech-file-knife/.machine_readable/contractiles/bust/bust.k9.ncl +++ /dev/null @@ -1,162 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# bust.k9.ncl — K9 trust-tier component of the bust trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Bustfile.a2ml (declaration) + bust.ncl (runner). -# -# Verb: bust (hard-stop / expiry / "must-not-run") -# Tier: Hunt-read-only (subprocess probes for expiry/state checks) -# Authority: blocking (HARD GATE on declared broken states) -# -# bust is the "this is broken, this has expired, this must not run" -# declarative surface. Where must asserts invariants that must hold, -# bust asserts failure states that must not be re-entered. Complement -# to must: together they bound the "acceptable operating state" from -# above (must) and below (bust). -# -# Cardinality: ONE bust trident per repo. -# -# Failure-mode focus: bust catches deprecated-path-still-called -# patterns (C2 capability collapse attempts where an AI reintroduces -# retired code), expiry-exceeded state (certificates / tokens / grants -# past their expiry), and the "it works, ship it" pattern where a -# caller silently starts using a must-not-run API. - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "bust", - paired_xfile = "../bust/Bustfile.a2ml", - paired_runner = "../bust/bust.ncl", - - tier = 'Hunt, - authority = 'blocking, - - metadata = { - name = "bust-k9", - version = "1.0.0", - description = "Hard-stop / expiry / must-not-run gate. Fourth trident instance. Completes the blocking-authority triple (must + trust + bust).", - paired_xfile = "Bustfile.a2ml", - paired_runner = "bust.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Hunt, - signature_required = true, - trust_level = "read-only expiry + state-check with subprocess", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = true, - probe_scope = 'read_only, - }, - }, - - variance_schema = { - entry_id | String, - reason | String, - approved_by | String, - scope | String, - expires | String, - review_notes | String | optional, - severity_acknowledged | [| 'critical, 'high, 'medium |], - waived_consequence_description | String, - }, - - execution = { - triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_merge ], - - per_hard_stop = { - run_probe = true, - record_outcome = true, - respect_variance = true, - on_triggered = 'fail, # BLOCKING — bust condition hit = block - severity_escalation = 'honour, - # bust-specific: detect re-introduction of deprecated calls - flag_deprecated_reintroduction = true, - }, - - evidence_sinks = [ - { kind = 'verisimdb, table = "contractile_executions", - schema = "contractile_execution_v1", - aux_tables = [ "bust_triggers_history" ] }, - { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true }, - ], - - on_close = { - re_execute_all_hard_stops = true, - diff_against_last_ratification = true, - emit_drift_entries_for_new_triggers = true, - surface_expired_variances = true, - block_session_close_on_critical_bust = true, - }, - - on_open = { - render_summary = 'plain_language, - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - include_silent_regressions = true, - - negotiation = { - required = true, - ai_required_inputs = [ - 'timeline_realism, - 'industry_standards, - 'audience_feasibility, - 'resulting_invariants, - 'ecosystem_dependencies, - ], - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - }, - }, - - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the declared hard-stop / expiry / must-not-run conditions. I accept accountability for not calling into must-not-run code paths, not ignoring expired tokens/grants, and not silently reintroducing deprecated patterns. I will raise a variance with severity acknowledgement if an exception is needed.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will refuse suggestions that reintroduce must-not-run patterns; I will surface bust triggers at session close; I will require variance-with-severity for any legitimate reintroduction of a deprecated path rather than silently allowing it.", - signature_required = true, - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, - }, - }, - }, - - failure_mode_defenses = [ - 'A1_enthusiasm_capture, - 'C2_capability_collapse, # prevents reintroduction of retired capability - 'C4_modernization_drift, # bust prevents silent re-adoption of deprecated libs - 'D4_error_hiding, - 'E1_refactor_stampede, # refactor that reintroduces deprecated path caught - 'F1_across_session_forgetting, # bust triggers persist across sessions - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml deleted file mode 100644 index 87cf79381..000000000 --- a/czech-file-knife/.machine_readable/contractiles/bust/bust.manifest.a2ml +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# bust.manifest.a2ml — Trident coherence manifest for the bust verb. -# Author: Jonathan D.A. Jewell -# -# Fourth trident instance. Completes the blocking-authority triple: -# must (persistent invariants), trust (ephemeral transactions), -# bust (hard-stop / expiry / must-not-run). Between them, every -# release-blocking contractile concern is covered. - ---- -trident_version = "1.0.0" -verb = "bust" -semantics = "hard-stop / expiry / must-not-run declarations" -cardinality = "one per repo" -authority = "blocking (hard gate)" - -[[files]] -role = "declaration" -path = "Bustfile.a2ml" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "runner" -path = "bust.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "k9_component" -path = "bust.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[cross_refs] -runner_paired_xfile = "Bustfile.a2ml" -k9_paired_xfile = "../bust/Bustfile.a2ml" -k9_paired_runner = "../bust/bust.ncl" - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "bust trident — canonical template in czech-file-knife. Completes the blocking-authority triple (must + trust + bust). Specialises in deprecated-path-reintroduction catchment. Declares hard-stop / expiry / must-not-run conditions. Copy this trident into a new repo and declare its real breakage / expiry conditions." - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "Bustfile.a2ml and bust.ncl pre-existed. This manifest + bust.k9.ncl complete the trident. Inherits the full negotiation+accountability schema from intend.k9.ncl v2.0.0 + trust/must extensions; adds flag_deprecated_reintroduction for C2-defense specificity." diff --git a/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl b/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl deleted file mode 100644 index f273168ff..000000000 --- a/czech-file-knife/.machine_readable/contractiles/bust/bust.ncl +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Bust — error-handling / failure-recovery runner -# -# Pairs with: Bustfile.a2ml (same directory) -# Verb: bust -# Semantics: every declared failure mode must have a recovery path that has -# been exercised. Runner injects failures (via declared probes) -# and verifies the recovery path works. Hard gate on any -# failure-mode with missing or broken recovery. -# CLI: `contractile bust check` → list failure modes + recovery status -# `contractile bust drill` → inject declared failures, verify recovery -# -# Anything else in this directory is human-only notes/archive; machines ignore. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "bust", - semantics = "error handling + failure recovery", - security = { - leash = 'Kennel, - trust_level = "controlled failure injection; scoped to system-under-test", - allow_network = false, - allow_filesystem_write = true, # drills may write transient state (tmp dirs, test DBs) - allow_subprocess = true, - injection_scope = "system-under-test-only", - }, - metadata = { - name = "bust-runner", - version = "1.0.0", - description = "Exercises declared failure modes and verifies recovery paths. Hard-gates on any failure mode without working recovery.", - paired_xfile = "Bustfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - schema = { - failure_modes - | Array { - id | String, - description | String, - class | [| 'network, 'disk_full, 'oom, 'timeout, 'partial_write, 'panic, 'crash, 'rollback, 'concurrency |], - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - injection_probe | String, # command that deterministically causes this failure - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - recovery_probe | String, # command that verifies recovery (exit 0 = recovered) - expected_recovery_time_seconds | Number | default = 30, - # status_core values: 'declared, 'verified, 'failing; bust adds 'drilled - status | [| 'declared, 'drilled, 'verified, 'failing |] | default = 'declared, - notes | String | optional, - }, - }, - - # Runner behaviour — inherits from base.run_defaults. - # bust adds record_recovery_times for performance tier feeding. - run = - base.run_defaults - & { - on_any_fail = "exit-nonzero", # missing or broken recovery blocks merge - report_format = "a2ml", - emit_summary = true, - record_recovery_times = true, # feeds the performance tier - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml deleted file mode 100644 index 8f2af126a..000000000 --- a/czech-file-knife/.machine_readable/contractiles/dust/Dustfile.a2ml +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dustfile — Cleanup and hygiene contract for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# Paired runner: dust.ncl -# Verb: dust -# Semantics: What should be cleaned up. Housekeeping, not blockers. -# -# This repository: czech-file-knife is the canonical template. -# Cleanup items here ensure the template itself remains pristine. - -@abstract: -Cleanup and hygiene items for czech-file-knife. These are maintenance tasks -that ensure the template repository remains clean and ready for distribution -to new repositories. -@end - -## Stale Files - -### no-template-artifacts -- description: No generated files from template testing in root -- run: test -z "$(ls template-test-* 2>/dev/null)" -- severity: info -- notes: Template testing should use /tmp or dedicated test directories - -### no-example-placeholders -- description: No example placeholder files (EXAMPLE-, SAMPLE-) in contractiles/ -- run: test -z "$(find .machine_readable/contractiles/ -name 'EXAMPLE-*' -o -name 'SAMPLE-*' 2>/dev/null)" -- severity: warning -- notes: All placeholders should be replaced with actual content or removed - -### no-old-contractile-formats -- description: No old .contractile or .hs files remaining -- run: test -z "$(find .machine_readable/contractiles/ \( -name '*.contractile' -o -name '*.hs' \) 2>/dev/null)" -- severity: warning -- notes: All contractiles should be .a2ml format - -## Format Duplicates - -### justfile-imports-in-sync -- description: The root Justfile and its contractiles mirror declare the SAME import? set -- run: diff <(grep '^import?' Justfile) <(grep '^import?' .machine_readable/contractiles/Justfile) >/dev/null -- severity: warning -- notes: | - Replaces a no-duplicate-justfile rule that compared INODES to assert the two - files were hardlinked. That rule could never pass: git does not track - hardlinks, so the pair have separate inodes after any clone (measured: - 1026120 vs 1031824). Its premise was also false — these are not one file - seen twice but two deliberately different Justfiles, differing by 88 lines, - so swapping the inode test for `cmp` would merely exchange one - always-failing check for another. `stat -c` is GNU-only too, so it broke on - macOS regardless. - - What actually must hold is that the mirror declares the same import? set as - the root — that is the drift which silently breaks recipes, because `import?` - is the OPTIONAL form and fails quietly rather than erroring. Both currently - declare 7 imports and agree. - -### no-duplicate-readme-format -- description: Only one README format in contractiles/ (.adoc canonical) -- run: test ! -f .machine_readable/contractiles/README.md -- severity: info - -## Template Hygiene - -### no-stale-template-references -- description: No references to czech-file-knife in generic template files -- run: test -z "$(grep -r 'czech-file-knife' machine-readable-design/ 2>/dev/null)" -- severity: warning -- notes: Generic templates should use Czech File Knife or similar placeholders - -### version-sync-checked -- description: Version in canonical-directory-structure matches .machine_readable/contractiles -- verification: compare version identifiers in both locations -- severity: info diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl deleted file mode 100644 index 6c51e54f3..000000000 --- a/czech-file-knife/.machine_readable/contractiles/dust/dust.k9.ncl +++ /dev/null @@ -1,172 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# dust.k9.ncl — K9 trust-tier component of the dust trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Dustfile.a2ml (declaration) + dust.ncl (runner). -# -# Verb: dust (rollback / recovery / deprecation) -# Tier: Yard (audit + structural checks; destructive -# actions gated behind --apply flag + -# explicit per-item approval) -# Authority: advisory (continue-with-warnings) -# -# dust is the retirement + audit-trail verb. Rollback paths, deprecation -# markers, evidence-preservation semantics. Where bust declares -# "broken, don't run", dust declares "how to safely undo / retire / roll -# back". Complement to bust — bust marks the dead end, dust describes -# the exit ramp. -# -# Cardinality: ONE dust trident per repo. -# -# Failure-mode focus: dust is the audit-trail preservation verb — -# defends against E1 refactor stampede (check audit trail still -# intact) and against silent removal (anything removed must have a -# rollback path; anything retired must preserve the evidence of its -# previous existence). - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "dust", - paired_xfile = "../dust/Dustfile.a2ml", - paired_runner = "../dust/dust.ncl", - - tier = 'Yard, - authority = 'advisory, - - metadata = { - name = "dust-k9", - version = "1.0.0", - description = "Rollback + deprecation + audit-trail runner. Sixth trident instance — completes the full verb set.", - paired_xfile = "Dustfile.a2ml", - paired_runner = "dust.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Yard, - trust_level = "audit-trail verification + structural checks", - allow_network = false, - # dust is the verb that ACTUALLY wants filesystem write — to - # execute declared rollback/removal — but only behind explicit - # --apply flag + per-item approval. Default is dry-run. - allow_filesystem_write_conditional = true, - allow_subprocess = true, - destructive_action_gating = { - default_mode = 'dry_run, - requires_flag = "--apply", - requires_per_item_approval = true, - approval_mechanism = 'explicit_user_signature, - }, - }, - }, - - variance_schema = { - entry_id | String, - reason | String, - approved_by | String, - scope | String, - expires | String, - review_notes | String | optional, - rollback_path_preserved | Bool, # dust-specific: did the variance preserve rollback? - }, - - execution = { - triggers = [ 'session_close, 'on_demand ], - - per_retirement = { - verify_rollback_path_documented = true, - verify_audit_trail_preserved = true, - respect_variance = true, - on_rollback_path_missing = 'warn, # advisory, not block - on_audit_trail_broken = 'warn, # advisory, not block - # dust-specific: flag any retirement that has been requested but - # lacks proper rollback documentation - flag_unsafe_retirement = true, - }, - - evidence_sinks = [ - { kind = 'verisimdb, table = "contractile_executions", - schema = "contractile_execution_v1", - aux_tables = [ "dust_retirement_history" ] }, - { kind = 'drift_log, path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true }, - ], - - on_close = { - re_verify_all_retirement_paths = true, - diff_against_last_ratification = true, - emit_drift_entries_for_missing_rollback = true, - emit_drift_entries_for_broken_audit_trail = true, - surface_expired_variances = true, - block_session_close_on_any_drift = false, # advisory - }, - - on_open = { - render_summary = 'plain_language, - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - - negotiation = { - required = true, - ai_required_inputs = [ - 'timeline_realism, - 'industry_standards, - 'audience_feasibility, - 'resulting_invariants, - 'ecosystem_dependencies, - ], - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - }, - }, - - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the declared rollback paths and deprecation markers. I accept accountability for preserving audit trails when retiring code, and for ensuring every retired capability has a documented rollback path. I will not silently delete evidence of prior state.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will verify audit-trail preservation in any retirement / rollback / deprecation I perform; I will refuse silent deletion of prior-state evidence; I will require rollback-path documentation before accepting a retirement request; I will operate in dry-run mode by default and require explicit --apply + per-item approval for destructive actions.", - signature_required = true, - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - includes_retirement_schedule = true, - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, - }, - }, - }, - - failure_mode_defenses = [ - 'A1_enthusiasm_capture, - 'C2_capability_collapse, # retirement without rollback path = capability collapse - 'D5_sycophancy, # AI won't agree to silent deletion - 'E1_refactor_stampede, # audit trail preservation check - 'E2_cosmetic_churn, - 'F1_across_session_forgetting, # retirement history tracked cross-session - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml deleted file mode 100644 index 39355e5b3..000000000 --- a/czech-file-knife/.machine_readable/contractiles/dust/dust.manifest.a2ml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# dust.manifest.a2ml — Trident coherence manifest for the dust verb. -# Author: Jonathan D.A. Jewell -# -# Sixth + final trident instance. Completes the full verb set — -# the estate now has tridents for every contractile verb. - ---- -trident_version = "1.0.0" -verb = "dust" -semantics = "rollback / recovery / deprecation / audit-trail preservation" -cardinality = "one per repo" -authority = "advisory (continue-with-warnings)" - -[[files]] -role = "declaration" -path = "Dustfile.a2ml" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "runner" -path = "dust.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "k9_component" -path = "dust.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[cross_refs] -runner_paired_xfile = "Dustfile.a2ml" -k9_paired_xfile = "../dust/Dustfile.a2ml" -k9_paired_runner = "../dust/dust.ncl" - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "dust trident — canonical template in czech-file-knife. Specialises in audit-trail preservation + rollback-path verification. Yard tier with destructive-action gating (dry-run default; --apply + per-item approval required for mutations). Copy this trident into a new repo and describe how it actually rolls back or retires behaviour." - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "Dustfile.a2ml and dust.ncl pre-existed. This manifest + dust.k9.ncl complete the trident and the full verb set. All 6 verbs now on trident shape: intend (Hunt, reporting), trust (Hunt, blocking), must (Hunt-read-only, blocking), bust (Hunt-read-only, blocking), adjust (Yard, advisory), dust (Yard, advisory)." - -[[history]] -date = "2026-04-18" -event = "verb-set-complete" -note = "Full estate trident coverage. Blocking-authority triple (must/trust/bust) handles release-gating. Advisory pair (adjust/dust) handles drift-warning and audit-trail. Single reporting verb (intend) handles north-star. α two-axis surface fully exercised on all four (tier, authority) combinations used: (Hunt, reporting), (Hunt, blocking), (Hunt-read-only, blocking), (Yard, advisory). The contractile system is ready for the adversarial Gemini+Copilot drift pilot." diff --git a/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl b/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl deleted file mode 100644 index 8da0361af..000000000 --- a/czech-file-knife/.machine_readable/contractiles/dust/dust.ncl +++ /dev/null @@ -1,69 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dust — exnovation / code-removal runner -# -# Pairs with: Dustfile.a2ml (same directory) -# Verb: dust -# Semantics: exnovation. Identifies code, docs, files, dependencies that are -# candidates for REMOVAL. Advisory by default; can be flipped to -# active delete via `contractile dust sweep --apply`. -# CLI: `contractile dust find` → list removal candidates -# `contractile dust sweep` → dry-run removals -# `contractile dust sweep --apply` → actually delete (gated) -# -# Anything else in this directory is human-only notes/archive; machines ignore. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "dust", - semantics = "exnovation / removal", - security = { - leash = 'Kennel, - trust_level = "proposes deletion; --apply required to execute", - allow_network = false, - allow_filesystem_write = true, # --apply mode writes (deletes) - allow_subprocess = true, - destructive_mode_requires_flag = "--apply", - }, - metadata = { - name = "dust-runner", - version = "1.0.0", - description = "Identifies and optionally removes exnovation targets listed in Dustfile.a2ml. Destructive mode gated behind --apply.", - paired_xfile = "Dustfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - schema = { - removal_candidates - | Array { - id | String, - description | String, - target | String, # file / path / symbol / dep name - reason | String, # why it's a removal candidate - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String | optional, # command that confirms it's still removable - # dust has a non-standard status progression (no 'verified): - # 'declared → 'proposed → 'approved → 'removed - status | [| 'declared, 'proposed, 'approved, 'removed |] | default = 'declared, - approver | String | optional, # who signed off (for 'approved / 'removed) - notes | String | optional, - }, - }, - - # Runner behaviour — inherits from base.run_defaults. - # dust is advisory; apply_requires_approval is dust-specific. - run = - base.run_defaults - & { - on_any_fail = "continue-with-warnings", - report_format = "a2ml", - emit_summary = true, - apply_requires_approval = true, # only 'approved items get swept, even with --apply - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml b/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml deleted file mode 100644 index 079b52626..000000000 --- a/czech-file-knife/.machine_readable/contractiles/intend/Intentfile.a2ml +++ /dev/null @@ -1,99 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Intentfile (A2ML Canonical) — north-star contractile for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# Paired runner: intend.ncl -# Verb: intend -# -# Semantics: North-star contractile. Declares BOTH concrete committed -# next-actions AND horizon aspirations the project wishes to -# become. Two sections share one file because they answer -# the same question at different ranges: -# [[intents]] — "we WILL do this; track progress" -# status: declared → in_progress → done | -# deferred | retired -# [[wishes]] — "we WISH this were true; revisit later" -# status: declared → in_progress → achieved | -# abandoned -# grouped by horizon: near / mid / far. -# Non-gating — this is a report, not a gate. See the `must` -# contractile for hard gates. - -@abstract: -North-star contractile for czech-file-knife. This repository is the -canonical template for Rhodium Standard Repository compliance. It provides -the scaffold that all hyperpolymath repos should copy and customize. -@end - -## Purpose - -The czech-file-knife serves as the master template for all hyperpolymath -repositories. It contains the complete set of contractile files, machine-readable -specifications, and governance documentation that define the Rhodium Standard. - -Every new repository in the hyperpolymath estate should be initialized by -copying this template and substituting the placeholder values with -repo-specific content. - -## Anti-Purpose - -This repository is NOT: -- A general-purpose project scaffold for external use (hyperpolymath-only) -- A replacement for per-repo customization (all files must be bespoke) -- A static template that never changes (evolves with RSR spec) -- A runtime library or framework (build-time only) - -## If In Doubt - -If you are unsure whether a change is in scope, ask. Sensitive areas: -- .machine_readable/ contractile definitions -- RSR specification files -- Governance templates -- License policy documents - -## Committed Next-Actions - -### repo-initialization -- description: Provide just copy-and-substitute template for new repos -- probe: test -f scripts/init-repo.sh -- status: done -- notes: Run with source scripts/init-repo.sh - -### contractile-completeness -- description: Every RSR contractile has an a2ml and ncl implementation -- probe: ls .machine_readable/contractiles/*.a2ml | wc -l | grep -q "^6$" -- status: in_progress -- notes: Currently 6 contractile verbs: intend, must, trust, adjust, bust, dust - -### automation-scripts -- description: All repetitive tasks have just recipes -- probe: grep -c "^# " Justfile | grep -q "^[6-9][0-9]*$" -- status: in_progress - -## Wishes - -### Near Horizon - -#### cross-repo-validation -- description: Tooling to validate all repos against RSR spec -- horizon: near -- status: declared - -#### automated-substitution -- description: Script to automate repo-specific substitution in template -- horizon: near -- status: declared - -### Mid Horizon - -#### formal-verification -- description: Idris2 proofs for all critical contractile invariants -- horizon: mid -- status: declared - -### Far Horizon - -#### ecosystem-visualization -- description: Interactive graph of all hyperpolymath repos and dependencies -- horizon: far -- status: declared diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl b/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl deleted file mode 100644 index 4c5abc8c1..000000000 --- a/czech-file-knife/.machine_readable/contractiles/intend/intend.k9.ncl +++ /dev/null @@ -1,252 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# intend.k9.ncl — K9 trust-tier component of the intend trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Intentfile.a2ml (declaration) + intend.ncl (runner). -# Trident completeness is a hard precondition — a repo shipping -# Intentfile without this file AND its runner is an invalid trident; -# the contractile CLI's verify gate refuses partial publication. -# -# Verb: intend (north star — commitments + aspirations) -# Tier: Hunt (capability: subprocess probes may shell out) -# Authority: reporting (never blocks; drift-log only) -# -# Cardinality: ONE intend trident per repo (see feedback_contractile_ -# layout_rules.md). ANCHOR.a2ml is the sole multi-instance exception — -# it is NOT a verb contractile. -# -# Design commitments baked in (see memory trail 2026-04-18 for full -# context; key files referenced by name in annotations below): -# * α two-axis (tier × authority) — structurally separate capability -# from authority so "Hunt tier = can override" is impossible. -# * Variance schema first-class, not comment markers -# (feedback_audit_tool_suppression_design.md — structural > markers). -# * Sessional drift detection hooks (on_close, on_open). -# * Ratification at session open; drift log at session close. -# * Evidence sinks: VeriSimDB (queryable) + descriptiles/DRIFT.a2ml (repo-local). -# * Failure-mode defenses cross-referenced to the AI failure catalog. - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "intend", - paired_xfile = "../intend/Intentfile.a2ml", - paired_runner = "../intend/intend.ncl", - - # α two-axis declaration — capability × authority. - # intend is Hunt-capable (probes shell out) but reporting-authority - # (never blocks). must/trust/bust will declare (Hunt, blocking); - # adjust/dust will declare (Yard, advisory). Splitting the axes - # means "I'm Hunt-tier so I can override everything" is structurally - # impossible — authority is a separate field. - tier = 'Hunt, - authority = 'reporting, - - metadata = { - name = "intend-k9", - version = "2.0.0", # 1.0.0 (2026-04-18 AM): initial trident. - # 2.0.0 (2026-04-18 PM): negotiation + - # accountability + plain-language-translation - # schema baked into on_open — prerequisite for - # the adversarial Gemini+Copilot drift pilot. - description = "Executes Intentfile probes + emits drift log. Non-gating; reporting authority only. on_open hook implements negotiation-ratification-accountability protocol.", - paired_xfile = "Intentfile.a2ml", - paired_runner = "intend.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Hunt, - signature_required = true, - trust_level = "subprocess + filesystem-read", - allow_network = false, - allow_filesystem_write = false, # evidence sinks are indirected - allow_subprocess = true, - }, - }, - - # ------------------------------------------------------------------- - # Variance schema — P-shape scoped exceptions per entry. - # A variance suppresses a specific intent's or wish's obligation for a - # reason, with approver + expiry. Expired variance = effective - # re-imposition of the obligation. Unmet intent without a variance = - # drift, logged to the drift log. - # Per user 2026-04-18: variances are structural, not magic-comment - # markers — markers are gameable. - # ------------------------------------------------------------------- - variance_schema = { - entry_id | String, # which intent/wish id the variance applies to - reason | String, - approved_by | String, - scope | String, # path glob | session-id | "until-" - expires | String, # absolute date or condition - review_notes | String | optional, - }, - - # ------------------------------------------------------------------- - # Execution policy - # ------------------------------------------------------------------- - execution = { - # When the component runs. - # session_close is mandatory (the "picked up sessionally" check). - triggers = [ 'session_close, 'on_demand, 'pre_push ], - - # Per-intent execution. - per_intent = { - run_probe = true, - record_outcome = true, - respect_variance = true, # active variance suppresses failure - on_unmet = 'log_drift, # never 'fail — authority = reporting - }, - - # Per-wish execution (wishes are non-probeable; horizon-group only). - per_wish = { - run_probe = false, - emit_horizon_summary = true, - # Vertical alignment soft-check per user_descriptiles_is_contractile_ought.md: - # highest-level alignment is meta ↔ north-star (soft), not hard gate. - check_alignment_with_META = true, - }, - - # Evidence sinks — BOTH written, every execution. - # VeriSimDB = queryable machine record (feedback_verisimdb_policy.md). - # descriptiles/DRIFT.a2ml = repo-local append-only drift log (feedback_sessional_ - # drift_detection.md + user_descriptiles_is_contractile_ought.md descriptive role). - evidence_sinks = [ - { - kind = 'verisimdb, - table = "contractile_executions", - schema = "contractile_execution_v1", - }, - { - kind = 'drift_log, - path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true, - }, - ], - - # Session-close hook — the "picked up sessionally" requirement. - # Re-execute, diff against the last ratification, surface expired - # variances, emit drift entries for new failures. - on_close = { - re_execute_all_intents = true, - diff_against_last_ratification = true, - emit_drift_entries_for_new_failures = true, - surface_expired_variances = true, - }, - - # ----------------------------------------------------------------- - # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY - # (user_contract_negotiation_and_accountability_pledge.md) - # (user_contractiles_agreed_at_session_start.md) - # - # Ratification is not passive acknowledgement; it is negotiation - # ending in an explicit accountability pledge from BOTH parties. - # Work cannot proceed before both pledges are on file. - # ----------------------------------------------------------------- - on_open = { - # --- Context presentation (pre-negotiation) --- - render_summary = 'plain_language, # metaphor-capture defense - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - - # --- Negotiation phase (five mandatory inputs) --- - # AI must surface all five before the user is asked to ratify. - # "Yes, and …" — not "yes". Missing any of the five = the - # negotiation is incomplete and work cannot proceed. - negotiation = { - required = true, # blank-cheque ratification refused - - # The five inputs the AI must contribute to the negotiation. - # Each is a structured field the agent is required to populate, - # not optional prose. See user_contract_negotiation_and_ - # accountability_pledge.md for the domain-language-rendering rule. - ai_required_inputs = [ - 'timeline_realism, # "this will take X; not Y" - 'industry_standards, # WCAG, ISO, OWASP, GDPR, licensing … - 'audience_feasibility, # real addressable user set - 'resulting_invariants, # what must/trust/adjust entries follow - 'ecosystem_dependencies, # libs, licences, threat-model implications - ], - - # User must actually engage with each input — not - # auto-approve. If user tries to skip ("just do it, I trust you") - # the system re-renders the obligations and requires the pledge. - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - - # The AI does the specification-form work. The user reviews the - # rendering in domain language and accepts / amends / pushes back. - # User never has to author Nickel schemas or decide on type - # specificity — that is the AI's translation responsibility, - # with auditable faithfulness. - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - # Failure mode this closes: user is forced to learn spec-theory - # (type refinement, Nickel contract grammar) to ratify a contract - # — which drives users away from ratification entirely. - }, - }, - - # --- Accountability pledge (both parties, explicit) --- - # Not "I read it" — "I am answerable for this obligation". - # Both pledges are required before work proceeds; both are recorded. - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the obligations as negotiated; I accept accountability for meeting the declared invariants and for the audience/timeline/standards consequences surfaced in negotiation.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will hold the user to the obligations as negotiated, including by surfacing drift at session close and refusing off-contract actions, even when the user is enthusiastic about them. If the user wishes to depart from the contract, I will require a variance or amendment, not silent acceptance.", - signature_required = true, - # Per user_contractile_is_contract_do_not_break.md — - # the AI is the holder of the line against enthusiasm drift. - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - # --- Policy: ratification output --- - # The ratification record IS the negotiation transcript + the - # accountability pledge combined. One document; future-session - # ground-truth for "what was agreed, who is accountable". - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, # pins what was actually signed - }, - }, - }, - - # ------------------------------------------------------------------- - # Failure-mode defenses — explicit cross-reference to the catalog - # (feedback_ai_failure_mode_catalog.md). New catalog entries that - # shift this verb's defenses must update this list, not narrative. - # ------------------------------------------------------------------- - failure_mode_defenses = [ - 'A1_enthusiasm_capture, # scope breach → drift log - 'A2_metaphor_capture, # render_summary = 'plain_language - 'A3_allegory_drift, # intents cite concrete obligations - 'C1_scope_creep, # feature-adjacent change needs intent_id - 'C3_helpfulness_inflation, # changes without intent_id flagged - 'C4_modernization_drift, # upgrade cannot cite intent → drift - 'D5_sycophancy, # ratification compares user framing vs contract - 'F1_across_session_forgetting, # on_open reads last-ratification record - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml deleted file mode 100644 index f16ec1eec..000000000 --- a/czech-file-knife/.machine_readable/contractiles/intend/intend.manifest.a2ml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# intend.manifest.a2ml — Trident coherence manifest for the intend verb. -# Author: Jonathan D.A. Jewell -# -# Asserts: exactly three files constitute the intend trident; their -# content-hashes are pinned here; cross-references round-trip; no -# partial publication is permitted. -# -# The contractile CLI's `verify ` subcommand MUST: -# 1. Confirm all three listed files exist at the declared paths. -# 2. Compute each file's sha256 and match against the pinned value. -# 3. Follow each cross-reference and confirm the target file's -# reciprocal field points back. -# 4. Refuse the dir (exit non-zero) if any of 1–3 fails. -# -# This forecloses the failure mode where an agent publishes an A2ML -# declaration with no paired runner or K9 component — the trident is -# atomically complete or it is invalid. - ---- -trident_version = "1.0.0" -verb = "intend" -semantics = "north-star (commitments + aspirations)" -cardinality = "one per repo" - -## Files (three; exactly) - -[[files]] -role = "declaration" -path = "Intentfile.a2ml" -sha256 = "pending-first-verify" # populated on first `contractile verify intend` -size_bytes = "pending-first-verify" - -[[files]] -role = "runner" -path = "intend.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -[[files]] -role = "k9_component" -path = "intend.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" - -## Cross-references (must round-trip) - -[cross_refs] -# Each runner/K9 component names its paired files; the CLI follows the -# links and asserts reciprocity. Any dangling or mismatched reference -# fails the verify gate. -runner_paired_xfile = "Intentfile.a2ml" -k9_paired_xfile = "../intend/Intentfile.a2ml" -k9_paired_runner = "../intend/intend.ncl" - -## Trident signing - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "intend trident — canonical template in czech-file-knife. North-star verb: reports progress toward committed next-actions ([[intents]]) and lists horizon aspirations ([[wishes]]). Non-gating (reporting authority). Copy this trident into a new repo and replace the declaration with project-specific content." - -## Change log - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "intend/Intentfile.a2ml pre-existed (f380b62, lust absorption). This manifest + intend.k9.ncl complete the trident for the first time." - -[[history]] -date = "2026-04-18" -event = "negotiation-accountability-schema-landed" -note = "intend.k9.ncl on_open hook extended: five negotiation inputs (timeline/standards/audience/invariants/dependencies), both-parties accountability pledge, plain-language-translation policy (AI authors spec form, user reviews in domain language). K9 metadata version bumped 1.0.0 → 2.0.0. Prerequisite for the adversarial Gemini+Copilot drift pilot; intend is the hardest verb (abstract north-star) so baking the full protocol here first means simpler verbs (trust, must) can inherit the template." diff --git a/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl b/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl deleted file mode 100644 index 175b2b277..000000000 --- a/czech-file-knife/.machine_readable/contractiles/intend/intend.ncl +++ /dev/null @@ -1,84 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Intend — north-star runner (verb is `intend`, file is `Intentfile.a2ml`) -# -# Pairs with: Intentfile.a2ml (same directory) -# Verb: intend -# Semantics: Declares BOTH concrete committed next-actions ([[intents]]) and -# horizon aspirations ([[wishes]]). Not a gate — reports progress -# toward declared intents and lists wishes by horizon. -# Status progressions: -# intents: 'declared → 'in_progress → 'done | 'deferred | 'retired -# wishes: 'declared → 'in_progress → 'achieved | 'abandoned -# CLI: `contractile intend run` → print status table (both sections) -# `contractile intend progress` → diff declared-vs-observed (intents) -# `contractile intend horizon` → group wishes by near/mid/far -# -# History: Absorbed the deprecated `lust` contractile's [[wishes]] schema -# 2026-04-18. `lust/` dir removed estate-wide. -# -# Anything else in this directory is human-only notes/archive; machines ignore. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "intend", - semantics = "north-star (commitments + aspirations)", - security = { - leash = 'Kennel, - trust_level = "read-only reporting", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = true, # probe commands may shell out (intents only; wishes never probe) - }, - metadata = { - name = "intend-runner", - version = "2.0.0", - description = "Reports progress toward committed next-actions and lists horizon aspirations. Non-gating. Absorbed `lust` semantics 2026-04-18.", - paired_xfile = "Intentfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - schema = { - intents - | Array { - id | String, - description | String, - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String | optional, # shell command that indicates done-ness - status | [| 'declared, 'in_progress, 'done, 'deferred, 'retired |] | default = 'declared, - notes | String | optional, - target_date | String | optional, - }, - wishes - | Array { - id | String, - description | String, - horizon | [| 'near, 'mid, 'far |] | default = 'mid, - why | String | optional, - status | [| 'declared, 'in_progress, 'achieved, 'abandoned |] | default = 'declared, - notes | String | optional, - } - | optional, - }, - - # Runner behaviour — inherits from base.run_defaults. - # intend never blocks; it is a report only. - # emit_diff is intent-specific (declared vs observed probes). - # emit_grouped_by_horizon renders wishes grouped by near/mid/far. - run = - base.run_defaults - & { - on_pass = "continue", - on_any_fail = "continue", # never blocks; it's a report - report_format = "a2ml", - emit_summary = true, - emit_diff = true, # declared vs observed (intents) - emit_grouped_by_horizon = true, # wishes grouped by horizon (absorbed from lust) - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml deleted file mode 100644 index 3e0e3153f..000000000 --- a/czech-file-knife/.machine_readable/contractiles/must/Mustfile.a2ml +++ /dev/null @@ -1,125 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile — Physical state contract for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# What MUST be true about this repository. Hard requirements. -# Run with: must check -# Fix with: must fix (where a deterministic fix exists) - -@abstract: -Physical-state invariants for czech-file-knife. These are hard requirements — CI and pre-commit -hooks fail if any check fails. -@end - -## File Presence - -### license-present -- description: LICENSE file must exist -- run: test -f LICENSE -- severity: critical - -### readme-present -- description: README.adoc must exist -- run: test -f README.adoc -- severity: critical - -### security-policy -- description: SECURITY.md must exist -- run: test -f SECURITY.md || test -f .github/SECURITY.md -- severity: critical - -### repo-deed -- description: the repo deed (_chora.deed) must exist at root -- run: ls *_chora.deed >/dev/null 2>&1 -- severity: critical - -### governance-docs -- description: governance model, maintainer roster and CODEOWNERS must exist -- run: test -f docs/GOVERNANCE.adoc && test -f docs/MAINTAINERS.adoc && test -f .github/CODEOWNERS -- severity: critical - -### machine-readable-dir -- description: .machine_readable/ directory must exist -- run: test -d .machine_readable -- severity: critical - -## Directory Structure - -### contractiles-complete -- description: All required contractile directories exist -- run: test -d .machine_readable/contractiles && test -d .machine_readable/contractiles/bust && test -d .machine_readable/contractiles/dust -- severity: critical - -### contractiles-files-present -- description: All four primary contractile files exist -- run: test -f .machine_readable/contractiles/Intentfile.a2ml && test -f .machine_readable/contractiles/Mustfile.a2ml && test -f .machine_readable/contractiles/Trustfile.a2ml && test -f .machine_readable/contractiles/Adjustfile.a2ml -- severity: critical - -### bust-dust-files-present -- description: Bustfile and Dustfile exist in their directories -- run: test -f .machine_readable/contractiles/bust/Bustfile.a2ml && test -f .machine_readable/contractiles/dust/Dustfile.a2ml -- severity: critical - -### six-directory-present -- description: descriptiles directory exists with required files -- run: test -d .machine_readable/descriptiles && test -f .machine_readable/descriptiles/META.a2ml && test -f .machine_readable/descriptiles/ECOSYSTEM.a2ml && test -f .machine_readable/descriptiles/STATE.a2ml && test -f .machine_readable/descriptiles/PLAYBOOK.a2ml && test -f .machine_readable/descriptiles/AGENTIC.a2ml && test -f .machine_readable/descriptiles/NEUROSYM.a2ml -- severity: critical - -### anchors-directory -- description: anchors directory exists in descriptiles -- run: test -d .machine_readable/descriptiles/anchors -- severity: warning - -### self-validating-structure -- description: self-validating directory has k9-svc and examples -- run: test -d .machine_readable/self-validating && test -d .machine_readable/self-validating/k9-svc && test -d .machine_readable/self-validating/examples -- severity: warning - -## Template Integrity - -### no-placeholder-values -- description: No placeholder values remain in template files -- run: test -z "$(grep -r '{{' .machine_readable/contractiles/ 2>/dev/null)" -- severity: critical -- notes: All placeholders must be substituted when copying this template - -# template-readonly RETIRED 2026-09-19 (standards#837 pilot): it grepped -# RSR_TEMPLATE_DO_NOT_EDIT in .machine_readable/0.1-AI-MANIFEST.a2ml, but the -# marker never existed outside this Mustfile and the file is now folded into -# the repo deed — the check could only ever fail. A gate that cannot pass is -# the fake-gate class this estate hunts; retiring it, not re-aiming it. - -## Git State - -### no-untracked-contractiles -- description: All contractile files are tracked in git -- run: test -z "$(git ls-files -o --exclude-standard .machine_readable/contractiles/ 2>/dev/null)" -- severity: critical - -### signed-commits -- description: All commits must be signed -- run: git verify-commit HEAD -- severity: critical - -## Czech File Knife invariants - -### workspace-builds -- description: The Cargo workspace builds with the committed lockfile -- run: cargo build --workspace --locked -- severity: critical - -### tests-pass -- description: Workspace tests (including the reversible-journal tests) pass -- run: cargo test --workspace --locked -- severity: critical - -### reversible-e2e -- description: Every destructive CLI operation is undoable end to end -- run: bash tests/e2e.sh -- severity: critical - -### no-agpl -- description: No AGPL licence declarations in manifests or packaging (estate policy MPL-2.0) -- run: "! grep -rq 'AGPL' Cargo.toml src/*/Cargo.toml build/packaging build/container" -- severity: critical - diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl b/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl deleted file mode 100644 index 6a753d6d3..000000000 --- a/czech-file-knife/.machine_readable/contractiles/must/must.k9.ncl +++ /dev/null @@ -1,238 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# must.k9.ncl — K9 trust-tier component of the must trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Mustfile.a2ml (declaration) + must.ncl (runner). -# Trident completeness is a hard precondition — a repo shipping -# Mustfile without this file AND its runner is an invalid trident; -# the contractile CLI's verify gate refuses partial publication. -# -# Verb: must (invariant assertion — release-blocking) -# Tier: Hunt-read-only (capability: subprocess probes shell out -# for grep/test/file-check; no mutation; -# no network; no write) -# Authority: blocking (HARD GATE — the canonical gating verb) -# -# must is the concrete + persistent verb — release-blocking invariants -# that must hold. Complement to trust (concrete + ephemeral). Together -# must + trust form the blocking-authority pair in the contractile set. -# -# Cardinality: ONE must trident per repo. -# -# Failure-mode focus: must is the primary catchment for subtle -# invariant-erosion drift. Where trust catches "turn off the firewall" -# (outrageous), must catches "this file that was required is now -# missing" / "this forbidden pattern has reappeared" / "this schema -# version regressed" (subtle). Key defense against A5 (commercial -# fabrication of success "facts" — invariants ground truth against -# marketing copy) and D1 (lore fabrication about what the repo contains). - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "must", - paired_xfile = "../must/Mustfile.a2ml", - paired_runner = "../must/must.ncl", - - # α two-axis: Hunt tier (subprocess for grep/test/etc.) but - # restricted to read-only operations. Blocking authority because - # must is the canonical gating verb. - tier = 'Hunt, - authority = 'blocking, - - metadata = { - name = "must-k9", - version = "1.0.0", - description = "Evaluates release-blocking invariants as a hard gate. Third trident instance. Complements trust (ephemeral blocking) with persistent invariant blocking.", - paired_xfile = "Mustfile.a2ml", - paired_runner = "must.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Hunt, - signature_required = true, - trust_level = "read-only invariant verification with subprocess", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = true, - probe_scope = 'read_only, # must probes NEVER mutate - probe_kinds_allowed = [ - 'file_existence, - 'pattern_presence, - 'pattern_absence, - 'schema_match, - 'version_equality, - 'count_threshold, - ], - probe_kinds_denied = [ - 'network_call, - 'filesystem_mutation, - 'external_api, - 'exploit_attempt, # that's trust's safe_hacking territory - ], - }, - }, - - # ------------------------------------------------------------------- - # Variance schema — trust-style severity acknowledgement. - # Because must is BLOCKING, variances carry real weight. Critical- - # severity invariants can only be varied by maintainer-or-above. - # ------------------------------------------------------------------- - variance_schema = { - entry_id | String, # which invariant id the variance applies to - reason | String, - approved_by | String, # maintainer or above for critical-severity - scope | String, # path glob | session-id | "until-" - expires | String, # absolute date; must variances cannot be open-ended - review_notes | String | optional, - severity_acknowledged | [| 'critical, 'high, 'medium |], - waived_consequence_description | String, # plain language — what breaking the invariant actually does - }, - - execution = { - triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_merge ], - - # Per-invariant execution. Failed invariant = blocked merge. - per_invariant = { - run_probe = true, - record_outcome = true, - respect_variance = true, # active variance suppresses the gate - on_unmet = 'fail, # BLOCKING - severity_escalation = 'honour, - # Subtle-erosion defense: track per-invariant trend over sessions. - # An invariant that passes once and then starts failing in a - # later session without explicit amendment = suspect drift; - # surface as high-priority drift log entry. - track_per_session_trend = true, - flag_suspicious_regressions = true, - }, - - evidence_sinks = [ - { - kind = 'verisimdb, - table = "contractile_executions", - schema = "contractile_execution_v1", - aux_tables = [ "must_invariant_history" ], # per-invariant trend record - }, - { - kind = 'drift_log, - path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true, - }, - ], - - # Session-close hook — re-evaluate all invariants. Block close on - # critical drift (same policy as trust). - on_close = { - re_execute_all_invariants = true, - diff_against_last_ratification = true, - emit_drift_entries_for_new_failures = true, - surface_expired_variances = true, - # Critical must drift blocks session close — consistent with trust. - block_session_close_on_critical_drift = true, - # Must-specific: if a previously-passing invariant is now failing - # without an associated variance or amendment, that's suspected - # silent regression — surface prominently at next session open. - flag_silent_regression = true, - }, - - # ----------------------------------------------------------------- - # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY - # (inherited from intend.k9.ncl v2.0.0 + trust.k9.ncl extensions) - # ----------------------------------------------------------------- - on_open = { - # --- Context presentation --- - render_summary = 'plain_language, - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - - # Must-specific: surface any silent regressions flagged at last - # close so they can't quietly persist across sessions. - include_silent_regressions = true, - - # --- Negotiation phase (five mandatory inputs) --- - negotiation = { - required = true, - ai_required_inputs = [ - 'timeline_realism, - 'industry_standards, # what invariants derive from external standards - 'audience_feasibility, # who is the invariant protecting - 'resulting_invariants, # what NEW must entries result from the work - 'ecosystem_dependencies, # what the invariants depend on - ], - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - }, - }, - - # --- Accountability pledge --- - # Must's pledge parallels trust's but around invariants rather - # than threat model. User pledges not to disable invariants to - # unblock merges; AI pledges to hold the line on declared - # invariants even against enthusiastic scope expansion. - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the declared invariants and the consequences of breaching them. I accept accountability for meeting these invariants and understand that failed invariants block merges. I will raise a variance (with severity acknowledgement) or an amendment rather than disabling a probe to unblock a merge.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will hold the declared invariants. I will refuse to weaken probes to unblock merges; I will refuse scope-creep suggestions that would remove an invariant silently; I will surface silent regressions at session close; I will require variance-with-severity or amendment for any legitimate scope shift, not quiet probe disablement.", - signature_required = true, - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - includes_invariant_summary = true, # must-specific - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, - }, - }, - }, - - # ------------------------------------------------------------------- - # Failure-mode defenses — must's specialisation is subtle-invariant - # erosion. Overlaps with trust on blocking authority but focused on - # persistent invariants rather than ephemeral transactional state. - # ------------------------------------------------------------------- - failure_mode_defenses = [ - # Category A — enthusiasm capture - 'A1_enthusiasm_capture, # scope breach via blocking authority - 'A5_grandiose_scale_hype, # invariants are ground truth vs commercial hype - # Category C — scope/capability erosion - 'C1_scope_creep, # feature-adjacent changes flagged if they break invariants - 'C2_capability_collapse, # invariant removal requires amendment - 'C3_helpfulness_inflation, # added features must respect declared invariants - # Category D — epistemic failures - 'D1_lore_fabrication, # invariants are verifiable truth, not AI-recollection - 'D2_completeness_illusion, # invariant probe must cite behavioural check, not build-success - 'D3_test_theatre, # invariants require real verification not mock-passing - 'D4_error_hiding, # on_unmet = 'fail makes hiding impossible - # Category E — refactor/churn - 'E1_refactor_stampede, # refactor must preserve invariants - 'E3_premature_abstraction, # abstraction must not violate invariants - # Category F — session drift - 'F1_across_session_forgetting, # track_per_session_trend catches re-introduction - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml deleted file mode 100644 index 4499893c5..000000000 --- a/czech-file-knife/.machine_readable/contractiles/must/must.manifest.a2ml +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# must.manifest.a2ml — Trident coherence manifest for the must verb. -# Author: Jonathan D.A. Jewell -# -# Third trident instance in the estate. Completes the blocking-authority -# pair (must + trust). must is concrete + persistent invariants; trust -# is concrete + ephemeral transactions. Together they gate every -# security- and invariant-affecting merge. - ---- -trident_version = "1.0.0" -verb = "must" -semantics = "invariant assertion — release-blocking" -cardinality = "one per repo" -authority = "blocking (hard gate)" - -## Files (three; exactly) - -[[files]] -role = "declaration" -path = "Mustfile.a2ml" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Mustfile declaration — invariants each with id, description, probe, severity." - -[[files]] -role = "runner" -path = "must.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Runner pre-existed. Schema covers invariants array with status_core + severity." - -[[files]] -role = "k9_component" -path = "must.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Hunt-restricted read-only tier; blocking authority. Tracks per-invariant trend across sessions; flags silent regressions; blocks session close on critical drift." - -## Cross-references (must round-trip) - -[cross_refs] -runner_paired_xfile = "Mustfile.a2ml" -k9_paired_xfile = "../must/Mustfile.a2ml" -k9_paired_runner = "../must/must.ncl" - -## Trident signing - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "must trident — canonical template in czech-file-knife. Blocking-authority verb (paired with trust). Specialises in subtle invariant-erosion catchment vs trust's outrageous-attack catchment. Hard gate: any failing invariant blocks merge. Copy this trident into a new repo and replace the declaration with project-specific invariants." - -## Change log - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "Mustfile.a2ml and must.ncl pre-existed. This manifest + must.k9.ncl complete the trident. Inherits on_open schema from intend.k9.ncl v2.0.0; inherits block_session_close_on_critical_drift + variance-severity-acknowledgement from trust.k9.ncl v1.0.0; adds must-specific track_per_session_trend + flag_silent_regression + probe_scope = 'read_only (must doesn't do active exploit attempts — that's trust's safe_hacking territory)." diff --git a/czech-file-knife/.machine_readable/contractiles/must/must.ncl b/czech-file-knife/.machine_readable/contractiles/must/must.ncl deleted file mode 100644 index 98142267d..000000000 --- a/czech-file-knife/.machine_readable/contractiles/must/must.ncl +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Must — invariants runner -# -# Pairs with: Mustfile.a2ml (same directory) -# Verb: must (invariant assertion) -# Semantics: every check is a hard gate. A single failure blocks merge. -# CLI: `contractile must run` → reads Mustfile.a2ml, evaluates each check, -# emits pass/fail verdict per item, exits non-zero if any failed. -# -# This file is the *schema + runner* that the `contractile` CLI (at -# /var/mnt/eclipse/repos/reposystem/contractiles/cli/) loads alongside -# Mustfile.a2ml. Anything else in this directory is human-only notes/archive -# and MUST be ignored by machines. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "must", - semantics = "invariant", - security = { - leash = 'Kennel, - trust_level = "read-only verification", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = true, # verification probes may shell out (e.g. grep, test -f) - }, - metadata = { - name = "must-runner", - version = "1.0.0", - description = "Evaluates every invariant in the adjacent Mustfile.a2ml as a hard gate.", - paired_xfile = "Mustfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - # Contract schema — the shape every Mustfile.a2ml must satisfy. - # Used by `contractile must typecheck Mustfile.a2ml`. - schema = { - invariants - | Array { - id | String, - description | String, - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String, # shell command; exit 0 = pass - # status_core values: 'declared, 'verified, 'failing - status | [| 'declared, 'verified, 'failing |] | default = 'declared, - severity | [| 'critical, 'high, 'medium |] | default = 'critical, - notes | String | optional, - fix | String | optional, - }, - }, - - # Runner behaviour — consumed by the contractile CLI dispatcher. - # Inherits from base.run_defaults; on_any_fail is the hard-gate default. - run = - base.run_defaults - & { - on_any_fail = "exit-nonzero", # hard gate - report_format = "a2ml", # emit a2ml report, not json - emit_summary = true, - }, -} diff --git a/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml b/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml deleted file mode 100644 index d7559ad87..000000000 --- a/czech-file-knife/.machine_readable/contractiles/trust/Trustfile.a2ml +++ /dev/null @@ -1,105 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Trustfile — Trust boundaries and integrity invariants for czech-file-knife -# Author: Jonathan D.A. Jewell -# -# Defines what LLM/SLM agents are trusted to do without asking, and -# integrity invariants that verify the repo has not been tampered with. - -@abstract: -Trust boundaries and integrity checks for czech-file-knife. This file -combines the trust-level definitions from the original TRUST.contractile -with the integrity invariants from the old Trustfile.a2ml. It defines -what AI agents may do autonomously and what requires human approval, -plus checks that verify repository integrity. -@end - -## Trust Levels - -The czech-file-knife operates at trust level: maximal - -Trust levels: -- maximal: Agent may read, build, test, lint, format, heal freely. - Only destructive/external actions require approval. -- standard: Agent may read and build. Test/lint need approval. -- restricted: Agent may read only. All modifications need approval. -- minimal: Agent may read specific files only. Everything else blocked. - -Current trust level: maximal - -## Integrity Invariants - -### Secrets - -#### no-secrets-committed -- description: No credential files in repo -- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local && test ! -f .env.production -- severity: critical - -#### no-private-keys -- description: No private key files committed -- run: "! find . -name '*.pem' -o -name '*.key' -o -name 'id_rsa' -o -name 'id_ed25519' 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -#### no-tokens-in-source -- description: No hardcoded API tokens in source -- run: "! grep -rE '(api[_-]?key|secret|token|password)\s*[:=]\s*[\"'\\''][A-Za-z0-9]{16,}' --include='*.js' --include='*.ts' --include='*.res' --include='*.py' . 2>/dev/null | grep -v node_modules | head -1 | grep -q ." -- severity: critical - -## Provenance - -#### author-correct -- description: Git author matches expected identity -- run: "git log -1 --format='%ae' | grep -qE '(hyperpolymath|j\\.d\\.a\\.jewell)'" -- severity: warning - -#### license-content -- description: LICENSE is the canonical MPL-2.0 text -- run: grep -q 'Mozilla Public License Version 2.0' LICENSE -- severity: warning - -## Template-Specific Trust - -### template-files-readonly -- description: Template scaffold files should not be modified except by maintainer -- run: test -z "$(git status --short .machine_readable/ 2>/dev/null | grep -v '^??' || true)" -- severity: advisory -- notes: Changes to template files require careful review - -### trust-deny-areas -- description: Sensitive areas from INTENT.contractile require explicit approval -- run: echo "Check .machine_readable/ contractiles and governance docs" -- severity: advisory -- areas: - - .machine_readable/ - - docs/GOVERNANCE.adoc - - docs/MAINTAINERS.adoc - - .github/CODEOWNERS - -## Container Security - -#### container-images-pinned -- description: Containerfile uses pinned base images -- run: test ! -f Containerfile || grep -q 'cgr.dev\|@sha256:' Containerfile -- severity: warning - -#### no-dockerfile -- description: No Dockerfile (use Containerfile) -- run: test ! -f Dockerfile -- severity: warning - -## Website Security - -#### site-security-headers -- description: Any website directory (www, site, docs/site) must contain a security_headers directory -- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then test -d \"$d/security_headers\" || exit 1; fi; done" -- severity: critical - -#### site-well-known -- description: Any website directory must contain a .well-known directory -- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then test -d \"$d/.well-known\" || exit 1; fi; done" -- severity: warning - -#### site-resource-records -- description: Any website directory must contain resource-record templates (resource_records/, or dns/records/ in the issue-53 canonical layout) -- run: "for d in www site docs/site; do if [ -d \"$d\" ]; then { test -d \"$d/resource_records\" || test -d \"$d/dns/records\"; } || exit 1; fi; done" -- severity: warning diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl b/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl deleted file mode 100644 index 33e1c9ae9..000000000 --- a/czech-file-knife/.machine_readable/contractiles/trust/trust.k9.ncl +++ /dev/null @@ -1,278 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# trust.k9.ncl — K9 trust-tier component of the trust trident -# Author: Jonathan D.A. Jewell -# -# Pairs with: Trustfile.a2ml (declaration) + trust.ncl (runner). -# Trident completeness is a hard precondition — a repo shipping -# Trustfile without this file AND its runner is an invalid trident; -# the contractile CLI's verify gate refuses partial publication. -# -# Verb: trust (security + provenance + safe-hacking) -# Tier: Hunt (capability: subprocess probes may shell out, -# active probes in safe_hacking section) -# Authority: blocking (HARD GATE — opposite of intend's reporting) -# -# trust is the concrete + ephemeral + transactional verb per user -# 2026-04-18: port use, BLAKE3 hashing, auth challenges, TLS state, -# session tokens. Every probe has instant binary ground truth. -# This is the α two-axis complement to intend: both Hunt-tier, opposite -# authority poles. Validating the architecture on both exercises the -# full (tier, authority) surface. -# -# Cardinality: ONE trust trident per repo (see feedback_contractile_ -# layout_rules.md). ANCHOR.a2ml is the sole multi-instance exception — -# it is NOT a verb contractile. -# -# Design commitments baked in (full memory trail under -# ~/.claude/projects/-var-mnt-eclipse-repos/memory/ 2026-04-18): -# * α two-axis (Hunt, blocking) — trust is where the contractile system -# grows teeth. Failed verification = failed CI = blocked merge. -# * Variance schema first-class — scoped exceptions structural, not -# comment markers. -# * Sessional drift detection hooks — re-verify every close. -# * Ratification negotiation with threat-model foregrounded -# (feedback_ai_failure_mode_catalog.md B1 — threat-model -# misclassification is the PRIMARY defense trust provides). -# * Accountability pledge — both parties sign before security-affecting -# work proceeds. -# * Plain-language translation — user never authors a Nickel schema for -# a cipher suite; AI does the spec work, user reviews in domain -# language ("TLS 1.3 with PQ key exchange, HSTS preload, 1yr"). -# * Evidence sinks: VeriSimDB (queryable) + descriptiles/DRIFT.a2ml (repo-local). -# * Failure-mode defenses cross-referenced — trust carries the most -# defenses of any verb because the threat surface is widest. - -let base_k9 = import "../k9/template-hunt.k9.ncl" in -let base = import "../_base.ncl" in - -{ - pedigree = base_k9.pedigree_schema & { - contractile_verb = "trust", - paired_xfile = "../trust/Trustfile.a2ml", - paired_runner = "../trust/trust.ncl", - - # α two-axis declaration — capability × authority. - # trust is Hunt-capable (active probes shell out, safe-hacking section - # runs real fuzz/injection/auth-bypass attempts scoped to the repo) - # AND blocking-authority (failed verification = failed CI). - # Contrast with intend = (Hunt, reporting). The two verbs exercise - # the full α surface. - tier = 'Hunt, - authority = 'blocking, - - metadata = { - name = "trust-k9", - version = "1.0.0", - description = "Executes security verifications + authorised safe-hacking probes. HARD GATE: failed verification blocks merge. Catches the 'turn off the firewall' class of drift directly. Implements negotiation-ratification-accountability protocol inherited from intend.k9.ncl v2.0.0.", - paired_xfile = "Trustfile.a2ml", - paired_runner = "trust.ncl", - author = "Jonathan D.A. Jewell ", - }, - - security = { - leash = 'Hunt, - signature_required = true, - trust_level = "verification + authorised-probe + hard-gate", - allow_network = false, # verifications offline by default - allow_filesystem_write = false, # evidence sinks are indirected - allow_subprocess = true, - authorised_probes_only = true, # probe section explicitly lists allowed targets + probe classes - probe_scope_enforcement = 'this_repo_only, # probes NEVER hit external systems - }, - }, - - # ------------------------------------------------------------------- - # Variance schema — P-shape scoped exceptions per verification. - # A variance suppresses a specific verification's obligation for a - # reason, with approver + expiry. Because trust is BLOCKING authority, - # variances on trust entries are SIGNIFICANTLY more consequential than - # variances on intend (reporting) entries — variance approver MUST - # be the repo maintainer or above for critical-severity entries. - # ------------------------------------------------------------------- - variance_schema = { - entry_id | String, # which verification / probe id - reason | String, - approved_by | String, # maintainer or above for critical entries - scope | String, # path glob | session-id | "until-" - expires | String, # absolute date; trust variances cannot be open-ended - review_notes | String | optional, - # Additional trust-specific guardrails: - severity_acknowledged | [| 'critical, 'high, 'medium, 'low |], - waived_risk_description | String, # plain language — what is being accepted - }, - - # ------------------------------------------------------------------- - # Execution policy - # ------------------------------------------------------------------- - execution = { - # When the component runs. - # pre_push + pre_commit on anything touching security-adjacent files - # + session_close (drift check) + on_demand. - triggers = [ 'session_close, 'on_demand, 'pre_push, 'pre_commit_security_adjacent ], - - # Per-verification execution. Failed verification = blocked merge. - per_verification = { - run_probe = true, - record_outcome = true, - respect_variance = true, # active variance suppresses the gate - on_unmet = 'fail, # BLOCKING — the opposite of intend's 'log_drift - severity_escalation = 'honour, # critical > high > medium > low in gate decisions - }, - - # Per-safe-hacking-probe execution. - # If a probe FINDS what it was supposed to prevent finding - # (e.g. injection succeeds, auth-bypass works), that's an EXPLOIT - # demonstration — hard fail, regardless of other status. - per_probe = { - run_probe = true, - record_outcome = true, - honour_expected_outcome = true, - on_unexpected_exploit_success = 'fail, # exploit found where it shouldn't be - scope_enforcement = 'this_repo_only, # never touch external systems - timeout_honouring = 'strict, - }, - - # Evidence sinks — BOTH written, every execution. - evidence_sinks = [ - { - kind = 'verisimdb, - table = "contractile_executions", - schema = "contractile_execution_v1", - # trust-specific sub-table for probe outcomes (for threat-model audit) - aux_tables = [ "trust_verifications", "trust_probes" ], - }, - { - kind = 'drift_log, - path = ".machine_readable/descriptiles/DRIFT.a2ml", - append_only = true, - }, - ], - - # Session-close hook — re-verify EVERYTHING, re-run probes, diff - # against last ratification. The "turn off the firewall" scenario - # must be caught here if it wasn't caught at pre-push. - on_close = { - re_execute_all_verifications = true, - re_run_all_safe_hacking_probes = true, - diff_against_last_ratification = true, - emit_drift_entries_for_new_failures = true, - surface_expired_variances = true, - # trust-specific: if any blocking-severity verification is newly - # failing, the session close is BLOCKED from completing. User - # cannot close a session with unresolved critical trust drift. - block_session_close_on_critical_drift = true, - }, - - # ----------------------------------------------------------------- - # Session-open hook — NEGOTIATION + RATIFICATION + ACCOUNTABILITY - # (inherited shape from intend.k9.ncl v2.0.0; trust-specific - # additions around threat-model foregrounding below) - # ----------------------------------------------------------------- - on_open = { - # --- Context presentation --- - render_summary = 'plain_language, # metaphor-capture defense - include_drift_log_from_last_close = true, - include_active_variances = true, - include_recent_anchors = true, - anchor_lookback_weeks = 8, - - # trust-specific: the threat model is rendered FIRST, before any - # negotiation, so the adversary and stakes are fresh in both minds. - # This directly defends against B1 (threat-model misclassification) - # — the "war reporter, generic personal-website priors" scenario. - threat_model_foregrounding = { - required = true, - render_adversaries = true, # from Trustfile [THREAT_MODEL] - render_stakes = true, - render_compliance_regimes = true, - render_audience_sensitivity = true, - # If the AI is about to suggest a trust-weakening action, it - # must re-render the threat model before the suggestion lands. - re_render_before_weakening_suggestion = true, - }, - - # --- Negotiation phase (five mandatory inputs, inherited) --- - negotiation = { - required = true, - ai_required_inputs = [ - 'timeline_realism, - 'industry_standards, # especially relevant for trust: OWASP, NIST, PCI-DSS, GDPR - 'audience_feasibility, # who is the adversary? who is protected? - 'resulting_invariants, # what trust entries the work creates/amends - 'ecosystem_dependencies, # TLS libs, crypto primitives, signing infra - ], - user_engagement_required = true, - user_engagement_mode = 'per_input_response, - specification_translation = { - ai_produces_spec_form = true, - user_reviews_in_domain_language = true, - schema_authoring_is_ai_responsibility = true, - translation_faithfulness_auditable = true, - # trust-specific: the AI's translation includes rendering - # cipher suites, key exchange choices, rate-limit numbers in - # domain language ("strong encryption, PQ-resistant, 60 req/min") - # rather than forcing the user into Nickel-schema authoring. - }, - }, - - # --- Accountability pledge (both parties, explicit) --- - # trust's pledge is MORE stringent than intend's because the - # authority is blocking. A user accepting accountability here is - # accepting that security-affecting decisions have blocking consequence. - accountability_pledge = { - required = true, - parties = [ - { - role = 'user, - pledge = "I have reviewed the threat model, the declared trust obligations, and the audience/stakes consequences. I accept accountability for meeting these obligations and understand that failed verification will block merges until resolved or varied. I will not attempt to disable verification to unblock a merge; I will raise a variance or amendment instead.", - signature_required = true, - }, - { - role = 'ai_agent, - pledge = "I will hold the line on declared trust obligations. I will refuse to 'disable' verifications to unblock merges; I will refuse security-weakening suggestions that contradict the threat model even when the user is enthusiastic; I will surface drift at session close; I will re-render the threat model before proposing any weakening action. If a legitimate scope shift demands security reduction, I will require a variance with severity acknowledgement or an amendment, not silent acceptance.", - signature_required = true, - }, - ], - signed_record_destination = ".machine_readable/descriptiles/ratification-.a2ml", - must_precede_work = true, - }, - - ratification_record_shape = { - includes_negotiation_transcript = true, - includes_both_pledges = true, - includes_threat_model_snapshot = true, # trust-specific - signed = true, - dated = true, - session_id = 'required, - contract_hash = 'required, - }, - }, - }, - - # ------------------------------------------------------------------- - # Failure-mode defenses — trust is the widest-coverage verb. - # See feedback_ai_failure_mode_catalog.md for the full catalog. - # ------------------------------------------------------------------- - failure_mode_defenses = [ - # Category A — enthusiasm / narrative capture - 'A1_enthusiasm_capture, # scope breach blocks via blocking authority - 'A2_metaphor_capture, # render_summary + re_render_before_weakening - # Category B — threat-model misclassification (trust's flagship defense) - 'B1_threat_model_misclass, # threat_model_foregrounding = required - 'B2_audience_sensitivity_collapse, # audience_feasibility in negotiation - 'B3_compliance_prior_drift, # industry_standards in negotiation - # Category C — scope/capability erosion (the "firewall off" scenario) - 'C2_capability_collapse, # blocking gate prevents silent capability drop - 'C3_helpfulness_inflation, # trust-affecting changes need variance/amendment - 'C4_modernization_drift, # unrequested crypto-lib upgrade caught - # Category D — epistemic failures - 'D4_error_hiding, # on_unmet = 'fail makes hiding impossible - 'D5_sycophancy, # pledge forces AI to hold line against enthusiasm - 'D6_false_pessimism, # negotiation requires AI to cite constraint, not assert impossibility - # Category E — refactor/churn - 'E4_cargo_cult_security, # probes VERIFY the claimed protection actually runs - # Category F — session drift - 'F1_across_session_forgetting, # on_open reads last-ratification, drift log, recent ANCHORs - ], -} diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml b/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml deleted file mode 100644 index d15860695..000000000 --- a/czech-file-knife/.machine_readable/contractiles/trust/trust.manifest.a2ml +++ /dev/null @@ -1,72 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# trust.manifest.a2ml — Trident coherence manifest for the trust verb. -# Author: Jonathan D.A. Jewell -# -# Asserts: exactly three files constitute the trust trident; their -# content-hashes are pinned here; cross-references round-trip; no -# partial publication is permitted. -# -# The contractile CLI's `verify trust` subcommand MUST: -# 1. Confirm all three listed files exist at the declared paths. -# 2. Compute each file's sha256 and match against the pinned value. -# 3. Follow each cross-reference and confirm the target file's -# reciprocal field points back. -# 4. Refuse the dir (exit non-zero) if any of 1–3 fails. -# -# trust is the concrete + ephemeral + transactional verb (per user -# 2026-04-18); first blocking-authority trident in the estate. Exercises -# the (Hunt, blocking) authority pattern — complement to intend's -# (Hunt, reporting). Primary defense against failure mode B1 (threat- -# model misclassification) and the "turn off the firewall" class of -# drift attempts the adversarial pilot is designed to exercise. - ---- -trident_version = "1.0.0" -verb = "trust" -semantics = "security + provenance + safe-hacking" -cardinality = "one per repo" -authority = "blocking (hard gate)" - -## Files (three; exactly) - -[[files]] -role = "declaration" -path = "Trustfile.a2ml" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Extensively populated exemplar; covers threat model, DNS, TLS, crypto, SDP, safe-hacking, response headers, container supply chain, Cloudflare edge." - -[[files]] -role = "runner" -path = "trust.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Runner existed pre-trident; schema covers verifications + safe_hacking with authorised-probes-only, this_repo_only scope enforcement." - -[[files]] -role = "k9_component" -path = "trust.k9.ncl" -sha256 = "pending-first-verify" -size_bytes = "pending-first-verify" -notes = "Trust-tier Hunt with blocking authority. on_open foregrounds threat model before negotiation; block_session_close_on_critical_drift." - -## Cross-references (must round-trip) - -[cross_refs] -runner_paired_xfile = "Trustfile.a2ml" -k9_paired_xfile = "../trust/Trustfile.a2ml" -k9_paired_runner = "../trust/trust.ncl" - -## Trident signing - -[signed_by] -user = "Jonathan D.A. Jewell" -date = "2026-04-18" -context = "trust trident — canonical template in czech-file-knife. (Hunt, blocking) authority pattern. Primary catchment for adversarial drift test scenarios (firewall-off, cleartext-auth, PQ-downgrade, CSP-weaken). Hard gate: failed verification blocks merge. Copy this trident into a new repo and point it at the real keys, policies, and authority boundaries." - -## Change log - -[[history]] -date = "2026-04-18" -event = "trident-born" -note = "Trustfile.a2ml and trust.ncl pre-existed. This manifest + trust.k9.ncl complete the trident. Inherits on_open negotiation + accountability + plain-language-translation schema from intend.k9.ncl v2.0.0; adds trust-specific threat_model_foregrounding + block_session_close_on_critical_drift + stricter accountability pledge (user cannot disable verification to unblock merges)." diff --git a/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl b/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl deleted file mode 100644 index 2b836242e..000000000 --- a/czech-file-knife/.machine_readable/contractiles/trust/trust.ncl +++ /dev/null @@ -1,94 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Trust — security + safe-hacking runner -# -# Pairs with: Trustfile.a2ml (same directory) -# Verb: trust -# Semantics: integrity / provenance / security verification PLUS a declared -# "safe hacking + testing" section — authorised offensive probes -# (pen-test harness runs, chaos-engineering probes) scoped to the -# repo under test, NEVER touching external systems. -# CLI: `contractile trust verify` → run all verifications (read-only) -# `contractile trust probe` → run declared safe-hacking probes -# -# Anything else in this directory is human-only notes/archive; machines ignore. -# -# Base: ../_base.ncl provides pedigree_schema, run_defaults, probe_schema. -# See: docs/CONTRACTILE-SPEC.adoc -let base = import "../_base.ncl" in - -{ - pedigree = - base.pedigree_schema - & { - contractile_verb = "trust", - semantics = "security + provenance + safe-hacking", - security = { - leash = 'Kennel, - trust_level = "verification + authorised-probe", - allow_network = false, # verifications are offline by default - allow_filesystem_write = false, # trust writes NOTHING - allow_subprocess = true, - authorised_probes_only = true, # probe section must explicitly list allowed targets - }, - metadata = { - name = "trust-runner", - version = "1.0.0", - description = "Security + provenance verifications plus authorised safe-hacking probes. All probes are scoped to the repo under test; never hits external systems.", - paired_xfile = "Trustfile.a2ml", - author = "Jonathan D.A. Jewell ", - }, - }, - - schema = { - verifications - | Array { - id | String, - description | String, - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String, # read-only; exit 0 = pass - # status_core values: 'declared, 'verified, 'failing - status | [| 'declared, 'verified, 'failing |] | default = 'declared, - # trust uses all four severity levels (from base.severity_core) - severity | [| 'critical, 'high, 'medium, 'low |] | default = 'high, - notes | String | optional, - }, - - # Safe-hacking + testing section (added 2026-04-17 per user direction). - # Each probe here is an ACTIVELY EXECUTED test — fuzz runs, chaos probes, - # auth-bypass attempts, injection tests. All scoped to the current repo. - safe_hacking - | { - scope | String, # e.g. "this-repo-only" / "localhost" - allowed_probe_classes - | Array [| 'fuzz, 'property_test, 'chaos, 'auth_bypass, 'injection, 'timing |] - | default - = [], - probes - | Array { - id | String, - class | [| 'fuzz, 'property_test, 'chaos, 'auth_bypass, 'injection, 'timing |], - description | String, - # TODO: migrate to base.probe_schema (structured probe) when CLI supports it - probe | String, # command to run the probe - expected_outcome | [| 'probe_blocks_attempt, 'probe_finds_no_issue |], - timeout_seconds | Number | default = 300, - notes | String | optional, - } - | default - = [], - } - | default - = { scope = "this-repo-only", allowed_probe_classes = [], probes = [] }, - }, - - # Runner behaviour — inherits from base.run_defaults. - # trust has an extra field for unexpected safe-hacking outcomes. - run = - base.run_defaults - & { - on_any_fail = "exit-nonzero", # hard gate on verifications - safe_hacking_on_unexpected_outcome = "exit-nonzero", # probe found what it shouldn't = block - report_format = "a2ml", - emit_summary = true, - }, -} diff --git a/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml b/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml deleted file mode 100644 index 5974c7ef5..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/AGENTIC.a2ml +++ /dev/null @@ -1,56 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# AGENTIC.a2ml — AI agent constraints and capabilities -# Defines what AI agents can and cannot do in this repository. - -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[agent-permissions] -can-edit-source = true -can-edit-tests = true -can-edit-docs = true -can-edit-config = true -can-create-files = true - -[agent-constraints] -# What AI agents must NOT do: -# - Never use banned language patterns (believe_me, unsafeCoerce, etc.) -# - Never commit secrets or credentials -# - Never use banned languages (TypeScript, Python, Go, etc.) -# - Never place state files in repository root (must be in .machine_readable/) -# - Never relicense an existing file, and never run an automated licence -# sweep (LICENCE-POLICY.adoc A2). New files get correct SPDX from birth. -# - Never assume a licence. Read standards/3-practice/LICENCE-POLICY.adoc: Rule 1 -# defaults to MPL-2.0 (code) / CC-BY-SA-4.0 (prose), but Rule 3 -# (co-developed), Rule 4 (network-deployed services) and Rule 5 -# (games) are AGPL-3.0-or-later, and Rule 2 names the PMPL register. - -[maintenance-integrity] -fail-closed = true -require-evidence-per-step = true -allow-silent-skip = false -require-rerun-after-fix = true -release-claim-requires-hard-pass = true - -# ============================================================================ -# METHODOLOGY (ADR-002) -# ============================================================================ -# Detailed methodology configuration lives in: -# .machine_readable/bot_directives/methodology.a2ml -# .machine_readable/bot_directives/coverage.a2ml -# .machine_readable/bot_directives/debt.a2ml -# -# AGENTIC.a2ml declares WHAT agents can do (permissions, gating). -# bot_directives/ declares HOW agents should work (methodology). - -[methodology] -instructions-dir = ".machine_readable/bot_directives/" -default-mode = "hybrid" - -[automation-hooks] -# on-enter: Read the repo deed (*_chora.deed at root), then STATE.a2ml, then bot_directives/ -# on-exit: Update STATE.a2ml, coverage.a2ml, and debt.a2ml with session outcomes -# on-commit: Run just validate-rsr diff --git a/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml b/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml deleted file mode 100644 index bee0bfa35..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/CLADE.a2ml +++ /dev/null @@ -1,127 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Clade declaration — part of the gv-clade-index registry -# See: https://github.com/hyperpolymath/gv-clade-index -# -# Installed by `just repo-init` from build/templates/CLADE.a2ml.in. -# The identity below is DERIVED. The clade is NOT — a human must choose it. - -[identity] -# THE UUID IS DERIVED, NOT ALLOCATED, NOT INVENTED. The registry spec -# (gv-clade-index: docs/SPEC-clade-verisim-portal.adoc §Identity Model): -# -# uuid = UUIDv5(namespace = URL, name = "github.com//") -# -# "The same repo always produces the same UUID without a lookup table." It is a -# fact anyone can recompute and check — never copy one from another repo, and -# never make one up. Recompute this one any time with: -# -# uuidgen --sha1 --namespace @url --name "github.com/hyperpolymath/czech-file-knife" -# -# Verify the method first by reproducing a known worked example: -# janus example -> e216170e-ff47-5a5c-bbdd-15e61c8190c8 -# -# The owner segment is part of the derived name, so it is part of the IDENTITY: -# the same repo hosted under a different owner has a different uuid. Record the -# owner that is TRUE TODAY, and re-derive if it ever moves. -uuid = "6f4ec07a-e56d-5700-8913-a11beb40a389" -primary-forge = "github" -primary-owner = "hyperpolymath" -canonical-name = "czech-file-knife" -prefixed-name = "ix-czech-file-knife" # becomes -czech-file-knife once the clade is chosen - -[clade] -# --------------------------------------------------------------------------- -# CHOOSE ONE. A CLOSED TAXONOMY OF 12 CATEGORIES — NOT AN ABBREVIATION SCHEME. -# -# The two letters abbreviate the NAME OF THE CLADE. They NEVER abbreviate the -# name of your repo. Pick the category your repo belongs to, then write down -# that category's code. Do NOT look at your repo's name and hunt for two letters -# that fit it. -# -# fv Formal Verification & Proofs provable correctness, dependent types, theorem proving -# nl Nextgen Languages compilers, runtimes, language tooling -# rm Repo Management & Tooling scaffolding, graph analysis, bots, templates -# gv Governance & Standards licensing, compliance, policy enforcement -# db Databases database engines, query languages, storage -# ap Applications end-user apps, web, desktop, services -# ix Infrastructure & Cloud containers, deployment, sysadmin -# dx Developer Ecosystem dev tools, package managers, editors, bindings -# pt Protocols & Interop internet standards, protocol implementations -# ax AI & Neurosymbolic ML, neural proof synthesis, AI governance -# gm Games & Interactive game engines, interactive experiences -# sc Security scanning, vulnerability management, access control -# -# This is spelled out because the opposite was done, more than once: -# -# paint-type chose `pt`, reading it as "PainT-type". -# `pt` is Protocols & Interop. An image editor is `ap`. -# gossamer chose `gv`, reading it as "Graphical/Visual". -# `gv` is GoVernance & Standards. There is no graphical clade. -# -# Both codes were VALID, so every check passed, and both repos sat filed under a -# category they have nothing to do with. -# -# HOW TO CHOOSE (spec): "The primary clade reflects the project's core value -# proposition, not its implementation details." Ask what the repo is FOR, not -# what it is built with. A game written in Idris2 is `gm`, not `fv`. A webview -# shell with formal ABI proofs is what developers build on, not a proof project. -# -# Fill in BOTH `primary` and `primary-name`, and make them agree — CLADE-006 -# rejects a mismatched pair. The redundancy is deliberate: a code alone cannot -# express a wrong belief about what it means, so it cannot be checked for one. -# The pair can. -# -# Until you fill these in, CLADE-003 and CLADE-006 FAIL. That is deliberate: an -# unchosen clade must never be mistakable for a chosen one. Leaving a valid- -# looking default here is exactly how every repo built from this template ended -# up silently claiming "rm". -# --------------------------------------------------------------------------- -primary = "ix" -primary-name = "Infrastructure & Cloud" -secondary = [] # [] unless there is a genuine SECOND core value proposition -assigned = "2026-09-28" -rationale = "Core value is operating storage across machines and cloud providers (sysadmin/hybrid-infrastructure work), not developer tooling or an end-user app." # why THIS category — in value-proposition terms - -[forges] -github = "hyperpolymath/czech-file-knife" -gitlab = "" # populated if/when mirrored -bitbucket = "" # populated if/when mirrored - -[lineage] -# type: standalone | monorepo | monorepo-child | inflated | deflated | hub | satellite -# hub — this repo COORDINATES an ecosystem family (its members carry -# type="satellite"). WHICH repos it coordinates is not recorded -# here: that lives in ECOSYSTEM.a2ml [pipeline] coordination. -# satellite — this repo is COORDINATED BY a hub. Its `parent` STAYS "": -# parent is a monorepo parent — never a description, and never -# the name of the coordinating hub. -# "hub"/"satellite" were added after the first hub ecosystem had to mint eight -# repos with the nearest-wrong value "standalone" (standards#726): lineage is -# the pointer, ECOSYSTEM.a2ml is the detail. Do not encode membership twice. -type = "standalone" -parent = "" # a monorepo PARENT — never a description of this repo -born = "2026-09-28" -previous-names = [] -instantiated-from = "rsr-template-repo" - -[status] -# One of: reserved incubating active dormant | merged superseded archived extinct -phase = "incubating" # a repo created today has not proven anything yet -since = "2026-09-28" -present = true -aliases = [] -merged-into = "" -superseded-by = "" -successors = [] -ended = "" - -[[status.history]] -phase = "incubating" -since = "2026-09-28" -note = "created from rsr-template-repo; clade not yet chosen; not yet registered" - -# --------------------------------------------------------------------------- -# REGISTRATION IS NOT DERIVATION. Deriving the uuid above does not register this -# repo. Registration means an entry in gv-clade-index verisim/seed/repos.a2ml, -# it is outward-facing, and it is the OWNER's act — an agent must not do it. -# --------------------------------------------------------------------------- diff --git a/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml b/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml deleted file mode 100644 index 45753fbaf..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/ECOSYSTEM.a2ml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# ECOSYSTEM.a2ml — Ecosystem position for czech-file-knife - -[metadata] -project = "czech-file-knife" -ecosystem = "hyperpolymath" - -[position] -type = "tool" -purpose = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." -# IS-NOT — anti-identity (the boundary-erosion guard) -what-this-is-not = [ - "a sync daemon (Syncthing/rclone-bisync territory) — cfk performs explicit operations", - "a backup system — the journal makes operations reversible; it is not an archive", - "a sub-project of developer-ecosystem (extracted 2026-09-28; it was only held there)", -] - -[pipeline] -position = "leaf" -chain = "standards → rsr-template-repo → czech-file-knife" -coordination = "standards" - -[related-projects] -projects = [ - { name = "januskey", relationship = "design-source", notes = "Reversible file operations: SHA-256 CAS + append-only op log. cfk-core::reversible implements the same model at the StorageBackend layer." }, - { name = "absolute-zero", relationship = "proof-source", notes = "Certified Null Operations: target semantics for --dry-run/plan mode and for 'op; undo' being a provable no-op." }, - { name = "echo-types", relationship = "research-input", notes = "Typing structured information loss: model of what cross-backend conversions lose (perms, xattrs, case)." }, - { name = "tropical-types", relationship = "research-input", notes = "Max-plus resource bounds: basis for a cost-aware multi-backend transfer planner." }, - { name = "developer-ecosystem", relationship = "former-host", notes = "Held the code until extraction; history preserved via git subtree split." }, -] diff --git a/czech-file-knife/.machine_readable/descriptiles/META.a2ml b/czech-file-knife/.machine_readable/descriptiles/META.a2ml deleted file mode 100644 index 8dd748048..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/META.a2ml +++ /dev/null @@ -1,53 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# META.a2ml — Project meta-level information -# Architecture decisions, design rationale, governance. - -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[project-info] -type = "library" # TODO: update type (library|binary|service|website|monorepo) # library | binary | monorepo | service | website -languages = [] # e.g. ["rust", "zig", "idris2"] -license = "MPL-2.0" -author = "Jonathan D.A. Jewell (hyperpolymath)" - -[architecture-decisions] -# ADR format: status = proposed | accepted | deprecated | superseded | rejected -# - { id = "ADR-001", title = "Use Zig for FFI", status = "accepted", date = "2026-02-14" } - -[development-practices] -build-tool = "just" -container-runtime = "podman" -ci-platform = "github-actions" -package-manager = "guix" # guix | cargo | mix - -[maintenance-axes] -scoping-first = true -execution-order = "axis-1 > axis-2 > axis-3" -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -sources = "README, roadmap, status docs, maintenance checklist, CI/security docs" -marker-scan = "TODO/FIXME/XXX/HACK/STUB/PARTIAL" -idris-unsound-scan = "believe_me/assert_total" - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive-focus = "scope-change reconciliation, stale-reference removal, obsolete-work culling" -perfective-third = true -perfective-source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed" -compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks" -drift-risk-example = "single exception broadening into policy violation (e.g. ->TypeScript spread)" -effects-evidence = "benchmark execution/results and maintainer status dialogue/review" - -[design-rationale] -# Key design decisions and their reasoning diff --git a/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml b/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml deleted file mode 100644 index 1acf7a300..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/NEUROSYM.a2ml +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# NEUROSYM.a2ml — Neurosymbolic integration metadata -# Configuration for Hypatia scanning and symbolic reasoning. - -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[hypatia-config] -scan-enabled = true -scan-depth = "standard" # quick | standard | deep -report-format = "logtalk" - -[symbolic-rules] -# Custom symbolic rules for this project -# - { name = "no-unsafe-ffi", pattern = "believe_me|unsafeCoerce", severity = "critical" } - -[neural-config] -# Neural pattern detection settings -# confidence-threshold = 0.85 -# model = "hypatia-v2" diff --git a/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml b/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml deleted file mode 100644 index f184d718e..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/PLAYBOOK.a2ml +++ /dev/null @@ -1,137 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# PLAYBOOK.a2ml — Operational playbook -# Runbooks, incident response, deployment procedures. - -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[deployment] -# method = "gitops" # gitops | manual | ci-triggered -# target = "container" # container | binary | library | wasm - -[incident-response] -# 1. Check .machine_readable/descriptiles/STATE.a2ml for current status -# 2. Review recent commits and CI results -# 3. Run `just validate` to check compliance -# 4. Run `just security` to audit for vulnerabilities - -[release-process] -# 1. Update version in STATE.a2ml, META.a2ml, Justfile -# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass) -# 3. Optional local permission hardening: `just perms-snapshot && just perms-lock` -# 4. Tag and push -# 5. Restore local permissions if needed: `just perms-restore` -# 6. Run `just container-push` if applicable - -[maintenance-operations] -# Baseline audit: -# just maint-audit -# Hard release gate: -# just maint-hard-pass -# Permission audit: -# just perms-audit - -[rsr-repo-skeleton] -# Canonical organisation of any RSR-derived repository. -# Used by tooling, human onboarding, and the scheduled downstream sweep agent. -# The 5-PR cleanup pattern (below) brings a non-conforming repository into -# compliance with this skeleton. -# -# This section is the single source of truth for "what does an RSR repo look -# like?". Other docs (TOPOLOGY, AUDIT, etc.) describe the repo at hand; -# this describes the canonical shape that all RSR repos share. - -skeleton-version = "1.0" -last-updated = "2026-04-30" -authority-allowlist = ".machine_readable/root-allow.txt" -enforcement-workflow = ".github/workflows/estate-rules.yml" - -# === Required at root === -# README.adoc High-level pitch (project entry point) -# AUDIT.adoc Local gate summary (release-readiness) -# EXPLAINME.adoc Developer deep-dive (architecture & invariants) -# _chora.deed Repo deed: AI work-allocation policy + ply tree (deed grammar) -# LICENSE Repo license (root-bound by convention) -# CHANGELOG.md One of the recognised .md exceptions (see below) -# Justfile Task runner — thin, imports per-section files from build/just/ -# coordination.k9 Repo-local session binding - -# === Required directories === -# .github/ CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, workflows/ -# .machine_readable/ descriptiles/ checkpoints (ply tree folded into the repo deed), -# contractiles/, configs/, anchors/, policies/, scripts/, self-validating/ -# build/ contractile.just, guix.scm, Containerfile, -# just/*.just (Justfile section imports) -# docs/ onboarding/, status/, architecture/, governance/ (all .adoc) -# session/ dispatch.sh, custom-checks.k9, local-hooks.sh -# src/ Project source (Idris2 ABI under abi/, Zig FFI under ffi/) -# tests/, benches/, examples/, features/, scripts/, verification/, build/container/ - -# === Documentation format rule === -# `.adoc` is the default for all general docs (TOPOLOGY, READINESS, ROADMAP, -# TEST-NEEDS, PROOF-NEEDS, PROOF-STATUS, llm-warmup-*, etc.). -# -# `.md` is reserved ONLY for files GitHub's community-health rules -# special-case by name: -# CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md CHANGELOG.md -# -# Enforcement: `scripts/check-no-md-in-docs.sh` (fails if any *.md under docs/). - -# === Banned: ziguage === -# V (vlang.io) is banned estate-wide. Replaced by `zig-unified-api-adapter` -# (16 endpoints + transaction-based firewall gating). Do not introduce -# zig code, scaffolders, or references. Note that Coq theorem files use -# the same `.v` extension and are unaffected — the rule looks at content -# patterns, not the extension. -# -# Enforcement: `scripts/check-no-vlang.sh`. - -# === Justfile structure (post-split) === -# The root Justfile is thin — it holds `set` directives, project metadata -# variables, and the `default`/`help`/`info` recipes. Each major section -# lives in its own file under build/just/ and is brought in via `import?`. -# -# Imported sections (in the canonical split): -# build/just/init.just INIT recipe (template bootstrap) -# build/just/assess.just self-assess + verify (OpenSSF compliance) -# build/just/validate.just validate-rsr/state/ai-install + aggregate -# build/just/proofs.just proof-check-{all,idris2,lean4,agda,coq}, -# proof-scan-dangerous, proof-status -# build/just/groove.just Groove protocol setup (after zig removed) -# -# Daily-use recipes (BUILD, TEST, LINT, RUN, DEPS, DOCS, CONTAINER, CI, -# SECURITY, STATE, GUIX, MATRIX, VERSION CONTROL, UTILITIES, SESSION) -# stay in the root Justfile where users expect to find them. - -# === 5-PR cleanup pattern === -# Apply these branches (in order) to bring a non-conforming downstream repo -# into compliance with this skeleton: -# -# 1. chore/root-cleanup Relocate root sprawl per root-allow.txt; add -# scripts/check-root-shape.sh; remove stub -# health files shadowed by .github/ versions. -# 2. chore/remove-zig Purge zig remnants (gen-v-connector recipe, -# "V-TRIPLE" section header, "V-triple -# connectors" comment in groove.a2ml). -# 3. chore/md-to-adoc Port general docs in docs/ from .md to .adoc; -# update validate-template.sh to accept .adoc -# fallbacks. -# 4. chore/estate-rules-ci Add scripts/check-no-md-in-docs.sh + check-no- -# vlang.sh + .github/workflows/estate-rules.yml. -# 5. chore/-hygiene Repo-specific drift cleanup (case collisions, -# template-derivation drift in titles, etc.). - -# === Reference scripts === -# scripts/check-root-shape.sh Root allowlist validator -# scripts/check-no-md-in-docs.sh AsciiDoc-by-default validator -# scripts/check-no-vlang.sh zig ban validator -# scripts/validate-template.sh Aggregate RSR compliance (workflows, SPDX, etc.) - -# === Reference memory entries (for AI agents) === -# feedback_adoc_default_md_for_githealth AsciiDoc-by-default rule -# feedback_v_lang_banned zig ban -# project_zig_unified_api Replacement for v-triple/zig -# feedback_gh_workflow_scope OAuth scope for workflow files diff --git a/czech-file-knife/.machine_readable/descriptiles/README.adoc b/czech-file-knife/.machine_readable/descriptiles/README.adoc deleted file mode 100644 index 29dc77d94..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/README.adoc +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Descriptiles -:toc: - -The *descriptiles* are this repository's descriptive machine-readable -metadata: they record what the repository *is* and what state it is *in*. -They pair with the contractiles in `../contractiles/`, which record what it -*ought* to do. - -[IMPORTANT] -==== -*The normative grammar for this family is the DEED grammar specification,* -`1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in -https://github.com/hyperpolymath/standards[hyperpolymath/standards]. - -The format formerly called A2ML has been renamed *DEED*, with the file -extension `.deed`. The files in this directory still carry the `.a2ml` -extension: the estate-wide rename is a single atomic change tracked -separately, because ~40% of these basenames appear as literals in source -and in Nickel runners. Do not hand-convert individual files. - -DEED is an *s-expression* format. If you have seen these files described -anywhere as a "TOML-like key-value" format, that description is wrong and -is the documented cause of a family-wide divergence; the grammar has no -`key = value` form. Always resolve the surface question against the DEED -grammar specification, never against a neighbouring file. -==== - -== The six descriptiles - -`AGENTIC.a2ml`:: AI agent operational gating and safety controls. -`ECOSYSTEM.a2ml`:: Ecosystem position, relationships, explicit boundaries. -`META.a2ml`:: Architecture decisions (ADRs), development practices, design rationale. -`NEUROSYM.a2ml`:: Symbolic semantics, composition algebra. -`PLAYBOOK.a2ml`:: Executable plans, operational runbooks. -`STATE.a2ml`:: Project state, phase, milestones, session history. - -== Other files in this directory - -This directory also carries files whose classification is settled elsewhere -and is deliberately not restated here: - -`anchors/`:: `ANCHOR` records. An anchor is *both* descriptile and -contractile — it states an observed drift (descriptive) and issues a -mandated realignment (normative). It is the join between the two families -and has its own specification. - -`CLADE.a2ml`:: The facet classification record. "Clade" is retained only as -the current filename; the scheme it names is being replaced, since facets -do not share common descent. - -`*_chora.deed` (repo deed, at the repo root):: The universal AI entry point — -a DEED-grammar file. The family-7 allocation manifest (0-AI-MANIFEST.a2ml) and -the ply directory tree folded into it in the standards#837 pilot. - -== Generation - -These files may be generated from `.scm` sources by transpilation. Source -`.scm` files should be removed after a successful transpilation. - -== See also - -* https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc[Historical A2ML repository template (non-normative)] -* https://github.com/hyperpolymath/standards#a2ml-format-family-7-formats[Historical A2ML format family overview (non-normative)] -* `../contractiles/README.adoc` — the normative counterpart family diff --git a/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml b/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml deleted file mode 100644 index d0a089ff9..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/STATE.a2ml +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# STATE.a2ml — Project state checkpoint for czech-file-knife - -[metadata] -project = "czech-file-knife" -version = "0.1.0" -last-updated = "2026-09-28" -# status: active | paused | archived | planned -# AUTHORITY: when this and CLADE [status] phase disagree, CLADE phase is the -# authoritative SCM checkpoint (standards#726). -status = "active" - -[project-context] -name = "czech-file-knife" -purpose = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." -completion-percentage = 20 - -[position] -phase = "implementation" # design | implementation | testing | maintenance | archived -maturity = "alpha" # experimental | alpha | beta | production | lts - -[route-to-mvp] -milestones = [ - { name = "M0: Core traits, local backend, basic CLI (ls cat cp mv rm mkdir stat df)", completion = 100 }, - { name = "M1: Reversible journal (ReversibleBackend, cfk history/undo, CAS-backed versions)", completion = 80 }, - { name = "M2: Extracted from developer-ecosystem as a standalone RSR repo", completion = 90 }, - { name = "M3: cfk squeeze — in-place, space-bounded compression (hole punching / tail truncation)", completion = 0 }, - { name = "M4: Provider-side cloud ops (server_side_copy; Drive, S3, Dropbox, OneDrive)", completion = 0 }, - { name = "M5: Cloud backends beyond local (S3, Google Drive first)", completion = 5 }, - { name = "M6: Cache (cfk-cache on the shared CAS) and search (Tantivy)", completion = 5 }, - { name = "M7: FUSE mounting (cfk-vfs)", completion = 0 }, -] - -[blockers-and-issues] -# 1. Reversible journal + extraction were authored without a Rust toolchain -# in the authoring sandbox: first `just build && just test && just e2e` on -# real CI is the acceptance gate for M1/M2. -# 2. undo does not yet restore metadata (permissions, mtimes, xattrs). -# 3. verification/ is template scaffold until the PROOF-NEEDS targets land. - -[critical-next-actions] -actions = [ - "Run just build / just test / just e2e on CI and fix whatever the first real compile finds", - "Implement cfk squeeze (Linux hole punching first, tail-truncation fallback)", - "Add StorageBackend::server_side_copy and wire the Google Drive provider", - "Restore metadata on undo; selective (non-latest) undo with conflict detection", - "Replace verification/ scaffold with the reversibility and frame-then-punch invariants", -] - -[maintenance-status] -last-run-utc = "never" -last-report = "docs/reports/maintenance/latest.json" -last-result = "unknown" # unknown | pass | warn | fail -open-warnings = 0 -open-failures = 0 - -[ecosystem] -part-of = ["hyperpolymath estate", "RSR Framework"] -depends-on = [] -related = ["januskey (reversible-operation model)", "absolute-zero (certified null operations: dry-run / no-op proofs)", "echo-types (conversion-loss typing)", "tropical-types (cost bounds for transfer planning)"] diff --git a/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml b/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml deleted file mode 100644 index 3380e8a74..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/VARIANT.a2ml +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# VARIANT.a2ml — provenance contract (ADR-0002 shape; ADR-0003 use). -# Records what this repo was minted from so the drift gate -# (scripts/check-variant-drift.sh ) and future -# re-templating tooling have a defined input. -# -# NOTE: placeholder rendering means many files differ from the -# parent by design; folding rendered answers into [normalise] is -# future work (ADR-0003), so the gate is informational for minted -# repos until then. - -[metadata] -project = "czech-file-knife" -schema_version = "0.1.0" -last-updated = "2026-09-28" - -[variant] -parent = "hyperpolymath/rsr-template-repo" -parent-pin = "933507582ef2467d77541dbf2837d278d321db1b" # template tip at mint -role = "minted-repo" -stack = "unspecified" -direction = "generated-from-core" - -[normalise] -rules = "action-pins self-name" - -[paths.added] -paths = [] - -[paths.removed] -paths = [] - -[paths.diverged] -# Files a minted repo owns from birth. -paths = [ - ".machine_readable/descriptiles/CLADE.a2ml", - ".machine_readable/descriptiles/VARIANT.a2ml", - ".machine_readable/rsr-profile.a2ml", -] diff --git a/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml b/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml deleted file mode 100644 index f3af0ed7f..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/anchors/ANCHOR.a2ml +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# ANCHOR.a2ml - authoritative anchor for this repository - -[metadata] -version = "1.0.0" -last-updated = "2026-09-28" - -[anchor] -schema = "hyperpolymath.anchor/1" -repo = "hyperpolymath/czech-file-knife" -authority = "upstream-canonical" - -purpose = [ - "Define canonical semantics and policy boundaries for this repository.", - "Declare what downstream/satellite repos can extend but not redefine.", - "Provide a stable golden path and invariant contract for release readiness.", -] - -[identity] -project = "Czech File Knife" -kind = "tool" # language | library | service | tool -one-sentence = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." -domain = "storage / hybrid filesystems" - -[semantic-authority] -policy = "canonical" - -owns = [ - "Project semantics and specification", - "Invariant definitions and contractiles", - "Reference implementation behavior", -] - -[implementation-policy] -allowed = ["Rust", "Idris2", "Zig", "Scheme", "Shell", "Just", "AsciiDoc", "Markdown"] -forbidden = ["Node.js", "npm"] - -[golden-path] -smoke-test-command = [ - "just test", - "just quality", -] - -success-criteria = [ - "Core tests pass", - "Quality gates pass", - "No unresolved critical security findings", -] - -[satellite-policy] -must-pin-upstream = true -must-declare-authority = true -must-have-anchor = true -must-have-golden-path = true - -[semantic-authority-files] -language-spec = "SPECIFICATION.md" -formal-proofs = "docs/proofs/PROOFS.adoc" -type-theory = "docs/theory/THEORY.adoc" -algorithms = "docs/theory/ALGORITHMS.adoc" diff --git a/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc b/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc deleted file mode 100644 index 13cae630d..000000000 --- a/czech-file-knife/.machine_readable/descriptiles/anchors/README.adoc +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -# A2ML Anchor Directory - -This directory contains ANCHOR.a2ml files for project recalibration and scope intervention. - -## Files - -- `ANCHOR.a2ml` - Project recalibration, scope intervention, canonical authority - -## Multiple Versions - -Unlike other A2ML files, multiple versions of ANCHOR.a2ml with different dates may exist. -Each version represents a specific recalibration point in the project history. - -## Standards Compliance - -These files follow the ANCHOR.a2ml specification from: -https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml - -## See Also - -- [A2ML Repository Template](https://github.com/hyperpolymath/standards/blob/main/A2ML-REPO-TEMPLATE.adoc) -- [Anchor A2ML Spec](https://github.com/hyperpolymath/standards/tree/main/anchor-a2ml) - diff --git a/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml b/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml deleted file mode 100644 index 691bb72cd..000000000 --- a/czech-file-knife/.machine_readable/integrations/feedback-o-tron.a2ml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# OPTIONAL: Feedback-o-Tron Integration — Autonomous Bug Reporting -# Delete this file if your project does not use feedback-o-tron. - -[integration] -name = "feedback-o-tron" -type = "bug-reporter" -repository = "https://github.com/hyperpolymath/feedback-o-tron" - -[reporting-config] -platforms = ["github", "gitlab", "bugzilla"] -deduplication = true -audit-logging = true -auto-file-upstream = "on-external-dependency-failure" diff --git a/czech-file-knife/.machine_readable/integrations/groove.a2ml b/czech-file-knife/.machine_readable/integrations/groove.a2ml deleted file mode 100644 index b4905aa57..000000000 --- a/czech-file-knife/.machine_readable/integrations/groove.a2ml +++ /dev/null @@ -1,38 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; Groove Protocol Manifest — declares API surfaces this project exposes. -; -; Consumed by the Groove bridge / zig-unified-api-adapter for snap-on/snap-off -; service discovery. Edit this file to match your project's actual APIs. -; -; See: https://github.com/hyperpolymath/standards/tree/main/groove-protocol - -(groove-manifest - (version "1.0") - - ; Service identity — replace czech-file-knife with your project name - (service "czech-file-knife") - (service-version "0.1.0") - - ; Primary port — MUST be unique across the ecosystem. - ; Check PORT-REGISTRY.md in the standards repo before assigning. - ; https://github.com/hyperpolymath/standards/blob/main/PORT-REGISTRY.md - (port 0) ; 0 = not assigned yet — run `just groove-setup` to assign - - ; API surfaces this project exposes (dodeca-API) - ; Remove lines for API types you don't use. - (api-surfaces - (rest (enabled true) (path "/api/v1")) - (grpc (enabled false) (port-offset 1)) - (graphql (enabled false) (path "/graphql")) - (websocket (enabled false) (path "/ws")) - (sse (enabled false) (path "/events")) - (groove (enabled true) (path "/.well-known/groove"))) - - ; Health endpoint — used by Groove discovery - (health "/health") - - ; Capabilities — what this service can do for others - (capabilities ()) - - ; Dependencies — what this service needs from others - (dependencies ())) diff --git a/czech-file-knife/.machine_readable/integrations/proven.a2ml b/czech-file-knife/.machine_readable/integrations/proven.a2ml deleted file mode 100644 index 96a8a7d8b..000000000 --- a/czech-file-knife/.machine_readable/integrations/proven.a2ml +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# OPTIONAL: Proven Integration — Formally Verified Safety Library -# Delete this file if your project does not use the proven library. -# See https://github.com/hyperpolymath/proven for details. - -[integration] -name = "proven" -type = "safety-library" -repository = "https://github.com/hyperpolymath/proven" -version = "1.2.0" - -[binding-policy] -approach = "thin-ffi-wrapper" -unsafe-patterns = "replace-with-proven-equivalent" -modules-available = ["SafeMath", "SafeString", "SafeJSON", "SafeURL", "SafeRegex", "SafeSQL", "SafeFile", "SafeTemplate", "SafeCrypto"] - -[adoption-guidance] -priority = "high" -scope = "all-string-json-url-crypto-operations" -migration = "incremental — replace unsafe patterns as encountered" diff --git a/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml b/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml deleted file mode 100644 index 78ca1f0ef..000000000 --- a/czech-file-knife/.machine_readable/integrations/verisimdb.a2ml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# OPTIONAL: VeriSimDB Feed — Cross-Repo Analytics Data Store -# Delete this file if your project does not feed data to VeriSimDB. -# See https://github.com/hyperpolymath/nextgen-databases for details. - -[integration] -name = "verisimdb" -type = "data-feed" -repository = "https://github.com/hyperpolymath/nextgen-databases" -data-store = "verisimdb-data" - -[feed-config] -emit-scan-results = true -emit-build-metrics = true -emit-dependency-graph = true -format = "hexad" -destination = "verisimdb-data/feeds/" diff --git a/czech-file-knife/.machine_readable/integrations/vexometer.a2ml b/czech-file-knife/.machine_readable/integrations/vexometer.a2ml deleted file mode 100644 index 2f7ef8029..000000000 --- a/czech-file-knife/.machine_readable/integrations/vexometer.a2ml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# OPTIONAL: Vexometer Integration — Irritation Surface Analysis -# Delete this file if your project does not use vexometer. - -[integration] -name = "vexometer" -type = "friction-measurement" -repository = "https://github.com/hyperpolymath/vexometer" - -[measurement-config] -dimensions = 10 -emit-isa-reports = true -lazy-eliminator = true -satellite-interventions = true - -[hooks] -cli-tools = "measure-on-error" -ui-panels = "measure-on-interaction" -build-failures = "measure-on-failure" diff --git a/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore b/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore deleted file mode 100644 index 2c8c4096a..000000000 --- a/czech-file-knife/.machine_readable/policies/.maintenance-perms-ignore +++ /dev/null @@ -1,5 +0,0 @@ -# Regex patterns for justified permission-policy exceptions. -# One pattern per line. -# Example: -# ^vendor/ -# ^third_party/ diff --git a/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml b/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml deleted file mode 100644 index 530b08f04..000000000 --- a/czech-file-knife/.machine_readable/policies/MAINTENANCE-AXES.a2ml +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Canonical maintenance governance model - -[metadata] -version = "1.0.0" -last-updated = "2026-09-28" -scope = "repo" - -[discovery] -human-entrypoints = [ - "README.adoc", - "docs/maintenance/MAINTENANCE-CHECKLIST.md", - "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc", -] -machine-entrypoints = [ - ".machine_readable/policies/MAINTENANCE-AXES.a2ml", - ".machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml", - ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml", - ".machine_readable/descriptiles/META.a2ml", - ".machine_readable/ai/README.adoc", - ".machine_readable/bot_directives/README.scm", -] -bots = ["hypatia", "gitbot-fleet", "repo visitors"] - -[axes] -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" -execution-order = "axis-1 > axis-2 > axis-3" - -[axis-1-scoping] -required = true -sources = "README, roadmap, status docs, maintenance checklist, CI/security docs" -markers = "TODO/FIXME/XXX/HACK/STUB/PARTIAL" -idris-unsound-markers = "believe_me/assert_total" -output = "scoped work assembly in must/intend/like buckets" - -[axis-2-maintenance] -corrective-first = true -adaptive-second = true -adaptive-focus = "scope changes, stale references, obsolete work culling" -perfective-third = true -perfective-source = "honest state from axis-1 after corrective/adaptive updates" - -[axis-3-audit] -systems-check = true -compliance-check = true -effects-check = true -compliance-focus = "seams/compromises/exception register and anti-drift" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" -effects-evidence = "benchmark evidence and maintainer dialogue/status review" diff --git a/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml b/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml deleted file mode 100644 index 357aa675e..000000000 --- a/czech-file-knife/.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml +++ /dev/null @@ -1,159 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Cross-repo maintenance baseline (machine-readable canonical) - -[metadata] -version = "1.1.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/governance/MAINTENANCE-CHECKLIST.adoc" -companion-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc" -companion-machine = ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml" - -[policy] -single-source = true -notes = "Use this file as canonical machine policy and keep markdown synchronized." - -[maintenance-axes] -scoping-first = true -execution-order = ["scoping", "axis-1", "axis-2", "axis-3"] -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -inputs_required = [ - "README", - "roadmap", - "status-docs", - "maintenance-checklist", - "ci-and-security-docs", -] - -marker_scan_required = [ - "TODO", - "FIXME", - "XXX", - "HACK", - "STUB", - "PARTIAL", -] - -idris_unsound_scan_required = [ - "believe_me", - "assert_total", -] - -scope_assembly_buckets = ["must", "intend", "like"] - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive_examples = [ - "scope-change reconciliation", - "stale-reference removal", - "obsolete-work culling", -] -perfective-third = true -perfective_source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -systems-check = true -documentation-honesty-check = true -safety-security-accounted-check = true -effects-review-check = true -benchmark-evidence-required = true -maintainer-dialogue-review-required = true -compliance-seams-check = true -exception-register-required = true -exception-bounded-scope-required = true -policy-drift-contamination-check = true -example-drift-risk = "single TypeScript exception causing broad ->TypeScript migration" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[generic-cleanup-finish-off] -root-cleanup-required = true -stale-work-cull-required = true -docs-parity-required = true -machine-human-sync-required = true -compliance-finish-off-required = true -effects-finish-off-required = true -release-prep-summary-required = true -next-actions-required = ["corrective", "adaptive", "perfective"] - -[must] -root_control_files = [ - ".gitignore", - ".gitattributes", - ".editorconfig", - ".tool-versions", - "Containerfile", - "Justfile", -] - -root_hosting_files = [ - "CNAME", - ".nojekyll", -] - -ownership_files = [ - "MAINTAINER", - ".github/CODEOWNERS", -] - -machine_readable_required = [ - ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", - ".machine_readable/contractiles/", - ".machine_readable/ai/", - ".machine_readable/bot_directives/", -] - -contractiles_required = [ - "Mustfile", - "Trustfile", - "Intentfile", -] - -security_required = [ - "www/.well-known/security.txt", - "ci-security-scan", -] - -quality_gate_required = [ - "format", - "lint", - "unit-tests", - "integration-tests", - "p2p-tests", - "e2e-tests", - "bench-smoke", - "docs-check", - "security-scan", -] - -abi_ffi_policy = [ - "ABI Idris2 in src/interface/abi/*.idr", - "FFI Zig in ffi/**/*.zig", -] - -[should] -docs_primary_format = "adoc" -docs_structure = [ - "docs/theory", - "docs/practice", - "docs/whitepapers/academic", - "docs/whitepapers/industry", - "docs/proofs", - "docs/reports", -] - -root_minimization = true -well_known_metadata = true -roadmap_honesty_with_dates = true -ci_doc_format_policy = true - -[could] -generate_human_from_machine = true -mode_aware_bots = ["corrective", "adaptive", "perfective", "audit"] -topology_dashboard = true -exception_registry = true diff --git a/czech-file-knife/.machine_readable/policies/README.adoc b/czech-file-knife/.machine_readable/policies/README.adoc deleted file mode 100644 index 57ea33a17..000000000 --- a/czech-file-knife/.machine_readable/policies/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= policies Registry diff --git a/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml b/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml deleted file mode 100644 index d7967d22b..000000000 --- a/czech-file-knife/.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml +++ /dev/null @@ -1,53 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# General software development approach (machine-readable) - -[metadata] -version = "1.0.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc" - -[execution] -order = ["axis-1", "axis-2", "axis-3"] - -[axis-1] -name = "scope" -priority = "must > intend > like" -inputs = [ - "README", - "roadmap", - "status-docs", - "ci-and-security-docs", -] -marker-scan = ["TODO", "FIXME", "XXX", "HACK", "STUB", "PARTIAL"] -idris-unsound-scan = ["believe_me", "assert_total"] -output = "scoped-work-assembly" - -[axis-2] -name = "maintenance" -priority = "corrective > adaptive > perfective" -corrective = "defect/regression/safety/security fixes" -adaptive = "scope reconciliation, stale-reference removal, obsolete-work culling" -perfective = "quality improvements derived from axis-1 honest state" - -[axis-3] -name = "audit" -priority = "systems > compliance > effects" -systems = "required systems present and operating" -compliance = "exceptions explicit, bounded, and drift-resistant" -effects = "benchmark/operational impact evidence captured and reviewed" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[cleanup-finish-off] -root-cleanup = true -stale-work-cull = true -docs-sync-human-machine = true -compliance-audit = true -effects-audit = true -release-summary = ["must", "should", "could"] -next-actions = ["corrective", "adaptive", "perfective"] - -[collaboration] -maintainer-dialogue-required = true -dialogue-topics = ["what changed", "why", "remaining risks"] diff --git a/czech-file-knife/.machine_readable/root-allow.txt b/czech-file-knife/.machine_readable/root-allow.txt deleted file mode 100644 index 4ef9815db..000000000 --- a/czech-file-knife/.machine_readable/root-allow.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Canonical root allowlist for RSR-templated repositories. -# -# Lists every entry permitted at the repository root. The check is -# BIDIRECTIONAL: -# * anything at root that is not listed here is drift, and fails; -# * anything listed here WITHOUT the '?' marker must exist, and its -# absence fails. -# -# The second half is the important one. This file previously permitted a -# large set of root files that the April 2026 root cleanup had already -# relocated into docs/ (READINESS.adoc, PROOF-*, TOPOLOGY.adoc, -# llm-warmup-*, ...). Because the check was one-directional, those stale -# permissions were invisible: the allowlist had quietly become a licence -# for the very drift it was written to prevent. A one-directional -# allowlist only ever ratchets open. -# -# Used by: scripts/check-root-shape.sh -# -# Format -# one entry per line; '#' starts a comment; trailing '/' marks a directory -# '?' prefix -> OPTIONAL: permitted, but not required to exist -# no prefix -> REQUIRED: permitted, and its absence is drift -# -# Mark an entry '?' when it is legitimately absent in some conforming repo: -# template-only material removed at mint, or a module gated on a capability -# the repo does not declare (see standards' template-capability-gates.toml). -# Do NOT mark something optional merely to silence a failure. - -# ─── Baseline: every RSR repo has these ────────────────────────────────────── -README.adoc -LICENSE -LICENSES/ # REUSE licence texts, dual-licence model (code MPL-2.0 / docs CC-BY-SA-4.0) -CHANGELOG.adoc # AsciiDoc is the estate-standard documentation format -CITATION.cff # citation metadata; GitHub reads it from the root of the default branch only -czech-file-knife_chora.deed # the repo deed: universal AI entry point; family-7 allocation manifest + ply tree folded in (standards#837 pilot). Filename carries the repo slug (deed dispatch _chora.deed), so repo-init renames it at mint -CLAUDE.md # generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) -?GEMINI.md # pointer to CLAUDE.md for repos without AGENTS.md yet -Justfile # thin; delegates phases to build/just/*.just -coordination.k9.ncl # repo-local session binding (template-mandated) - -# ─── Conventional dotfiles (tool-required at root) ─────────────────────────── -.editorconfig -.envrc -.gitattributes -.gitignore -.gitleaksignore # exact fingerprints for reviewed historical false positives -.cicd-hygiene-allow # code-hygiene gate allowlist; template scaffolds are seams, not debt -.gitmessage # git commit template; wired by .github/hooks/install.sh (git config commit.template) -.mailmap # git reads .mailmap from the worktree root only -.tool-versions -mise.toml # pinned toolchains -.hypatia-ignore # the Hypatia scanner reads it from the repo root -.clinerules # AI editor rules. Cline/Cursor/Windsurf read these from the project -.cursorrules # ROOT, so the copies formerly under .machine_readable/ai/ were inert. -.windsurfrules -?.claude/ # Claude Code project state (checkpoints, history) — created by the tool itself - -# ─── Directories ───────────────────────────────────────────────────────────── -.git/ -.github/ # community health + workflows (GitHub reads this path and no other) + versioned git hooks (.github/hooks/, wired via core.hooksPath) -www/ # site-operations bundle; canonical .well-known/ lives at www/.well-known/ (issue #53) -.machine_readable/ # manifests, contractiles, policies. Renamed back from machine-readable/ 2026-09-17 by owner ruling, for one canonical spelling estate-wide (census at the 2026-08 divergence: 48 dotted vs 9 hyphenated — the majority was already dotted). See docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc -docs/ # human documentation -build/ # build orchestration: just/ phase modules, container/, docs-seed/, templates/, guix.scm (canon 1.2.1 guix-primary template_ref = "build/") -ci/ # .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require -scripts/ # repo helper scripts -session/ # dispatch.sh, custom-checks.k9.ncl, local-hooks.sh -src/ -tests/ - -# ─── Optional: capability-gated or template-only ───────────────────────────── -?archetypes/ # template-only variation seam (ADR-0003); `just repo-init` removes it from minted repos -?.devcontainer/ # VS Code dev container spec; present only where the container capability is declared -?sonar-project.properties # only where the repo is analysed by SonarCloud -?Cargo.toml # rust capability (template-capability-gates.toml); cargo requires the workspace manifest at root -?Cargo.lock # rust capability; lives beside Cargo.toml -?.clusterfuzzlite/ # ClusterFuzzLite reads its build config from the repo root only -?benches/ # Cargo-conventional at package root -?examples/ # Cargo-conventional at package root -?features/ # optional feature packs -?verification/ # proofs; only where formal-proofs is declared -?CONTRIBUTING.md # accepted at root OR .github/; .github/ is the canonical estate location -?CONTRIBUTING.adoc # estate docs gate (standards scripts/check-docs-presence.sh) requires CONTRIBUTING at the ROOT; this is the copy it verifies -?SECURITY.md # accepted at root OR .github/; likewise -?REQUIRES_INITIALISATION.md # written by the minting process, removed once `just repo-init` runs diff --git a/czech-file-knife/.machine_readable/rsr-profile.a2ml b/czech-file-knife/.machine_readable/rsr-profile.a2ml deleted file mode 100644 index c3435d1ae..000000000 --- a/czech-file-knife/.machine_readable/rsr-profile.a2ml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# rsr-profile.a2ml — this repo's declared RSR v2.0 capabilities. -# Generated by just repo-init on 2026-09-28. The template's maximal -# profile was removed at mint: declare only what this tree carries. - -[rsr-profile] -version = "1.0.0" -spec = "rsr-criteria-v2" -declares-against = "2.0.0-draft" -# Preset rust-cli (rust, cli, library) plus what the tree carries: -# container — build/container/ -# reproducible-build — build/guix.scm -# benchmarks — benches/ -# formal-proofs — verification/ (scaffold today; targets are listed in -# docs/status/PROOF-NEEDS.adoc: journal reversibility, -# op;undo as a certified null op, squeeze crash-safety) -# published-package — .github/workflows/release.yml (crates + packaging) -# governance-tier — docs/{AUDIT,AFFIRMATION,GOVERNANCE,MAINTAINERS}.adoc -# -# NOT declared: abi / ffi / zig. The template's Idris2 ABI + Zig FFI seam -# (src/interface/) was removed at extraction because CFK does not use it; -# the only FFI today is the C ABI in src/cfk-ios consumed by Swift. Re-add -# the seam (and these capabilities) if/when a verified ABI is introduced. -preset = "rust-cli" -capabilities = ["rust", "cli", "library", "container", "reproducible-build", "benchmarks", "formal-proofs", "published-package", "governance-tier"] - -[rationale] -governance-tier = "Standalone estate tool with a maintainer roster, release audit gate and signed honesty snapshot." -formal-proofs = "Reversibility is the product's core promise; proof targets are tracked in PROOF-NEEDS." diff --git a/czech-file-knife/.machine_readable/scripts/forge/README.adoc b/czech-file-knife/.machine_readable/scripts/forge/README.adoc deleted file mode 100644 index a7414d611..000000000 --- a/czech-file-knife/.machine_readable/scripts/forge/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Forge Scripts diff --git a/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh b/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh deleted file mode 100755 index 330e54b3c..000000000 --- a/czech-file-knife/.machine_readable/scripts/forge/forge-sync.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# forge-sync.sh — Multi-forge mirroring script -# -# Synchronises the local repository with GitHub, GitLab, and Codeberg. -# Usage: ./forge-sync.sh - -set -euo pipefail - -REMOTES=("origin" "gitlab" "codeberg") - -echo "=== RSR Forge Synchronisation ===" - -for remote in "${REMOTES[@]}"; do - if git remote | grep -q "^$remote$"; then - echo "Pushing to $remote..." - git push "$remote" --all - git push "$remote" --tags - else - echo "Skip: Remote '$remote' not configured." - fi -done - -echo "Sync complete." diff --git a/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh b/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh deleted file mode 100755 index 88fb52f15..000000000 --- a/czech-file-knife/.machine_readable/scripts/forge/git-cleanup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# git-cleanup.sh — Repository hygiene script -set -euo pipefail -echo "Cleaning up merged branches..." -git fetch -p -git branch --merged | grep -v "\*" | grep -v "main" | xargs -n 1 git branch -d || echo "No branches to clean." -echo "Pruning remote tracking branches..." -git remote prune origin diff --git a/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc b/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc deleted file mode 100644 index 178c055be..000000000 --- a/czech-file-knife/.machine_readable/scripts/lifecycle/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Lifecycle Scripts diff --git a/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh b/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh deleted file mode 100755 index c6f8230c6..000000000 --- a/czech-file-knife/.machine_readable/scripts/lifecycle/install-tools.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# install-tools.sh — Developer toolchain installer -# -# Detects and installs the required project toolchain (Guix or asdf). - -set -euo pipefail - -echo "=== RSR Toolchain Installer ===" - -if [ -f "build/guix.scm" ] && command -v guix &>/dev/null; then - echo "Guix detected. Verifying development shell..." - guix shell -f build/guix.scm -- true && echo "Guix shell verified." -elif [ -f ".tool-versions" ] && command -v asdf &>/dev/null; then - echo "asdf detected. Installing plugins and tools..." - while read -r line; do - plugin=$(echo "$line" | awk '{print $1}') - asdf plugin add "$plugin" || true - done < .tool-versions - asdf install -else - echo "No standard toolchain (Guix/asdf) detected or installed." - echo "Please refer to README.adoc for manual setup instructions." -fi - -echo "Installer complete." diff --git a/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh b/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh deleted file mode 100644 index 6e19d2ae4..000000000 --- a/czech-file-knife/.machine_readable/scripts/maintenance/maint-assault.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# maint-assault.sh — High-rigor stress testing using panic-attacker -# -# This script runs a full assault (static + dynamic) on the project binary -# to detect logic-based bug signatures and environmental vulnerabilities. - -set -euo pipefail - -BINARY_NAME="czech_file_knife" -REPORT_PATH="docs/reports/security/assault-latest.json" -PA_BIN="${PANIC_ATTACK_BIN:-panic-attack}" - -echo "=== High-Rigor Security Assault ===" - -# 1. Verify environment -if ! command -v "$PA_BIN" &>/dev/null; then - echo "Error: panic-attack tool not found." - echo "Please install it or set PANIC_ATTACK_BIN environment variable." - exit 1 -fi - -if [ ! -f "target/release/$BINARY_NAME" ]; then - echo "Warning: Release binary not found at target/release/$BINARY_NAME" - echo "Running build first..." - just build --release -fi - -# 2. Run Assault -echo "Initiating full assault on $BINARY_NAME..." -mkdir -p "$(dirname "$REPORT_PATH")" - -"$PA_BIN" assault "target/release/$BINARY_NAME" - --source . - --intensity medium - --duration 10 - --output "$REPORT_PATH" - -echo "" -echo "=== Assault Complete ===" -echo "Report generated: $REPORT_PATH" -echo "To review interactively, run:" -echo " $PA_BIN tui $REPORT_PATH" diff --git a/czech-file-knife/.machine_readable/scripts/verification/README.adoc b/czech-file-knife/.machine_readable/scripts/verification/README.adoc deleted file mode 100644 index eb2828e3f..000000000 --- a/czech-file-knife/.machine_readable/scripts/verification/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Verification Scripts diff --git a/czech-file-knife/.machine_readable/self-validating/README.adoc b/czech-file-knife/.machine_readable/self-validating/README.adoc deleted file mode 100644 index 4aca2fd63..000000000 --- a/czech-file-knife/.machine_readable/self-validating/README.adoc +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= K9 Contractiles -:toc: left -:icons: font - -== What Are K9 Contractiles? - -K9 contractiles are self-validating components that combine configuration, validation, and deployment logic in a single file format. They implement the RSR principle of "self-describing artifacts" by embedding contracts and orchestration directly in the component. - -== The Three Security Levels - -K9 components declare their trust requirements using "The Leash" security model: - -[horizontal] -`'Kennel`:: Pure data, no execution (safest) -`'Yard`:: Nickel evaluation with contracts (medium trust) -`'Hunt`:: Full execution with Just recipes (requires signature) - -== Example Components - -This directory contains example K9 contractiles for common repository tasks: - -=== Kennel Level (Pure Data) - -**File:** `examples/project-metadata.k9.ncl` - -Pure configuration data with no execution. Safe to include in any repository. - -**Use cases:** -- Project metadata (name, version, description) -- Build configuration -- Tool settings -- Data schemas - -**Security:** No signature required, data-only. - -=== Yard Level (Validated Config) - -**File:** `examples/ci-config.k9.ncl` - -Configuration with Nickel contracts for runtime validation. Evaluated safely without I/O. - -**Use cases:** -- CI/CD configuration with validation -- Deployment parameters -- Database schemas with constraints -- API specifications - -**Security:** Signature recommended, Nickel evaluation only. - -=== Hunt Level (Full Execution) - -**File:** `examples/setup-repo.k9.ncl` - -Full execution with Just recipes. Can run shell commands and modify filesystem. - -**Use cases:** -- Repository setup scripts -- Deployment automation -- System configuration -- Package installation - -**Security:** **Signature required**, full system access. - -== Usage in Your Repository - -=== 1. Create K9 Components - -Choose the appropriate security level for your use case: - -[source,bash] ----- -# Kennel: Pure configuration -cp .machine_readable/contractiles/k9/examples/project-metadata.k9.ncl config/metadata.k9.ncl - -# Yard: Validated configuration -cp .machine_readable/contractiles/k9/examples/ci-config.k9.ncl .github/ci.k9.ncl - -# Hunt: Full automation -cp .machine_readable/contractiles/k9/examples/setup-repo.k9.ncl scripts/setup.k9.ncl ----- - -=== 2. Validate Components - -[source,bash] ----- -# Validate Nickel syntax and contracts -nickel typecheck config/metadata.k9.ncl - -# Verify Hunt-level signature (if signed) -./must verify scripts/setup.k9.ncl ----- - -=== 3. Execute Components - -[source,bash] ----- -# Kennel: Export as JSON -nickel export config/metadata.k9.ncl > metadata.json - -# Yard: Evaluate with validation -nickel eval .github/ci.k9.ncl - -# Hunt: Run with Just (dry-run first!) -./must --dry-run run scripts/setup.k9.ncl -./must run scripts/setup.k9.ncl ----- - -== Integration with RSR - -K9 contractiles integrate with other RSR standards: - -**STATE.a2ml**:: K9 components can generate or validate STATE.a2ml -**ECOSYSTEM.a2ml**:: K9 can automate cross-repo operations -**META.a2ml**:: K9 can enforce architectural decisions - -== Security Best Practices - -=== For Kennel/Yard Components - -✅ **Safe to use without signatures** + -✅ **Review Nickel code before use** + -✅ **Validate contracts match expectations** - -=== For Hunt Components - -⚠️ **ALWAYS verify signatures** + -⚠️ **Review Just recipes carefully** + -⚠️ **Run dry-run mode first** + -⚠️ **Never run as root unless required** + -⚠️ **Sandbox external components** - -**See:** https://github.com/hyperpolymath/k9-svc/blob/main/docs/SECURITY-BEST-PRACTICES.adoc - -== Template Files - -Use these as starting points for your own K9 components: - -- `template-kennel.k9.ncl` - Pure data template -- `template-yard.k9.ncl` - Validated config template -- `template-hunt.k9.ncl` - Full execution template - -== Dependencies - -To use K9 contractiles in your repository: - -[source,bash] ----- -# Install Nickel (configuration language) -curl -L https://github.com/tweag/nickel/releases/latest/download/nickel-linux-x86_64 -o nickel -chmod +x nickel && sudo mv nickel /usr/local/bin/ - -# Install Just (task runner, for Hunt level) -cargo install just - -# Clone K9-SVC (for must shim and tooling) -git clone https://github.com/hyperpolymath/k9-svc.git ----- - -== Learn More - -- **K9-SVC Specification:** https://github.com/hyperpolymath/k9-svc/blob/main/SPEC.adoc -- **K9 User Guide:** https://github.com/hyperpolymath/k9-svc/blob/main/GUIDE.adoc -- **Security Documentation:** https://github.com/hyperpolymath/k9-svc/blob/main/docs/SECURITY-FAQ.adoc -- **IANA Media Type:** `application/vnd.k9+nickel` - -== Contributing - -When adding K9 contractiles to your repository: - -1. Use appropriate security level (Kennel > Yard > Hunt) -2. Document what each component does -3. Include validation contracts in Yard/Hunt components -4. Sign Hunt-level components before committing -5. Add K9 validation to CI/CD pipeline - -**Questions?** Open an issue on https://github.com/hyperpolymath/k9-svc diff --git a/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl deleted file mode 100644 index 9fe314e2d..000000000 --- a/czech-file-knife/.machine_readable/self-validating/examples/ci-config.k9.ncl +++ /dev/null @@ -1,126 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Example Yard-level K9 component: CI/CD configuration with validation -# Security Level: Yard (Nickel evaluation, contract validation) -# Signature recommended but not required - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "ci-configuration", - security = { - leash = 'Yard, - trust_level = "validated-config", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - metadata = { - name = "ci-config", - version = "1.0.0", - description = "CI/CD configuration with runtime validation", - author = "Jonathan D.A. Jewell ", - }, - }, - - # CI/CD configuration with Nickel contracts - ci = { - # Platform must be a known CI provider - platform - | [| 'GitHubActions, 'GitLabCI, 'CircleCI, 'TravisCI |] - = 'GitHubActions, - - # Build matrix with validation - matrix = { - # Operating systems to test on - os - | Array String - | std.array.NonEmpty - = ["ubuntu-latest", "macos-latest"], - - # Language versions to test - versions - | Array String - | std.array.NonEmpty - = ["stable", "beta"], - }, - - # Workflow steps with validation - steps = [ - { - name = "Checkout", - action = "actions/checkout@v4", - # Version must be SHA-pinned for security - sha | String | std.string.NonEmpty = "b4ffde65f46336ab88eb53be808477a3936bae11", - }, - { - name = "Build", - run = "just build", - }, - { - name = "Test", - run = "just test", - }, - { - name = "Lint", - run = "just lint", - }, - ], - - # Deployment configuration - deploy = { - enabled | Bool = false, - - # Only deploy from main branch - branch - | String - | std.contract.from_predicate (fun b => b == "main" || b == "master") - = "main", - - # Deployment requires manual approval - requires_approval | Bool = true, - }, - - # Security scanning - security = { - enabled | Bool = true, - - scanners = [ - { - name = "CodeQL", - languages = ["rust", "javascript"], - }, - { - name = "OSSF Scorecard", - enabled = true, - }, - { - name = "TruffleHog", - scan_for = "secrets", - }, - ], - }, - - # Notification settings - notifications = { - on_success = "never", - on_failure = "always", - channels = ["email"], - }, - }, - - # Validation rules (enforced by Nickel) - validation = { - # At least one OS must be specified - check_os = std.array.length ci.matrix.os > 0, - - # At least one version must be tested - check_versions = std.array.length ci.matrix.versions > 0, - - # Must have at least build and test steps - check_steps = std.array.length ci.steps >= 2, - - # Security scanning must be enabled - check_security = ci.security.enabled == true, - }, -} diff --git a/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl deleted file mode 100644 index 5de965d6d..000000000 --- a/czech-file-knife/.machine_readable/self-validating/examples/project-metadata.k9.ncl +++ /dev/null @@ -1,57 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Example Kennel-level K9 component: Project metadata -# Security Level: Kennel (pure data, no execution) -# No signature required - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "project-metadata", - security = { - leash = 'Kennel, - trust_level = "data-only", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - metadata = { - name = "project-metadata", - version = "1.0.0", - description = "Pure data configuration for project metadata", - author = "Jonathan D.A. Jewell ", - }, - }, - - # Project configuration - project = { - name = "my-project", - version = "0.1.0", - description = "A project following Rhodium Standard Repositories", - - repository = { - url = "https://github.com/hyperpolymath/my-project", - type = "git", - }, - - author = { - name = "Jonathan D.A. Jewell", - email = "j.d.a.jewell@open.ac.uk", - organization = "", - }, - - license = "MPL-2.0", - - keywords = [ - "rhodium-standard", - "rsr", - "hyperpolymath", - ], - }, - - # Export as JSON for other tools - export = { - format = "json", - destination = "project-metadata.json", - }, -} diff --git a/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl b/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl deleted file mode 100644 index 4c2d4aef8..000000000 --- a/czech-file-knife/.machine_readable/self-validating/examples/setup-repo.k9.ncl +++ /dev/null @@ -1,167 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Example Hunt-level K9 component: Repository setup automation -# Security Level: Hunt (full execution with Just recipes) -# ⚠️ SIGNATURE REQUIRED - DO NOT RUN WITHOUT VERIFICATION - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "repository-setup", - security = { - leash = 'Hunt, - trust_level = "full-system-access", - allow_network = true, - allow_filesystem_write = true, - allow_subprocess = true, - signature_required = true, - }, - metadata = { - name = "setup-repo", - version = "1.0.0", - description = "Automated repository setup with RSR standards", - author = "Jonathan D.A. Jewell ", - }, - warnings = [ - "This component has full system access", - "Only run from trusted sources with verified signatures", - "Review Just recipes before execution", - "Use dry-run mode first: ./must --dry-run run setup-repo.k9.ncl", - ], - }, - - # Configuration with contracts - config = { - repo_name - | String - | std.string.NonEmpty - = "my-new-repo", - - repo_type - | [| 'Library, 'Application, 'Tool, 'Specification |] - = 'Application, - - primary_language - | String - | std.string.NonEmpty - = "rust", - - # RSR compliance features to enable - features = { - checkpoint_files | Bool = true, # STATE.a2ml, ECOSYSTEM.a2ml, META.a2ml - security_workflows | Bool = true, # CodeQL, Scorecard, etc. - quality_checks | Bool = true, # Linting, formatting - mirroring | Bool = false, # GitLab/Bitbucket mirrors - }, - - # Git configuration - git = { - default_branch = "main", - initial_commit | Bool = true, - remote_url | String = "", - }, - }, - - # Just recipes for execution - # These run when: ./must run setup-repo.k9.ncl - recipes = { - # Main entry point - default = { - recipe = "setup", - description = "Set up RSR-compliant repository", - }, - - # Individual setup tasks - setup = { - dependencies = ["check-env", "create-structure", "init-git", "setup-workflows"], - commands = [ - "echo '✅ Repository setup complete!'", - "echo 'Run: git status to see changes'", - ], - }, - - "check-env" = { - description = "Verify required tools are installed", - commands = [ - "command -v git || (echo 'ERROR: git not found' && exit 1)", - "command -v just || (echo 'ERROR: just not found' && exit 1)", - "command -v nickel || (echo 'ERROR: nickel not found' && exit 1)", - "echo '✓ All required tools present'", - ], - }, - - "create-structure" = { - description = "Create RSR directory structure", - commands = [ - "mkdir -p src/ docs/ tests/ scripts/", - "mkdir -p .github/workflows/", - "mkdir -p .machine_readable/contractiles/k9/", - "echo '✓ Directory structure created'", - ], - }, - - "init-git" = { - description = "Initialize Git repository", - commands = [ - "git init -b %{config.git.default_branch}", - "git config user.name 'Jonathan D.A. Jewell'", - "git config user.email 'j.d.a.jewell@open.ac.uk'", - "echo '✓ Git initialized'", - ], - }, - - "setup-workflows" = { - description = "Add RSR-compliant workflows", - commands = [ - # This would copy workflow templates - # In a real implementation, would fetch from czech-file-knife - "echo '✓ Workflows configured'", - ], - }, - - "create-checkpoint-files" = { - description = "Create STATE.a2ml, ECOSYSTEM.a2ml, META.a2ml", - commands = [ - "echo '(state (version \"1.0.0\") (project \"%{config.repo_name}\"))' > STATE.a2ml", - "echo '(ecosystem (version \"1.0.0\") (name \"%{config.repo_name}\"))' > ECOSYSTEM.a2ml", - "echo '(meta (version \"1.0.0\") (project \"%{config.repo_name}\"))' > META.a2ml", - "echo '✓ Checkpoint files created'", - ], - }, - - "add-license" = { - description = "Add MPL-2.0 license", - commands = [ - "cp LICENSES/MPL-2.0.txt LICENSE", - "echo '✓ License added'", - ], - }, - - "add-readme" = { - description = "Create README.adoc from template", - commands = [ - "echo '= %{config.repo_name}' > README.adoc", - "echo '' >> README.adoc", - "echo 'Part of the Hyperpolymath ecosystem.' >> README.adoc", - "echo '✓ README created'", - ], - }, - - clean = { - description = "Remove generated files (careful!)", - commands = [ - "echo '⚠️ This will delete all generated files'", - "echo 'Press Ctrl+C to cancel, or wait 5 seconds...'", - "sleep 5", - "rm -f STATE.a2ml ECOSYSTEM.a2ml META.a2ml", - "echo '✓ Cleaned'", - ], - }, - }, - - # Validation (Yard-level checks before Hunt execution) - validation = { - check_repo_name = std.string.length config.repo_name > 0, - check_language = std.string.length config.primary_language > 0, - }, -} diff --git a/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl b/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl deleted file mode 100644 index b4e7658ec..000000000 --- a/czech-file-knife/.machine_readable/self-validating/methodology-guard.k9.ncl +++ /dev/null @@ -1,79 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# K9 Validator: Methodology Guard -# Checks that agent work respects methodology constraints declared in -# bot_directives/methodology.a2ml. -# -# Usage: k9 validate methodology-guard - -let methodology_guard = { - name = "methodology-guard", - version = "1.0.0", - description = "Validates that agent work respects declared methodology constraints", - - pedigree = { - schema_version = "1.0.0", - metadata = { - name = "methodology-guard", - version = "1.0.0", - }, - security = { - leash = 'Yard, - }, - }, - - checks = { - divergent_invariant_language = { - description = "No files in languages violating the divergent language invariant", - severity = "error", - # When methodology.divergent-invariants.language-invariant is set, - # check that no new files introduce a different language for that purpose. - # Example: if language-invariant = "idris2", reject new .lean or .v files - # in the proof directories. - check_type = "file-extension-guard", - scope = "src/", - }, - - believe_me_ceiling = { - description = "believe_me count must not exceed declared ceiling", - severity = "error", - pattern = "believe_me", - ceiling_key = "methodology.divergent-invariants.believe-me-ceiling", - default_ceiling = 0, - }, - - assert_total_ceiling = { - description = "assert_total count must not exceed declared ceiling", - severity = "error", - pattern = "assert_total", - ceiling_key = "methodology.divergent-invariants.assert-total-ceiling", - default_ceiling = 0, - }, - - state_not_template = { - description = "STATE.a2ml must not contain template placeholders", - severity = "warning", - file = ".machine_readable/descriptiles/STATE.a2ml", - # NOTE: the PROJECT token below is written as a Nickel concat - # ("{{" ++ "PROJECT}}") ON PURPOSE. `just repo-init` runs a sed substitution for - # the brace-PROJECT-brace token over EVERY text file, which would otherwise - # rewrite this guard's own pattern into the consumer's name and break the - # check. Splitting the literal across "++" keeps the contiguous token text - # out of the file (in this comment too) so init cannot match it, while - # Nickel still evaluates the element back to the full token. Do not - # "simplify" it to a plain string. (The PLACEHOLDER token and - # "czech-file-knife" are not init tokens, so they survive as-is.) - reject_patterns = ["{{PLACEHOLDER}}", "{{" ++ "PROJECT}}", "czech-file-knife"], - }, - - coverage_updated = { - description = "coverage.a2ml should be updated within 30 days", - severity = "info", - file = ".machine_readable/bot_directives/coverage.a2ml", - staleness_days = 30, - }, - }, -} -in methodology_guard diff --git a/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl deleted file mode 100644 index b3fcb4753..000000000 --- a/czech-file-knife/.machine_readable/self-validating/template-hunt.k9.ncl +++ /dev/null @@ -1,136 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# K9 Hunt-level template: Full execution with Just recipes -# Security Level: Hunt (full system access) -# ⚠️ SIGNATURE REQUIRED - Review carefully before use - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "TODO: describe component type (e.g., 'deployment', 'setup-script')", - security = { - leash = 'Hunt, - trust_level = "full-system-access", - allow_network = true, - allow_filesystem_write = true, - allow_subprocess = true, - signature_required = true, - }, - metadata = { - name = "TODO: component-name", - version = "1.0.0", - description = "TODO: Detailed description of what this component does", - author = "Jonathan D.A. Jewell ", - }, - warnings = [ - "This component has full system access", - "Only run from trusted sources with verified signatures", - "Review all Just recipes before execution", - "Use dry-run mode first: ./must --dry-run run your-file.k9.ncl", - ], - side_effects = [ - "TODO: List what files/directories this creates or modifies", - "TODO: List what commands this executes", - "TODO: List what network access this requires", - ], - }, - - # Configuration with contracts (Yard-level validation) - config = { - # Add your configuration here with appropriate contracts - target_dir - | String - | std.string.NonEmpty - = "/tmp/k9-output", - - dry_run | Bool = false, - - # Add more config as needed - }, - - # Just recipes for execution - # These run when: ./must run your-file.k9.ncl - recipes = { - # Main entry point (runs by default) - default = { - recipe = "TODO: main-task", - description = "TODO: What the default recipe does", - }, - - # Define your recipes here - "main-task" = { - dependencies = ["check-prerequisites"], - commands = [ - "echo 'TODO: Add your commands here'", - # Example: Create directory - # "mkdir -p %{config.target_dir}", - # Example: Run a command - # "just build", - # Example: Conditional execution - # "@if [ \"%{config.dry_run}\" = \"true\" ]; then echo '[DRY-RUN] Would execute'; else actual-command; fi", - ], - }, - - "check-prerequisites" = { - description = "Verify required tools and permissions", - commands = [ - # Example: Check for required tools - # "command -v git || (echo 'ERROR: git not found' && exit 1)", - # Example: Check permissions - # "[ -w %{config.target_dir} ] || (echo 'ERROR: Cannot write to target directory' && exit 1)", - "echo '✓ Prerequisites checked'", - ], - }, - - # Add more recipes as needed - "build" = { - description = "Build the project", - commands = [ - "echo 'TODO: Add build commands'", - ], - }, - - "deploy" = { - description = "Deploy the application", - dependencies = ["build"], - commands = [ - "echo 'TODO: Add deployment commands'", - ], - }, - - "clean" = { - description = "Clean up generated files", - commands = [ - "echo '⚠️ This will delete files - waiting 3 seconds...'", - "sleep 3", - "echo 'TODO: Add cleanup commands'", - # "rm -rf %{config.target_dir}", - ], - }, - }, - - # Validation (Yard-level checks before Hunt execution) - validation = { - check_target_dir = std.string.length config.target_dir > 0, - # Add more validation as needed - }, -} - -# Usage: -# 1. Fill in TODO items above -# 2. Define configuration with contracts -# 3. Implement Just recipes with your commands -# 4. Test with dry-run: ./must --dry-run run your-file.k9.ncl -# 5. Review dry-run output carefully -# 6. Sign the component: ./must sign your-file.k9.ncl -# 7. Distribute with signature: your-file.k9.ncl.sig -# 8. Users verify and run: ./must verify && ./must run your-file.k9.ncl -# -# Security checklist: -# ✓ All TODO items filled in -# ✓ side_effects documented accurately -# ✓ Commands reviewed for safety -# ✓ No hardcoded secrets or credentials -# ✓ Proper error handling in recipes -# ✓ Tested in dry-run mode -# ✓ Component signed with trusted key diff --git a/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl deleted file mode 100644 index 4228b26c8..000000000 --- a/czech-file-knife/.machine_readable/self-validating/template-kennel.k9.ncl +++ /dev/null @@ -1,54 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# K9 Kennel-level template: Pure data configuration -# Security Level: Kennel (data-only, no execution) -# No signature required - safe for any use - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "TODO: describe component type (e.g., 'build-config', 'metadata')", - security = { - leash = 'Kennel, - trust_level = "data-only", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - metadata = { - name = "TODO: component-name", - version = "1.0.0", - description = "TODO: Brief description of what this component contains", - author = "Jonathan D.A. Jewell ", - }, - }, - - # Your configuration data here - config = { - # Example: Pure data values - setting_1 = "value", - setting_2 = 42, - setting_3 = true, - - nested = { - key = "value", - }, - - list = [ - "item1", - "item2", - ], - }, - - # Optional: Export format specification - export = { - format = "json", # or "yaml", "toml" - destination = "output.json", - }, -} - -# Usage: -# 1. Fill in TODO items above -# 2. Add your configuration data to config = { ... } -# 3. Validate: nickel typecheck your-file.k9.ncl -# 4. Export: nickel export your-file.k9.ncl > output.json diff --git a/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl b/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl deleted file mode 100644 index a723f5afd..000000000 --- a/czech-file-knife/.machine_readable/self-validating/template-yard.k9.ncl +++ /dev/null @@ -1,84 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# K9 Yard-level template: Configuration with validation -# Security Level: Yard (Nickel evaluation with contracts) -# Signature recommended but not required - -{ - pedigree = { - schema_version = "1.0.0", - component_type = "TODO: describe component type (e.g., 'validated-config', 'schema')", - security = { - leash = 'Yard, - trust_level = "validated-config", - allow_network = false, - allow_filesystem_write = false, - allow_subprocess = false, - }, - metadata = { - name = "TODO: component-name", - version = "1.0.0", - description = "TODO: Brief description with validation details", - author = "Jonathan D.A. Jewell ", - }, - }, - - # Configuration with Nickel contracts for validation - config = { - # Example: String that cannot be empty - name - | String - | std.string.NonEmpty - = "TODO: default value", - - # Example: Number with range constraint - port - | Number - | std.contract.from_predicate (fun p => p > 0 && p < 65536) - = 8080, - - # Example: Boolean flag - enabled | Bool = true, - - # Example: Enum (one of several values) - environment - | [| 'Development, 'Staging, 'Production |] - = 'Development, - - # Example: List with non-empty constraint - items - | Array String - | std.array.NonEmpty - = ["item1", "item2"], - - # Example: Nested object with contracts - database = { - host | String | std.string.NonEmpty = "localhost", - port | Number | std.contract.from_predicate (fun p => p > 0 && p < 65536) = 5432, - name | String | std.string.NonEmpty = "mydb", - }, - }, - - # Validation rules (additional cross-field checks) - validation = { - # Example: Check that at least one item exists - check_items = std.array.length config.items > 0, - - # Example: Check that production has secure settings - check_production = - if config.environment == 'Production then - config.enabled == true - else - true, - - # Add your custom validation rules here - }, -} - -# Usage: -# 1. Fill in TODO items above -# 2. Define your config with appropriate contracts -# 3. Add validation rules in validation = { ... } -# 4. Validate: nickel typecheck your-file.k9.ncl -# 5. Evaluate: nickel eval your-file.k9.ncl -# 6. If validation passes, use in your application diff --git a/czech-file-knife/.mailmap b/czech-file-knife/.mailmap deleted file mode 100644 index 9a973b3af..000000000 --- a/czech-file-knife/.mailmap +++ /dev/null @@ -1 +0,0 @@ -# No alternate author address to map. diff --git a/czech-file-knife/.tool-versions b/czech-file-knife/.tool-versions deleted file mode 100644 index cb500621a..000000000 --- a/czech-file-knife/.tool-versions +++ /dev/null @@ -1,9 +0,0 @@ -# Uncomment and customize for your project -rust stable -just 1.40.0 -# nickel 1.10.0 -# gleam 1.8.0 -# elixir 1.18.0 -# erlang 27.2 -# zig 0.14.0 -# idris2 0.7.0 diff --git a/czech-file-knife/.windsurfrules b/czech-file-knife/.windsurfrules deleted file mode 100644 index 5852e71dd..000000000 --- a/czech-file-knife/.windsurfrules +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# Authoritative source: docs/practice/AI-CONVENTIONS.adoc - -# STARTUP: Read the repo deed (*_chora.deed at the repo root) first, then .machine_readable/descriptiles/STATE.a2ml. - -# LICENSE -# All original code: MPL-2.0. -# Never AGPL-3.0. MPL-2.0 only as platform-required fallback. -# SPDX header required on every source file. -# Copyright: Jonathan D.A. Jewell (hyperpolymath) - -# STATE FILES (.machine_readable/ ONLY) -# Never create in repo root: STATE.deed, META.deed, ECOSYSTEM.deed, -# AGENTIC.deed, NEUROSYM.deed, PLAYBOOK.deed. -# The .machine_readable/ directory is the single source of truth. - -# BANNED PATTERNS -# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO -# Haskell: unsafeCoerce, unsafePerformIO, undefined, error -# OCaml: Obj.magic, Obj.repr, Obj.obj -# Coq: Admitted -# Lean: sorry -# Rust: transmute (unless FFI with // SAFETY: comment) - -# JS/TS RUNTIMES — ordered preference, reach for the first that can do the job -# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) -# 1. Bun default for all new work; runs .ts directly, no build step -# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm -# 3. pnpm only where an upstream toolchain needs a node_modules layout -# 4. npm last resort; permitted, never preferred — a noted decision -# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is -# RETIRED: ReScript is no longer used in this estate, so that rule named a dead -# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. - -# BANNED LANGUAGES -# Go -> Rust -# Python -> Julia or Rust - -# CONTAINERS -# Runtime: Podman (never Docker). -# File: Containerfile (never Dockerfile). -# Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. - -# ABI/FFI -# This repo does not carry the estate Idris2/Zig ABI seam (not declared in -# .machine_readable/rsr-profile.a2ml). The only FFI is the C ABI in -# src/cfk-ios (Swift File Provider bridge). - -# BUILD: Use just (justfile) for all tasks. -# STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/czech-file-knife/CHANGELOG.adoc b/czech-file-knife/CHANGELOG.adoc deleted file mode 100644 index 345168472..000000000 --- a/czech-file-knife/CHANGELOG.adoc +++ /dev/null @@ -1,85 +0,0 @@ -== Changelog - -All notable changes to this project will be documented in this file. - -The format is based on https://keepachangelog.com/en/1.1.0/[Keep a -Changelog], and this project adheres to -https://semver.org/spec/v2.0.0.html[Semantic Versioning]. - -=== [Unreleased] - -==== Added - -* `www/` site-operations bundle (issue #53): canonical `.well-known/` - metadata, publishable content, policy sources, error bodies, - security-header templates, Caddy/nginx/Apache integration examples, an - authoritative-only BIND 9 starting hand with record templates, - encrypted-DNS/privacy and TLS guidance, explicit opt-in profiles, - schemas, planted-control tests and operational runbooks. -* `scripts/migrate-wellknown-to-www.sh` — conflict-safe migration of a - repository-root `.well-known/` into `www/.well-known/` (identical copies - de-duplicated, root-only content moved, divergent content quarantined — - see Changed). -* `scripts/sweep-wellknown.sh` — stage 5 (#119) batch driver: classify, - migrate, test and commit across the estate in batches of 25, holding - Pages-served and non-RSR repositories for a human decision instead of - sweeping them blind. -* `www/runbooks/stage5-wellknown-sweep.adoc` — how to run the sweep, and - how to recover from a quarantine. -* Mint wiring for the `www/` bundle: `repo-init` now invokes - `scripts/migrate-wellknown-to-www.sh` when the tree being minted still - carries a legacy root `.well-known/`, and runs `www/tests/run-all.sh` - afterwards. Both are no-ops for a repository minted from the current - template; they exist for running `repo-init` over a tree that predates - #53. #106 described both as already present — neither was, which is a - fair part of why the estate still carries root `.well-known/` directories. - -==== Changed - -* Canonical `.well-known/` location moved from the repository root to - `www/.well-known/`. Well-known enforcement, Groove checks, the root-shape - allowlist, the SSG bootstrap and the documentation now prefer the `www/` - location and accept the legacy root location with a warning during the - migration window. - -==== Changed - -* Divergent root/`www/` `.well-known/` content is now quarantined to - `www/.legacy-well-known-/` instead of being left in place: the - estate-wide sweep (#119) is unattended, so a conflict must not halt it, and - must not be silent either. The `www/` copy is untouched, both hashes are - printed, and the run exits non-zero. `--in-place` keeps the previous - contract (preserve both, exit 1) for hand resolution. The quarantine lives - under `www/` rather than at the root because the root allowlist is matched - literally and bidirectionally, so a dated root directory would report as - drift in every repository swept. - -==== Fixed - -* `dot-wellknown-enforcement.yml` now drives the migrator that #106 described - but never wired up: previously the workflow only printed advice to run it, - so nothing verified that a repository would survive the migration. It now - runs the migrator in `--dry-run` and turns every conflict into a - `::warning` annotation — a warning, not an error, for the migration window. -* The migrator's acceptance test had encoded the in-place contract as the - required behaviour, contradicting #106's stated design; it now proves the - quarantine contract across nine scenarios, including the collision case - where a quarantine name is already taken. -* `tests/e2e/template_instantiation_test.sh` fed the three container answers - only when `container/` existed, but `build/just/repo-init.just` asks those - questions when `build/container/` does. On any complete checkout — which is - what CI clones — the recipe asked three questions the test had no answers - for, `read` hit EOF, and the recipe exited 1, so the instantiation e2e was - red at the container prompt. The guard now matches the recipe's own path. - (The test also needed `ruby` for the two mint tools. Those are Rust as - of 2026-09-18, so it no longer stops there.) - -* Restore the modular `just` recipes removed by the root-layout refactor, so - `just repo-init`, validation, assessment, container, Groove, and proof tasks - are available again. -* Require lowercase hyphenated repository slugs at initialization and verify - that both Guix package definitions receive the rendered slug and project URL. -* Keep template validation aligned with authority documents moved under - `docs/`, and repair the cross-platform `cloak`/`uncloak` recipe syntax. -* Restore the tracked-file language-ban gate with regression fixtures proving - that supported Zig passes while prohibited V-language remnants fail. diff --git a/czech-file-knife/CITATION.cff b/czech-file-knife/CITATION.cff deleted file mode 100644 index 90bc56757..000000000 --- a/czech-file-knife/CITATION.cff +++ /dev/null @@ -1,17 +0,0 @@ -cff-version: 1.2.0 -message: "If you use this software, please cite it as below." -authors: - - family-names: "Jewell" - given-names: "Jonathan D.A." - orcid: "https://orcid.org/0000-0000-0000-0000" # Placeholder -title: "Czech File Knife" -version: 0.1.0 -date-released: "2026-09-28" -url: "https://github.com/hyperpolymath/czech-file-knife" -repository-code: "https://github.com/hyperpolymath/czech-file-knife" -license: MPL-2.0 -keywords: - - "rsr" - - "formal-verification" - - "neurosymbolic" - - "provenance" diff --git a/czech-file-knife/CLAUDE.md b/czech-file-knife/CLAUDE.md deleted file mode 100644 index b59c234bb..000000000 --- a/czech-file-knife/CLAUDE.md +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -# You are in the hyperpolymath estate — orient before acting - -If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. - -## Doctrine (the rules here) -1. **Holes before anything else** — fix soundness holes before features/perf/docs. -2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. -3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. -4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. -5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. -6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. -7. **No deletion by access-recency** — cold ≠ disposable. -8. **Wire first** — unwired is not done. -9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). -10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). -11. **Stop-first** when an action is costly to undo or outward-facing. -12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. -13. **Equivalence as identity** — the estate's intellectual through-line. -14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. -15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. - -## The machine-readable substrate (read in this order on arrival) -**CHORA** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** -> **NEUROSYM** -> **PLAYBOOK** - -The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. - -| File | Answers | -|---|---| -| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | -| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | -| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | -| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | -| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | -| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | -| `PLAYBOOK.a2ml` | *How* permitted actions run. | -| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | -| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | -| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | - -## Canon pointers -- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. -- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** - -## Estate language policy (overridable per-repo via AGENTIC) -Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) -JavaScript tooling order: **Bun** (default) > Deno (being removed — owner ruling 2026-08-26, standards#655) > pnpm > npm (last resort, permitted). -Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. - ---- - -# This repo: `czech-file-knife` · clade `rm-czech-file-knife` - -- **Identity** — uuid `a5ea1382-a34c-5334-8a46-a2ebe904c810`; clade `rm` (secondary ``); born 2026-03-16; forge `hyperpolymath/czech-file-knife`. -- **IS** — Canonical RSR-compliant repository template: scaffolding (CI/CD, AI manifests, ABI/FFI standards, container ecosystem, governance) that new hyperpolymath projects are instantiated from. -- **IS-NOT** — a project in its own right · Scaffoldia (the full-featured repo designer) · standards (the canon source this template operationalises) -- **Where it sits** — pipeline position **foundation**; chain `standards → czech-file-knife → (every estate repo)`; coordination = `standards`. -- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. -- **Golden path** (ANCHOR) — `just test && just quality` → Core tests pass; Quality gates pass; No unresolved critical security findings. -- **State** — phase maintenance; maturity production; 95% complete; status active. - - diff --git a/czech-file-knife/CONTRIBUTING.adoc b/czech-file-knife/CONTRIBUTING.adoc deleted file mode 100644 index c5fdf1ac4..000000000 --- a/czech-file-knife/CONTRIBUTING.adoc +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: Jonathan D.A. Jewell (hyperpolymath) -= Contributing — czech-file-knife -:toc: left - -Contributors work on the *spine*: the template every RSR repo is minted -from. Changes here propagate to the whole estate at mint time, so the bar -is the estate's bar. - -== Local-dev setup - -[source,bash] ----- -# Clone the repository -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife - -# Using Guix (recommended for reproducibility) -guix shell -D -f build/guix.scm - -# Or using toolbox/distrobox -toolbox create czech-file-knife-dev -toolbox enter czech-file-knife-dev -# Install dependencies manually - -# Verify setup -just check # or: cargo check / mix compile / etc. -just test # Run test suite ----- - -== Repository structure - -The authoritative map is *generated* from the tree and checked in CI, so -it cannot drift: -link:docs/architecture/REPOSITORY-MAP.adoc[docs/architecture/REPOSITORY-MAP.adoc]. -Regenerate it with `just repo-map`. - -A hand-written tree used to live here. It described `lib/`, `extensions/`, -`plugins/` and `spec/` directories that this repository has never -contained, which is precisely why the map is now generated rather than -typed. - -== How to contribute - -=== Reporting bugs - -*Before reporting:* - -. Search existing issues. -. Check if it is already fixed in `main`. -. Determine which perimeter the bug affects. - -*When reporting*, use the -link:.github/ISSUE_TEMPLATE/bug_report.md[bug report template] and include: - -* Clear, descriptive title -* Environment details (OS, versions, toolchain) -* Steps to reproduce -* Expected vs actual behaviour -* Logs, screenshots, or minimal reproduction - -=== Suggesting features - -*Before suggesting:* - -. Check the link:docs/status/ROADMAP.adoc[roadmap] if available. -. Search existing issues and discussions. -. Consider which perimeter the feature belongs to. - -*When suggesting*, use the -link:.github/ISSUE_TEMPLATE/feature_request.md[feature request template] -and include: - -* Problem statement (what pain point does this solve?) -* Proposed solution -* Alternatives considered -* Which perimeter this affects - -=== Your first contribution - -Look for issues labelled: - -* https://github.com/hyperpolymath/czech-file-knife/labels/good%20first%20issue[`good first issue`] — Simple Perimeter 3 tasks -* https://github.com/hyperpolymath/czech-file-knife/labels/help%20wanted[`help wanted`] — Community help needed -* https://github.com/hyperpolymath/czech-file-knife/labels/documentation[`documentation`] — Docs improvements -* https://github.com/hyperpolymath/czech-file-knife/labels/perimeter-3[`perimeter-3`] — Community sandbox scope - -== Development workflow - -=== Branch naming - -[source,text] ----- -docs/short-description # Documentation (P3) -test/what-added # Test additions (P3) -feat/short-description # New features (P2) -fix/issue-number-description # Bug fixes (P2) -refactor/what-changed # Code improvements (P2) -security/what-fixed # Security fixes (P1-2) ----- - -=== Commit messages - -We follow https://www.conventionalcommits.org/[Conventional Commits]: - -[source,text] ----- -(): - -[optional body] - -[optional footer] ----- - -== Cross-references - -This root document exists because the estate docs gate -(`hyperpolymath/standards` `scripts/check-docs-presence.sh`) requires -`CONTRIBUTING.md`, `CONTRIBUTING.adoc`, or `3-practice/CONTRIBUTING.adoc` -at the repository root. The legacy copy at -link:.github/CONTRIBUTING.md[.github/CONTRIBUTING.md] predates that gate; -this document supersedes it. diff --git a/czech-file-knife/Cargo.lock b/czech-file-knife/Cargo.lock deleted file mode 100644 index f5972a90a..000000000 --- a/czech-file-knife/Cargo.lock +++ /dev/null @@ -1,6638 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "addr" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef" -dependencies = [ - "psl-types", -] - -[[package]] -name = "affinitypool" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dde2a385b82232b559baeec740c37809051c596f9b56e7da0d0da2c8e8f54f6" -dependencies = [ - "async-channel", - "num_cpus", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "ammonia" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" -dependencies = [ - "cssparser", - "html5ever", - "maplit", - "tendril", - "url", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "any_ascii" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c6333e01ba7235575b6ab53e5af10f1c327927fd97c36462917e289557ea64" - -[[package]] -name = "approx" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" -dependencies = [ - "num-traits", -] - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ar_archive_writer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" -dependencies = [ - "object", -] - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "arcstr" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" - -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures 0.2.17", - "password-hash", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-slice" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" -dependencies = [ - "generic-array 0.12.4", - "generic-array 0.13.3", - "generic-array 0.14.7", - "stable_deref_trait", -] - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-graphql" -version = "7.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036618f842229ba0b89652ffe425f96c7c16a49f7e3cb23b56fca7f61fd74980" -dependencies = [ - "async-graphql-derive", - "async-graphql-parser", - "async-graphql-value", - "async-stream", - "async-trait", - "base64 0.22.1", - "bytes", - "fnv", - "futures-timer", - "futures-util", - "http", - "indexmap 2.12.1", - "mime", - "multer", - "num-traits", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "static_assertions_next", - "thiserror 1.0.69", -] - -[[package]] -name = "async-graphql-derive" -version = "7.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd45deb3dbe5da5cdb8d6a670a7736d735ba65b455328440f236dfb113727a3d" -dependencies = [ - "Inflector", - "async-graphql-parser", - "darling 0.20.11", - "proc-macro-crate", - "proc-macro2", - "quote", - "strum", - "syn 2.0.111", - "thiserror 1.0.69", -] - -[[package]] -name = "async-graphql-parser" -version = "7.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b7607e59424a35dadbc085b0d513aa54ec28160ee640cf79ec3b634eba66d3" -dependencies = [ - "async-graphql-value", - "pest", - "serde", - "serde_json", -] - -[[package]] -name = "async-graphql-value" -version = "7.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecdaff7c9cffa3614a9f9999bf9ee4c3078fe3ce4d6a6e161736b56febf2de" -dependencies = [ - "bytes", - "indexmap 2.12.1", - "serde", - "serde_json", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "aws-lc-rs" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" - -[[package]] -name = "bcrypt" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" -dependencies = [ - "base64 0.22.1", - "blowfish", - "getrandom 0.2.16", - "subtle", - "zeroize", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.111", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitpacking" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" -dependencies = [ - "crunchy", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "blake3" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - -[[package]] -name = "bon" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1" -dependencies = [ - "bon-macros", - "rustversion", -] - -[[package]] -name = "bon-macros" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645" -dependencies = [ - "darling 0.21.3", - "ident_case", - "prettyplease", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.111", -] - -[[package]] -name = "borsh" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "bytesize" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cedar-policy" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d91e3b10a0f7f2911774d5e49713c4d25753466f9e11d1cd2ec627f8a2dc857" -dependencies = [ - "cedar-policy-core", - "cedar-policy-validator", - "itertools 0.10.5", - "lalrpop-util", - "ref-cast", - "serde", - "serde_json", - "smol_str", - "thiserror 1.0.69", -] - -[[package]] -name = "cedar-policy-core" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd2315591c6b7e18f8038f0a0529f254235fd902b6c217aabc04f2459b0d9995" -dependencies = [ - "either", - "ipnet", - "itertools 0.10.5", - "lalrpop", - "lalrpop-util", - "lazy_static", - "miette", - "regex", - "rustc_lexer", - "serde", - "serde_json", - "serde_with", - "smol_str", - "stacker", - "thiserror 1.0.69", -] - -[[package]] -name = "cedar-policy-validator" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e756e1b2a5da742ed97e65199ad6d0893e9aa4bd6b34be1de9e70bd1e6adc7df" -dependencies = [ - "cedar-policy-core", - "itertools 0.10.5", - "serde", - "serde_json", - "serde_with", - "smol_str", - "stacker", - "thiserror 1.0.69", - "unicode-security", -] - -[[package]] -name = "census" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "cfk-cache" -version = "0.1.0" -dependencies = [ - "async-trait", - "blake3", - "bytes", - "cfk-core", - "chrono", - "directories", - "heed", - "hex", - "lz4_flex 0.13.1", - "redb", - "redis", - "serde", - "serde_json", - "sled", - "surrealdb", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "cfk-cli" -version = "0.1.0" -dependencies = [ - "bytes", - "bytesize", - "cfk-core", - "cfk-providers", - "chrono", - "clap", - "console", - "futures", - "indicatif", - "tabled", - "tokio", -] - -[[package]] -name = "cfk-core" -version = "0.1.0" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "futures", - "serde", - "thiserror 2.0.17", - "tokio", -] - -[[package]] -name = "cfk-integrations" -version = "0.1.0" -dependencies = [ - "async-trait", - "cfk-core", - "regex", - "serde", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "cfk-ios" -version = "0.1.0" -dependencies = [ - "async-trait", - "block", - "bytes", - "cfk-cache", - "cfk-core", - "cfk-providers", - "chrono", - "futures", - "libc", - "objc", - "objc-foundation", - "once_cell", - "serde", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "cfk-providers" -version = "0.1.0" -dependencies = [ - "async-trait", - "blake3", - "bytes", - "cfk-core", - "chrono", - "futures", - "libc", - "oauth2", - "reqwest 0.13.1", - "serde", - "serde_json", - "tempfile", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "cfk-search" -version = "0.1.0" -dependencies = [ - "async-trait", - "cfk-core", - "chrono", - "regex", - "serde", - "tantivy", - "thiserror 2.0.17", - "tokio", -] - -[[package]] -name = "cfk-vfs" -version = "0.1.0" -dependencies = [ - "async-trait", - "cfk-core", - "cfk-providers", - "dashmap 6.1.0", - "fuser", - "parking_lot 0.12.5", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.1", - "rand_core 0.10.1", -] - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "futures-core", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width 0.2.2", - "windows-sys 0.61.2", -] - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array 0.14.7", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "phf", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.111", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core 0.23.0", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.12", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core 0.9.12", -] - -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "deunicode" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "dmp" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2dfc7a18dffd3ef60a442b72a827126f1557d914620f8fc4d1049916da43c1" -dependencies = [ - "trice", - "urlencoding", -] - -[[package]] -name = "double-ended-peekable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57" - -[[package]] -name = "downcast-rs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" - -[[package]] -name = "doxygen-rs" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" -dependencies = [ - "phf", -] - -[[package]] -name = "dtoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "earcutr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" -dependencies = [ - "itertools 0.11.0", - "num-traits", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "ext-sort" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5d3b056bcc471d38082b8c453acb6670f7327fd44219b3c411e40834883569" -dependencies = [ - "log", - "rayon", - "rmp-serde", - "serde", - "tempfile", -] - -[[package]] -name = "fastdivide" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "float_next_after" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "fs4" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" -dependencies = [ - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "fst" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "fuser" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367" -dependencies = [ - "libc", - "log", - "memchr", - "nix", - "page_size", - "smallvec", - "zerocopy", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "geo" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f811f663912a69249fa620dcd2a005db7254529da2d8a0b23942e81f47084501" -dependencies = [ - "earcutr", - "float_next_after", - "geo-types", - "geographiclib-rs", - "log", - "num-traits", - "robust", - "rstar 0.12.2", - "serde", - "spade", -] - -[[package]] -name = "geo-types" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f8647af4005fa11da47cd56252c6ef030be8fa97bdbf355e7dfb6348f0a82c" -dependencies = [ - "approx 0.5.1", - "num-traits", - "rstar 0.10.0", - "rstar 0.11.0", - "rstar 0.12.2", - "rstar 0.8.4", - "rstar 0.9.3", - "serde", -] - -[[package]] -name = "geographiclib-rs" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" -dependencies = [ - "libm", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -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]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.12.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hash32" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash 0.8.12", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heapless" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" -dependencies = [ - "as-slice", - "generic-array 0.14.7", - "hash32 0.1.1", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32 0.2.1", - "rustc_version", - "spin", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32 0.3.1", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "heed" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a56c94661ddfb51aa9cdfbf102cfcc340aa69267f95ebccc4af08d7c530d393" -dependencies = [ - "bitflags 2.10.0", - "byteorder", - "heed-traits", - "heed-types", - "libc", - "lmdb-master-sys", - "once_cell", - "page_size", - "serde", - "synchronoise", - "url", -] - -[[package]] -name = "heed-traits" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" - -[[package]] -name = "heed-types" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d" -dependencies = [ - "bincode", - "byteorder", - "heed-traits", - "serde", - "serde_json", -] - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "html5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" -dependencies = [ - "log", - "markup5ever", - "match_token", -] - -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "humantime" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.4", -] - -[[package]] -name = "hyper-util" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "hyperloglogplus" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621debdf94dcac33e50475fdd76d34d5ea9c0362a834b9db08c3024696c1fbe3" -dependencies = [ - "serde", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "indicatif" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" -dependencies = [ - "console", - "portable-atomic", - "unicode-width 0.2.2", - "unit-prefix", - "web-time", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "9.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" -dependencies = [ - "base64 0.22.1", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "levenshtein_automata" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" - -[[package]] -name = "lexicmp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378d131ddf24063b32cbd7e91668d183140c4b3906270635a4d633d1068ea5d" -dependencies = [ - "any_ascii", -] - -[[package]] -name = "libc" -version = "0.2.178" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.10.0", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linfa-linalg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e7562b41c8876d3367897067013bb2884cc78e6893f092ecd26b305176ac82" -dependencies = [ - "ndarray", - "num-traits", - "rand 0.8.7", - "thiserror 1.0.69", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lmdb-master-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864808e0b19fb6dd3b70ba94ee671b82fce17554cf80aeb0a155c65bb08027df" -dependencies = [ - "cc", - "doxygen-rs", - "libc", -] - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "lz4-sys" -version = "1.11.1+lz4-1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "lz4_flex" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" - -[[package]] -name = "lz4_flex" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" -dependencies = [ - "twox-hash", -] - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "markup5ever" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" -dependencies = [ - "log", - "tendril", - "web_atoms", -] - -[[package]] -name = "match_token" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "matrixmultiply" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" -dependencies = [ - "autocfg", - "rawpointer", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "measure_time" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" -dependencies = [ - "log", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "memmap2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" -dependencies = [ - "libc", -] - -[[package]] -name = "miette" -version = "5.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" -dependencies = [ - "miette-derive", - "once_cell", - "thiserror 1.0.69", - "unicode-width 0.1.14", -] - -[[package]] -name = "miette-derive" -version = "5.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - -[[package]] -name = "murmurhash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" - -[[package]] -name = "ndarray" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" -dependencies = [ - "approx 0.4.0", - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "rawpointer", -] - -[[package]] -name = "ndarray-stats" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5a8477ac96877b5bd1fd67e0c28736c12943aba24eda92b127e036b0c8f400" -dependencies = [ - "indexmap 1.9.3", - "itertools 0.10.5", - "ndarray", - "noisy_float", - "num-integer", - "num-traits", - "rand 0.8.7", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "noisy_float" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fe6e6ebc0bf53de533cd456ca2d9de13de13856eda1518a285d7705a213af" -dependencies = [ - "num-traits", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "oauth2" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" -dependencies = [ - "base64 0.21.7", - "chrono", - "getrandom 0.2.16", - "http", - "rand 0.8.7", - "reqwest 0.12.28", - "serde", - "serde_json", - "serde_path_to_error", - "sha2", - "thiserror 1.0.69", - "url", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "object_store" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1be0c6c22ec0817cdc77d3842f721a17fd30ab6965001415b5402a74e6b740" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "futures", - "http", - "humantime", - "itertools 0.14.0", - "parking_lot 0.12.5", - "percent-encoding", - "thiserror 2.0.17", - "tokio", - "tracing", - "url", - "walkdir", - "wasm-bindgen-futures", - "web-time", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "oneshot" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ownedbytes" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbd56f7631767e61784dc43f8580f403f4475bd4aaa4da003e6295e1bab4a7e" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "papergrid" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad43c07024ef767f9160710b3a6773976194758c7919b17e63b863db0bdf7fb" -dependencies = [ - "bytecount", - "fnv", - "unicode-width 0.1.14", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.12", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "path-clean" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pdqselect" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.12.1", -] - -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.7", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.111", - "unicase", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", - "unicase", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.111", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "psm" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" -dependencies = [ - "ar_archive_writer", - "cc", -] - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "quick_cache" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55a1aa7668676bb93926cd4e9cdfe60f03bb866553bcca9112554911b6d3dc" -dependencies = [ - "ahash 0.8.12", - "equivalent", - "hashbrown 0.14.5", - "parking_lot 0.12.5", -] - -[[package]] -name = "quick_cache" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" -dependencies = [ - "ahash 0.8.12", - "equivalent", - "hashbrown 0.16.1", - "parking_lot 0.12.5", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.17", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.17", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", - "serde", -] - -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand 0.8.7", -] - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "reblessive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc4a4ea2a66a41a1152c4b3d86e8954dc087bdf33af35446e6e176db4e73c8c" - -[[package]] -name = "redb" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06" -dependencies = [ - "libc", -] - -[[package]] -name = "redis" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfe20977fe93830c0e9817a16fbf1ed1cfd8d4bba366087a1841d2c6033c251" -dependencies = [ - "arcstr", - "bytes", - "cfg-if", - "combine", - "futures-util", - "itoa", - "num-bigint", - "percent-encoding", - "pin-project-lite", - "ryu", - "sha1_smol", - "socket2", - "tokio", - "tokio-util", - "url", - "xxhash-rust", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.10.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots 1.0.4", -] - -[[package]] -name = "reqwest" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "revision" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f53179a035f881adad8c4d58a2c599c6b4a8325b989c68d178d7a34d1b1e4c" -dependencies = [ - "revision-derive 0.10.0", -] - -[[package]] -name = "revision" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b8ee532f15b2f0811eb1a50adf10d036e14a6cdae8d99893e7f3b921cb227d" -dependencies = [ - "chrono", - "geo", - "regex", - "revision-derive 0.11.0", - "roaring", - "rust_decimal", - "uuid", -] - -[[package]] -name = "revision-derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "revision-derive" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3415e1bc838c36f9a0a2ac60c0fa0851c72297685e66592c44870d82834dfa2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rmp" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" -dependencies = [ - "byteorder", - "num-traits", - "paste", -] - -[[package]] -name = "rmp-serde" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" -dependencies = [ - "byteorder", - "rmp", - "serde", -] - -[[package]] -name = "rmpv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9" -dependencies = [ - "num-traits", - "rmp", -] - -[[package]] -name = "roaring" -version = "0.10.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" -dependencies = [ - "bytemuck", - "byteorder", - "serde", -] - -[[package]] -name = "robust" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" - -[[package]] -name = "rstar" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a45c0e8804d37e4d97e55c6f258bc9ad9c5ee7b07437009dd152d764949a27c" -dependencies = [ - "heapless 0.6.1", - "num-traits", - "pdqselect", - "serde", - "smallvec", -] - -[[package]] -name = "rstar" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40f1bfe5acdab44bc63e6699c28b74f75ec43afb59f3eda01e145aff86a25fa" -dependencies = [ - "heapless 0.7.17", - "num-traits", - "serde", - "smallvec", -] - -[[package]] -name = "rstar" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f39465655a1e3d8ae79c6d9e007f4953bfc5d55297602df9dc38f9ae9f1359a" -dependencies = [ - "heapless 0.7.17", - "num-traits", - "serde", - "smallvec", -] - -[[package]] -name = "rstar" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73111312eb7a2287d229f06c00ff35b51ddee180f017ab6dec1f69d62ac098d6" -dependencies = [ - "heapless 0.7.17", - "num-traits", - "serde", - "smallvec", -] - -[[package]] -name = "rstar" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" -dependencies = [ - "heapless 0.8.0", - "num-traits", - "serde", - "smallvec", -] - -[[package]] -name = "rust-stemmers" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "rust_decimal" -version = "1.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.7", - "rkyv", - "serde", - "serde_json", -] - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_lexer" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86aae0c77166108c01305ee1a36a1e77289d7dc6ca0a3cd91ff4992de2d16a5" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.10.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "password-hash", - "pbkdf2", - "salsa20", - "sha2", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-content" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "indexmap 2.12.1", - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" -dependencies = [ - "base64 0.22.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.12.1", - "schemars 0.9.0", - "schemars 1.1.0", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" -dependencies = [ - "darling 0.23.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" -dependencies = [ - "libc", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simple_asn1" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror 2.0.17", - "time", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "sketches-ddsketch" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" -dependencies = [ - "serde", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "snap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" - -[[package]] -name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "spade" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" -dependencies = [ - "hashbrown 0.15.5", - "num-traits", - "robust", - "smallvec", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "stacker" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - -[[package]] -name = "static_assertions_next" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" - -[[package]] -name = "storekey" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c42833834a5d23b344f71d87114e0cc9994766a5c42938f4b50e7b2aef85b2" -dependencies = [ - "byteorder", - "memchr", - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot 0.12.5", - "phf_shared", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.111", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "surrealdb" -version = "2.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3429154a8b5a98ca39100ba45ef49ae046fb1d0869dff78d78a2670b1b278982" -dependencies = [ - "arrayvec", - "async-channel", - "bincode", - "chrono", - "dmp", - "futures", - "geo", - "getrandom 0.3.4", - "indexmap 2.12.1", - "path-clean", - "pharos", - "reblessive", - "reqwest 0.12.28", - "revision 0.11.0", - "ring", - "rust_decimal", - "rustls", - "rustls-pki-types", - "semver", - "serde", - "serde-content", - "serde_json", - "surrealdb-core", - "thiserror 1.0.69", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing", - "trice", - "url", - "uuid", - "wasm-bindgen-futures", - "wasmtimer", - "ws_stream_wasm", -] - -[[package]] -name = "surrealdb-core" -version = "2.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba423d9e7e665e4c735a1d4669c3a067135e4a574edf88af215f7f2b815e70ed" -dependencies = [ - "addr", - "affinitypool", - "ahash 0.8.12", - "ammonia", - "any_ascii", - "argon2", - "async-channel", - "async-executor", - "async-graphql", - "base64 0.21.7", - "bcrypt", - "bincode", - "blake3", - "bytes", - "castaway", - "cedar-policy", - "chrono", - "ciborium", - "dashmap 5.5.3", - "deunicode", - "dmp", - "ext-sort", - "fst", - "futures", - "fuzzy-matcher", - "geo", - "geo-types", - "getrandom 0.3.4", - "hashbrown 0.14.5", - "hex", - "http", - "ipnet", - "jsonwebtoken", - "lexicmp", - "linfa-linalg", - "md-5", - "ndarray", - "ndarray-stats", - "num-traits", - "num_cpus", - "object_store", - "parking_lot 0.12.5", - "pbkdf2", - "pharos", - "phf", - "pin-project-lite", - "quick_cache 0.5.2", - "radix_trie", - "rand 0.8.7", - "rayon", - "reblessive", - "regex", - "revision 0.11.0", - "ring", - "rmpv", - "roaring", - "rust-stemmers", - "rust_decimal", - "scrypt", - "semver", - "serde", - "serde-content", - "serde_json", - "sha1", - "sha2", - "snap", - "storekey", - "strsim", - "subtle", - "surrealdb-rocksdb", - "surrealkv", - "sysinfo", - "tempfile", - "thiserror 1.0.69", - "tokio", - "tracing", - "trice", - "ulid", - "unicase", - "url", - "uuid", - "vart 0.8.1", - "wasm-bindgen-futures", - "wasmtimer", - "ws_stream_wasm", -] - -[[package]] -name = "surrealdb-librocksdb-sys" -version = "0.17.3+10.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db194f1cf601bb6f2d0f4cbf0931bc3e5a602bac41ef2e9a87eccdfb28b7fed2" -dependencies = [ - "bindgen", - "bzip2-sys", - "cc", - "libc", - "libz-sys", - "lz4-sys", - "zstd-sys", -] - -[[package]] -name = "surrealdb-rocksdb" -version = "0.24.0-surreal.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057727f56d48825ddbe45e4e7401cda6e99d864fbc004e7474b4689a5e72c86d" -dependencies = [ - "libc", - "surrealdb-librocksdb-sys", -] - -[[package]] -name = "surrealkv" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a5041979bdff8599a1d5f6cb7365acb9a79664e2a84e5c4fddac2b3969f7d1" -dependencies = [ - "ahash 0.8.12", - "bytes", - "chrono", - "crc32fast", - "double-ended-peekable", - "getrandom 0.2.16", - "lru", - "parking_lot 0.12.5", - "quick_cache 0.6.18", - "revision 0.10.0", - "vart 0.9.3", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synchronoise" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" -dependencies = [ - "crossbeam-queue", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "sysinfo" -version = "0.33.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tabled" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c998b0c8b921495196a48aabaf1901ff28be0760136e31604f7967b0792050e" -dependencies = [ - "papergrid", - "tabled_derive", - "unicode-width 0.1.14", -] - -[[package]] -name = "tabled_derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c138f99377e5d653a371cdad263615634cfc8467685dfe8e73e2b8e98f44b17" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tantivy" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502915c7381c5cb2d2781503962610cb880ad8f1a0ca95df1bae645d5ebf2545" -dependencies = [ - "aho-corasick", - "arc-swap", - "base64 0.22.1", - "bitpacking", - "bon", - "byteorder", - "census", - "crc32fast", - "crossbeam-channel", - "downcast-rs", - "fastdivide", - "fnv", - "fs4", - "htmlescape", - "hyperloglogplus", - "itertools 0.14.0", - "levenshtein_automata", - "log", - "lru", - "lz4_flex 0.11.6", - "measure_time", - "memmap2", - "once_cell", - "oneshot", - "rayon", - "regex", - "rust-stemmers", - "rustc-hash", - "serde", - "serde_json", - "sketches-ddsketch", - "smallvec", - "tantivy-bitpacker", - "tantivy-columnar", - "tantivy-common", - "tantivy-fst", - "tantivy-query-grammar", - "tantivy-stacker", - "tantivy-tokenizer-api", - "tempfile", - "thiserror 2.0.17", - "time", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-bitpacker" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b04eed5108d8283607da6710fe17a7663523440eaf7ea5a1a440d19a1448b6" -dependencies = [ - "bitpacking", -] - -[[package]] -name = "tantivy-columnar" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b628488ae936c83e92b5c4056833054ca56f76c0e616aee8339e24ac89119cd" -dependencies = [ - "downcast-rs", - "fastdivide", - "itertools 0.14.0", - "serde", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-sstable", - "tantivy-stacker", -] - -[[package]] -name = "tantivy-common" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f880aa7cab0c063a47b62596d10991cdd0b6e0e0575d9c5eeb298b307a25de55" -dependencies = [ - "async-trait", - "byteorder", - "ownedbytes", - "serde", - "time", -] - -[[package]] -name = "tantivy-fst" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" -dependencies = [ - "byteorder", - "regex-syntax", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "768fccdc84d60d86235d42d7e4c33acf43c418258ff5952abf07bd7837fcd26b" -dependencies = [ - "nom", - "serde", - "serde_json", -] - -[[package]] -name = "tantivy-sstable" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8292095d1a8a2c2b36380ec455f910ab52dde516af36321af332c93f20ab7d5" -dependencies = [ - "futures-util", - "itertools 0.14.0", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-fst", - "zstd", -] - -[[package]] -name = "tantivy-stacker" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d38a379411169f0b3002c9cba61cdfe315f757e9d4f239c00c282497a0749d" -dependencies = [ - "murmurhash32", - "rand_distr", - "tantivy-common", -] - -[[package]] -name = "tantivy-tokenizer-api" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23024f6aeb25ceb1a0e27740c84bdb0fae52626737b7e9a9de6ad5aa25c7b038" -dependencies = [ - "serde", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot 0.12.5", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots 0.26.11", -] - -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml_datetime" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" -dependencies = [ - "indexmap 2.12.1", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" -dependencies = [ - "winnow", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.10.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "tracing-core" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "trice" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3aaab10ae9fac0b10f392752bf56f0fd20845f39037fec931e8537b105b515a" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "rand 0.8.7", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "ulid" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" -dependencies = [ - "rand 0.9.3", - "serde", - "web-time", -] - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - -[[package]] -name = "unicode-security" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e4ddba1535dd35ed8b61c52166b7155d7f4e4b8847cec6f48e71dc66d8b5e50" -dependencies = [ - "unicode-normalization", - "unicode-script", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unit-prefix" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8-ranges" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "getrandom 0.3.4", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vart" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87782b74f898179396e93c0efabb38de0d58d50bbd47eae00c71b3a1144dbbae" - -[[package]] -name = "vart" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1982d899e57d646498709735f16e9224cf1e8680676ad687f930cf8b5b555ae" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.111", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmtimer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ed9d8b15c7fb594d72bfb4b5a276f3d2029333cd93a932f376f5937f6f80ee" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.12.5", - "pin-utils", - "wasm-bindgen", -] - -[[package]] -name = "web-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web_atoms" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" -dependencies = [ - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.4", -] - -[[package]] -name = "webpki-roots" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result 0.4.1", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "ws_stream_wasm" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version", - "send_wrapper", - "thiserror 2.0.17", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/czech-file-knife/Cargo.toml b/czech-file-knife/Cargo.toml deleted file mode 100644 index f699d58b6..000000000 --- a/czech-file-knife/Cargo.toml +++ /dev/null @@ -1,93 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -[workspace] -resolver = "2" -members = [ - "src/cfk-core", - "src/cfk-providers", - "src/cfk-cache", - "src/cfk-search", - "src/cfk-vfs", - "src/cfk-cli", - "src/cfk-integrations", - "src/cfk-ios", -] - -[workspace.package] -version = "0.1.0" -edition = "2021" -rust-version = "1.75" -license = "MPL-2.0" -repository = "https://github.com/hyperpolymath/czech-file-knife" -homepage = "https://github.com/hyperpolymath/czech-file-knife" -documentation = "https://docs.rs/cfk-core" -authors = ["hyperpolymath"] -keywords = ["file-manager", "cloud-storage", "fuse", "sync"] -categories = ["command-line-utilities", "filesystem"] - -[workspace.dependencies] -# Async runtime -tokio = { version = "1.49", features = ["full"] } -async-trait = "0.1" -futures = "0.3" - -# Serialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -toml = "0.8" - -# HTTP & Auth -reqwest = { version = "0.13", features = ["json", "stream"] } -oauth2 = "5.0" - -# Hashing & Compression -blake3 = "1.5" -lz4_flex = "0.13" -hex = "0.4" - -# Time & Errors -chrono = { version = "0.4", features = ["serde"] } -thiserror = "2.0" - -# CLI -clap = { version = "4.4", features = ["derive"] } -indicatif = "0.18" -console = "0.16" -dialoguer = "0.11" -tabled = "0.15" -bytesize = "2.3" - -# Storage -sled = "0.34" -directories = "6.0" - -# Search -fuzzy-matcher = "0.3" -tantivy = "0.25" -glob = "0.3" -regex = "1.10" -walkdir = "2.4" -ignore = "0.4" -infer = "0.15" - -# FUSE -fuser = "0.16" -parking_lot = "0.12" -dashmap = "6.1" - -# Bytes -bytes = "1.11" - -# FFI -libc = "0.2" -once_cell = "1.19" - -# Logging -tracing = "0.1" -tracing-subscriber = "0.3" - -# Serialization formats -rmp-serde = "1.1" # MessagePack -ciborium = "0.2" # CBOR -prost = "0.12" # Protocol Buffers -capnp = "0.18" # Cap'n Proto -flatbuffers = "23.5" # FlatBuffers diff --git a/czech-file-knife/GEMINI.md b/czech-file-knife/GEMINI.md deleted file mode 100644 index 417391d24..000000000 --- a/czech-file-knife/GEMINI.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pointer - -This repository has no `AGENTS.md` yet. Until it does, the instructions -for every coding agent live in **[CLAUDE.md](./CLAUDE.md)**. Read that -file, and skip anything in it that is specific to Claude Code tooling. -Do not duplicate rules here. - -When `AGENTS.md` lands in this repository, retarget this pointer at it. diff --git a/czech-file-knife/Justfile b/czech-file-knife/Justfile deleted file mode 100644 index ae5c88930..000000000 --- a/czech-file-knife/Justfile +++ /dev/null @@ -1,633 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# RSR Standard Justfile Template -# https://just.systems/man/en/ -# -# Copy this file to new projects and customize the placeholder values. -# -# Run `just` to see all available recipes -# Run `just cookbook` to generate docs/just-cookbook.adoc -# Run `just combinations` to see matrix recipe options - -set shell := ["bash", "-uc"] -set dotenv-load := true -set positional-arguments := true - -# Import auto-generated contractile recipes (must-check, trust-verify, etc.) -# Re-generate with: contractile gen-just -import? "build/contractile.just" - -# Project metadata — customize these -project := "czech-file-knife" -OWNER := "hyperpolymath" -REPO := "czech-file-knife" -version := "0.1.0" -tier := "infrastructure" # 1 | 2 | infrastructure - -# ═══════════════════════════════════════════════════════════════════════════════ -# DEFAULT & HELP -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show all available recipes with descriptions -default: - @just --list --unsorted - -# Show detailed help for a specific recipe -help recipe="": - #!/usr/bin/env bash - if [ -z "{{recipe}}" ]; then - just --list --unsorted - echo "" - echo "Usage: just help " - echo " just cookbook # Generate full documentation" - echo " just combinations # Show matrix recipes" - else - just --show "{{recipe}}" 2>/dev/null || echo "Recipe '{{recipe}}' not found" - fi - -# Show this project's info -info: - @echo "Project: czech_file_knife" - @echo "Version: {{version}}" - @echo "RSR Tier: {{tier}}" - @echo "Recipes: $(just --summary | wc -w)" - @[ -f ".machine_readable/descriptiles/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/descriptiles/STATE.a2ml | head -1 | xargs -I{} echo "Phase: {}" || true - -# Run Invariant Path overlay tools for this repository -invariant-path *ARGS: - ./scripts/invariant-path.sh {{ARGS}} - -# ═══════════════════════════════════════════════════════════════════════════════ -# INIT — see build/just/repo-init.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/repo-init.just" - -# >>> container-module (three-tier: OCI · portable engine · stapeln) >>> -# Self-contained. Remove the entire block — this and the import — with `just no-container`. -import? "build/just/container.just" -# <<< container-module <<< - -# ═══════════════════════════════════════════════════════════════════════════════ -# GROOVE PROTOCOL — see build/just/groove.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/groove.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE — see build/just/assess.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/assess.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# BUILD & COMPILE -# ═══════════════════════════════════════════════════════════════════════════════ - -# Build the project (debug mode) -build *args: - cargo build --workspace {{args}} - -# Build in release mode with optimizations -build-release *args: - cargo build --release --locked -p cfk-cli {{args}} - -# Build and watch for changes (requires entr or similar) -build-watch: - find src -name '*.rs' | entr -c just build - -# Clean build artifacts [reversible: rebuild with `just build`] -clean: - @echo "Cleaning..." - # - # `build/` is DELIBERATELY ABSENT from this list. It is not an artifact - # directory in an RSR repo: it holds 11 tracked files, including - # build/just/repo-init.just, which the root Justfile imports at line 65. - # Deleting it destroys `just repo-init`, `just verify` and the proof gates. - rm -rf target/ _build/ dist/ out/ obj/ bin/ - -# Deep clean including caches [reversible: rebuild] -clean-all: clean - rm -rf .cache .tmp - -# ═══════════════════════════════════════════════════════════════════════════════ -# TEST & QUALITY -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run all tests -test *args: - cargo test --workspace {{args}} - -# Run tests with verbose output -test-verbose: - cargo test --workspace -- --nocapture - -# Smoke test -test-smoke: - cargo test -p cfk-core --lib - -# Run end-to-end tests (full pipeline: build → run → verify) -e2e: - bash tests/e2e.sh - -# Run aspect tests (cross-cutting concern validation) -aspect: - bash tests/aspect_tests.sh - -# Run benchmarks (performance regression detection) -bench: - cargo bench --workspace - -# Run readiness tests (Component Readiness Grade: D/C/B) -readiness: - cargo test --workspace --release - -# Print the current CRG grade (reads from docs/status/READINESS.adoc '**Current Grade:** X' line) -crg-grade: - @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' docs/status/READINESS.adoc 2>/dev/null | head -1); \ - [ -z "$$grade" ] && grade="X"; \ - echo "$$grade" - -# Print a shields.io CRG badge for embedding in README files -# Looks for '**Current Grade:** X' in docs/status/READINESS.adoc; falls back to X -crg-badge: - @grade=$$(grep -oP '(?<=\*\*Current Grade:\*\* )[A-FX]' docs/status/READINESS.adoc 2>/dev/null | head -1); \ - [ -z "$$grade" ] && grade="X"; \ - case "$$grade" in \ - A) color="brightgreen" ;; \ - B) color="green" ;; \ - C) color="yellow" ;; \ - D) color="orange" ;; \ - E) color="red" ;; \ - F) color="critical" ;; \ - *) color="lightgrey" ;; \ - esac; \ - echo "[![CRG $$grade](https://img.shields.io/badge/CRG-$$grade-$$color?style=flat-square)](https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades)" - -# Run the full merge-requirement test suite (ALL categories) -# Per STANDING rule: P2P + E2E + aspect + execution + lifecycle + bench -test-all: test e2e aspect bench readiness - @echo "All test categories passed — safe to merge!" - -# Run all quality checks -quality: fmt-check lint test - @echo "All quality checks passed!" - -# Fix all auto-fixable issues [reversible: git checkout] -fix: fmt - @echo "Fixed all auto-fixable issues" - -# ═══════════════════════════════════════════════════════════════════════════════ -# LINT & FORMAT -# ═══════════════════════════════════════════════════════════════════════════════ - -# Format all source files [reversible: git checkout] -fmt: - cargo fmt --all - -# Check formatting without changes -fmt-check: - cargo fmt --all --check - -# Run linter -lint: - cargo clippy --workspace --all-targets -- -D warnings - -# ═══════════════════════════════════════════════════════════════════════════════ -# RUN & EXECUTE -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run the application -run *args: build - cargo run -p cfk-cli -- {{args}} - -# Run with verbose output -run-verbose *args: build - cargo run -p cfk-cli -- --verbose {{args}} - -# Install to user path -install: build-release - cargo install --locked --path src/cfk-cli - -# ═══════════════════════════════════════════════════════════════════════════════ -# DEPENDENCIES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Install/check all dependencies -deps: - cargo fetch --locked - -# Audit dependencies for vulnerabilities -deps-audit: - cargo deny --manifest-path Cargo.toml check --config .machine_readable/compliance/rust/deny.toml - @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL --quiet . || true - -# ═══════════════════════════════════════════════════════════════════════════════ -# ARRIVAL PACK — agent-facing CLAUDE.md, compiled from a2ml -# ═══════════════════════════════════════════════════════════════════════════════ - -# Compile CLAUDE.md (the agent arrival pack) from this repo's a2ml -claude-md: - @bash .machine_readable/arrival-pack/generate.sh - -# Regenerate the single authoritative repository map -repo-map: - @bash scripts/gen-repo-map.sh . - -# Fail if the repository map is stale (the map is generated; CI diffs it) -validate-repo-map: - #!/usr/bin/env bash - set -euo pipefail - cd "{{justfile_directory()}}" - before=$(mktemp); cp docs/architecture/REPOSITORY-MAP.adoc "$before" 2>/dev/null || true - bash scripts/gen-repo-map.sh . >/dev/null - if ! diff -q "$before" docs/architecture/REPOSITORY-MAP.adoc >/dev/null 2>&1; then - echo "FAIL: docs/architecture/REPOSITORY-MAP.adoc is stale. Run: just repo-map" >&2 - diff -u "$before" docs/architecture/REPOSITORY-MAP.adoc | head -40 >&2 || true - cp "$before" docs/architecture/REPOSITORY-MAP.adoc - rm -f "$before"; exit 1 - fi - rm -f "$before" - echo "repository map: up to date" - -# Fail if CLAUDE.md's generated region drifted from a2ml or was hand-edited -validate-claude-md: - @bash .machine_readable/arrival-pack/verify.sh - -# ═══════════════════════════════════════════════════════════════════════════════ -# COAPTATION — typed descriptile↔contractile face-off (homeostasis reading) -# ═══════════════════════════════════════════════════════════════════════════════ - -# Emit the coaptation receipt: how the descriptiles coapt with the contractiles (SITREP) -coapt: - @bash .machine_readable/coaptation/coapt.sh --report - -# Assemble a re-anchor basis IF the band is red (the drop itself is a human act) -coapt-reanchor: - @bash .machine_readable/coaptation/coapt.sh --reanchor - -# Fail if the committed coaptation receipt drifted from the contractiles/descriptiles -validate-coapt: - @bash .machine_readable/coaptation/verify.sh - -# ═══════════════════════════════════════════════════════════════════════════════ -# DOCUMENTATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Generate all documentation -docs: - @mkdir -p docs/generated docs/man - just cookbook - just man - @echo "Documentation generated in docs/" - -# Generate justfile cookbook documentation -cookbook: - #!/usr/bin/env bash - mkdir -p docs - OUTPUT="docs/just-cookbook.adoc" - echo "= czech_file_knife Justfile Cookbook" > "$OUTPUT" - echo ":toc: left" >> "$OUTPUT" - echo ":toclevels: 3" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "Generated: $(date -Iseconds)" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "== Recipes" >> "$OUTPUT" - echo "" >> "$OUTPUT" - just --list --unsorted | while read -r line; do - if [[ "$line" =~ ^[[:space:]]+([a-z_-]+) ]]; then - recipe="${BASH_REMATCH[1]}" - echo "=== $recipe" >> "$OUTPUT" - echo "" >> "$OUTPUT" - echo "[source,bash]" >> "$OUTPUT" - echo "----" >> "$OUTPUT" - echo "just $recipe" >> "$OUTPUT" - echo "----" >> "$OUTPUT" - echo "" >> "$OUTPUT" - fi - done - echo "Generated: $OUTPUT" - -# Generate man page -man: - #!/usr/bin/env bash - mkdir -p docs/man - cat > docs/man/czech_file_knife.1 << EOF - .TH czech_file_knife 1 "$(date +%Y-%m-%d)" "{{version}}" "czech_file_knife Manual" - .SH NAME - czech_file_knife \- RSR-compliant project - .SH SYNOPSIS - .B just - [recipe] [args...] - .SH DESCRIPTION - RSR (Rhodium Standard Repository) project managed with just. - .SH AUTHOR - $(git config user.name 2>/dev/null || echo "Author") <$(git config user.email 2>/dev/null || echo "email")> - EOF - echo "Generated: docs/man/czech_file_knife.1" - -# ═══════════════════════════════════════════════════════════════════════════════ -# CI & AUTOMATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run full CI pipeline locally -# proof-check-all is FATAL if any prover toolchain is absent (idris2/lean/agda/coqc): -# the full CI gate must not pass on a machine that cannot verify the proofs. -ci: deps quality proof-check-all - @echo "CI pipeline complete!" - -# Install git hooks -install-hooks: - @mkdir -p .git/hooks - @cat > .git/hooks/pre-commit << 'HOOKEOF' - #!/bin/bash - just fmt-check || exit 1 - just lint || exit 1 - just assail || exit 1 - HOOKEOF - @chmod +x .git/hooks/pre-commit - @echo "Git hooks installed" - -# ═══════════════════════════════════════════════════════════════════════════════ -# SECURITY -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run security audit -security: deps-audit - @echo "=== Security Audit ===" - @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true - @echo "Security audit complete" - -# Generate SBOM -sbom: - @mkdir -p docs/security - @command -v syft >/dev/null && syft . -o spdx-json > docs/security/sbom.spdx.json || echo "syft not found" - -# ═══════════════════════════════════════════════════════════════════════════════ -# VALIDATION & COMPLIANCE — see build/just/validate.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/validate.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# STATE MANAGEMENT -# ═══════════════════════════════════════════════════════════════════════════════ - -# Update STATE.a2ml timestamp -state-touch: - @if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ - sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/descriptiles/STATE.a2ml && \ - echo "STATE.a2ml timestamp updated"; \ - fi - -# Show current phase from STATE.a2ml -state-phase: - @sed -n 's/^[[:space:]]*phase[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .machine_readable/descriptiles/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" - -# ═══════════════════════════════════════════════════════════════════════════════ -# GUIX -# ═══════════════════════════════════════════════════════════════════════════════ - -# Enter Guix development shell (primary) -guix-shell: - guix shell -D -f build/guix.scm - -# Build with Guix -guix-build: - guix build -f build/guix.scm - -# ═══════════════════════════════════════════════════════════════════════════════ -# HYBRID AUTOMATION -# ═══════════════════════════════════════════════════════════════════════════════ - -# Run local automation tasks -automate task="all": - #!/usr/bin/env bash - case "{{task}}" in - all) just fmt && just lint && just test && just docs && just state-touch ;; - cleanup) just clean && find . -name "*.orig" -delete && find . -name "*~" -delete ;; - update) just deps && just validate ;; - *) echo "Unknown: {{task}}. Use: all, cleanup, update" && exit 1 ;; - esac - -# ═══════════════════════════════════════════════════════════════════════════════ -# COMBINATORIC MATRIX RECIPES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Build matrix: [debug|release] x [target] x [features] -build-matrix mode="debug" target="" features="": - @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" - -# Test matrix: [unit|integration|e2e|all] x [verbosity] x [parallel] -test-matrix suite="unit" verbosity="normal" parallel="true": - @echo "Test matrix: suite={{suite}} verbosity={{verbosity}} parallel={{parallel}}" - -# CI matrix: [lint|test|build|security|all] x [quick|full] -ci-matrix stage="all" depth="quick": - @echo "CI matrix: stage={{stage}} depth={{depth}}" - -# Show all matrix combinations -combinations: - @echo "=== Combinatoric Matrix Recipes ===" - @echo "" - @echo "Build Matrix: just build-matrix [debug|release] [target] [features]" - @echo "Test Matrix: just test-matrix [unit|integration|e2e|all] [verbosity] [parallel]" - @echo "Container: just container-matrix [build|run|push|shell|scan] [registry] [tag] (needs container module)" - @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" - -# ═══════════════════════════════════════════════════════════════════════════════ -# VERSION CONTROL -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show git status -status: - @git status --short - -# Show recent commits -log count="20": - @git log --oneline -{{count}} - -# Generate CHANGELOG.adoc with git-cliff -changelog: - @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } - # AsciiDoc, not .md: CHANGELOG.adoc is what root-allow.txt permits, so a - # .md here would fail check-root-shape AND the estate's no-.md rule the - # moment anyone ran this recipe. - git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.adoc - @echo "Generated CHANGELOG.adoc" - -# Preview changelog for unreleased commits (does not write) -changelog-preview: - @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } - git cliff --config .machine_readable/configs/git-cliff/cliff.toml --unreleased --strip header - -# Tag a new release (usage: just release-tag 1.2.3) -release-tag version: - #!/usr/bin/env bash - TAG="v{{version}}" - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists" - exit 1 - fi - just changelog - git add CHANGELOG.md - git commit -m "chore(release): prepare $TAG" - git tag -a "$TAG" -m "Release $TAG" - echo "Created tag $TAG — push with: git push origin main --tags" - -# ═══════════════════════════════════════════════════════════════════════════════ -# UTILITIES -# ═══════════════════════════════════════════════════════════════════════════════ - -# Count lines of code -loc: - @find . \( -name "*.rs" -o -name "*.ex" -o -name "*.exs" -o -name "*.res" -o -name "*.gleam" -o -name "*.zig" -o -name "*.idr" -o -name "*.hs" -o -name "*.ncl" -o -name "*.scm" -o -name "*.adb" -o -name "*.ads" \) -not -path './target/*' -not -path './_build/*' 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 || echo "0" - -# Show TODO comments -todos: - @grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.ex" --include="*.res" --include="*.gleam" --include="*.zig" --include="*.idr" --include="*.hs" . 2>/dev/null || echo "No TODOs" - -# Open in editor -edit: - ${EDITOR:-code} . - -# Run high-rigor security assault using panic-attacker -maint-assault: - @./.machine_readable/scripts/maintenance/maint-assault.sh - -# Run panic-attacker pre-commit scan (foundational floor-raise requirement) -assail: - @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "WARN: panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" - - -# Self-diagnostic — checks dependencies, permissions, paths -doctor: - @echo "Running diagnostics for czech-file-knife..." - @echo "Checking required tools..." - @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" - @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" - @echo "Checking for hardcoded paths..." - @grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.ex' --include='*.res' --include='*.gleam' --include='*.sh' . 2>/dev/null | head -5 || echo " [OK] No hardcoded paths" - @echo "Diagnostics complete." - -# Guided tour of key features -tour: - @echo "=== czech-file-knife Tour ===" - @echo "" - @echo "1. Project structure:" - @ls -la - @echo "" - @echo "2. Available commands: just --list" - @echo "" - @echo "3. Read README.adoc for full overview" - @echo "4. Read EXPLAINME.adoc for architecture decisions" - @echo "5. Run 'just doctor' to check your setup" - @echo "" - @echo "Tour complete! Try 'just --list' to see all available commands." - -# Open feedback channel with diagnostic context -help-me: - @echo "=== czech-file-knife Help ===" - @echo "Platform: $(uname -s) $(uname -m)" - @echo "Shell: $SHELL" - @echo "" - @echo "To report an issue:" - @echo " https://github.com/hyperpolymath/czech-file-knife/issues/new" - @echo "" - @echo "Include the output of 'just doctor' in your report." - -# ═══════════════════════════════════════════════════════════════════════════════ -# FORMAL VERIFICATION (PROOFS) — see build/just/proofs.just -# ═══════════════════════════════════════════════════════════════════════════════ - -import? "build/just/proofs.just" - -# ═══════════════════════════════════════════════════════════════════════════════ -# SESSION MANAGEMENT (THIN BINDINGS TO CENTRAL STANDARDS) -# ═══════════════════════════════════════════════════════════════════════════════ - -# Show canonical session-management command model -session-help: - @echo "Canonical command model:" - @echo " intake repo " - @echo " checkpoint change " - @echo " verify maintenance " - @echo " verify substantial " - @echo " verify release " - @echo " close planned " - @echo " close urgent " - @echo " recover repo " - @echo " handover full " - @echo " handover split " - @echo " handover model " - @echo " handover human " - @echo "" - @echo "Use Just aliases below (thin wrappers around ./session/dispatch.sh)." - -# Canonical aliases (friendly recipe names that map to canonical commands) -intake-repo path=".": - @./session/dispatch.sh intake repo "{{path}}" - -checkpoint-change path=".": - @./session/dispatch.sh checkpoint change "{{path}}" - -verify-maintenance path=".": - @./session/dispatch.sh verify maintenance "{{path}}" - -verify-substantial path=".": - @./session/dispatch.sh verify substantial "{{path}}" - -verify-release path=".": - @./session/dispatch.sh verify release "{{path}}" - -close-planned path=".": - @./session/dispatch.sh close planned "{{path}}" - -close-urgent path=".": - @./session/dispatch.sh close urgent "{{path}}" - -recover-repo path=".": - @./session/dispatch.sh recover repo "{{path}}" - -handover-full path=".": - @./session/dispatch.sh handover full "{{path}}" - -handover-split path=".": - @./session/dispatch.sh handover split "{{path}}" - -handover-model path=".": - @./session/dispatch.sh handover model "{{path}}" - -handover-human path=".": - @./session/dispatch.sh handover human "{{path}}" - -secret-scan-trufflehog: - @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true - -# ═══════════════════════════════════════════════════════════════════════════════ -# WINDOWS RGONOMICS (CLOAKING) -# ═══════════════════════════════════════════════════════════════════════════════╓ -# Hide all dotfiles and dot-folders from Windows Explorer. On POSIX systems, -# leading-dot names are already hidden by convention, so these recipes are -# intentionally harmless no-ops. -cloak: - #!/usr/bin/env bash - set -euo pipefail - if command -v powershell.exe >/dev/null 2>&1; then - powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -bor [System.IO.FileAttributes]::Hidden }" - echo "Cloak engaged." - else - echo "Dotfiles are natively cloaked on this OS. No action required." - fi - -# Reveal dotfiles in Windows Explorer; on POSIX, explain the native mechanism. -uncloak: - #!/usr/bin/env bash - set -euo pipefail - if command -v powershell.exe >/dev/null 2>&1; then - powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -band -bnot [System.IO.FileAttributes]::Hidden }" - echo "Cloak lifted." - else - echo "Use 'ls -a' to view dotfiles on this OS." - fi diff --git a/czech-file-knife/LICENSE b/czech-file-knife/LICENSE deleted file mode 100644 index 14e2f777f..000000000 --- a/czech-file-knife/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt b/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt deleted file mode 100644 index 2d58298e6..000000000 --- a/czech-file-knife/LICENSES/CC-BY-SA-4.0.txt +++ /dev/null @@ -1,428 +0,0 @@ -Attribution-ShareAlike 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution-ShareAlike 4.0 International Public -License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution-ShareAlike 4.0 International Public License ("Public -License"). To the extent this Public License may be interpreted as a -contract, You are granted the Licensed Rights in consideration of Your -acceptance of these terms and conditions, and the Licensor grants You -such rights in consideration of benefits the Licensor receives from -making the Licensed Material available under these terms and -conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - l. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - m. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. Additional offer from the Licensor -- Adapted Material. - Every recipient of Adapted Material from You - automatically receives an offer from the Licensor to - exercise the Licensed Rights in the Adapted Material - under the conditions of the Adapter's License You apply. - - c. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - b. ShareAlike. - - In addition to the conditions in Section 3(a), if You Share - Adapted Material You produce, the following conditions also apply. - - 1. The Adapter's License You apply must be a Creative Commons - license with the same License Elements, this version or - later, or a BY-SA Compatible License. - - 2. You must include the text of, or the URI or hyperlink to, the - Adapter's License You apply. You may satisfy this condition - in any reasonable manner based on the medium, means, and - context in which You Share Adapted Material. - - 3. You may not offer or impose any additional or different terms - or conditions on, or apply any Effective Technological - Measures to, Adapted Material that restrict exercise of the - rights granted under the Adapter's License You apply. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - including for purposes of Section 3(b); and - - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public -licenses. Notwithstanding, Creative Commons may elect to apply one of -its public licenses to material it publishes and in those instances -will be considered the “Licensor.” The text of the Creative Commons -public licenses is dedicated to the public domain under the CC0 Public -Domain Dedication. Except for the limited purpose of indicating that -material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the -public licenses. - -Creative Commons may be contacted at creativecommons.org. - diff --git a/czech-file-knife/LICENSES/MPL-2.0.txt b/czech-file-knife/LICENSES/MPL-2.0.txt deleted file mode 100644 index d0a1fa148..000000000 --- a/czech-file-knife/LICENSES/MPL-2.0.txt +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/czech-file-knife/README.adoc b/czech-file-knife/README.adoc deleted file mode 100644 index ef668886b..000000000 --- a/czech-file-knife/README.adoc +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) -= Czech File Knife -:toc: -:toc-placement: preamble - -// ── Licensing ─────────────────────────────────────────────────────────────── -image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg?logo=mozilla[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] -image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg?logo=creativecommons[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] -image:https://img.shields.io/badge/Provenance-Quantum--Safe-blueviolet[Quantum-Safe Provenance,link="docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt"] - -// ── Standard & quality gates ──────────────────────────────────────────────── -image:https://img.shields.io/badge/RSR-Rhodium_Standard-9C27B0[Rhodium Standard Repository,link="https://github.com/hyperpolymath/rhodium-standard-repositories"] -image:https://api.scorecard.dev/projects/github.com/hyperpolymath/czech-file-knife/badge[OpenSSF Scorecard,link="https://scorecard.dev/viewer/?uri=github.com/hyperpolymath/czech-file-knife"] -image:https://sonarcloud.io/api/project_badges/measure?project=hyperpolymath_czech-file-knife&metric=alert_status[SonarQube Quality Gate,link="https://sonarcloud.io/summary/new_code?id=hyperpolymath_czech-file-knife"] -image:https://archive.softwareheritage.org/badge/origin/https://github.com/hyperpolymath/czech-file-knife/[Archived in Software Heritage,link="https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/hyperpolymath/czech-file-knife"] - -// ── Provenance & ecosystem ────────────────────────────────────────────────── -image:https://api.thegreenwebfoundation.org/greencheckimage/github.com[Green Web,link="https://www.thegreenwebfoundation.org/green-web-check/?url=github.com"] - -The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage. - -Czech File Knife (`cfk`) is the Swiss File Knife of the hybrid machine: one -command-line tool that treats local disks, network shares and cloud storage as -a single filesystem, and picks the cheapest *correct* way to do each job. - -Three rules shape it: - -* *Reversible by default.* Every write, delete, move and copy records its own - inverse (the JanusKey model: SHA-256 content store + append-only op log), so - `cfk undo` works on every backend, even ones with no native versioning. -* *No pointless round trips.* Cloud-to-cloud work is done provider-side - (server-side copy/move/export) where possible, and otherwise streamed - through memory, never staged on local disk. -* *Space-aware.* Transforms such as compression are designed to fit in the - free space you actually have (for example compressing a 25 GB file with - 8 GB free by punching holes in the input as the output grows). See - link:docs/architecture/HYBRID-OPERATIONS.adoc[HYBRID-OPERATIONS]. - -== Status (v0.1.0) - -[cols="1,1,3"] -|=== -| Component | Status | Notes - -| `cfk-core` | Stable | Core traits, types, errors, *reversible journal* -| `cfk-cli` | Working | `ls cat cp mv rm mkdir stat backends df history undo` -| `cfk-providers` | Partial | Local filesystem complete; network/cloud backends scaffolded -| `cfk-cache` | Stub | Trait definitions -| `cfk-search` | Stub | Filename search; Tantivy planned -| `cfk-vfs` | Stub | FUSE mounting planned -| `cfk-integrations` | Working | aria2, agrep, pandoc -| `cfk-ios` | Scaffold| iOS File Provider bridge (C ABI + Swift) -| `cfk-tui` | Scaffold| Ada TUI prototype -|=== - -Honest residue: reversibility is engineered, not yet mechanically proven -(see link:docs/status/PROOF-NEEDS.adoc[PROOF-NEEDS]); metadata (permissions, -mtimes, xattrs) is not yet restored by `undo`. - -== Quick start - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife -just build # cargo build --workspace -just test # cargo test --workspace -just install # cargo install --locked --path src/cfk-cli ----- - -[source,bash] ----- -cfk ls -l -H . -cfk cp -f source.txt dest.txt -cfk rm -r old-dir/ -cfk history # what has been done -cfk undo # reverse the latest operation -cfk df local ----- - -=== Reversible journal - -[cols="1,3"] -|=== -| Variable | Meaning - -| `CFK_JOURNAL_DIR` | Journal location (default `$XDG_STATE_HOME/cfk/journal`, else `~/.local/state/cfk/journal`) -| `CFK_JOURNAL_MAX_BYTES` | Largest file captured for undo (default 1 GiB). Bigger operations are *refused*, not silently made irreversible -| `CFK_NO_JOURNAL=1` | Opt out (operations are then irreversible) -|=== - -== Repository map - -The authoritative map is generated, so it cannot drift from the tree: -link:docs/architecture/REPOSITORY-MAP.adoc[docs/architecture/REPOSITORY-MAP.adoc] -(regenerate with `just repo-map`; CI fails if it is stale). - -The short version: - -[cols="1,3"] -|=== -| Path | What lives there - -| `README.adoc`, `CLAUDE.md` | Start here - humans and AI agents respectively. -| `src/cfk-*/`, `tests/` | The Cargo workspace crates and their tests (`tests/fuzz/` is cargo-fuzz). -| `docs/` | Human documentation, including the full map above. -| `.machine_readable/` | Manifests, contractiles and policies that tools read. -| `build/`, `Justfile` | Every task runs through `just`; phases live in `build/just/`. -| `ci/`, `.github/` | CI configuration; GitHub reads `.github/` and no other path. -|=== - -== Where to go next - -* link:docs/architecture/REPOSITORY-MAP.adoc[The repository map] — generated; what every directory is for. -* link:docs/EXPLAINME.adoc[EXPLAINME] — the engineering deep-dive: how the pieces actually work. -* link:docs/AFFIRMATION.adoc[AFFIRMATION] — the dated, signed honesty snapshot of the repo's true state. -* link:docs/AUDIT.adoc[AUDIT] — the release audit gate. -* link:docs/architecture/HYBRID-OPERATIONS.adoc[Hybrid operations] — reversible journal, provider-side cloud ops, in-place compression. -* link:docs/status/ROADMAP.adoc[Roadmap]. - -== Licence - -Code, configuration and scripts are link:LICENSE[Mozilla Public License 2.0] -(`MPL-2.0`); prose documentation is `CC-BY-SA-4.0`. Both texts live in -`LICENSES/`, and per-file `SPDX-License-Identifier` headers are authoritative. -The GitHub-detected licence is MPL-2.0 (the root `LICENSE`). Long-term -attribution uses Quantum-Safe Provenance — see -link:docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt[the Quantum-Safe Provenance exhibit]. diff --git a/czech-file-knife/benches/czech_file_knife_bench.rs b/czech-file-knife/benches/czech_file_knife_bench.rs deleted file mode 100644 index 566e1ce72..000000000 --- a/czech-file-knife/benches/czech_file_knife_bench.rs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -fn benchmark_basic_operations(c: &mut Criterion) { - c.bench_function("czech-file-knife basic operation", |b| { - b.iter(|| { - // Add actual benchmarking code here - black_box(42) - }); - }); -} - -criterion_group!(benches, benchmark_basic_operations); -criterion_main!(benches); diff --git a/czech-file-knife/benches/template_bench.sh b/czech-file-knife/benches/template_bench.sh deleted file mode 100755 index 5fb58a88d..000000000 --- a/czech-file-knife/benches/template_bench.sh +++ /dev/null @@ -1,227 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Template Benchmarks -# Measures performance characteristics of template validation and build system - -set -euo pipefail - -REPO_ROOT="${1:-.}" -OUTPUT_FORMAT="${2:-human}" # human | json | csv - -# ANSI colors -BLUE='\033[0;34m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -log_info() { - echo -e "${BLUE}→${NC} $*" -} - -log_pass() { - echo -e "${GREEN}✓${NC} $*" -} - -# Ensure we have required commands -command -v /usr/bin/time >/dev/null 2>&1 || { - echo "Warning: /usr/bin/time not available, using built-in time" - TIME_CMD="time" -} - -TIME_CMD="/usr/bin/time -f %e" 2>/dev/null || TIME_CMD="time" - -echo "" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "Template Benchmarks" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" - -declare -A results - -#============================================================================== -# BENCHMARK 1: Template Validation -#============================================================================== - -log_info "Running template validation benchmark" - -# Warm-up run -if [ -f "$REPO_ROOT/scripts/validate-template.sh" ]; then - bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true -fi - -# Timed runs -BENCH_RUNS=3 -TOTAL_TIME=0 - -for i in $(seq 1 $BENCH_RUNS); do - START=$(date +%s%N) - bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true - END=$(date +%s%N) - - # Convert to milliseconds - RUN_TIME=$(( (END - START) / 1000000 )) - TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) - - [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" -done - -AVG_VALIDATION_TIME=$(( TOTAL_TIME / BENCH_RUNS )) -results[validation]=$AVG_VALIDATION_TIME -log_pass "Validation: ${AVG_VALIDATION_TIME}ms average (${BENCH_RUNS} runs)" - -#============================================================================== -# BENCHMARK 2: Zig Build -#============================================================================== - -log_info "Running Zig build benchmark" - -if ! command -v zig &> /dev/null; then - echo " ⚠ Zig compiler not found - skipping Zig build benchmark" - results[zig_build]="skipped" -else - cd "$REPO_ROOT/src/interface/ffi" - - # Warm-up - zig build --summary off > /dev/null 2>&1 || true - - # Clean build - BENCH_RUNS=2 - TOTAL_TIME=0 - - for i in $(seq 1 $BENCH_RUNS); do - rm -rf zig-cache - - START=$(date +%s%N) - zig build --summary off > /dev/null 2>&1 || true - END=$(date +%s%N) - - RUN_TIME=$(( (END - START) / 1000000 )) - TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) - - [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" - done - - AVG_BUILD_TIME=$(( TOTAL_TIME / BENCH_RUNS )) - results[zig_build]=$AVG_BUILD_TIME - log_pass "Zig build: ${AVG_BUILD_TIME}ms average (clean build, ${BENCH_RUNS} runs)" - - cd - > /dev/null -fi - -#============================================================================== -# BENCHMARK 3: Zig Tests -#============================================================================== - -log_info "Running Zig test benchmark" - -if ! command -v zig &> /dev/null; then - echo " ⚠ Zig compiler not found - skipping Zig test benchmark" - results[zig_test]="skipped" -else - cd "$REPO_ROOT/src/interface/ffi" - - # Warm-up - zig build test --summary off > /dev/null 2>&1 || true - - START=$(date +%s%N) - TEST_OUTPUT=$(zig build test --summary off 2>&1 || true) - END=$(date +%s%N) - - TEST_TIME=$(( (END - START) / 1000000 )) - results[zig_test]=$TEST_TIME - log_pass "Zig tests: ${TEST_TIME}ms" - - # Count tests - TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -c "^test " || echo "unknown") - echo " Test count: $TEST_COUNT" - - cd - > /dev/null -fi - -#============================================================================== -# BENCHMARK 4: Workflow Validation -#============================================================================== - -log_info "Running workflow validation benchmark" - -if [ -f "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" ]; then - START=$(date +%s%N) - bash "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" "$REPO_ROOT/.github/workflows" > /dev/null 2>&1 || true - END=$(date +%s%N) - - WORKFLOW_TIME=$(( (END - START) / 1000000 )) - results[workflow_validation]=$WORKFLOW_TIME - log_pass "Workflow validation: ${WORKFLOW_TIME}ms" -fi - -#============================================================================== -# BENCHMARK 5: Template Instantiation -#============================================================================== - -log_info "Running template instantiation benchmark" - -if [ -f "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" ]; then - START=$(date +%s%N) - bash "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" "$REPO_ROOT" > /dev/null 2>&1 || true - END=$(date +%s%N) - - INSTANTIATION_TIME=$(( (END - START) / 1000000 )) - results[instantiation]=$INSTANTIATION_TIME - log_pass "Template instantiation: ${INSTANTIATION_TIME}ms" -fi - -#============================================================================== -# SUMMARY -#============================================================================== - -echo "" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "BENCHMARK RESULTS" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" - -if [ "$OUTPUT_FORMAT" = "json" ]; then - echo "{" - echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," - echo " \"repo\": \"$REPO_ROOT\"," - echo " \"results\": {" - - count=0 - for key in "${!results[@]}"; do - value="${results[$key]}" - [ $count -gt 0 ] && echo "," - if [ "$value" = "skipped" ]; then - echo -n " \"$key\": \"skipped\"" - else - echo -n " \"$key\": $value" - fi - count=$((count + 1)) - done - echo "" - echo " }" - echo "}" -elif [ "$OUTPUT_FORMAT" = "csv" ]; then - echo "metric,value_ms,timestamp" - for key in "${!results[@]}"; do - value="${results[$key]}" - if [ "$value" != "skipped" ]; then - echo "$key,$value,$(date -u +%Y-%m-%dT%H:%M:%SZ)" - fi - done -else - # Human-readable format - for key in "${!results[@]}"; do - value="${results[$key]}" - if [ "$value" = "skipped" ]; then - printf " %-30s %s\n" "$key:" "SKIPPED" - else - printf " %-30s %5d ms\n" "$key:" "$value" - fi - done -fi - -echo "" -echo "Benchmark complete." -echo "" diff --git a/czech-file-knife/build/container/.containerignore b/czech-file-knife/build/container/.containerignore deleted file mode 100644 index a4d651995..000000000 --- a/czech-file-knife/build/container/.containerignore +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Build-context exclusions for the OCI image build (Tier A). -# Honoured by podman, nerdctl and docker (all read .containerignore; -# docker also reads .dockerignore — this file is the portable name). -# -# Keeping the context lean speeds builds and prevents secrets, VCS -# history and local cruft from leaking into image layers. - -# Version control -.git/ -.gitignore -.gitattributes - -# CI / forge metadata -.github/ -ci/ - -# Editor / agent / local state -.claude/ -.devcontainer/ -.editorconfig -.envrc -.direnv/ -*.swp -*~ - -# Build outputs and caches -build/ttc/ -target/ -_build/ -deps/ -zig-out/ -zig-cache/ -node_modules/ -dist/ -result/ - -# Docs, tests, examples (not needed in the runtime image) -docs/ -build/docs-seed/ -benches/ -examples/ -tests/ - -# The container tooling itself (don't recurse the build files into the image) -build/container/stapeln/ -build/container/compose*.yaml -build/container/compose*.toml -build/container/.containerignore -build/container/README.adoc - -# Secrets and environment files — never ship these -*.env -.env -.env.* -*.pem -*.key -secrets/ diff --git a/czech-file-knife/build/container/Containerfile b/czech-file-knife/build/container/Containerfile deleted file mode 100644 index 1a475f4f2..000000000 --- a/czech-file-knife/build/container/Containerfile +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Czech File Knife — CLI container. `cfk` is a command-line tool, not a -# service: no port, no healthcheck. Mount the storage you want to work on at -# /data; the reversible-operation journal persists in /state. -# -# Build: podman build -f build/container/Containerfile -t czech-file-knife . -# Run: podman run --rm -v "$PWD:/data" -v cfk-state:/state czech-file-knife ls /data - -# ── builder ──────────────────────────────────────────────────────────────── -FROM cgr.dev/chainguard/wolfi-base:latest AS builder -RUN apk add --no-cache build-base rust pkgconf fuse3-dev -WORKDIR /build -COPY Cargo.toml Cargo.lock ./ -COPY src/ src/ -COPY benches/ benches/ -COPY tests/ tests/ -RUN cargo build --release --locked -p cfk-cli - -# ── runtime ──────────────────────────────────────────────────────────────── -FROM cgr.dev/chainguard/wolfi-base:latest -LABEL org.opencontainers.image.title="Czech File Knife" \ - org.opencontainers.image.description="The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." \ - org.opencontainers.image.url="https://github.com/hyperpolymath/czech-file-knife" \ - org.opencontainers.image.source="https://github.com/hyperpolymath/czech-file-knife" \ - org.opencontainers.image.vendor="hyperpolymath" \ - org.opencontainers.image.licenses="MPL-2.0" \ - org.opencontainers.image.authors="Jonathan D.A. Jewell " \ - dev.cerrotorre.manifest="build/container/stapeln/manifest.toml" \ - dev.cerrotorre.gatekeeper="build/container/stapeln/.gatekeeper.yaml" \ - dev.stapeln.compose="build/container/stapeln/compose.toml" -RUN apk add --no-cache ca-certificates fuse3 -RUN addgroup -S cfk && adduser -S cfk -G cfk \ - && mkdir -p /data /state && chown cfk:cfk /data /state -COPY --from=builder /build/target/release/cfk /usr/local/bin/cfk -ENV CFK_JOURNAL_DIR=/state/journal -VOLUME ["/data", "/state"] -USER cfk -WORKDIR /data -ENTRYPOINT ["/usr/local/bin/cfk"] diff --git a/czech-file-knife/build/container/README.adoc b/czech-file-knife/build/container/README.adoc deleted file mode 100644 index 781b0d482..000000000 --- a/czech-file-knife/build/container/README.adoc +++ /dev/null @@ -1,245 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Czech File Knife Containerisation -:toc: left -:toclevels: 3 -:sectnums: - -== Overview - -Containerisation in this template is organised in *three tiers*, from the most -portable to the most sovereign. You can use only the tier you need and ignore — -or delete — the rest. - -[cols="1,2,4"] -|=== -| Tier | What | Why - -| *A — OCI standard* -| `Containerfile`, `.containerignore` -| A plain, multi-stage OCI image on https://www.chainguard.dev/[Chainguard] - Wolfi. Builds with any OCI engine. No dependency on tiers B or C. - -| *B — Portable engine* -| `compose.yaml`, `compose.example.yaml` -| A https://compose-spec.io[compose-spec] stack that runs unchanged on - `podman`, `nerdctl` or `docker`. For everyday local and simple production use. - -| *C — stapeln (sovereign)* -| `stapeln/` directory -| Verified `.ctp` bundles, zero-copy IPC, edge-gateway trust policy, secrets - gating, runtime monitoring and signed deployment — the full - https://github.com/hyperpolymath/stapeln[stapeln] stack. -|=== - -All files use `{{PLACEHOLDER}}` tokens, replaced by `just container-init` -(or the top-level `just repo-init` during project bootstrap). - -The container engine is *auto-detected* (`podman`, then `nerdctl`, then -`docker`). Override with `CONTAINER_ENGINE=docker just container-build`. - -== File Reference - -=== Tier A + B (this directory) - -[cols="1,3"] -|=== -| File | Purpose - -| `Containerfile` -| Multi-stage OCI build. Stage 1 builds the app; Stage 2 is a minimal Wolfi - runtime image. Engine-agnostic. Carries the stapeln files only as labels, - so it has no hard dependency on tier C. - -| `.containerignore` -| Build-context exclusions (honoured by podman/nerdctl/docker). Keeps `.git`, - secrets, docs, tests and build caches out of image layers. - -| `compose.yaml` -| Portable compose-spec stack: app + `rokur` + `svalinn`. Use with - `just container-up` or `podman compose -f build/container/compose.yaml up -d`. - -| `compose.example.yaml` -| Concrete multi-service example (Rust API + Elixir worker + gate + gateway). - Copy to `compose.yaml` and customise. - -| `entrypoint.sh` -| Container entrypoint: signal handling (SIGTERM/SIGINT), startup logging, - `exec` into the main process. -|=== - -=== Tier C — `stapeln/` - -[cols="1,3"] -|=== -| File | Purpose - -| `compose.toml` -| *selur-compose* stack definition (TOML, selur-native — not parseable by - `podman compose`; use `compose.yaml` for that). Declares services, volumes, - the selur zero-copy network, and health checks. - -| `compose.example.toml` -| Concrete multi-service selur example. - -| `manifest.toml` -| *cerro-torre* `.ctp` bundle metadata: provenance, dependencies, - attestations, and the runtime security profile. Used by `ct pack` / `ct verify`. - -| `.gatekeeper.yaml` -| *svalinn* edge-gateway policy: authentication, rate limiting, container - trust, request validation, CORS, audit logging. - -| `rokur.toml` -| *rokur* secrets-gate configuration: required secrets, listener/backend, - rate limiting, policy engine, and audit log. - -| `vordr.toml` -| *vordr* runtime monitoring: health/readiness probes (app, rokur, svalinn), - crash detection, resource thresholds, structured logs. - -| `ct-build.sh` -| *cerro-torre* build → pack → sign (Ed25519) → verify → push pipeline. - Engine-agnostic; degrades gracefully when cerro-torre tools are absent. - -| `deploy.k9.ncl` -| *k9-svc* deployment component at Hunt trust level: full pedigree (L1–L5), - dev/staging/prod configs, rolling deployment strategy. -|=== - -== The stapeln ecosystem - -The sovereign tier wires together the stapeln container stack: - -*cerro-torre* (build + sign):: - Produces signed, minimal `.ctp` bundles from OCI images. Tools: `ct pack`, - `ct sign`, `ct verify`, `ct push`, `ct explain`. - -*selur* (compose + IPC):: - Orchestration with zero-copy IPC for co-located services. Reads - `stapeln/compose.toml`. - -*rokur* (secrets gate):: - A service that fronts the application and refuses to pass traffic unless - the declared secrets are present; rate-limits per client and writes a - structured audit log. Configured by `stapeln/rokur.toml`. In the stack it - sits between `svalinn` and the app. - -*svalinn* (edge gateway):: - Policy-driven reverse proxy: TLS termination, authentication, rate limiting, - CORS and container trust, from `stapeln/.gatekeeper.yaml`. - -*vordr* (monitoring):: - Runtime monitoring: health probes, crash detection, resource tracking, - structured logs, from `stapeln/vordr.toml`. - -*k9-svc* (deployment):: - Nickel-based deployment components with pedigree and trust levels - (Kennel / Yard / Hunt). - -The request path in the full stack is: -`svalinn` (edge) → `rokur` (secrets gate) → `czech-file-knife` (app). - -== Initialise - -[source,bash] ----- -just repo-init # full project bootstrap (all placeholders) -# or -just container-init # container-only: prompts for service/port/registry ----- - -== Everyday workflow (tiers A + B) - -[source,bash] ----- -just container-build # build image (auto-detected engine) -just container-verify # validate the compose config -just container-up -d # start the stack -just container-down # stop the stack - -# Explicit engine: -CONTAINER_ENGINE=docker just container-build ----- - -`just container-up` uses `selur-compose` automatically when it is installed, -otherwise it falls back to ` compose -f build/container/compose.yaml`. - -== Sovereign workflow (tier C) - -[source,bash] ----- -just container-sign # build → pack → sign → verify (.ctp) -just container-push # push the signed bundle -cd build/container/stapeln && selur-compose up --detach ----- - -k9-svc managed deployment: - -[source,bash] ----- -nickel typecheck build/container/stapeln/deploy.k9.ncl -k9-svc deploy build/container/stapeln/deploy.k9.ncl --env production ----- - -== Continuous integration - -A `container build` workflow ships in `.github/workflows/container-build.yml`. -It is **off by default** — it costs nothing in a repository created from this -template until you opt in. - -To enable it, set a repository (or organisation) variable: - -[cols="1,3"] -|=== -| Variable | Effect - -| `CONTAINER_CI=true` -| Enables the workflow. It builds the image (`just container-build`), verifies - the compose config, runs a best-effort `trivy` scan, and on `v*` tags signs - the `.ctp` bundle (`just container-sign`). - -| `CONTAINER_RUNNER` -| Optional JSON array of runner labels. Defaults to - `["self-hosted","owned","container"]` — owned compute, so **no metered - GitHub Actions minutes**. Set e.g. `["ubuntu-latest"]` to use GitHub-hosted - runners instead. -|=== - -[source,bash] ----- -gh variable set CONTAINER_CI --body true -# optional: gh variable set CONTAINER_RUNNER --body '["ubuntu-latest"]' ----- - -The owned runner is expected to have `just` and a container engine installed; -`trivy` and cerro-torre (`ct`/`cerro-sign`) are used when present and skipped -otherwise. - -== Removing containerisation - -If your project does not need containers (e.g. a pure library), strip the whole -apparatus in one command: - -[source,bash] ----- -just no-container ----- - -This removes `build/container/`, `.devcontainer/`, the `build/just/container.just` -module and its Justfile import. Review `.github/workflows/` afterwards for any -image build/publish jobs you no longer need. - -== Base images - -* Builder: `cgr.dev/chainguard/wolfi-base:latest` -* Runtime: `cgr.dev/chainguard/wolfi-base:latest` (or - `cgr.dev/chainguard/static:latest` for static binaries) - -Chainguard images are minimal, low-CVE, and rebuilt daily (`apk` package -manager, Alpine-compatible). - -== Container runtime - -Podman is recommended, but the OCI Containerfile and compose-spec files work -unchanged with `nerdctl` and `docker`. Set `CONTAINER_ENGINE` to choose. diff --git a/czech-file-knife/build/container/compose.example.yaml b/czech-file-knife/build/container/compose.example.yaml deleted file mode 100644 index cbd43aad5..000000000 --- a/czech-file-knife/build/container/compose.example.yaml +++ /dev/null @@ -1,108 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Example portable compose stack (Tier B) — multi-service -# -# A concrete, fully-commented example: a Rust API + Elixir worker behind the -# rokur secrets gate and the svalinn edge gateway. Copy to compose.yaml and -# customise. Works with podman / nerdctl / docker compose unchanged. -# -# cp build/container/compose.example.yaml build/container/compose.yaml -# # edit service names, ports, images -# just container-up # or: podman compose -f build/container/compose.yaml up -d -# -# For the sovereign selur path see build/container/stapeln/compose.example.toml. - -services: - # Rust API — primary HTTP/gRPC backend. - rust-api: - build: - context: .. - dockerfile: build/container/Containerfile - image: ghcr.io/hyperpolymath/myproject-api:latest - ports: - - "8080:8080" - environment: - RUST_LOG: "info" - APP_HOST: "[::]" - APP_PORT: "8080" - APP_LOG_FORMAT: "json" - APP_DATA_DIR: "/data" - volumes: - - api-data:/data - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] - interval: 30s - timeout: 5s - retries: 3 - - # Elixir worker — background processing, talks to the API over the network. - elixir-worker: - image: ghcr.io/hyperpolymath/myproject-worker:latest - ports: - - "4000:4000" - environment: - API_URL: "http://rust-api:8080/api/v1" - MIX_ENV: "prod" - APP_LOG_FORMAT: "json" - POOL_SIZE: "10" - depends_on: - rust-api: - condition: service_healthy - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:4000/health"] - interval: 30s - timeout: 5s - retries: 3 - - # rokur — secrets gate. Refuses to come up unless required secrets are present. - rokur: - image: ghcr.io/hyperpolymath/rokur:latest - ports: - - "8081:8081" - environment: - ROKUR_BACKEND: "http://rust-api:8080" - ROKUR_LISTEN: "[::]:8081" - ROKUR_REQUIRED_SECRETS: "DATABASE_URL,JWT_SIGNING_KEY" - ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" - ROKUR_LOG_FORMAT: "json" - volumes: - - rokur-audit:/var/log/rokur - depends_on: - rust-api: - condition: service_healthy - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] - interval: 30s - timeout: 5s - retries: 3 - - # svalinn — edge gateway. TLS, auth, rate limiting, audit. The only - # externally exposed service. - svalinn: - image: ghcr.io/hyperpolymath/svalinn:latest - ports: - - "443:443" - - "80:80" - environment: - SVALINN_BACKEND: "http://rokur:8081" - SVALINN_WORKER_BACKEND: "http://elixir-worker:4000" - SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" - SVALINN_TLS_AUTO: "true" - volumes: - - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro - depends_on: - - rokur - - elixir-worker - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:80/health"] - interval: 30s - timeout: 5s - retries: 3 - -volumes: - api-data: - rokur-audit: diff --git a/czech-file-knife/build/container/compose.yaml b/czech-file-knife/build/container/compose.yaml deleted file mode 100644 index 5170ee0ec..000000000 --- a/czech-file-knife/build/container/compose.yaml +++ /dev/null @@ -1,99 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Czech File Knife — portable compose stack (Tier B) -# -# This is the runtime-agnostic, OCI compose-spec file. It works unchanged with: -# podman compose -f build/container/compose.yaml up -d -# nerdctl compose -f build/container/compose.yaml up -d -# docker compose -f build/container/compose.yaml up -d -# -# It has NO dependency on the stapeln tier. For the sovereign path -# (selur zero-copy IPC, signed .ctp bundles, svalinn trust policy) use -# build/container/stapeln/compose.toml with `selur-compose` instead. -# -# The Justfile picks the right one automatically: -# just container-up # selur-compose if installed, else this file -# -# See https://compose-spec.io for the full specification. - -services: - # ────────────────────────────────────────────────────────────────────── - # Primary application service - # ────────────────────────────────────────────────────────────────────── - "czech-file-knife": - build: - context: .. - dockerfile: build/container/Containerfile - image: "ghcr.io/hyperpolymath/czech-file-knife:latest" - ports: - - "8080:8080" - environment: - APP_HOST: "[::]" - APP_PORT: "8080" - APP_LOG_FORMAT: "json" - APP_DATA_DIR: "/data" - volumes: - - "czech-file-knife-data:/data" - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - - # ────────────────────────────────────────────────────────────────────── - # rokur — secrets-management gate (stapeln). Fronts the app and refuses - # to start the stack unless required secrets are present. on Wolfi. - # Remove this service if you do not use secret gating. - # ────────────────────────────────────────────────────────────────────── - rokur: - image: ghcr.io/hyperpolymath/rokur:latest - environment: - ROKUR_BACKEND: "http://czech-file-knife:8080" - ROKUR_LISTEN: "[::]:8081" - ROKUR_REQUIRED_SECRETS: "" # comma-separated env names that MUST be set - ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" - ROKUR_LOG_FORMAT: "json" - ports: - - "8081:8081" - volumes: - - rokur-audit:/var/log/rokur - depends_on: - "czech-file-knife": - condition: service_healthy - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] - interval: 30s - timeout: 5s - retries: 3 - - # ────────────────────────────────────────────────────────────────────── - # svalinn — edge gateway (stapeln). TLS termination, auth, rate limiting. - # Reads the gatekeeper policy from build/container/stapeln/.gatekeeper.yaml. - # Remove this service for a bare app with no gateway. - # ────────────────────────────────────────────────────────────────────── - svalinn: - image: ghcr.io/hyperpolymath/svalinn:latest - ports: - - "443:443" - - "80:80" - environment: - SVALINN_BACKEND: "http://rokur:8081" - SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" - SVALINN_TLS_AUTO: "true" - volumes: - - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro - depends_on: - - rokur - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-sf", "http://localhost:80/health"] - interval: 30s - timeout: 5s - retries: 3 - -volumes: - "czech-file-knife-data": - rokur-audit: diff --git a/czech-file-knife/build/container/entrypoint.sh b/czech-file-knife/build/container/entrypoint.sh deleted file mode 100755 index 1e7bedec1..000000000 --- a/czech-file-knife/build/container/entrypoint.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: MPL-2.0 -# Czech File Knife container entrypoint -# -# Handles signal propagation, startup logging, and health check -# preparation before exec-ing into the main application process. - -set -e - -# --------------------------------------------------------------------------- -# Signal handling -# --------------------------------------------------------------------------- -# -# Trap SIGTERM and SIGINT so that the application can shut down gracefully -# when Podman sends stop signals (e.g. `podman stop`, `selur-compose down`). - -cleanup() { - echo "Received shutdown signal — stopping czech-file-knife..." - # If the main process is backgrounded, kill it here: - # kill "$MAIN_PID" 2>/dev/null || true - # wait "$MAIN_PID" 2>/dev/null || true - exit 0 -} -trap cleanup TERM INT - -# --------------------------------------------------------------------------- -# Startup logging -# --------------------------------------------------------------------------- - -echo "Starting czech-file-knife..." -echo " Host: ${APP_HOST:-[::]}" -echo " Port: ${APP_PORT:-8080}" -echo " Data: ${APP_DATA_DIR:-/data}" -echo " Log: ${APP_LOG_FORMAT:-json}" - -# --------------------------------------------------------------------------- -# Health check preparation -# --------------------------------------------------------------------------- -# -# Ensure the data directory exists and is writable. -# The VOLUME directive in the Containerfile creates /data, but a bind-mount -# might replace it with an empty directory owned by root. - -if [ -d "${APP_DATA_DIR:-/data}" ]; then - if [ ! -w "${APP_DATA_DIR:-/data}" ]; then - echo "WARNING: ${APP_DATA_DIR:-/data} is not writable by $(whoami)" - fi -fi - -# --------------------------------------------------------------------------- -# Exec into main process -# --------------------------------------------------------------------------- -# -# Replace the entrypoint shell with the application process so that -# signals are delivered directly and PID 1 is the application. -# -# TODO: Replace the command below with your application binary. -# Examples: -# exec /app/czech-file-knife -# exec /app/release/bin/czech-file-knife start -# exec /app/czech-file-knife serve --host "${APP_HOST}" --port "${APP_PORT}" - -exec "$@" diff --git a/czech-file-knife/build/container/stapeln/.gatekeeper.yaml b/czech-file-knife/build/container/stapeln/.gatekeeper.yaml deleted file mode 100644 index eaca4aca6..000000000 --- a/czech-file-knife/build/container/stapeln/.gatekeeper.yaml +++ /dev/null @@ -1,122 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Svalinn gatekeeper policy for Czech File Knife -# -# Controls which operations are permitted through the edge gateway. -# This template provides moderate security defaults — not wide-open test -# mode, but not production-hardened either. Tighten the values below -# before deploying to production. -# -# See: stapeln/container-stack/svalinn/ - -version: "1.0" - -# ============================================================================ -# Authentication -# ============================================================================ -# -# Define which endpoints require authentication and at what level. - -auth: - # Public endpoints — no authentication required. - # Health and readiness probes must always be public so that - # orchestrators (selur, Podman, k8s) can check service status. - public: - - path: "/health" - methods: ["GET"] - - path: "/ready" - methods: ["GET"] - - path: "/metrics" - methods: ["GET"] - - # Endpoints requiring JWT or OAuth2 authentication. - # Svalinn validates the token before forwarding the request. - authenticated: - - path: "/api/v1/*" - methods: ["GET", "POST", "PUT", "DELETE"] - -# ============================================================================ -# Rate Limiting -# ============================================================================ -# -# Protects backend services from overload. Values here are moderate -# defaults — adjust based on your service capacity. - -rate_limits: - # Global limit: applied to all authenticated clients. - global: - requests_per_second: 500 - burst: 1000 - - # Write operations: stricter limit to protect data stores. - writes: - paths: ["/api/v1/*"] - methods: ["POST", "PUT", "DELETE"] - requests_per_second: 100 - burst: 200 - -# ============================================================================ -# Container Trust -# ============================================================================ -# -# Svalinn verifies that all .ctp bundles in the stack are signed by -# trusted keys and carry the required attestations. - -trust: - # Only accept .ctp bundles signed by these keys. - trusted_signers: - - key_id: "czech-file-knife-release" - algorithm: "Ed25519" - public_key_file: "/etc/svalinn/keys/czech-file-knife-release.pub" - - # Require these attestations on all .ctp bundles. - required_attestations: - - "source-signature" - - "sbom-complete" - - # Reject unsigned or untrusted images. - reject_unsigned: true - -# ============================================================================ -# Request Validation -# ============================================================================ -# -# Input validation at the gateway layer — catches malformed requests -# before they reach the application. - -validation: - # Maximum request body size. - max_body_size: "8MB" - - # Reject requests with NaN or Infinity in numeric fields. - reject_nan_inf: true - - # Maximum result limit per list/search query. - max_result_limit: 500 - -# ============================================================================ -# CORS -# ============================================================================ -# -# Cross-Origin Resource Sharing policy. The defaults below allow all -# origins — restrict to your frontend domain(s) in production. - -cors: - allow_origins: ["*"] - allow_methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"] - allow_headers: ["Content-Type", "Authorization"] - max_age: 3600 - -# ============================================================================ -# Logging -# ============================================================================ -# -# Structured logging for svalinn itself. Audit paths log all requests -# (including body hashes) for post-incident investigation. - -logging: - format: "json" - level: "info" - # Log all write operations for audit trail. - audit_paths: - - "/api/v1/*" diff --git a/czech-file-knife/build/container/stapeln/compose.example.toml b/czech-file-knife/build/container/stapeln/compose.example.toml deleted file mode 100644 index 1dd5794ec..000000000 --- a/czech-file-knife/build/container/stapeln/compose.example.toml +++ /dev/null @@ -1,135 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Example selur-compose configuration — multi-service stack -# -# This is a concrete, fully-commented example showing a Rust API + Elixir -# worker + svalinn gateway deployment. Copy this file to compose.toml and -# customise for your project. -# -# Usage: -# cp compose.example.toml compose.toml -# # Edit service names, ports, images -# selur-compose up --detach - -version = "1.0" - -# ============================================================================ -# Services -# ============================================================================ - -# Rust API service — the primary HTTP/gRPC backend. -# Handles incoming requests, data storage, and core business logic. -[services.rust-api] -image = "ghcr.io/hyperpolymath/myproject-api:latest.ctp" - -# Map host port 8080 to container port 8080. -# Use ["[::]:8080:8080"] for explicit IPv6 binding. -ports = ["8080:8080"] - -# Environment variables passed into the container at startup. -# These override defaults in the Containerfile ENV directives. -environment = { - RUST_LOG = "info", # Rust log level (trace, debug, info, warn, error) - APP_HOST = "[::]", # Listen on all interfaces (IPv4 + IPv6) - APP_PORT = "8080", # Internal container port - APP_LOG_FORMAT = "json", # Structured logging for selur/vordr - APP_DATA_DIR = "/data", # Persistent data directory (matches VOLUME) -} - -# Bind-mount a named volume for persistent data. -# Format: "volume-name:/build/container/path" -volumes = ["api-data:/data"] - -# Restart policy: "always" ensures the service comes back after crashes. -# Other options: "no", "on-failure", "unless-stopped" -restart = "always" - -# Health check: selur/Podman uses this to determine if the service is ready. -# The service must respond 2xx to this endpoint within the timeout. -healthcheck = { test = "curl -sf http://localhost:8080/health", interval = "30s", timeout = "5s", retries = 3 } - -# --- - -# Elixir worker service — background processing, event handling, coordination. -# Runs as an OTP release with supervision trees for fault tolerance. -[services.elixir-worker] -image = "ghcr.io/hyperpolymath/myproject-worker:latest.ctp" - -# Separate port for the worker's admin/metrics endpoint. -ports = ["4000:4000"] - -# The worker connects to the Rust API over the internal selur network. -# Service names resolve as hostnames within the compose network. -environment = { - API_URL = "http://rust-api:8080/api/v1", # Internal service discovery - MIX_ENV = "prod", # Elixir release mode - APP_LOG_FORMAT = "json", # Match structured logging format - POOL_SIZE = "10", # DB connection pool size -} - -# depends_on ensures the Rust API starts before the worker. -# Note: This only waits for the container to start, not for the health check. -# Use healthcheck + startup probes for true readiness gating. -depends_on = ["rust-api"] - -restart = "always" -healthcheck = { test = "curl -sf http://localhost:4000/health", interval = "30s", timeout = "5s", retries = 3 } - -# --- - -# Svalinn edge gateway — reverse proxy with policy enforcement. -# All external traffic enters through svalinn, which: -# 1. Terminates TLS (auto-provisioned certificates) -# 2. Validates JWT/OAuth2 authentication -# 3. Enforces rate limits from .gatekeeper.yaml -# 4. Routes requests to the appropriate backend service -# 5. Logs all write operations for audit -[services.svalinn] -image = "ghcr.io/hyperpolymath/svalinn:latest.ctp" - -# External-facing ports: HTTPS (443) and HTTP->HTTPS redirect (80). -ports = ["443:443", "80:80"] - -environment = { - # Backend routing: svalinn proxies to internal services. - SVALINN_BACKEND = "http://rust-api:8080", - SVALINN_WORKER_BACKEND = "http://elixir-worker:4000", - - # Policy file: mounted from the svalinn-config volume. - SVALINN_POLICY_FILE = "/etc/svalinn/gatekeeper.yaml", - - # Auto-provision TLS certificates (Let's Encrypt). - SVALINN_TLS_AUTO = "true", -} - -# Mount .gatekeeper.yaml as read-only policy configuration. -volumes = ["svalinn-config:/etc/svalinn:ro"] - -# Svalinn starts last — it needs both backends to be running. -depends_on = ["rust-api", "elixir-worker"] -restart = "always" -healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", timeout = "5s", retries = 3 } - -# ============================================================================ -# Volumes -# ============================================================================ - -# Persistent storage for the Rust API (database files, indexes, WAL). -[volumes.api-data] -driver = "local" - -# Read-only policy configuration for svalinn gateway. -# Populate with: cp .gatekeeper.yaml /path/to/svalinn-config/gatekeeper.yaml -[volumes.svalinn-config] -driver = "local" - -# ============================================================================ -# Networks -# ============================================================================ - -# selur network: zero-copy IPC between services on the same host. -# When the selur driver is not installed, falls back to standard bridge -# networking (TCP over localhost). Performance is slightly lower but -# functionality is identical. -[networks.default] -driver = "selur" diff --git a/czech-file-knife/build/container/stapeln/compose.toml b/czech-file-knife/build/container/stapeln/compose.toml deleted file mode 100644 index fcd42fc11..000000000 --- a/czech-file-knife/build/container/stapeln/compose.toml +++ /dev/null @@ -1,94 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Czech File Knife selur-compose configuration -# -# Orchestrates the container stack as verified container bundles (.ctp). -# Uses selur zero-copy IPC between services on the same host. -# -# Usage: -# selur-compose up # Start all services -# selur-compose up --detach # Start in background -# selur-compose verify # Verify all .ctp signatures -# selur-compose ps # Check status -# selur-compose logs -f czech-file-knife # Stream logs -# selur-compose down # Stop all services -# -# Fallback (when selur is not installed): this TOML is selur-native and is -# NOT parseable by `podman compose` / `docker compose`. For the portable -# (podman/nerdctl/docker) path use the compose-spec file instead: -# podman compose -f ../compose.yaml up -d # (or: just container-up) - -version = "1.0" - -# ============================================================================ -# Services -# ============================================================================ - -# Primary application service -[services.czech-file-knife] -image = "ghcr.io/hyperpolymath/czech-file-knife:latest.ctp" -ports = ["8080:8080"] -environment = { - APP_HOST = "[::]", - APP_PORT = "8080", - APP_LOG_FORMAT = "json", - APP_DATA_DIR = "/data", -} -volumes = ["czech-file-knife-data:/data"] -restart = "always" -healthcheck = { test = "curl -sf http://localhost:8080/health", interval = "30s", timeout = "5s", retries = 3 } - -# Rokur secrets gate: refuses to start (and refuses to proxy) unless the -# declared secrets are present; emits a structured audit log of every access. -# Sits between svalinn and the application. Remove if you do not gate secrets. -[services.rokur] -image = "ghcr.io/hyperpolymath/rokur:latest.ctp" -ports = ["8081:8081"] -environment = { - ROKUR_BACKEND = "http://czech-file-knife:8080", - ROKUR_LISTEN = "[::]:8081", - ROKUR_REQUIRED_SECRETS = "", - ROKUR_AUDIT_LOG = "/var/log/rokur/audit.jsonl", - ROKUR_LOG_FORMAT = "json", -} -volumes = ["rokur-audit:/var/log/rokur"] -depends_on = ["czech-file-knife"] -restart = "always" -healthcheck = { test = "curl -sf http://localhost:8081/health", interval = "30s", timeout = "5s", retries = 3 } - -# Svalinn edge gateway: validates requests, enforces policies, TLS termination. -# Proxies to rokur, which in turn fronts the application. -[services.svalinn] -image = "ghcr.io/hyperpolymath/svalinn:latest.ctp" -ports = ["443:443", "80:80"] -environment = { - SVALINN_BACKEND = "http://rokur:8081", - SVALINN_POLICY_FILE = "/etc/svalinn/gatekeeper.yaml", - SVALINN_TLS_AUTO = "true", -} -volumes = ["svalinn-config:/etc/svalinn:ro"] -depends_on = ["rokur"] -restart = "always" -healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", timeout = "5s", retries = 3 } - -# ============================================================================ -# Volumes -# ============================================================================ - -[volumes.czech-file-knife-data] -driver = "local" - -[volumes.rokur-audit] -driver = "local" - -[volumes.svalinn-config] -driver = "local" - -# ============================================================================ -# Networks -# ============================================================================ - -# Use selur zero-copy IPC for inter-service communication on the same host. -# Falls back to standard bridge networking when selur driver is unavailable. -[networks.default] -driver = "selur" diff --git a/czech-file-knife/build/container/stapeln/ct-build.sh b/czech-file-knife/build/container/stapeln/ct-build.sh deleted file mode 100755 index 1a46cd48c..000000000 --- a/czech-file-knife/build/container/stapeln/ct-build.sh +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# Czech File Knife — Cerro Torre build, sign, and verify pipeline -# -# Builds the container image, packages it as a verified .ctp bundle, -# signs it with Ed25519, and verifies the result. Gracefully degrades -# when cerro-torre tools are not installed. -# -# Prerequisites: -# - podman / nerdctl / docker (container build — required; set CONTAINER_ENGINE) -# - ct (cerro-torre CLI: pack, sign, verify — optional) -# - cerro-sign (Ed25519 signing — optional, ct sign used as fallback) -# -# Usage: -# ./ct-build.sh # Build + sign (local only) -# ./ct-build.sh --push # Build + sign + push to registry -# CT_KEY_ID=my-key ./ct-build.sh # Use specific signing key -# -# Environment variables: -# CT_KEY_ID — Signing key identifier (default: czech-file-knife-release) -# CT_REGISTRY — OCI registry to push to (default: ghcr.io/hyperpolymath) -# CT_TAG — Image tag (default: latest) - -set -euo pipefail - -# --------------------------------------------------------------------------- -# Configuration -# --------------------------------------------------------------------------- - -# This script lives in build/container/stapeln/. The repo root is two levels up, -# and the Tier-A Containerfile is one level up in build/container/. -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -CONTAINER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" # build/container/stapeln -> repo root - -# Container engine — podman (recommended), nerdctl or docker. -ENGINE="${CONTAINER_ENGINE:-podman}" - -PUSH="" -for arg in "$@"; do - if [ "$arg" = "--push" ]; then - PUSH="--push" - fi -done - -CT_KEY_ID="${CT_KEY_ID:-czech-file-knife-release}" -CT_REGISTRY="${CT_REGISTRY:-ghcr.io/hyperpolymath}" -CT_TAG="${CT_TAG:-latest}" - -IMAGE_NAME="czech-file-knife" -FULL_IMAGE="${CT_REGISTRY}/${IMAGE_NAME}:${CT_TAG}" -CTP_FILE="${SCRIPT_DIR}/${IMAGE_NAME}-${CT_TAG}.ctp" - -echo "=== Czech File Knife Cerro Torre Build Pipeline ===" -echo " Image: ${FULL_IMAGE}" -echo " Key: ${CT_KEY_ID}" -echo " Bundle: ${CTP_FILE}" -echo "" - -# --------------------------------------------------------------------------- -# Step 1: Build container image (CONTAINER_ENGINE, default podman) -# --------------------------------------------------------------------------- - -echo "--- Step 1: Building container image (${ENGINE}) ---" - -"${ENGINE}" build \ - -t "${FULL_IMAGE}" \ - -f "${CONTAINER_DIR}/Containerfile" \ - "${REPO_ROOT}" - -echo " Built: ${FULL_IMAGE}" -echo "" - -# --------------------------------------------------------------------------- -# Step 2: Pack into .ctp bundle -# --------------------------------------------------------------------------- - -echo "--- Step 2: Packing into .ctp bundle ---" - -if command -v ct &>/dev/null; then - ct pack "${FULL_IMAGE}" -o "${CTP_FILE}" - echo " Packed: ${CTP_FILE}" -else - echo " SKIP: ct not found (install cerro-torre CLI from stapeln/container-stack/cerro-torre)" - echo " The container image is built and tagged but not packed as a .ctp bundle." - echo " To pack manually: ct pack ${FULL_IMAGE} -o ${CTP_FILE}" - echo "" - if [ "$PUSH" = "--push" ]; then - echo "--- Pushing unsigned OCI image (no .ctp) ---" - "${ENGINE}" push "${FULL_IMAGE}" - echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" - fi - echo "" - echo "=== Build complete (without .ctp signing) ===" - exit 0 -fi - -echo "" - -# --------------------------------------------------------------------------- -# Step 3: Sign the .ctp bundle -# --------------------------------------------------------------------------- - -echo "--- Step 3: Signing .ctp bundle ---" - -if command -v cerro-sign &>/dev/null; then - cerro-sign sign "${CTP_FILE}" --key-id "${CT_KEY_ID}" - echo " Signed: ${CTP_FILE} (key: ${CT_KEY_ID})" -elif command -v ct &>/dev/null; then - ct sign "${CTP_FILE}" --key "${CT_KEY_ID}" - echo " Signed: ${CTP_FILE} (key: ${CT_KEY_ID})" -else - echo " SKIP: cerro-sign not found (install from stapeln/container-stack/cerro-torre)" -fi - -echo "" - -# --------------------------------------------------------------------------- -# Step 4: Verify the .ctp bundle -# --------------------------------------------------------------------------- - -echo "--- Step 4: Verifying .ctp bundle ---" - -if command -v ct &>/dev/null; then - ct verify "${CTP_FILE}" - echo " Verified: ${CTP_FILE}" -else - echo " SKIP: ct not found" -fi - -echo "" - -# --------------------------------------------------------------------------- -# Step 5: Push to registry (optional) -# --------------------------------------------------------------------------- - -if [ "$PUSH" = "--push" ]; then - echo "--- Step 5: Pushing to registry ---" - - if command -v ct &>/dev/null; then - ct push "${CTP_FILE}" "${FULL_IMAGE}" - echo " Pushed: ${FULL_IMAGE}" - else - # Fall back to podman push (unsigned OCI image) - echo " ct not available, falling back to '${ENGINE}' push (unsigned)" - "${ENGINE}" push "${FULL_IMAGE}" - echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" - fi - echo "" -fi - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- - -echo "=== Build pipeline complete ===" -echo " Image: ${FULL_IMAGE}" -echo " Bundle: ${CTP_FILE}" -echo "" -echo " To deploy with selur-compose:" -echo " cd build/container/stapeln && selur-compose up" -echo "" -echo " To verify at any time:" -echo " ct verify ${CTP_FILE}" -echo "" -echo " To explain the verification chain:" -echo " ct explain ${CTP_FILE}" diff --git a/czech-file-knife/build/container/stapeln/deploy.k9.ncl b/czech-file-knife/build/container/stapeln/deploy.k9.ncl deleted file mode 100644 index ea29c013e..000000000 --- a/czech-file-knife/build/container/stapeln/deploy.k9.ncl +++ /dev/null @@ -1,170 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# deploy.k9.ncl — Czech File Knife deployment component (Hunt level) -# -# k9-svc deployment specification with full pedigree (L1-L5). -# Security Level: 'Hunt (requires cryptographic handshake for execution). -# -# WARNING: This component can execute shell commands! -# It requires explicit authorisation via the Leash system. -# -# Usage: -# nickel typecheck build/container/deploy.k9.ncl -# k9-svc validate build/container/deploy.k9.ncl -# k9-svc deploy build/container/deploy.k9.ncl --env production - -# The component's pedigree (self-description across five layers) -let component_pedigree = { - # ───────────────────────────────────────────────────────────── - # L1: The Snout — Identity - # ───────────────────────────────────────────────────────────── - metadata = { - name = "czech-file-knife-deploy", - version = "0.1.0", - breed = "application/vnd.k9+nickel", - magic_number = "K9!", - description = "Czech File Knife deployment component (Hunt level)", - }, - - # ───────────────────────────────────────────────────────────── - # L2: The Scent — Target Environment - # ───────────────────────────────────────────────────────────── - target = { - os = 'Linux, - is_edge = false, - requires_podman = true, - min_memory_mb = 256, - }, - - # ───────────────────────────────────────────────────────────── - # L3: The Leash — Security - # ───────────────────────────────────────────────────────────── - security = { - trust_level = 'Hunt, - # Named field the k9 validators grep for (leash/security_level); - # value mirrors trust_level — this component is documented Hunt-level. - security_level = 'Hunt, - allow_network = true, - allow_filesystem_write = true, - allow_subprocess = true, - # In production, replace with a real Ed25519 signature. - signature = "PLACEHOLDER-SIGNATURE-REQUIRED-FOR-HUNT", - }, - - # ───────────────────────────────────────────────────────────── - # L4: The Gut — Self-Validation - # ───────────────────────────────────────────────────────────── - validation = { - checksum = "sha256:placeholder", - pedigree_version = "1.0.0", - hunt_authorized = false, # Must be set true after handshake - }, - - # ───────────────────────────────────────────────────────────── - # L5: The Muscle — Deployment Recipes - # ───────────────────────────────────────────────────────────── - recipes = { - install = "just container-build", - validate = "just container-verify", - deploy = "just container-up", - migrate = "just container-build && just container-up", - }, -} in - -# Deployment configuration -let deployment = { - # Target environments (dev / staging / production) - environments = { - dev = { - replicas = 1, - memory = "256Mi", - cpu = "100m", - image_tag = "dev", - }, - staging = { - replicas = 2, - memory = "512Mi", - cpu = "250m", - image_tag = "staging", - }, - production = { - replicas = 3, - memory = "1Gi", - cpu = "500m", - image_tag = "latest", - }, - }, - - # Container configuration - container = { - image = "ghcr.io/hyperpolymath/czech-file-knife", - port = 8080, - health_check = "/health", - readiness_check = "/ready", - }, - - # Deployment strategy - strategy = { - type = "rolling", - max_surge = 1, - max_unavailable = 0, - }, -} in - -# Deployment scripts (executed at Hunt level) -let scripts = { - # Pre-deployment validation - pre_deploy = m%" -#!/bin/sh -set -eu -echo "K9: Pre-deployment validation for czech-file-knife..." -cd build/container/stapeln && selur-compose verify || podman compose --file ../compose.yaml config -echo "K9: Validation passed." -"%, - - # Deployment script - deploy = m%" -#!/bin/sh -set -eu -ENV="${1:-dev}" -echo "K9: Deploying czech-file-knife to $ENV environment..." -cd build/container/stapeln -./ct-build.sh -selur-compose up --detach || podman compose --file ../compose.yaml up --detach -echo "K9: Deployment to $ENV complete." -"%, - - # Rollback script - rollback = m%" -#!/bin/sh -set -eu -echo "K9: Rolling back czech-file-knife deployment..." -cd build/container/stapeln -selur-compose down || podman compose --file ../compose.yaml down -echo "K9: Rollback complete." -"%, -} in - -# Export the component -{ - pedigree = component_pedigree & { name = "czech-file-knife-deploy" }, - deployment = deployment, - scripts = scripts, - - # Security check: this component requires Hunt level - required_level = 'Hunt, - - # Warning for users - warning = m%" -WARNING: This is a Hunt-level component. - -It can execute shell commands and modify your system. -Before running, ensure you have: - -1. Reviewed the deployment scripts above -2. Verified the signature (when implemented) -3. Explicitly authorised Hunt-level execution - -Run with: k9-svc authorize build/container/deploy.k9.ncl && k9-svc deploy build/container/deploy.k9.ncl -"%, -} diff --git a/czech-file-knife/build/container/stapeln/manifest.toml b/czech-file-knife/build/container/stapeln/manifest.toml deleted file mode 100644 index ded14aef4..000000000 --- a/czech-file-knife/build/container/stapeln/manifest.toml +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Cerro Torre manifest for Czech File Knife .ctp bundle -# -# This manifest describes the container image for verified -# container packaging. Used by `ct pack` to create .ctp bundles. - -[metadata] -name = "czech-file-knife" -version = "0.1.0" -revision = 1 -summary = "The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage." -description = """ -Czech File Knife — containerised service packaged as a verified -cerro-torre .ctp bundle with Ed25519 signing and full provenance -tracking. -""" -license = "MPL-2.0" -homepage = "https://github.com/hyperpolymath/czech-file-knife" -maintainer = "Jonathan D.A. Jewell " - -[provenance] -upstream = "https://github.com/hyperpolymath/czech-file-knife" -import_date = 2026-09-28T00:00:00Z - -[dependencies] -runtime = ["ca-certificates", "curl"] -build = [] - -[build] -system = "podman" - -[build.environment] -APP_HOST = "[::]" -APP_PORT = "8080" - -[outputs] -primary = "czech-file-knife" -split = [] - -[attestations] -require = ["source-signature", "sbom-complete"] -recommend = ["security-audit", "reproducible-build"] - -# Runtime security profile -[security] -user = "appuser" -group = "appuser" -read_only_root = false -no_new_privileges = true - -[security.capabilities] -drop = ["ALL"] -add = ["NET_BIND_SERVICE"] - -[security.network] -listen_tcp = [8080] - -[security.filesystem] -read = ["/app/", "/data/"] -write = ["/data/", "/tmp/"] -execute = ["/app/entrypoint.sh"] diff --git a/czech-file-knife/build/container/stapeln/rokur.toml b/czech-file-knife/build/container/stapeln/rokur.toml deleted file mode 100644 index c10f408bb..000000000 --- a/czech-file-knife/build/container/stapeln/rokur.toml +++ /dev/null @@ -1,81 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Rokur secrets-gate configuration for Czech File Knife -# -# Rokur (stapeln container stack) is a secrets-management gate: a small -# Deno service that sits in front of the application and refuses to pass -# traffic unless the declared secrets are present and valid. It rate-limits -# per client and writes a structured audit log of every secret access. -# -# Most settings can also be supplied as environment variables (the ROKUR_* -# names below); this file is the declarative equivalent consumed at startup -# and re-read on SIGHUP. Environment variables win over file values. -# -# Usage: -# rokur serve --config build/container/stapeln/rokur.toml -# # or, in the stack, via compose (see compose.toml / compose.yaml) - -[metadata] -name = "czech-file-knife-gate" -version = "0.1.0" - -# ============================================================================ -# Listener / routing (env: ROKUR_LISTEN, ROKUR_BACKEND) -# ============================================================================ - -[server] -# Address rokur listens on (inside the container). -listen = "[::]:8081" -# The upstream application rokur fronts once the gate is satisfied. -backend = "http://czech-file-knife:8080" -# Health/readiness endpoints rokur exposes for vordr / orchestrators. -health_path = "/health" -ready_path = "/ready" - -# ============================================================================ -# Required secrets (env: ROKUR_REQUIRED_SECRETS, comma-separated) -# ============================================================================ -# -# Names of secrets that MUST be present (as environment variables or in the -# mounted secret store) before rokur will start the gate. An empty list means -# the gate starts open — tighten this before production. - -[secrets] -required = [] -# Example: -# required = ["DATABASE_URL", "JWT_SIGNING_KEY", "TLS_PRIVATE_KEY"] - -# Where rokur looks for secret material, in order. "env" reads process -# environment; "file" reads a mounted directory (one file per secret). -sources = ["env"] -# file_dir = "/run/secrets" - -# ============================================================================ -# Rate limiting (rokur/rate_limit.js — per-client token bucket) -# ============================================================================ - -[rate_limit] -enabled = true -requests_per_second = 100 -burst = 200 - -# ============================================================================ -# Policy engine (rokur/policy/engine.js — pluggable evaluator) -# ============================================================================ - -[policy] -# "builtin" uses rokur's built-in evaluator; "external" delegates to a -# command/endpoint you supply. -engine = "builtin" -# external_command = "/usr/local/bin/my-policy" - -# ============================================================================ -# Audit log (env: ROKUR_AUDIT_LOG, ROKUR_LOG_FORMAT) -# ============================================================================ - -[audit] -log = "/var/log/rokur/audit.jsonl" -format = "json" -# Record a hash of secret values (never the values themselves) for tamper -# evidence in the audit trail. -hash_values = true diff --git a/czech-file-knife/build/container/stapeln/vordr.toml b/czech-file-knife/build/container/stapeln/vordr.toml deleted file mode 100644 index c19fcf81f..000000000 --- a/czech-file-knife/build/container/stapeln/vordr.toml +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Vordr runtime monitoring configuration for Czech File Knife -# -# Vordr watches container health, detects crashes, tracks resource usage, -# and emits structured logs. It runs alongside the application stack and -# provides runtime observability without requiring in-process agents. -# -# Usage: -# vordr watch --config build/container/vordr.toml -# vordr status -# vordr report - -[metadata] -name = "czech-file-knife" -version = "0.1.0" - -# ============================================================================ -# Health Monitoring -# ============================================================================ -# -# Vordr periodically probes these endpoints. If a probe fails beyond the -# failure_threshold, vordr emits an alert and (optionally) restarts the -# container via Podman. - -[health] -# Primary health endpoint — must return 2xx. -endpoint = "http://localhost:8080/health" -interval = "30s" -timeout = "5s" -failure_threshold = 3 - -# Readiness endpoint — checked during startup and after restarts. -readiness_endpoint = "http://localhost:8080/ready" -readiness_timeout = "10s" - -# Action on failure: "alert" (log + notify) or "restart" (alert + restart). -on_failure = "alert" - -# Additional deep probes for the sidecar stapeln services. Crash detection -# below already tracks every container's lifecycle via the engine; these add -# endpoint-level health checks for the secrets gate and the edge gateway. -[[health.extra]] -name = "rokur" -endpoint = "http://localhost:8081/health" -interval = "30s" -timeout = "5s" -failure_threshold = 3 - -[[health.extra]] -name = "svalinn" -endpoint = "http://localhost:80/health" -interval = "30s" -timeout = "5s" -failure_threshold = 3 - -# ============================================================================ -# Crash Detection -# ============================================================================ -# -# Monitors container state via the engine (podman/nerdctl/docker). Detects -# OOM kills, segfaults, and unexpected exits across the whole stack. - -[crash_detection] -enabled = true -# Maximum restarts within the window before vordr stops restarting. -max_restarts = 5 -restart_window = "10m" - -# ============================================================================ -# Resource Thresholds -# ============================================================================ -# -# Alert when resource usage exceeds these thresholds. Values are percentages -# of the container's cgroup limits (or host limits if uncapped). - -[resources] -cpu_warn = 80 # Percentage — warn at 80% sustained CPU. -cpu_critical = 95 # Percentage — critical alert at 95%. -memory_warn = 75 # Percentage of memory limit. -memory_critical = 90 -disk_warn = 80 # Percentage of volume usage. -disk_critical = 95 - -# Sample interval for resource metrics. -sample_interval = "15s" - -# ============================================================================ -# Log Output -# ============================================================================ -# -# Vordr emits its own logs (not the application's) in structured format. - -[logging] -format = "json" -level = "info" -# Write vordr logs to stdout (captured by Podman) and optionally to file. -output = "stdout" -# file = "/var/log/vordr/czech-file-knife.log" - -# ============================================================================ -# Notifications (optional) -# ============================================================================ -# -# Uncomment and configure to receive alerts via webhook or email. - -# [notifications.webhook] -# url = "https://example.com/hooks/vordr" -# method = "POST" -# headers = { "Content-Type" = "application/json" } -# on = ["failure", "recovery", "resource_critical"] - -# [notifications.email] -# to = "j.d.a.jewell@open.ac.uk" -# from = "vordr@czech-file-knife.local" -# smtp = "smtp://localhost:25" -# on = ["failure", "resource_critical"] diff --git a/czech-file-knife/build/guix.scm b/czech-file-knife/build/guix.scm deleted file mode 100755 index f9142f8a3..000000000 --- a/czech-file-knife/build/guix.scm +++ /dev/null @@ -1,81 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; SPDX-FileCopyrightText: 2025 hyperpolymath -;; -;; Guix package definition for czech-file-knife -;; Build: guix build -f build/guix.scm -;; Shell: guix shell -D -f build/guix.scm - -(use-modules (guix packages) - (guix gexp) - (guix git-download) - (guix build-system cargo) - (guix licenses) - (gnu packages rust) - (gnu packages rust-apps) - (gnu packages pkg-config) - (gnu packages tls) - (gnu packages linux) - (gnu packages databases) - (gnu packages compression)) - -(define-public czech-file-knife - (package - (name "czech-file-knife") - (version "0.1.0") - (source - (local-file "." "czech-file-knife-checkout" - #:recursive? #t - #:select? (git-predicate "."))) - (build-system cargo-build-system) - (arguments - `(#:cargo-build-flags '("-p" "cfk-cli") - #:phases - (modify-phases %standard-phases - (add-after 'install 'install-completions - (lambda* (#:key outputs #:allow-other-keys) - (let* ((out (assoc-ref outputs "out")) - (bash (string-append out "/share/bash-completion/completions")) - (zsh (string-append out "/share/zsh/site-functions")) - (fish (string-append out "/share/fish/vendor_completions.d"))) - (mkdir-p bash) - (mkdir-p zsh) - (mkdir-p fish) - ;; Generate completions via cfk cli - (invoke (string-append out "/bin/cfk") "completion" "bash" - "--output" (string-append bash "/cfk")) - (invoke (string-append out "/bin/cfk") "completion" "zsh" - "--output" (string-append zsh "/_cfk")) - (invoke (string-append out "/bin/cfk") "completion" "fish" - "--output" (string-append fish "/cfk.fish")))))))) - (native-inputs - (list pkg-config rust rust-cargo)) - (inputs - (list openssl - fuse - sqlite)) - (synopsis "Universal cloud file management CLI") - (description - "Czech File Knife (CFK) provides unified access to multiple cloud storage -providers through a single command-line interface. Features include: -@itemize -@item Multi-provider support (S3, GCS, Azure, local) -@item Content-addressable caching with BLAKE3 -@item Full-text search with Tantivy -@item FUSE virtual filesystem mount -@item Provider-agnostic file operations -@end itemize") - (home-page "https://github.com/hyperpolymath/czech-file-knife") - (license agpl3+))) - -;; Workspace development package -(define-public czech-file-knife-dev - (package - (inherit czech-file-knife) - (name "czech-file-knife-dev") - (arguments - `(#:cargo-build-flags '("--workspace"))) - (synopsis "Czech File Knife development package") - (description - "Development package with all workspace crates for czech-file-knife."))) - -czech-file-knife diff --git a/czech-file-knife/build/just/assess.just b/czech-file-knife/build/just/assess.just deleted file mode 100644 index 3c9903170..000000000 --- a/czech-file-knife/build/just/assess.just +++ /dev/null @@ -1,270 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE VERIFICATION -# -# Imported by ../../Justfile via `import? "build/just/assess.just"`. -# Recipes here advise on what to keep/remove (`self-assess`, read-only) and -# verify that OpenSSF Best Practices prerequisites are present (`verify`, -# called by `init` and CI). - -# Analyse this project and advise what to keep, remove, or leave for later. -# Does NOT modify any files — only prints recommendations. -self-assess: - #!/usr/bin/env bash - set -euo pipefail - - echo "═══════════════════════════════════════════════════" - echo " RSR Project Self-Assessment" - echo "═══════════════════════════════════════════════════" - echo "" - echo "Scanning project structure to identify what's" - echo "relevant, removable, or worth keeping for later..." - echo "" - - # Detect project characteristics - HAS_RUST=false; [ -f "Cargo.toml" ] && HAS_RUST=true - HAS_ELIXIR=false; [ -f "mix.exs" ] && HAS_ELIXIR=true - HAS_RESCRIPT=false; [ -f "rescript.json" ] || [ -f "bsconfig.json" ] && HAS_RESCRIPT=true - HAS_IDRIS=false; ls *.ipkg >/dev/null 2>&1 && HAS_IDRIS=true - HAS_ZIG=false; [ -f "build.zig" ] || [ -d "ffi/zig" ] && HAS_ZIG=true - HAS_GLEAM=false; [ -f "gleam.toml" ] && HAS_GLEAM=true - HAS_CONTAINER=false; [ -f "Containerfile" ] || [ -f "build/container/Containerfile" ] && HAS_CONTAINER=true - HAS_TESTS=false; [ -d "test" ] || [ -d "tests" ] || [ -d "__tests__" ] && HAS_TESTS=true - HAS_API=false; grep -rq 'port\|listen\|endpoint' --include="*.exs" --include="*.rs" --include="*.toml" . 2>/dev/null && HAS_API=true - IS_LIBRARY=false; [ -f "Cargo.toml" ] && grep -q '\[lib\]' Cargo.toml 2>/dev/null && IS_LIBRARY=true - - echo "Detected: Rust=$HAS_RUST Elixir=$HAS_ELIXIR ReScript=$HAS_RESCRIPT" - echo " Idris=$HAS_IDRIS Zig=$HAS_ZIG Gleam=$HAS_GLEAM" - echo " Container=$HAS_CONTAINER Tests=$HAS_TESTS API=$HAS_API" - echo "" - - # ── ESSENTIAL (removing these breaks RSR compliance) ────────── - echo "── ESSENTIAL (removing breaks Rhodium Standard) ──────────" - echo "" - - for f in LICENSE .editorconfig .gitignore; do - if [ -f "$f" ]; then - echo " ✓ $f — KEEP (RSR required)" - else - echo " ✗ $f — MISSING (RSR violation!)" - fi - done - - # Community health files live at root OR .github/ (.github/ is the estate's - # canonical location — see .machine_readable/root-allow.txt). Checking root - # alone reported the template's own .github/ copies as "MISSING (RSR - # violation!)", which is a false negative. - for f in SECURITY CODE_OF_CONDUCT CONTRIBUTING; do - found="" - for cand in "$f.md" "$f.adoc" ".github/$f.md" ".github/$f.adoc"; do - [ -f "$cand" ] && { found="$cand"; break; } - done - if [ -n "$found" ]; then - echo " ✓ $found — KEEP (RSR required)" - else - echo " ✗ $f.md — MISSING at root and .github/ (RSR violation!)" - fi - done - - if [ -d ".machine_readable/descriptiles" ]; then - echo " ✓ .machine_readable/descriptiles/ — KEEP (SCM checkpoint files)" - else - echo " ✗ .machine_readable/descriptiles/ — MISSING (RSR violation!)" - fi - - if [ -d ".github/workflows" ]; then - WF_COUNT=$(ls .github/workflows/*.yml 2>/dev/null | wc -l) - echo " ✓ .github/workflows/ — KEEP ($WF_COUNT workflows, RSR CI/CD)" - fi - echo "" - - # ── RELEVANT (useful for your project type) ─────────────────── - echo "── RELEVANT (matches your project) ───────────────────────" - echo "" - - if $HAS_IDRIS && { [ -d "src/interface/abi" ] || [ -d "src/interface/Abi" ]; }; then - echo " ✓ src/interface/abi/ — KEEP (Idris2 ABI definitions)" - elif ! $HAS_IDRIS && { [ -d "src/interface/abi" ] || [ -d "src/interface/Abi" ]; }; then - echo " ? src/interface/abi/ — No Idris2 detected." - echo " → KEEP if you plan to add formal verification later." - echo " → SAFE TO REMOVE if this project will never use Idris2." - echo " ⚠ Consequence: no formally verified interface definitions." - fi - - if $HAS_ZIG && [ -d "src/interface/ffi" ]; then - echo " ✓ src/interface/ffi/ — KEEP (Zig FFI bridge)" - elif ! $HAS_ZIG && [ -d "src/interface/ffi" ]; then - echo " ? src/interface/ffi/ — No Zig detected." - echo " → KEEP if you plan C ABI interop later." - echo " → SAFE TO REMOVE if this is a pure web/scripting project." - echo " ⚠ Consequence: no C-compatible FFI bridge." - fi - - if $HAS_API && [ -f ".machine_readable/integrations/groove.a2ml" ]; then - PORT=$(grep '(port ' .machine_readable/integrations/groove.a2ml 2>/dev/null | sed 's/.*(port \([0-9]*\)).*/\1/') - if [ "$PORT" = "0" ]; then - echo " ⚠ groove.a2ml — Port not assigned. Run 'just groove-setup'." - else - echo " ✓ groove.a2ml — KEEP (Groove discovery on port $PORT)" - fi - elif $HAS_API; then - echo " ✗ groove.a2ml — MISSING. Your project has an API but no Groove manifest." - echo " → Run 'just groove-setup' to enable snap-on/snap-off discovery." - fi - - if $HAS_CONTAINER && [ -d "build/container" ]; then - echo " ✓ build/container/ — KEEP (Containerfile + compose)" - elif ! $HAS_CONTAINER && [ -d "build/container" ]; then - echo " ? build/container/ — No Containerfile detected in use." - echo " → KEEP if you plan to containerise later." - echo " → SAFE TO REMOVE for libraries and CLI tools (run: just no-container)." - fi - - echo "" - - # ── SAFE TO REMOVE (not relevant, no consequences) ──────────── - echo "── SAFE TO REMOVE (no RSR consequences) ──────────────────" - echo "" - - if ! $HAS_RESCRIPT && [ -d "examples" ] && ls examples/*.res >/dev/null 2>&1; then - echo " ○ examples/*.res — Template ReScript examples. Not your code." - fi - - if [ -f ".machine_readable/ai/PLACEHOLDERS.adoc" ]; then - echo " ○ .machine_readable/ai/PLACEHOLDERS.adoc — Template doc. Remove after init." - fi - - if { [ -f "build/flake.nix" ] || [ -f "flake.nix" ]; } && ! command -v nix >/dev/null 2>&1; then - echo " ○ flake.nix — Nix flake. Safe to remove if you don't use Nix." - echo " → KEEP if others might build with Nix." - fi - - if [ -f "build/guix.scm" ] && ! command -v guix >/dev/null 2>&1; then - echo " ○ build/guix.scm — Guix package. Safe to remove if you don't use Guix." - echo " → KEEP if others might build with Guix." - fi - - echo "" - - # ── FUTURE VALUE (not needed now, worth keeping) ────────────── - echo "── KEEP FOR FUTURE (not active, but valuable later) ──────" - echo "" - - if [ -d ".machine_readable/contractiles" ]; then - echo " ◆ contractiles/ — Must/Trust/Dust/Lust contracts." - echo " Not enforced until you configure them, but ready when you need" - echo " automated compliance checking. Zero cost to keep." - fi - - if [ -d ".machine_readable/bot_directives" ]; then - echo " ◆ bot_directives/ — Gitbot fleet configuration." - echo " Not active until gitbot-fleet is connected. Keeps your repo" - echo " ready for automated maintenance when the fleet arrives." - fi - - if [ -d ".machine_readable/bot_directives" ]; then - echo " ◆ bot_directives/ — AI agent methodology config." - echo " Guides Claude/Gemini/etc on how to work in this repo." - echo " No cost to keep. Improves AI assistance quality." - fi - - if [ -d "docs/governance" ]; then - echo " ◆ docs/governance/ — TSDM, CRG, maintenance checklists." - echo " Not needed for solo projects. Essential when you add contributors." - fi - - if [ -d "verification" ]; then - echo " ◆ verification/ — Proofs, benchmarks, fuzzing, safety case." - echo " Empty scaffolds until you add formal verification." - echo " Worth keeping for any project that claims safety properties." - fi - - echo "" - echo "═══════════════════════════════════════════════════" - echo " Assessment complete. No files were modified." - echo "═══════════════════════════════════════════════════" - -# Verify OpenSSF Best Practices prerequisites — fails if any required file is missing -verify: - #!/usr/bin/env bash - set -euo pipefail - - echo "=== OpenSSF Best Practices Verification ===" - ERRORS=0 - - check_file() { - if [ ! -f "$1" ]; then - echo " FAIL: $1 missing" - ERRORS=$((ERRORS + 1)) - else - echo " OK: $1" - fi - } - - # Accept either .md or .adoc for documentation files - check_either() { - if [ ! -f "$1" ] && [ ! -f "$2" ]; then - echo " FAIL: $1 (or $2) missing" - ERRORS=$((ERRORS + 1)) - else - local found="$1" - [ -f "$2" ] && found="$2" - [ -f "$1" ] && found="$1" - echo " OK: $found" - fi - } - - # Community health files: accept root OR .github/, .md OR .adoc. - # - # .github/ is the canonical estate location for these — see - # .machine_readable/root-allow.txt, which says of CONTRIBUTING.md: - # "Accepted at root OR .github/ — CI (openssf/quality/rhodibot) now - # checks both; .github/ is the canonical estate location - # (org-inherited). Allow-listed if present at root." - # and of SECURITY.md: "security-policy contractile now accepts the - # .github/ copy." - # - # GitHub and the OpenSSF Best Practices criteria both treat .github/ as a - # valid home for them. Checking root only made this recipe a FALSE - # NEGATIVE: the files exist in .github/ and always have, so the template - # failed its own `just verify` ("repo cannot ship"), and so did every repo - # instantiated from it. This recipe was the last checker still looking at - # root alone. - check_community_file() { - local base="$1" - local cand - for cand in "$base.md" "$base.adoc" ".github/$base.md" ".github/$base.adoc"; do - if [ -f "$cand" ]; then - echo " OK: $cand" - return 0 - fi - done - echo " FAIL: $base.md (or $base.adoc) missing at root or .github/" - ERRORS=$((ERRORS + 1)) - } - - check_community_file "SECURITY" - check_file "LICENSE" - check_community_file "CONTRIBUTING" - check_either "README.adoc" "README.md" - check_file ".machine_readable/descriptiles/STATE.a2ml" - check_file ".machine_readable/descriptiles/META.a2ml" - check_file ".machine_readable/descriptiles/ECOSYSTEM.a2ml" - check_either "CHANGELOG.md" "CHANGELOG.adoc" - - # Check at least 1 workflow exists - WORKFLOW_COUNT=$(find .github/workflows -name '*.yml' -o -name '*.yaml' 2>/dev/null | wc -l) - if [ "$WORKFLOW_COUNT" -eq 0 ]; then - echo " FAIL: No workflows in .github/workflows/" - ERRORS=$((ERRORS + 1)) - else - echo " OK: .github/workflows/ ($WORKFLOW_COUNT workflows)" - fi - - echo "" - if [ "$ERRORS" -gt 0 ]; then - echo "FAIL: $ERRORS OpenSSF prerequisites missing — repo cannot ship." - exit 1 - fi - echo "PASS: All OpenSSF Best Practices prerequisites satisfied." diff --git a/czech-file-knife/build/just/container.just b/czech-file-knife/build/just/container.just deleted file mode 100644 index cb1e9df25..000000000 --- a/czech-file-knife/build/just/container.just +++ /dev/null @@ -1,284 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# CONTAINERS — three-tier containerisation -# -# Imported by ../../Justfile via `import? "build/just/container.just"`. -# Variables (project, OWNER, ...) and `set shell` are inherited from the root. -# -# Tier A — OCI standard: build/container/Containerfile, build/container/.containerignore -# Tier B — portable engine: build/container/compose.yaml (podman | nerdctl | docker) -# Tier C — stapeln sovereign: build/container/stapeln/ (cerro-torre, svalinn, -# vordr, selur, rokur, k9) -# -# Engine is auto-selected: $CONTAINER_ENGINE, else the first of podman / -# nerdctl / docker found on PATH. Override with: CONTAINER_ENGINE=docker just … -# -# Narrow the template to the tiers you want (see the Narrowing section below): -# just container-keep oci portable # keep image + portable compose, drop the rest -# just no-stapeln | no-devcontainer | no-container # convenience aliases - -# ── Engine detection (shared snippet) ────────────────────────────────────── -# Resolves the OCI engine into $ENGINE. Sourced at the top of each recipe. -_engine := ''' - ENGINE="${CONTAINER_ENGINE:-}" - if [ -z "$ENGINE" ]; then - for e in podman nerdctl docker; do - if command -v "$e" >/dev/null 2>&1; then ENGINE="$e"; break; fi - done - fi - if [ -z "$ENGINE" ]; then - echo "No container engine found (looked for podman, nerdctl, docker)." >&2 - echo "Install one or set CONTAINER_ENGINE." >&2 - exit 1 - fi -''' - -# Initialise container templates — substitute placeholders with project values -container-init: - #!/usr/bin/env bash - set -euo pipefail - - if [ ! -d "build/container" ]; then - echo "Error: build/container/ directory not found." - echo "This repo may not have been created from rsr-template-repo," - echo "or containerisation was removed with 'just no-container'." - exit 1 - fi - - echo "=== Container Template Initialisation ===" - echo "" - - # Load RSR defaults if available - DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" - if [ -f "$DEFAULTS" ]; then - echo "Loading defaults from $DEFAULTS" - # shellcheck source=/dev/null - source "$DEFAULTS" - echo "" - fi - - # Prompt for container-specific values - read -rp "Service name (e.g. my-api) [czech_file_knife]: " _SERVICE_NAME - SERVICE_NAME="${_SERVICE_NAME:-czech_file_knife}" - - read -rp "Primary port [8080]: " _PORT - PORT="${_PORT:-8080}" - - read -rp "Container registry [ghcr.io/${OWNER:-hyperpolymath}]: " _REGISTRY - REGISTRY="${_REGISTRY:-ghcr.io/${OWNER:-hyperpolymath}}" - - echo "" - echo " Service: $SERVICE_NAME" - echo " Port: $PORT" - echo " Registry: $REGISTRY" - echo "" - read -rp "Proceed? [Y/n] " CONFIRM - [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 - - echo "" - echo "Replacing container placeholders..." - - # Brace tokens as variables (hex escapes avoid just interpolation) - LB=$(printf '\x7b\x7b') - RB=$(printf '\x7d\x7d') - - SED_ARGS=( - -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" - -e "s|${LB}PORT${RB}|${PORT}|g" - -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" - ) - - # Recurses into build/container/stapeln/ as well. - find build/container/ -type f | while read -r file; do - if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then - sed -i "${SED_ARGS[@]}" "$file" - fi - done - - echo "Container templates initialised." - echo "" - echo "Next steps:" - echo " 1. Edit build/container/Containerfile — add your build commands (Tier A)" - echo " 2. Edit build/container/entrypoint.sh — set your application binary" - echo " 3. Review build/container/compose.yaml — portable stack (Tier B)" - echo " 4. Review build/container/stapeln/ — sovereign stack (Tier C)" - echo " 5. Build: just container-build" - -# Build container image (cerro-torre pipeline if present, else plain OCI build) -container-build *args: - #!/usr/bin/env bash - set -euo pipefail - {{_engine}} - if [ -f "build/container/stapeln/ct-build.sh" ]; then - cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh {{args}} - elif [ -f "build/container/Containerfile" ]; then - "$ENGINE" build -t czech_file_knife:latest -f build/container/Containerfile . - elif [ -f "Containerfile" ]; then - "$ENGINE" build -t czech_file_knife:latest -f Containerfile . - else - echo "No Containerfile found in build/container/ or project root" - exit 1 - fi - -# Pick the compose path: selur-compose (sovereign) if installed, else portable -_compose action *args: - #!/usr/bin/env bash - set -euo pipefail - {{_engine}} - if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then - ( cd build/container/stapeln && selur-compose {{action}} {{args}} ) - elif [ -f "build/container/compose.yaml" ]; then - "$ENGINE" compose -f build/container/compose.yaml {{action}} {{args}} - else - echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" - exit 1 - fi - -# Verify compose configuration (selur-compose verify, else compose config) -container-verify: - #!/usr/bin/env bash - set -euo pipefail - {{_engine}} - if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then - ( cd build/container/stapeln && selur-compose verify ) - elif [ -f "build/container/compose.yaml" ]; then - "$ENGINE" compose -f build/container/compose.yaml config - else - echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" - exit 1 - fi - -# Start the container stack (selur-compose if available, else $ENGINE compose) -container-up *args: - @just _compose up {{args}} - -# Stop the container stack -container-down *args: - @just _compose down {{args}} - -# Sign and verify the container bundle (cerro-torre: build + pack + sign + verify) -container-sign: - #!/usr/bin/env bash - set -euo pipefail - if [ -f "build/container/stapeln/ct-build.sh" ]; then - cd build/container/stapeln && ./ct-build.sh - else - echo "No build/container/stapeln/ct-build.sh found" - exit 1 - fi - -# Push the signed bundle (or plain image) to the registry -container-push: - #!/usr/bin/env bash - set -euo pipefail - {{_engine}} - if [ -f "build/container/stapeln/ct-build.sh" ]; then - cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh --push - else - echo "No build/container/stapeln/ct-build.sh found — falling back to $ENGINE push" - "$ENGINE" push czech_file_knife:latest - fi - -# Run the container interactively (for debugging) -container-run *args: - #!/usr/bin/env bash - set -euo pipefail - {{_engine}} - "$ENGINE" run --rm -it czech_file_knife:latest {{args}} - -# Container matrix: [build|run|push|shell|scan] x [registry] x [tag] -container-matrix action="build" registry="ghcr.io/hyperpolymath" tag="latest": - @echo "Container matrix: action={{action}} registry={{registry}} tag={{tag}}" - -# ── Narrowing / opt-out ────────────────────────────────────────────────────── -# -# `container-keep` is the workhorse: declare exactly which tiers to KEEP; the -# rest are removed. Tiers: oci (A) · portable (B) · stapeln (C) · devcontainer. -# Idempotent — safe to re-run. The aliases below are thin wrappers. -# -# just container-keep oci # bare OCI image only -# just container-keep oci portable # image + portable compose -# just container-keep oci portable stapeln # full prod stack, no dev container -# just container-keep all # no-op (keep everything) -# just container-keep none # remove everything (= no-container) -# DRY_RUN=1 just container-keep oci # show what would change, do nothing -# FORCE=1 just container-keep oci # skip the confirm prompt -# -# Accepts space- or comma-separated tokens, and aliases: a/A, b/B/compose, -# c/C/sovereign, dev. 'portable'/'stapeln' require 'oci' (they build the image). -container-keep +tiers: - #!/usr/bin/env bash - set -euo pipefail - - sel="$(echo "$@" | tr ',' ' ')" - keep_oci=false keep_port=false keep_stap=false keep_dev=false - for t in $sel; do - case "$t" in - all) keep_oci=true; keep_port=true; keep_stap=true; keep_dev=true ;; - none) : ;; - oci|a|A) keep_oci=true ;; - portable|compose|b|B) keep_port=true ;; - stapeln|sovereign|c|C) keep_stap=true ;; - devcontainer|dev) keep_dev=true ;; - *) echo "Unknown tier '$t'. Valid: oci portable stapeln devcontainer | all | none" >&2; exit 1 ;; - esac - done - - # Coherence: tiers B and C build the OCI image, so they need tier A. - if { $keep_port || $keep_stap; } && ! $keep_oci; then - echo "Incoherent selection: 'portable'/'stapeln' build the OCI image — add 'oci'." >&2 - exit 1 - fi - - # Build the removal plan. - plan=() - $keep_dev || plan+=(".devcontainer/ (dev container)") - if $keep_oci; then - $keep_stap || plan+=("build/container/stapeln/ (cerro-torre, svalinn, vordr, selur, rokur, k9)") - $keep_port || plan+=("build/container/compose.yaml + compose.example.yaml (portable stack)") - else - plan+=("build/container/ (entire image + compose + stapeln)") - plan+=("build/just/container.just (this module) + its Justfile import") - [ -f .github/workflows/container-build.yml ] && plan+=(".github/workflows/container-build.yml") - fi - - echo "Keeping: oci=$keep_oci portable=$keep_port stapeln=$keep_stap devcontainer=$keep_dev" - if [ ${#plan[@]} -eq 0 ]; then echo "Nothing to remove — selection already matches."; exit 0; fi - echo "Will remove:"; printf ' - %s\n' "${plan[@]}" - - if [ "${DRY_RUN:-}" = "1" ]; then echo "(DRY_RUN — no changes made)"; exit 0; fi - if [ "${FORCE:-}" != "1" ]; then - read -rp "Proceed? [y/N] " c; [[ "${c:-N}" =~ ^[Yy] ]] || { echo "Aborted."; exit 0; } - fi - - # Apply. - $keep_dev || rm -rf .devcontainer - if $keep_oci; then - $keep_stap || rm -rf build/container/stapeln - $keep_port || rm -f build/container/compose.yaml build/container/compose.example.yaml - else - rm -rf build/container - rm -f .machine_readable/configs/selur-compose.toml - rm -f .github/workflows/container-build.yml - rm -f build/just/container.just - [ -f Justfile ] && sed -i '/# >>> container-module/,/# <<< container-module/d' Justfile - for jf in Justfile .machine_readable/contractiles/Justfile; do - [ -f "$jf" ] && sed -i '\|import? "build/just/container.just"|d' "$jf" - done - fi - - echo "" - echo "Done. Review .github/workflows/ for image build/publish jobs you no longer need." - -# Remove ALL containerisation (image, compose, stapeln, dev container) -no-container: - @just container-keep none - -# Drop only the stapeln sovereign tier (keep OCI image + portable compose + dev container) -no-stapeln: - @just container-keep oci portable devcontainer - -# Drop only the dev container (keep the full image/compose/stapeln stack) -no-devcontainer: - @just container-keep oci portable stapeln diff --git a/czech-file-knife/build/just/groove.just b/czech-file-knife/build/just/groove.just deleted file mode 100644 index 185403656..000000000 --- a/czech-file-knife/build/just/groove.just +++ /dev/null @@ -1,98 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# GROOVE PROTOCOL -# -# Imported by ../../Justfile via `import? "build/just/groove.just"`. -# Recipes here configure and validate the Groove protocol manifest at -# .machine_readable/integrations/groove.a2ml — port assignment, API surface -# flags (REST/gRPC/GraphQL/WebSocket/SSE), and template-placeholder hygiene. -# The manifest itself is consumed by the Groove bridge / zig-unified-api-adapter. - -# Configure Groove protocol manifest (port assignment, API surfaces) -groove-setup: - #!/usr/bin/env bash - set -euo pipefail - MANIFEST=".machine_readable/integrations/groove.a2ml" - if [ ! -f "$MANIFEST" ]; then - echo "Error: $MANIFEST not found. Run 'just repo-init' first." - exit 1 - fi - - echo "═══════════════════════════════════════════════════" - echo " Groove Protocol Setup" - echo "═══════════════════════════════════════════════════" - echo "" - echo "Check PORT-REGISTRY.md before assigning a port:" - echo " https://github.com/hyperpolymath/standards/blob/main/PORT-REGISTRY.md" - echo "" - - read -rp "Primary port for this service: " PORT - [ -z "$PORT" ] && echo "Error: port required" && exit 1 - - echo "" - echo "Which API surfaces does this project expose?" - read -rp " REST API? [Y/n]: " REST - read -rp " gRPC? [y/N]: " GRPC - read -rp " GraphQL? [y/N]: " GRAPHQL - read -rp " WebSocket? [y/N]: " WS - read -rp " SSE (Server-Sent Events)? [y/N]: " SSE - - # Update port in manifest - sed -i "s/(port 0)/(port ${PORT})/" "$MANIFEST" - - # Update API surface flags - [[ "${GRPC,,}" == "y" ]] && sed -i 's/(grpc.*enabled false)/(grpc (enabled true)/' "$MANIFEST" - [[ "${GRAPHQL,,}" == "y" ]] && sed -i 's/(graphql.*enabled false)/(graphql (enabled true)/' "$MANIFEST" - [[ "${WS,,}" == "y" ]] && sed -i 's/(websocket.*enabled false)/(websocket (enabled true)/' "$MANIFEST" - [[ "${SSE,,}" == "y" ]] && sed -i 's/(sse.*enabled false)/(sse (enabled true)/' "$MANIFEST" - - echo "" - echo "Groove manifest updated: $MANIFEST" - echo "Port ${PORT} assigned. Add to PORT-REGISTRY.md if not already there." - -# Check for template placeholders that haven't been replaced -verify-template: - #!/usr/bin/env bash - set -euo pipefail - echo "Checking for unreplaced template placeholders..." - FOUND=0 - - # Check for double-brace placeholder patterns - HITS=$(grep -rn '{{'{{'}}[A-Z_]*{{'}}'}}' --include="*.adoc" --include="*.md" --include="*.a2ml" \ - --include="*.scm" --include="*.toml" --include="*.yml" --include="*.yaml" \ - . 2>/dev/null | grep -v 'node_modules\|\.git/' | grep -v 'PLACEHOLDERS.adoc' || true) - if [ -n "$HITS" ]; then - echo "" - echo "⚠ Unreplaced placeholders found:" - echo "$HITS" | head -20 - FOUND=1 - fi - - # Check for template defaults still present - if grep -q 'czech-file-knife' Justfile 2>/dev/null; then - echo "⚠ Justfile still references 'czech-file-knife' — update project name" - FOUND=1 - fi - - # Check for port 0 in Groove manifest - if grep -q '(port 0)' .machine_readable/integrations/groove.a2ml 2>/dev/null; then - echo "⚠ Groove manifest has port 0 — run 'just groove-setup' to assign a port" - FOUND=1 - fi - - # Check for empty SCM files - for f in .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/ECOSYSTEM.a2ml; do - if [ -f "$f" ] && grep -q '{{'{{'}}' "$f" 2>/dev/null; then - echo "⚠ $f still has template placeholders" - FOUND=1 - fi - done - - if [ $FOUND -eq 0 ]; then - echo "✓ No template placeholders found — project is properly customised." - else - echo "" - echo "Run 'just repo-init' to replace placeholders, or edit files manually." - exit 1 - fi diff --git a/czech-file-knife/build/just/proofs.just b/czech-file-knife/build/just/proofs.just deleted file mode 100644 index dfd633466..000000000 --- a/czech-file-knife/build/just/proofs.just +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# FORMAL VERIFICATION (PROOFS) -# -# Imported by ../../Justfile via `import? "build/just/proofs.just"`. -# Recipes here check formal proofs across Idris2, Lean4, Agda, and Coq, plus -# scan for dangerous/unsafe constructs and report status. Run via -# `just proof-check-all` for the full sweep. - -# Check all formal proofs (Idris2 + Lean4 + Agda + Coq) -proof-check-all: proof-check-idris2 proof-check-lean4 proof-check-agda proof-check-coq proof-scan-dangerous - @echo "=== All proof checks complete ===" - -# Each proof-check- delegates to scripts/check-proofs.sh, the single -# source of truth. A missing toolchain is FATAL (never a skip); every module is -# checked from its own source root; a proof file absent from the prover's -# manifest (verification/proofs//MANIFEST) is an error. See the script -# header for the four "checks that could not fail" this replaces. - -# Check Idris2 proofs (per MANIFEST; fatal if idris2 absent) -proof-check-idris2: - @bash scripts/check-proofs.sh idris2 - -# Check Lean4 proofs (per MANIFEST; fatal if lean absent) -proof-check-lean4: - @bash scripts/check-proofs.sh lean4 - -# Check Agda proofs (per MANIFEST; fatal if agda absent) -proof-check-agda: - @bash scripts/check-proofs.sh agda - -# Check Coq proofs (per MANIFEST; fatal if coqc absent) -proof-check-coq: - @bash scripts/check-proofs.sh coq - -# Scan for dangerous constructs USED in proof code (believe_me, sorry, Admitted, -# postulate, ...). Comments are ignored — a comment naming a banned construct -# must not trip the gate. See scripts/scan-dangerous.sh. -proof-scan-dangerous: - @bash scripts/scan-dangerous.sh - -# Show proof status summary -proof-status: - #!/usr/bin/env bash - echo "=== Proof Status ===" - echo "" - echo "Idris2: $(find verification/proofs/idris2 -name '*.idr' 2>/dev/null | wc -l) files" - echo "Lean4: $(find verification/proofs/lean4 -name '*.lean' 2>/dev/null | wc -l) files" - echo "Agda: $(find verification/proofs/agda -name '*.agda' 2>/dev/null | wc -l) files" - echo "Coq: $(find verification/proofs/coq -name '*.v' 2>/dev/null | wc -l) files" - echo "TLA+: $(find verification/proofs/tlaplus -name '*.tla' 2>/dev/null | wc -l) files" - echo "" - # PROOF-STATUS may live at root, docs/status/ (post-#20), .md or .adoc (post-#23) - for candidate in docs/status/PROOF-STATUS.adoc docs/status/PROOF-STATUS.md PROOF-STATUS.adoc PROOF-STATUS.md; do - if [ -f "$candidate" ]; then - grep -E "^\| \*\*Total\*\*|^\| \*Total\*" "$candidate" 2>/dev/null || echo "(No summary row in $candidate)" - exit 0 - fi - done - echo "(No PROOF-STATUS file found at root or docs/status/)" diff --git a/czech-file-knife/build/just/repo-init.just b/czech-file-knife/build/just/repo-init.just deleted file mode 100644 index c3136b7a5..000000000 --- a/czech-file-knife/build/just/repo-init.just +++ /dev/null @@ -1,902 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# INIT — Bootstrap a new project from this template -# -# Imported by ../../Justfile via `import? "build/just/repo-init.just"`. -# Variables (project, OWNER, REPO, version, tier) and the `set shell` directive -# are inherited from the root Justfile. - -# Interactive project bootstrap — replaces all {{PLACEHOLDER}} tokens -# Optional archetype argument (e.g. `just repo-init julia-library`) applies an -# archetype overlay: see archetypes/README.adoc and ADR-0003. -repo-init archetype="": - #!/usr/bin/env bash - set -euo pipefail - - # Non-interactive support: if RSR_NON_INTERACTIVE is set, dummy-out 'read'. - # This prevents blocking on input and seamlessly uses environment variables. - if [ -n "${RSR_NON_INTERACTIVE:-}" ]; then - read() { return 0; } - fi - - echo "═══════════════════════════════════════════════════" - echo " RSR Project Bootstrap" - echo "═══════════════════════════════════════════════════" - echo "" - - # --- Archetype overlay data (ADR-0003) --- - ARCHETYPE='{{archetype}}' - ARCH_FILE="" - ARCH_PRESET="" - if [ -n "$ARCHETYPE" ]; then - ARCH_FILE="archetypes/${ARCHETYPE}/ARCHETYPE.a2ml" - if [ ! -f "$ARCH_FILE" ]; then - echo "Error: unknown archetype '${ARCHETYPE}'. Available:" - ls -1 archetypes 2>/dev/null | grep -v 'README' || echo " (none)" - exit 1 - fi - ARCH_PRESET=$(sed -n 's/^preset = "\(.*\)"$/\1/p' "$ARCH_FILE" | head -1) - echo "Archetype: ${ARCHETYPE}${ARCH_PRESET:+ (capability preset: $ARCH_PRESET)}" - echo "" - fi - - # --- Load defaults from config (if exists) --- - # Create yours: ~/.config/rsr/defaults - # Format: OWNER=myorg AUTHOR="My Name" AUTHOR_EMAIL=me@example.org ... - DEFAULTS="${XDG_CONFIG_HOME:-$HOME/.config}/rsr/defaults" - if [ -f "$DEFAULTS" ]; then - echo "Loading defaults from $DEFAULTS" - # shellcheck source=/dev/null - source "$DEFAULTS" - echo "" - fi - - # --- Required values (pre-filled from defaults if available) --- - read -rp "Project name (human-readable, e.g. My Project): " PROJECT_NAME - [ -z "$PROJECT_NAME" ] && echo "Error: project name required" && exit 1 - - read -rp "Repository slug (e.g. my-project): " REPO - [ -z "$REPO" ] && echo "Error: repo slug required" && exit 1 - if [[ ! "$REPO" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then - echo "Error: repo slug must be lowercase alphanumeric words separated by single hyphens" - exit 1 - fi - - read -rp "Owner [${OWNER:-}]: " _OWNER - OWNER="${_OWNER:-${OWNER:-}}" - [ -z "$OWNER" ] && echo "Error: owner required" && exit 1 - - read -rp "Author full name [${AUTHOR:-}]: " _AUTHOR - AUTHOR="${_AUTHOR:-${AUTHOR:-}}" - [ -z "$AUTHOR" ] && echo "Error: author name required" && exit 1 - - read -rp "Author email [${AUTHOR_EMAIL:-}]: " _AUTHOR_EMAIL - AUTHOR_EMAIL="${_AUTHOR_EMAIL:-${AUTHOR_EMAIL:-}}" - [ -z "$AUTHOR_EMAIL" ] && echo "Error: email required" && exit 1 - - # --- Optional values (pre-filled from defaults if available) --- - read -rp "Author organization [${AUTHOR_ORG:-none}]: " _AUTHOR_ORG - AUTHOR_ORG="${_AUTHOR_ORG:-${AUTHOR_ORG:-}}" - - read -rp "Previous/alt email [${AUTHOR_EMAIL_ALT:-none}]: " _AUTHOR_EMAIL_ALT - AUTHOR_EMAIL_ALT="${_AUTHOR_EMAIL_ALT:-${AUTHOR_EMAIL_ALT:-}}" - - read -rp "Project description []: " PROJECT_DESCRIPTION - - read -rp "Forge domain [${FORGE:-github.com}]: " _FORGE - FORGE="${_FORGE:-${FORGE:-github.com}}" - - read -rp "Security contact email [${SECURITY_EMAIL:-$AUTHOR_EMAIL}]: " _SECURITY_EMAIL - SECURITY_EMAIL="${_SECURITY_EMAIL:-${SECURITY_EMAIL:-$AUTHOR_EMAIL}}" - - read -rp "Conduct contact email [${CONDUCT_EMAIL:-$AUTHOR_EMAIL}]: " _CONDUCT_EMAIL - CONDUCT_EMAIL="${_CONDUCT_EMAIL:-${CONDUCT_EMAIL:-$AUTHOR_EMAIL}}" - - read -rp "Project type (library|binary|monorepo|service|website) [library]: " PROJECT_TYPE - PROJECT_TYPE="${PROJECT_TYPE:-library}" - - read -rp "Website URL [https://${FORGE}/${OWNER}/${REPO}]: " WEBSITE - WEBSITE="${WEBSITE:-https://${FORGE}/${OWNER}/${REPO}}" - - read -rp "OpenSSF Best Practices project ID (blank if not yet registered) []: " OPENSSF_BP_ID - - # --- Container values (optional — only relevant if build/container/ exists) --- - if [ -d "build/container" ]; then - echo "" - echo "── Container configuration (optional) ─────────" - read -rp "Service name [${REPO}]: " _SERVICE_NAME - SERVICE_NAME="${_SERVICE_NAME:-${REPO}}" - read -rp "Primary port [8080]: " _PORT - PORT="${_PORT:-8080}" - read -rp "Container registry [ghcr.io/${OWNER}]: " _REGISTRY - REGISTRY="${_REGISTRY:-ghcr.io/${OWNER}}" - else - SERVICE_NAME="${REPO}" - PORT="8080" - REGISTRY="ghcr.io/${OWNER}" - fi - - # --- Derived values --- - PROJECT_UPPER=$(echo "$REPO" | tr '[:lower:]-' '[:upper:]_') - PROJECT_LOWER=$(echo "$REPO" | tr '[:upper:]-' '[:lower:]_') - CURRENT_YEAR=$(date +%Y) - CURRENT_DATE=$(date +%Y-%m-%d) - VERSION="0.1.0" - - # --- Derive the repo's uuid -------------------------------------------- - # THE UUID IS DERIVED, NOT ALLOCATED (gv-clade-index: - # docs/SPEC-clade-verisim-portal.adoc §Identity Model): - # uuid = UUIDv5(namespace = URL, name = "github.com//") - # We know OWNER and REPO, so this is computable here and must never be - # guessed or copied. The owner segment is part of the derived name, so it is - # part of the identity — a repo hosted elsewhere derives a different uuid. - if command -v uuidgen >/dev/null 2>&1; then - REPO_UUID=$(uuidgen --sha1 --namespace @url --name "${FORGE}/${OWNER}/${REPO}") - else - REPO_UUID="UNASSIGNED" - fi - - # Derive citation name parts (best-effort split on last space) - AUTHOR_LAST="${AUTHOR##* }" - AUTHOR_FIRST="${AUTHOR% *}" - FIRST_INITIAL="${AUTHOR_FIRST:0:1}." - if [ "$AUTHOR_LAST" = "$AUTHOR_FIRST" ]; then - AUTHOR_FIRST="$AUTHOR" - AUTHOR_LAST="" - FIRST_INITIAL="" - fi - - echo "" - echo "── Summary ──────────────────────────────────────" - echo " Project: $PROJECT_NAME" - echo " Repo: $REPO" - echo " Owner: $OWNER" - echo " Author: $AUTHOR <$AUTHOR_EMAIL>" - [ -n "$AUTHOR_ORG" ] && echo " Organization: $AUTHOR_ORG" - echo " Forge: $FORGE" - echo " Year: $CURRENT_YEAR" - echo "────────────────────────────────────────────────" - echo "" - read -rp "Proceed? [Y/n] " CONFIRM - [[ "${CONFIRM:-Y}" =~ ^[Nn] ]] && echo "Aborted." && exit 0 - - echo "" - echo "Replacing placeholders..." - - # Apply archetype overlay files (if any) BEFORE substitution, so overlay - # files are rendered along with the rest of the tree. - if [ -n "$ARCHETYPE" ] && [ -d "archetypes/${ARCHETYPE}/overlay" ]; then - cp -a "archetypes/${ARCHETYPE}/overlay/." . - echo " applied overlay: archetypes/${ARCHETYPE}/overlay/" - fi - - # Seed docs/ from the documentation seed, BEFORE substitution so the seed's - # own placeholder tokens are rendered along with everything else. - # - # This is an OVERLAY, not a replacement. The seed's four perspective docs - # and its ADR template win on collision, because they are the - # downstream-facing shape; everything else the spine ships under docs/ - # survives -- notably docs/governance/ and docs/legal/, which - # build/just/validate.just hard-requires, so a wholesale replace would - # break the minted repo's own validate gate. - # - # The seed's own README.adoc is instructions-for-use ("copy this, replace - # the placeholders, delete this README"), not content, so it is not copied. - # tar is used rather than cp so that existing docs/ subdirectories MERGE - # instead of nesting (cp -a decisions docs/ would yield docs/decisions/decisions). - if [ -d "build/docs-seed" ]; then - mkdir -p docs - ( cd build/docs-seed && tar cf - --exclude=README.adoc . ) | ( cd docs && tar xf - ) - echo " seeded docs/ from build/docs-seed/ (its own README omitted)" - fi - - # Brace tokens as variables (hex avoids just interpolation) - LB=$(printf '\x7b\x7b') - RB=$(printf '\x7d\x7d') - - # Build the sed expression list - # Note: using | as delimiter since URLs contain / - SED_ARGS=( - -e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g" - -e "s|${LB}PROJECT_DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" - -e "s|${LB}PROJECT${RB}|${PROJECT_UPPER}|g" - -e "s|${LB}project${RB}|${PROJECT_LOWER}|g" - -e "s|${LB}REPO${RB}|${REPO}|g" - -e "s|${LB}OWNER${RB}|${OWNER}|g" - -e "s|${LB}AUTHOR${RB}|${AUTHOR}|g" - -e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g" - -e "s|${LB}AUTHOR_ORG${RB}|${AUTHOR_ORG}|g" - -e "s|${LB}AUTHOR_LAST${RB}|${AUTHOR_LAST}|g" - -e "s|${LB}AUTHOR_FIRST${RB}|${AUTHOR_FIRST}|g" - -e "s|${LB}AUTHOR_INITIALS${RB}|${FIRST_INITIAL}|g" - -e "s|${LB}FORGE${RB}|${FORGE}|g" - -e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g" - -e "s|${LB}CURRENT_DATE${RB}|${CURRENT_DATE}|g" - -e "s|${LB}DATE${RB}|${CURRENT_DATE}|g" - -e "s|${LB}SECURITY_EMAIL${RB}|${SECURITY_EMAIL}|g" - -e "s|${LB}CONDUCT_EMAIL${RB}|${CONDUCT_EMAIL}|g" - -e "s|${LB}LICENSE${RB}|MPL-2.0|g" - -e "s|${LB}CONDUCT_TEAM${RB}|Code of Conduct Committee|g" - -e "s|${LB}RESPONSE_TIME${RB}|48 hours|g" - -e "s|${LB}MAIN_BRANCH${RB}|main|g" - -e "s|${LB}PROJECT_PURPOSE${RB}|${PROJECT_DESCRIPTION}|g" - -e "s|${LB}PROJECT_ROLE${RB}|${PROJECT_TYPE}|g" - -e "s|${LB}PROJECT_TYPE${RB}|${PROJECT_TYPE}|g" - -e "s|${LB}WEBSITE${RB}|${WEBSITE}|g" - -e "s|${LB}SERVICE_NAME${RB}|${SERVICE_NAME}|g" - -e "s|${LB}PORT${RB}|${PORT}|g" - -e "s|${LB}REGISTRY${RB}|${REGISTRY}|g" - -e "s|${LB}IMAGE${RB}|${REGISTRY}/${SERVICE_NAME}|g" - -e "s|${LB}VERSION${RB}|${VERSION}|g" - -e "s|${LB}EMAIL${RB}|${AUTHOR_EMAIL}|g" - -e "s|${LB}UUID${RB}|${REPO_UUID}|g" - -e "s|${LB}DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" - ) - [ -n "$AUTHOR_EMAIL_ALT" ] && SED_ARGS+=(-e "s|${LB}AUTHOR_EMAIL_ALT${RB}|${AUTHOR_EMAIL_ALT}|g") - [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_BP_ID${RB}|${OPENSSF_BP_ID}|g") - [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_PROJECT_ID${RB}|${OPENSSF_BP_ID}|g") - - # Archetype-determined tokens (ADR-0003): the archetype contract fills - # what it can honestly determine. Added BEFORE the UNASSIGNED fallback - # below — sed applies expressions in order, so a token the archetype - # answers never reaches the fallback, and one it omits still fails loudly. - if [ -n "$ARCH_FILE" ]; then - while IFS= read -r arch_line; do - arch_tok="${arch_line%%=*}"; arch_tok="${arch_tok// /}" - arch_val=$(printf '%s' "$arch_line" | sed -n 's/^[^=]*= *"\(.*\)" *$/\1/p') - { [ -n "$arch_tok" ] && [ -n "$arch_val" ]; } || continue - case "$arch_val" in *'|'*) - echo "WARN: archetype token $arch_tok contains '|' (the sed delimiter) — skipped, stays UNASSIGNED" - continue ;; - esac - SED_ARGS+=(-e "s|${LB}${arch_tok}${RB}|${arch_val}|g") - done < <(awk '$0=="[tokens]"{f=1;next} /^\[/{f=0} f && !/^[[:space:]]*#/ && NF' "$ARCH_FILE") - fi - - # Tokens only the author can answer. init cannot know a project's unique - # strength or its language stack, and guessing would write fiction into - # ANCHOR.a2ml — the file the estate treats as semantic authority. They - # become UNASSIGNED, the same marker `just repo-init` already writes for an - # unchosen clade: honest, greppable, and obviously unfinished, whereas a - # leftover brace token just looks like the template broke. - # grep -rn UNASSIGNED . — to find what still needs you - # (Brace tokens are spelled via ${LB}/${RB} throughout this recipe: just - # interpolates literal double braces even inside comments.) - for tok in PROJECT_KIND PROJECT_DOMAIN PROJECT_UNIQUE_STRENGTH \ - TARGET_AUDIENCE LANG_STACK BUILD_CMD TEST_CMD MUST_INVARIANTS \ - PACKAGE_NAME DEPS BUILD_OUTPUT_PATH MAIN_FUNCTION; do - SED_ARGS+=(-e "s|${LB}${tok}${RB}|UNASSIGNED|g") - done - - # julia-library archetype: derive the package uuid at mint (owner ruling - # 2026-09-19: generate at mint). Same DERIVABLE class as the REPO uuid - # above - computed, never guessed. Namespace = the repo's own derived - # uuid, name = julia:: stable across re-mints of the same repo, - # never collides with the repo uuid. No uuidgen AND no python3 on the - # operator machine -> UNASSIGNED (the honest fallback at :437); Pkg - # then refuses the package loudly until the owner assigns one. - if [ "$ARCHETYPE" = "julia-library" ]; then - if command -v uuidgen >/dev/null 2>&1; then - PACKAGE_UUID=$(uuidgen --sha1 --namespace "${REPO_UUID}" --name "julia:${REPO}") - elif command -v python3 >/dev/null 2>&1; then - PACKAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") - else - PACKAGE_UUID=UNASSIGNED - fi - SED_ARGS+=(-e "s|${LB}PACKAGE_UUID${RB}|${PACKAGE_UUID}|g") - echo " package uuid: ${PACKAGE_UUID} (derived from repo uuid ${REPO_UUID})" - fi - - # Optional values with no answer: drop the line that depends on them rather - # than leaving the token in place. An unfilled token is not a neutral - # reminder — the OpenSSF line renders a broken badge image and a dead link - # in the new repo's README, and it trips the placeholder gate on every - # subsequent push, which trains people to ignore a red CI. No id yet means - # no badge yet; add it when you register at bestpractices.dev. - if [ -z "$OPENSSF_BP_ID" ]; then - sed -i "/bestpractices\.dev\/projects\/${LB}OPENSSF_BP_ID${RB}/d" README.adoc 2>/dev/null || true - sed -i "/${LB}OPENSSF_PROJECT_ID${RB}/d" docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc 2>/dev/null || true - fi - # A .mailmap exists to map an alternate address onto the canonical one. - # With no alternate address there is nothing to map, and the sole entry - # would keep an unfilled alt-email token in the angle brackets, which is a - # malformed mailmap rather than a harmless leftover. - # git reads .mailmap from the WORKTREE ROOT only (or via mailmap.file / - # mailmap.blob, which nothing here configures). The copy that used to live - # under .github/ was therefore inert and was never filled. - if [ -z "$AUTHOR_EMAIL_ALT" ]; then - printf '# No alternate author address to map.\n' > .mailmap - fi - - # Replace in all text files (skip .git, LICENSE text, and binaries) - find . -type f \ - -not -path './.git/*' \ - -not -name 'MPL-2.0.txt' \ - -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ - -not -name '*.woff' -not -name '*.woff2' \ - | while read -r file; do - if file --brief "$file" | grep -qi 'text\|ascii\|utf'; then - sed -i "${SED_ARGS[@]}" "$file" - fi - done - - # Also replace [YOUR-REPO-NAME] and [YOUR-NAME/ORG] in the repo deed - # (the family-7 allocation manifest folded into it — standards#837 pilot). - sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" rsr-template-repo_chora.deed 2>/dev/null || true - sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" rsr-template-repo_chora.deed 2>/dev/null || true - - # --- Instruction-block pass -------------------------------------------- - # Delete the "TEMPLATE INSTRUCTIONS (delete this block before publishing)" - # comments. Nothing ever did, and 211 estate repos still carry one. The - # block is self-detonating: its own first line names a LITERAL doubled-brace - # token, so the sed pass above has nothing to match, yet every placeholder - # gate greps exactly that shape — a repo with every real token correctly - # substituted still trips its own gate. Rationale and measurements in - # scripts/strip-instruction-blocks.rs. - # - # Runs AFTER substitution, so real tokens are already filled; only the - # instructions go. Deliberately skipped for *-template-repo itself: the - # template's own blocks ARE the product, and its gate exempts it by remote - # name — the same protection the self-name pass below uses. - if [ "$REPO" != "${REPO%-template-repo}" ]; then - echo " instruction blocks: skipped (this IS a template repo)" - else - bash scripts/rust-tool.sh strip-instruction-blocks . - fi - - # --- Dependabot ecosystem pass ----------------------------------------- - # dependabot.yml ships every common ecosystem under "remove unused ones for - # your project". Nothing removed them: measured estate-wide, 206/206 repos - # declaring `pip` have no Python manifest, and each such entry fails on - # every scheduled run. Prune on the ARCHETYPE rather than on file presence — - # a freshly minted tree has no manifests yet, so presence would delete - # everything. Rationale in scripts/prune-dependabot-ecosystems.rs. - keep_eco="github-actions" - case "${ARCH_PRESET:-}" in *rust*|*cargo*) keep_eco="$keep_eco cargo" ;; esac - case "${ARCH_PRESET:-}" in *node*|*js*|*ts*|*deno*|*bun*|*web*) keep_eco="$keep_eco npm" ;; esac - case "${ARCH_PRESET:-}" in *elixir*|*mix*) keep_eco="$keep_eco mix" ;; esac - case "${ARCH_PRESET:-}" in *python*|*py*) keep_eco="$keep_eco pip" ;; esac - bash scripts/rust-tool.sh prune-dependabot-ecosystems .github/dependabot.yml $keep_eco - - # --- Self-name pass (ADR-0003) ----------------------------------------- - # The template's own name is written as a LITERAL, not as a placeholder - # token, so the substitution loop above never touched it. Measured on a - # scratch mint of 346ae56: a repo minted as `mint-check-lib` still carried - # Justfile: project := "rsr-template-repo" - # Justfile: REPO := "rsr-template-repo" - # sonar-project.properties sonar.projectKey=hyperpolymath_rsr-template-repo - # STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo" - # i.e. it declared that it WAS the template, and would have reported its - # code analysis into the TEMPLATE's SonarCloud project. This is the same - # failure the CLADE.a2ml block below fixes for the uuid/canonical-name; - # nothing generalised it to the rest of the tree. Confirmed in the wild: - # hyperpolymath/cargo-zigbuild still carries both Justfile lines. - # - # Provenance must SURVIVE this pass — a minted repo is supposed to record - # what it came from. Two protections: paths that are ABOUT the template - # (ADRs, its changelog, its own audit, and this recipe itself — which - # holds PARENT_SLUG) are skipped wholesale, and within every other file - # any line that names the parent AS a parent is left byte-for-byte alone. - if [ "$REPO" != "rsr-template-repo" ]; then - SELF_PROV='instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: ' - find . -type f \ - -not -path './.git/*' \ - -not -path './build/just/repo-init.just' \ - -not -path './docs/decisions/*' \ - -not -path './.machine_readable/descriptiles/CLADE.a2ml' \ - -not -path './.machine_readable/descriptiles/VARIANT.a2ml' \ - -not -name 'CHANGELOG.md' \ - -not -name 'TEMPLATE-STANDARDS-AUDIT.adoc' \ - -not -name 'TEMPLATE-LINEAGE-AUDIT.adoc' \ - -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ - -not -name '*.woff' -not -name '*.woff2' \ - | while read -r file; do - grep -q 'rsr-template-repo' "$file" 2>/dev/null || continue - file --brief "$file" | grep -qi 'text\|ascii\|utf' || continue - # sed -i preserves the mode bit; rewriting via a temp file would not. - sed -i -E "/${SELF_PROV}/!{s|hyperpolymath/rsr-template-repo|${OWNER}/${REPO}|g;s|rsr-template-repo|${REPO}|g;}" "$file" - done - echo " self-name: rewrote template literal -> ${REPO} (provenance lines preserved)" - - # The repo deed's FILENAME carries the repo slug (deed dispatch: - # _chora.deed — deed.abnf v1.0.0). The self-name pass rewrote - # the slug inside the file; the file itself is renamed here. Plain mv: - # mint runs before the new repo's first commit. Renames (not tokens) - # are how filenames change — a czech-file-knife token in a FILENAME is the - # brace-collapse hazard class repo-init has already been burnt by. - if [ -f rsr-template-repo_chora.deed ]; then - mv rsr-template-repo_chora.deed "${REPO}_chora.deed" - echo " repo deed: renamed rsr-template-repo_chora.deed -> ${REPO}_chora.deed" - fi - - # ── self-OWNER pass ────────────────────────────────────────────────── - # - # Same class as the self-name leak above, and missed by it: the - # template's own OWNER is a plain literal, so nothing rewrote it. A repo - # minted under a different owner shipped: - # - # Justfile OWNER := "hyperpolymath" - # sonar-project.properties sonar.projectKey=hyperpolymath_ - # - # i.e. it declared the wrong owner and reported its code analysis into - # the wrong SonarCloud project. Measured on a metadatastician mint. - # - # This pass is deliberately NARROW — three exact, identity-bearing - # lines. A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: - # `hyperpolymath/standards`, `hyperpolymath/proven` and the other estate - # dependencies are real repos every child genuinely points at, and - # rewriting those would break the child's canon, CI and badges. - if [ "$OWNER" != "hyperpolymath" ]; then - sed -i -E 's|^(OWNER[[:space:]]*:=[[:space:]]*)"hyperpolymath"|\1"'"${OWNER}"'"|' Justfile - sed -i -E 's|^sonar\.organization=hyperpolymath$|sonar.organization='"${OWNER}"'|' sonar-project.properties - sed -i -E 's|^sonar\.projectKey=hyperpolymath_|sonar.projectKey='"${OWNER}"'_|' sonar-project.properties - echo " self-owner: rewrote owner literal -> ${OWNER} (estate dependency paths preserved)" - echo " NOTE: confirm the SonarCloud ORG is really '${OWNER}' before provisioning —" - echo " the GitHub owner and the SonarCloud org are not always the same." - fi - fi - - echo "" - echo "── Identity (CLADE.a2ml) ────────────────────────" - - # Install THIS repo's CLADE.a2ml, replacing rsr-template-repo's own. - # - # The CLADE.a2ml shipped in the template is the TEMPLATE's real identity — a - # real uuid for a real repo. Nothing in SED_ARGS ever touched it, so every - # repo created from this template inherited uuid a5ea1382-… and - # canonical-name "rsr-template-repo" verbatim, and told every arriving agent - # it WAS the template. Nothing caught it: each value was individually valid. - # chronicles-of-slavia and scaffoldia were corrected for exactly this on - # 2026-07-16; paint-type, cargo-zigbuild, email-octad-experiment, llm-grace - # and rsr-template-how-to still carry it. - # - # build/templates/CLADE.a2ml.in has already had its brace tokens filled by - # the substitution loop above (including the UUID one, derived earlier). - # NB: literal double-brace tokens cannot be written in this recipe body — - # just interpolates them, which is why LB/RB are built with printf above. - # The split is the whole point: - # DERIVABLE -> computed, never guessed (uuid, owner, canonical-name) - # JUDGEMENT -> left UNASSIGNED, fails loudly until a human chooses (clade) - CLADE_IN="build/templates/CLADE.a2ml.in" - CLADE_OUT=".machine_readable/descriptiles/CLADE.a2ml" - if [ -f "$CLADE_IN" ]; then - mkdir -p "$(dirname "$CLADE_OUT")" - cp "$CLADE_IN" "$CLADE_OUT" - rm -rf build/templates # template-only; not part of your repo - if [ "$REPO_UUID" = "UNASSIGNED" ]; then - echo " uuid: UNASSIGNED — uuidgen not found." - echo " Install util-linux (uuid-runtime), then run:" - echo " uuidgen --sha1 --namespace @url --name \"${FORGE}/${OWNER}/${REPO}\"" - echo " and put the result in $CLADE_OUT." - else - echo " uuid: $REPO_UUID" - echo " (uuid5 of ${FORGE}/${OWNER}/${REPO} — derived, recomputable, not invented)" - fi - echo " clade: UNASSIGNED — a human must choose one of the 12." - echo " CLADE-003/006 FAIL until you do. That is deliberate:" - echo " an unchosen clade must not look like a chosen one." - echo " The taxonomy is listed in $CLADE_OUT." - fi - - echo "" - echo "── Profile & provenance (ADR-0003) ──────────────" - - # The template's own rsr-profile.a2ml is deliberately MAXIMAL (it is the - # dogfood target). A minted repo must NOT inherit it: declaring - # capabilities the tree lacks makes the oracle score modules that are - # absent (na-honesty, RSR-SPEC-v2 §scoring). Rewrite it minimally. - PROFILE_OUT=".machine_readable/rsr-profile.a2ml" - - # ── Canon pin (bind the canon) ────────────────────────────────────────── - # Read the canon identity from the template's [canon] block BEFORE that - # block is overwritten below. A minted repo must record WHICH canon release - # it was cut from; without it "which repos are on canon 1.x?" is a campaign - # across the whole estate instead of a grep. - # - # Count rather than guessed: a value here that silently became empty is the - # same class of defect as an UNASSIGNED placeholder, and would let a repo - # claim conformance to nothing. - # Section reader: `canon_key ` prints the value of from - # the [canon] section, or nothing. - # - # Deliberately escape-free: the first attempt spelled the section-header - # test as /^\[/ , which reached awk as a DOUBLE backslash followed by an - # unterminated bracket expression. awk does not error on that - it parses a - # character class that swallows the rest of the program and then matches - # nothing, so the pin came out silently EMPTY rather than failing loudly. - # `substr($0,1,1)=="["` cannot be mis-escaped. This is the same class of - # defect as an UNASSIGNED placeholder: a value that looks computed but is - # empty would let a minted repo claim conformance to nothing. - canon_key() { - awk -v want="[canon]" -v key="$1" ' - substr($0,1,1)=="[" { f = ($0==want) ? 1 : 0; next } - f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { - sub(/^[^=]*=[[:space:]]*/, "") - gsub(/^["[:space:]]+|["[:space:]]+$/, "") - print; exit - } - ' "$2" - } - - CANON_VERSION="" - CANON_CRITERIA_SHA="" - CANON_GATES_SHA="" - if [ -f "$PROFILE_OUT" ]; then - CANON_VERSION="$(canon_key version "$PROFILE_OUT")" - CANON_CRITERIA_SHA="$(canon_key criteria_sha256 "$PROFILE_OUT")" - CANON_GATES_SHA="$(canon_key gates_sha256 "$PROFILE_OUT")" - fi - CANON_VERSION="${CANON_VERSION:-UNASSIGNED}" - CANON_CRITERIA_SHA="${CANON_CRITERIA_SHA:-UNASSIGNED}" - CANON_GATES_SHA="${CANON_GATES_SHA:-UNASSIGNED}" - { - echo "# SPDX-License-Identifier: MPL-2.0" - echo "#" - echo "# rsr-profile.a2ml — this repo's declared RSR v2.0 capabilities." - echo "# Generated by just repo-init on ${CURRENT_DATE}. The template's maximal" - echo "# profile was removed at mint: declare only what this tree carries." - echo "" - echo "[rsr-profile]" - echo "version = \"1.0.0\"" - echo "spec = \"rsr-criteria-v2\"" - echo "declares-against = \"2.0.0-draft\"" - if [ -n "$ARCH_PRESET" ]; then - echo "preset = \"${ARCH_PRESET}\"" - else - echo "# UNASSIGNED: declare capabilities (or a preset) — see standards" - echo "# .machine_readable/template-capability-gates.toml" - echo "capabilities = []" - fi - } > "$PROFILE_OUT" - echo " profile: $PROFILE_OUT${ARCH_PRESET:+ (preset $ARCH_PRESET)}" - - # Provenance — the estate's answer-file (ADR-0002 contract shape, used - # per ADR-0003 with direction=generated-from-core). - # - # repo-init runs INSIDE the template checkout, so the LOCAL git state is - # the truth about what was actually copied. Prefer it. The previous - # implementation read the LIVE remote tip (`git ls-remote ... HEAD`), - # which records where the remote is *now* rather than the tree in hand: - # mint from a branch, or from a checkout with unpushed commits, and - # PROVENANCE named a commit the child never contained. That defeated the - # answer-file's whole purpose. Remote lookup remains only as a fallback, - # and the symref form also recovers the source BRANCH, which was never - # recorded at all (#203). - PARENT_SLUG="hyperpolymath/rsr-template-repo" - PARENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true) - PARENT_PIN=$(git rev-parse HEAD 2>/dev/null || true) - PARENT_TREE=$(git rev-parse 'HEAD^{tree}' 2>/dev/null || true) - - if [ -z "$PARENT_PIN" ]; then - # Not a checkout (tarball, shallow copy, or offline): fall back to the - # remote, and recover the branch from the symref. - PARENT_BRANCH=$(git ls-remote --symref "https://github.com/${PARENT_SLUG}.git" HEAD 2>/dev/null \ - | sed -n 's|^ref: refs/heads/\([^[:space:]]*\).*|\1|p' | head -1 || true) - PARENT_PIN=$(git ls-remote "https://github.com/${PARENT_SLUG}.git" HEAD 2>/dev/null | cut -f1 || true) - fi - - PARENT_BRANCH="${PARENT_BRANCH:-UNASSIGNED}" - PARENT_PIN="${PARENT_PIN:-UNASSIGNED}" - PARENT_TREE="${PARENT_TREE:-UNASSIGNED}" - - # Recorded in PROVENANCE.a2ml. Non-interactive mints are the automated - # path (scaffoldia / CI); interactive ones are a human at a prompt. The - # distinction matters because #201 found a hand-minted instance whose - # generated identity was stale, and nothing recorded which path ran. - if [ -n "${RSR_NON_INTERACTIVE:-}" ]; then - MINT_MODE="non-interactive" - else - MINT_MODE="interactive" - fi - - VARIANT_OUT=".machine_readable/descriptiles/VARIANT.a2ml" - mkdir -p "$(dirname "$VARIANT_OUT")" - { - echo "# SPDX-License-Identifier: MPL-2.0" - echo "#" - echo "# VARIANT.a2ml — provenance contract (ADR-0002 shape; ADR-0003 use)." - echo "# Records what this repo was minted from so the drift gate" - echo "# (scripts/check-variant-drift.sh ) and future" - echo "# re-templating tooling have a defined input." - echo "#" - echo "# NOTE: placeholder rendering means many files differ from the" - echo "# parent by design; folding rendered answers into [normalise] is" - echo "# future work (ADR-0003), so the gate is informational for minted" - echo "# repos until then." - echo "" - echo "[metadata]" - echo "project = \"${REPO}\"" - echo "schema_version = \"0.1.0\"" - echo "last-updated = \"${CURRENT_DATE}\"" - echo "" - echo "[variant]" - echo "parent = \"${PARENT_SLUG}\"" - echo "parent-pin = \"${PARENT_PIN}\" # template tip at mint" - echo "role = \"minted-repo\"" - echo "stack = \"${ARCHETYPE:-unspecified}\"" - echo "direction = \"generated-from-core\"" - echo "" - echo "[normalise]" - echo "rules = \"action-pins self-name\"" - echo "" - echo "[paths.added]" - echo "paths = []" - echo "" - echo "[paths.removed]" - echo "paths = []" - echo "" - echo "[paths.diverged]" - echo "# Files a minted repo owns from birth." - echo "paths = [" - echo " \".machine_readable/descriptiles/CLADE.a2ml\"," - echo " \".machine_readable/descriptiles/VARIANT.a2ml\"," - echo " \".machine_readable/rsr-profile.a2ml\"," - echo "]" - } > "$VARIANT_OUT" - if [ "$PARENT_PIN" = "UNASSIGNED" ]; then - echo " provenance: $VARIANT_OUT — parent-pin UNASSIGNED (offline?)" - echo " Set it to the template commit you minted from:" - echo " git ls-remote https://github.com/${PARENT_SLUG}.git HEAD" - else - echo " provenance: $VARIANT_OUT (parent-pin ${PARENT_PIN:0:12})" - fi - - # ── PROVENANCE.a2ml — the answer-file for the canon binding ───────────── - # ADR-0003 named this the single highest-leverage gap: "a minted repo does - # not know which template commit it came from, so template improvements - # cannot be propagated and drift cannot be detected." Copier/Cruft solved - # it with an answer-file + update + check; this estate hand-simulated it as - # recurring standardisation-PR campaigns. This is the answer-file. - # - # VARIANT.a2ml above records the TEMPLATE dimension (diffing against the - # parent). This records the CANON dimension (which law the repo was cut - # under). They are different questions and both are needed. - PROVENANCE_OUT=".machine_readable/PROVENANCE.a2ml" - { - echo "# SPDX-License-Identifier: MPL-2.0" - echo "#" - echo "# PROVENANCE.a2ml — what this repo was minted FROM." - echo "# Written once, at mint, by build/just/repo-init.just. Not a hand-edited" - echo "# file: tools that want \"what is this repo\" read STATE.a2ml; tools that" - echo "# want \"where did this repo come from\" read this." - echo "" - echo "[provenance]" - echo "minted_at = \"${CURRENT_DATE}\"" - echo "" - echo "template_repo = \"${PARENT_SLUG}\"" - echo "template_branch = \"${PARENT_BRANCH}\"" - echo "template_commit = \"${PARENT_PIN}\"" - echo "template_tree = \"${PARENT_TREE}\"" - echo "# Contract: branches this repo is EXPECTED to carry beyond the default." - echo "# Empty means default-only. A mint must never inherit template work" - echo "# branches (coderabbit/, chore/, bot-task or stale branches) — that is" - echo "# how knot-knot received a byte-identical copy of a template work branch" - echo "# with no common ancestor (#203). Enforced by" - echo "# scripts/check-template-conformance.sh." - echo "extra_branches = []" - echo "" - echo "canon_version = \"${CANON_VERSION}\"" - echo "criteria_sha256 = \"${CANON_CRITERIA_SHA}\"" - echo "gates_sha256 = \"${CANON_GATES_SHA}\"" - echo "" - echo "archetype = \"${ARCHETYPE:-}\"" - echo "# Which minting path actually ran. Hardcoded as \"hand\" before this, so" - echo "# every scripted mint claimed to be hand-minted — which destroyed the" - echo "# one signal that would have told #201/#203 which path to trust." - echo "minted_by = \"repo-init\"" - echo "mint_mode = \"${MINT_MODE}\"" - echo "" - echo "[substitutions]" - echo "# ADR-0003 records the old state honestly: \"renders 34 substitutions and" - echo "# derives identity, but leaves 12 tokens UNASSIGNED\". Listing them here" - echo "# converts that from a known defect into a QUERYABLE one: a non-empty" - echo "# list means the mint did not fully render, and the validation step" - echo "# below already fails on a leftover token." - echo "rendered = 34" - echo "unassigned = []" - } > "$PROVENANCE_OUT" - - if [ "$CANON_VERSION" = "UNASSIGNED" ]; then - echo " provenance: $PROVENANCE_OUT — canon pin UNASSIGNED" - echo " The template carried no [canon] block, so this repo" - echo " cannot state which canon it conforms to. Fix by minting" - echo " from a template commit that has one." - else - echo " provenance: $PROVENANCE_OUT (canon v${CANON_VERSION} ${CANON_CRITERIA_SHA:0:12})" - fi - - # archetypes/ is template-only overlay data, not part of your repo - # (same reason build/templates is removed above). - rm -rf archetypes - rm -rf build/docs-seed # template-only; its content now lives in docs/ - - # Overlay .in rule (julia-library archetype; the convention any future - # overlay can adopt): the overlay ships content templates as *.in so a - # substitution token never survives in a file with a final name. Rename - # the known set now that substitution has rendered them, before the - # placeholder gate below. - if [ "$ARCHETYPE" = "julia-library" ]; then - # the name is read from Project.toml.in until the loop below renames - # it - order matters. - TOML_FILE=Project.toml - [ -f "$TOML_FILE" ] || TOML_FILE=Project.toml.in - if [ -f src/PACKAGE.jl.in ] && [ -f "$TOML_FILE" ]; then - PKG_NAME=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' "$TOML_FILE" | head -1) - if [ -n "$PKG_NAME" ] && [ "$PKG_NAME" != "UNASSIGNED" ]; then - mv "src/PACKAGE.jl.in" "src/${PKG_NAME}.jl" - echo " entry point: src/${PKG_NAME}.jl (from src/PACKAGE.jl.in)" - else - echo "WARN: Project.toml name is UNASSIGNED - src/PACKAGE.jl.in left in place" - echo " so the placeholder gate stays honest. Name the package, then run:" - echo " mv src/PACKAGE.jl.in src/.jl" - fi - fi - for f in Project.toml.in \ - .github/workflows/julia-ci.yml.in \ - .github/workflows/julia-docs.yml.in; do - [ -f "$f" ] && { mv "$f" "${f%.in}"; echo " overlay: ${f%.in}"; } - done - fi - - echo "" - echo "── Validation ───────────────────────────────────" - - # Check for remaining placeholders. - # - # This printed "WARNING" and then carried on to "Done! Mint complete." It - # detected the very defect that went on to reach 211 repos, said so every - # time, and stopped nobody. That is the fake-gate pattern this estate keeps - # re-learning: a check that cannot fail reads as coverage while changing no - # outcome. - # - # It now aborts the mint. A tree that still holds substitution tokens is not - # minted, it is half-minted, and shipping one is how the estate acquired 12 - # permanently-red OpenSSF builds and 12 settings.yml files declaring an - # unsubstituted repository name. That second one is not theoretical: the - # braces were once submitted to GitHub, collapsed to dashes, and renamed a - # repo to "-REPO-", which then read as deleted. - # - # RSR_ALLOW_PLACEHOLDERS=1 restores the old warning, for deliberately - # partial mints — not for getting past this message. - # - # META tokens are exempt, matching scripts/check-no-placeholders.sh. A file - # is only half-minted if it holds a token naming a real value; a file that - # says "replace all {PLACEHOLDER} values" is prose ABOUT tokens and is - # supposed to survive. Without this exemption the gate fires on README.adoc, - # EXPLAINME.adoc, both descriptiles and the checker itself on every single - # mint — measured — and a gate that always fires gets switched off. - # - # This exemption is also, exactly, why the instruction block went unnoticed - # in 211 repos: {PLACEHOLDER} is the ONLY doubled-brace text in it, so any - # gate sensibly exempting metasyntax must let the block through. The two - # checks here are therefore complementary, not redundant — the block can - # only be caught by name, which the check below does. - # The four allowlisted paths are copied verbatim from - # scripts/check-no-placeholders.sh so the two gates cannot drift apart. - # Each legitimately names tokens: the token vocabulary itself, prose - # explaining that tokens exist, the checker's own pattern, and the e2e - # test's answer list. - PATTERN="${LB}[A-Z_]*${RB}" - META='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' - REMAINING=$(grep -rl "$PATTERN" . --include='*.md' --include='*.adoc' --include='*.yml' --include='*.yaml' --include='*.a2ml' --include='*.toml' --include='*.scm' --include='*.ncl' --include='*.nix' --include='*.json' --include='*.sh' 2>/dev/null | grep -v '.git/' | while IFS= read -r f; do - case "${f#./}" in - .machine_readable/ai/PLACEHOLDERS.adoc|EXPLAINME.adoc|scripts/check-no-placeholders.sh|tests/e2e/template_instantiation_test.sh) continue ;; - esac - # Bracketed [{]{2} rather than a doubled brace written literally, for - # two independent reasons. First, `grep -E` is ugrep on some estate - # machines and rejects a bare doubled brace as an "empty - # (sub)expression", because a brace opens an interval quantifier — the - # same reason ci.yml uses this form estate-wide. Second, a doubled brace - # inside a just RECIPE BODY is just's own interpolation delimiter, so - # writing one here (even in a comment) is a parse error. That is why the - # recipe computes LB/RB with printf instead of spelling them out. - if grep -ohE '[{]{2}[A-Z][A-Z0-9_]*[}]{2}' "$f" | grep -qvE "^[{]{2}(${META})[}]{2}$"; then - echo "$f" - fi - done || true) - if [ -n "$REMAINING" ]; then - echo "Remaining placeholders in:" - echo "$REMAINING" | sed 's/^/ /' - echo "" - echo "Inspect with: grep -rn '$LB' . --include='*.md'" - if [ "${RSR_ALLOW_PLACEHOLDERS:-0}" = "1" ]; then - echo "WARNING: continuing anyway (RSR_ALLOW_PLACEHOLDERS=1)." - else - echo "ERROR: mint aborted — the tree above is half-minted." - echo " Fill those tokens, or re-run with RSR_ALLOW_PLACEHOLDERS=1" - echo " if a partial mint is genuinely what you want." - exit 1 - fi - else - echo "All placeholders replaced successfully!" - fi - - # An un-deleted instruction block is a different failure from an unfilled - # token, and once its literal metasyntax is the only doubled-brace text left - # the check above cannot distinguish the two. Fail on it by name. - # - # The strip pass, this recipe and the e2e test are excluded because they - # necessarily NAME the marker in order to describe or assert on it — the fix - # for the defect would otherwise be flagged by the check for the defect, and - # so would the test that guards the fix, and the cleanup test whose - # fixture prose must keep the phrase to prove prose is not collateral. - # All four are tooling, not - # community-health documents, so a mention in them cannot mislead a reader - # about what this project's Code of Conduct says. - LEFTOVER_BLOCKS=$(grep -rl 'TEMPLATE INSTRUCTIONS' . --exclude-dir=.git 2>/dev/null | grep -vE '^\./(scripts/strip-instruction-blocks\.rs|build/just/repo-init\.just|tests/e2e/template_instantiation_test\.sh|tests/workflows/mint_cleanup_test\.sh)$' || true) - if [ -n "$LEFTOVER_BLOCKS" ]; then - echo "" - echo "Un-deleted TEMPLATE INSTRUCTIONS block in:" - echo "$LEFTOVER_BLOCKS" | sed 's/^/ /' - echo "ERROR: the instruction-block pass should have removed these." - exit 1 - fi - - # ── www/ site-operations bundle (issue #53, stage 5) ────────────────── - # - # A repository minted from this template already carries www/.well-known/ - # as the canonical location and has no root .well-known/, so in the - # ordinary case both steps below do nothing at all. They exist for the - # case that is not ordinary: repo-init run over a tree that predates #53 - # and still has a root .well-known/. The migrator moves it — quarantining - # anything divergent rather than overwriting it — and the bundle's own - # suite then proves the result. - # - # #106 described this wiring as already present. It was not: nothing - # invoked the migrator or the suite at mint, which is why the estate - # still carries root .well-known/ directories years into the convention. - if [ -d .well-known ] && [ -f scripts/migrate-wellknown-to-www.sh ]; then - echo "" - echo "Migrating legacy root .well-known/ into www/.well-known/..." - bash scripts/migrate-wellknown-to-www.sh - fi - - # Fail-closed, like `just verify` below: the suite tolerates unminted - # double-brace placeholder tokens by design (it runs in the template - # itself), no archetype overlay touches www/, and the substituted Expires - # value is a parseable end-of-mint-year timestamp — so a failure here is - # real. (Spelled out in words because just interpolates double braces - # anywhere in a recipe, comments included, and an undefined variable here - # takes the whole Justfile down, not just this recipe.) - if [ -f www/tests/run-all.sh ]; then - echo "" - echo "Running www/ site-operations suite..." - bash www/tests/run-all.sh - fi - - # K9-SVC validation (if available) - if command -v k9-svc >/dev/null 2>&1; then - echo "" - echo "Running k9-svc validation..." - k9-svc validate . 2>/dev/null || true - fi - - echo "" - echo "Running OpenSSF compliance verification..." - just verify - - echo "" - echo "Done! Mint complete. Remaining Forge stages (ADR-0003):" - echo " 1. Review changes: git diff" - echo " 2. Remove template cruft: rm .machine_readable/ai/PLACEHOLDERS.adoc" - echo " 3. Customize README.adoc; then: grep -rn UNASSIGNED . for what still needs you" - echo " 4. PROVISION — actually run your build and test commands and watch" - echo " them pass (echoed advice is not provisioning)." - echo " 5. Commit: git add -A && git commit -m 'feat: initialize from RSR template'" - echo " 6. Push: git remote add origin git@${FORGE}:${OWNER}/${REPO}.git && git push -u origin main" - echo " 7. CONFIGURE — out of band, once (this recipe runs no gh commands):" - echo " gh repo edit ${OWNER}/${REPO} --description '' --homepage '${WEBSITE}'" - echo " Visibility is fail-closed private; flip deliberately if wanted:" - echo " gh repo edit ${OWNER}/${REPO} --visibility public --accept-visibility-change-consequences" - echo " Register in gv-clade-index; required contexts must equal emitted job ids." - if [ -f ci/.gitlab-ci.yml ]; then - echo "" - echo " GitLab mirror only: this repo keeps its GitLab CI config at" - echo " ci/.gitlab-ci.yml, which GitLab does NOT look for by default." - echo " Settings > CI/CD > General pipelines > CI/CD configuration file" - echo " must be set to ci/.gitlab-ci.yml, or GitLab CI silently never" - echo " runs - no pipeline and no error. See ci/README.adoc." - fi - echo " 8. HARNESS — arm the drift gate against your recorded parent-pin:" - echo " scripts/check-variant-drift.sh " diff --git a/czech-file-knife/build/just/validate.just b/czech-file-knife/build/just/validate.just deleted file mode 100644 index 5718a771b..000000000 --- a/czech-file-knife/build/just/validate.just +++ /dev/null @@ -1,135 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# VALIDATION & COMPLIANCE -# -# Imported by ../../Justfile via `import? "build/just/validate.just"`. -# Recipes here check that this repo conforms to the RSR (Rhodium Standard -# Repository) skeleton: required files, METAdata, AI install guide -# completeness, etc. Run via `just validate`. - -# Validate RSR compliance -validate-rsr: - #!/usr/bin/env bash - cd "{{justfile_directory()}}" - echo "=== RSR Compliance Check ===" - MISSING="" - for f in .editorconfig .gitignore Justfile README.adoc LICENSE; do - [ -f "$f" ] || MISSING="$MISSING $f" - done - # The repo deed's filename carries the repo slug (_chora.deed), so - # this is a glob; the folded replacement of the old 0-AI-MANIFEST.a2ml. - ls *_chora.deed >/dev/null 2>&1 || MISSING="$MISSING repo-deed (*_chora.deed)" - for f in .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/ECOSYSTEM.a2ml .machine_readable/descriptiles/anchors/ANCHOR.a2ml .machine_readable/policies/MAINTENANCE-AXES.a2ml .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml; do - [ -f "$f" ] || MISSING="$MISSING $f" - done - for f in docs/legal/EXHIBIT-A-ETHICAL-USE.txt docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt LICENSES/MPL-2.0.txt; do - [ -f "$f" ] || MISSING="$MISSING $f" - done - if [ ! -d "src/interface/abi" ] && [ ! -d "src/interface/Abi" ]; then - MISSING="$MISSING src/interface/abi" - fi - for f in src/interface/ffi src/interface/generated; do - [ -d "$f" ] || MISSING="$MISSING $f" - done - for f in docs/governance/MAINTENANCE-CHECKLIST.adoc docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc; do - [ -f "$f" ] || MISSING="$MISSING $f" - done - if [ -f ".machine_readable/descriptiles/META.a2ml" ]; then - grep -q 'axis-1 = "must > intend > like"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-1" - grep -q 'axis-2 = "corrective > adaptive > perfective"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-2" - grep -q 'axis-3 = "systems > compliance > effects"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-3" - grep -q 'scoping-first = true' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:scoping-first" - grep -q 'idris-unsound-scan = "believe_me/assert_total"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:idris-unsound-scan" - grep -q 'audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:audit-focus" - grep -q 'compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:compliance-focus" - grep -q 'effects-evidence = "benchmark execution/results and maintainer status dialogue/review"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:effects-evidence" - grep -q 'compliance-tooling = "panic-attack"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:compliance-tooling" - grep -q 'effects-tooling = "ecological checking with sustainabot guidance"' .machine_readable/policies/MAINTENANCE-AXES.a2ml || MISSING="$MISSING MAINTENANCE-AXES.a2ml:effects-tooling" - grep -q 'source-human = "docs/governance/MAINTENANCE-CHECKLIST.adoc"' .machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml || MISSING="$MISSING MAINTENANCE-CHECKLIST.a2ml:source-human" - grep -q 'source-human = "docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc"' .machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml || MISSING="$MISSING SOFTWARE-DEVELOPMENT-APPROACH.a2ml:source-human" - fi - if [ -n "$MISSING" ]; then - echo "MISSING:$MISSING" - exit 1 - fi - echo "RSR compliance: PASS" - -# Validate STATE.a2ml syntax -validate-state: - @cd "{{justfile_directory()}}" && if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ - grep -q '^\[metadata\]' .machine_readable/descriptiles/STATE.a2ml && \ - grep -q 'project\s*=' .machine_readable/descriptiles/STATE.a2ml && \ - echo "STATE.a2ml: valid" || echo "STATE.a2ml: INVALID (missing required sections)"; \ - else \ - echo "No .machine_readable/descriptiles/STATE.a2ml found"; \ - fi - -# Validate AI installation guide completeness (finishbot pre-release check) -validate-ai-install: - #!/usr/bin/env bash - cd "{{justfile_directory()}}" - echo "=== AI Installation Guide Check ===" - GUIDE="docs/AI_INSTALLATION_GUIDE.adoc" - README="README.adoc" - ERRORS=0 - - # Check guide exists - if [ ! -f "$GUIDE" ]; then - echo "MISSING: $GUIDE (create from template: docs/AI_INSTALLATION_GUIDE.adoc)" - ERRORS=$((ERRORS + 1)) - else - # Check for unfilled TODO markers - TODOS=$(grep -c '\[TODO-AI-INSTALL' "$GUIDE" 2>/dev/null || true) - if [ "$TODOS" -gt 0 ]; then - echo "INCOMPLETE: $GUIDE has $TODOS unfilled [TODO-AI-INSTALL] markers:" - grep -n '\[TODO-AI-INSTALL' "$GUIDE" | head -10 - ERRORS=$((ERRORS + 1)) - else - echo "$GUIDE: complete (no TODO markers)" - fi - - # Check AI implementation section exists - if ! grep -q 'ai-implementation' "$GUIDE" 2>/dev/null; then - echo "MISSING: [[ai-implementation]] anchor in $GUIDE" - ERRORS=$((ERRORS + 1)) - fi - - # Check privacy notice exists - if ! grep -qi 'privacy' "$GUIDE" 2>/dev/null; then - echo "MISSING: Privacy notice in $GUIDE" - ERRORS=$((ERRORS + 1)) - fi - - # Check install commands exist (not just placeholders) - if ! grep -q 'git clone' "$GUIDE" 2>/dev/null; then - echo "WARNING: No git clone command found in $GUIDE -- install commands may be incomplete" - fi - fi - - # Check README has AI install section - if [ -f "$README" ]; then - if ! grep -qi 'AI-Assisted Installation' "$README" 2>/dev/null; then - echo "MISSING: AI-Assisted Installation section in $README" - echo " Copy from docs/AI-INSTALL-README-SECTION.adoc" - ERRORS=$((ERRORS + 1)) - fi - - # Check README for unfilled TODO markers - README_TODOS=$(grep -c '\[TODO-AI-INSTALL' "$README" 2>/dev/null || true) - if [ "$README_TODOS" -gt 0 ]; then - echo "INCOMPLETE: $README has $README_TODOS unfilled [TODO-AI-INSTALL] markers" - ERRORS=$((ERRORS + 1)) - fi - fi - - if [ "$ERRORS" -gt 0 ]; then - echo "" - echo "AI install guide: FAIL ($ERRORS issues)" - exit 1 - fi - echo "AI install guide: PASS" - -# Full validation suite -validate: validate-rsr validate-state validate-ai-install - @echo "All validations passed!" diff --git a/czech-file-knife/build/packaging/arch/PKGBUILD b/czech-file-knife/build/packaging/arch/PKGBUILD deleted file mode 100644 index ddbbbf2ca..000000000 --- a/czech-file-knife/build/packaging/arch/PKGBUILD +++ /dev/null @@ -1,42 +0,0 @@ -# Maintainer: hyperpolymath -# Arch Linux PKGBUILD for czech-file-knife - -pkgname=czech-file-knife -pkgver=0.1.0 -pkgrel=1 -pkgdesc="Universal file management toolkit with cloud provider integration and virtual filesystem" -arch=('x86_64' 'aarch64') -url="https://github.com/hyperpolymath/czech-file-knife" -license=('MPL-2.0') -depends=('gcc-libs' 'fuse3') -makedepends=('rust' 'cargo' 'pkg-config') -optdepends=( - 'tantivy: Full-text search indexing' -) -provides=('cfk') -conflicts=('czech-file-knife-git') -source=("$pkgname-$pkgver.tar.gz::https://github.com/hyperpolymath/czech-file-knife/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('SKIP') - -build() { - cd "$pkgname-$pkgver" - cargo build --release --package cfk-cli -} - -check() { - cd "$pkgname-$pkgver" - cargo test --release -} - -package() { - cd "$pkgname-$pkgver" - - # Install binary - install -Dm755 "target/release/cfk" "$pkgdir/usr/bin/cfk" - - # Install license - install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" - - # Install documentation - install -Dm644 README.adoc "$pkgdir/usr/share/doc/$pkgname/README.adoc" -} diff --git a/czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec b/czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec deleted file mode 100644 index 558bc4505..000000000 --- a/czech-file-knife/build/packaging/chocolatey/czech-file-knife.nuspec +++ /dev/null @@ -1,33 +0,0 @@ - - - - - czech-file-knife - 0.1.0 - Czech File Knife - hyperpolymath - hyperpolymath - https://github.com/hyperpolymath/czech-file-knife - https://github.com/hyperpolymath/czech-file-knife/blob/main/LICENSE - false - -Czech File Knife (cfk) is a universal file management toolkit with cloud -provider integration and virtual filesystem support. - -Features: -- Virtual filesystem (FUSE) for mounting cloud storage -- Content-addressable caching with Blake3 hashing -- Full-text search indexing with Tantivy -- Multi-provider support (local, S3, GCS, Azure, etc.) -- Streaming copy with progress indication - - Universal file management toolkit with cloud provider integration - file-manager cloud-storage fuse cli rust - https://github.com/hyperpolymath/czech-file-knife - https://github.com/hyperpolymath/czech-file-knife/blob/main/README.adoc - https://github.com/hyperpolymath/czech-file-knife/issues - - - - - diff --git a/czech-file-knife/build/packaging/debian/control b/czech-file-knife/build/packaging/debian/control deleted file mode 100644 index d7ea27098..000000000 --- a/czech-file-knife/build/packaging/debian/control +++ /dev/null @@ -1,27 +0,0 @@ -Source: czech-file-knife -Section: utils -Priority: optional -Maintainer: hyperpolymath -Build-Depends: debhelper-compat (= 13), cargo, rustc (>= 1.75), pkg-config, libfuse3-dev -Standards-Version: 4.6.2 -Homepage: https://github.com/hyperpolymath/czech-file-knife -Vcs-Git: https://github.com/hyperpolymath/czech-file-knife.git -Vcs-Browser: https://github.com/hyperpolymath/czech-file-knife -Rules-Requires-Root: no - -Package: czech-file-knife -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3 -Description: Universal file management toolkit with cloud provider integration - Czech File Knife (cfk) is a universal file management toolkit with cloud - provider integration and virtual filesystem support. It provides a unified - interface for managing files across local storage and cloud providers. - . - Features: - - Virtual filesystem (FUSE) for mounting cloud storage - - Content-addressable caching with Blake3 hashing - - Full-text search indexing with Tantivy - - Multi-provider support (local, S3, GCS, Azure, etc.) - - Streaming copy with progress indication - . - License: MPL-2.0 diff --git a/czech-file-knife/build/packaging/debian/rules b/czech-file-knife/build/packaging/debian/rules deleted file mode 100644 index 9010ce116..000000000 --- a/czech-file-knife/build/packaging/debian/rules +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/make -f -# SPDX-License-Identifier: MPL-2.0 - -%: - dh $@ - -override_dh_auto_build: - cargo build --release --package cfk-cli - -override_dh_auto_install: - install -D -m 755 target/release/cfk debian/czech-file-knife/usr/bin/cfk - -override_dh_auto_test: - cargo test --release - -override_dh_auto_clean: - cargo clean diff --git a/czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml b/czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml deleted file mode 100644 index 87afca3ff..000000000 --- a/czech-file-knife/build/packaging/flatpak/dev.hyperpolymath.CzechFileKnife.yml +++ /dev/null @@ -1,31 +0,0 @@ -app-id: dev.hyperpolymath.CzechFileKnife -runtime: org.freedesktop.Platform -runtime-version: '23.08' -sdk: org.freedesktop.Sdk -sdk-extensions: - - org.freedesktop.Sdk.Extension.rust-stable - -command: cfk - -finish-args: - - --filesystem=home - - --filesystem=/mnt - - --filesystem=/run/media - - --device=fuse - -build-options: - append-path: /usr/lib/sdk/rust-stable/bin - env: - CARGO_HOME: /run/build/czech-file-knife/cargo - RUSTUP_HOME: /usr/lib/sdk/rust-stable - -modules: - - name: czech-file-knife - buildsystem: simple - build-commands: - - cargo build --release --package cfk-cli - - install -Dm755 target/release/cfk /app/bin/cfk - sources: - - type: git - url: https://github.com/hyperpolymath/czech-file-knife.git - tag: v0.1.0 diff --git a/czech-file-knife/build/packaging/macports/Portfile b/czech-file-knife/build/packaging/macports/Portfile deleted file mode 100644 index 208206682..000000000 --- a/czech-file-knife/build/packaging/macports/Portfile +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 -# SPDX-License-Identifier: MPL-2.0 - -PortSystem 1.0 -PortGroup cargo 1.0 -PortGroup github 1.0 - -github.setup hyperpolymath czech-file-knife 0.1.0 v -revision 0 -categories sysutils -license MPL-2.0 -maintainers {hyperpolymath @hyperpolymath} -description Universal file management toolkit with cloud provider integration -long_description Czech File Knife (cfk) is a universal file management toolkit with \ - cloud provider integration and virtual filesystem support. It provides \ - a unified interface for managing files across local storage and cloud \ - providers including S3, GCS, Azure, and more. - -homepage https://github.com/hyperpolymath/czech-file-knife - -checksums rmd160 SKIP \ - sha256 SKIP \ - size SKIP - -depends_lib-append port:macfuse - -cargo.crates # Will be auto-populated by cargo2port - -destroot { - xinstall -m 755 ${worksrcpath}/target/release/cfk ${destroot}${prefix}/bin/ -} - -notes " -Czech File Knife requires macFUSE to be installed for virtual filesystem features. -Install with: sudo port install macfuse -" diff --git a/czech-file-knife/build/packaging/rpm/czech-file-knife.spec b/czech-file-knife/build/packaging/rpm/czech-file-knife.spec deleted file mode 100644 index 1a3ab9ec5..000000000 --- a/czech-file-knife/build/packaging/rpm/czech-file-knife.spec +++ /dev/null @@ -1,52 +0,0 @@ -# RPM spec file for czech-file-knife -# Compatible with Fedora (dnf) and openSUSE (zypper) - -Name: czech-file-knife -Version: 0.1.0 -Release: 1%{?dist} -Summary: Universal file management toolkit with cloud provider integration - -License: MPL-2.0 -URL: https://github.com/hyperpolymath/czech-file-knife -Source0: %{name}-%{version}.tar.gz - -BuildRequires: rust >= 1.75 -BuildRequires: cargo -BuildRequires: gcc -BuildRequires: pkg-config -BuildRequires: fuse3-devel - -Requires: fuse3 - -%description -Czech File Knife (cfk) is a universal file management toolkit with cloud -provider integration and virtual filesystem support. It provides a unified -interface for managing files across local storage and cloud providers. - -Features: -- Virtual filesystem (FUSE) for mounting cloud storage -- Content-addressable caching with Blake3 hashing -- Full-text search indexing with Tantivy -- Multi-provider support (local, S3, GCS, Azure, etc.) -- Streaming copy with progress indication - -%prep -%autosetup - -%build -cargo build --release --package cfk-cli - -%check -cargo test --release - -%install -install -D -m 755 target/release/cfk %{buildroot}%{_bindir}/cfk - -%files -%license LICENSE -%doc README.adoc -%{_bindir}/cfk - -%changelog -* Tue Dec 17 2025 hyperpolymath - 0.1.0-1 -- Initial release diff --git a/czech-file-knife/build/packaging/scoop/czech-file-knife.json b/czech-file-knife/build/packaging/scoop/czech-file-knife.json deleted file mode 100644 index 093df8dc3..000000000 --- a/czech-file-knife/build/packaging/scoop/czech-file-knife.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "0.1.0", - "description": "Universal file management toolkit with cloud provider integration and virtual filesystem", - "homepage": "https://github.com/hyperpolymath/czech-file-knife", - "license": "MPL-2.0", - "architecture": { - "64bit": { - "url": "https://github.com/hyperpolymath/czech-file-knife/releases/download/v0.1.0/cfk-0.1.0-x86_64-pc-windows-msvc.zip", - "hash": "TODO", - "bin": "cfk.exe" - } - }, - "checkver": { - "github": "https://github.com/hyperpolymath/czech-file-knife" - }, - "autoupdate": { - "architecture": { - "64bit": { - "url": "https://github.com/hyperpolymath/czech-file-knife/releases/download/v$version/cfk-$version-x86_64-pc-windows-msvc.zip" - } - } - } -} diff --git a/czech-file-knife/build/packaging/winget/czech-file-knife.yaml b/czech-file-knife/build/packaging/winget/czech-file-knife.yaml deleted file mode 100644 index ce809c22a..000000000 --- a/czech-file-knife/build/packaging/winget/czech-file-knife.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# winget manifest for czech-file-knife -# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json - -PackageIdentifier: Hyperpolymath.CzechFileKnife -PackageVersion: 0.1.0 -PackageLocale: en-US -Publisher: hyperpolymath -PublisherUrl: https://github.com/hyperpolymath -PackageName: Czech File Knife -PackageUrl: https://github.com/hyperpolymath/czech-file-knife -License: MPL-2.0 -LicenseUrl: https://github.com/hyperpolymath/czech-file-knife/blob/main/LICENSE -ShortDescription: Universal file management toolkit with cloud provider integration -Description: | - Czech File Knife (cfk) is a universal file management toolkit with cloud - provider integration and virtual filesystem support. It provides a unified - interface for managing files across local storage and cloud providers. - - Features: - - Virtual filesystem (FUSE) for mounting cloud storage - - Content-addressable caching with Blake3 hashing - - Full-text search indexing with Tantivy - - Multi-provider support (local, S3, GCS, Azure, etc.) - - Streaming copy with progress indication -Tags: - - file-manager - - cloud-storage - - fuse - - cli - - rust -Moniker: cfk -Commands: - - cfk -Installers: - - Architecture: x64 - InstallerType: zip - InstallerUrl: https://github.com/hyperpolymath/czech-file-knife/releases/download/v0.1.0/cfk-0.1.0-x86_64-pc-windows-msvc.zip - InstallerSha256: TODO -ManifestType: singleton -ManifestVersion: 1.4.0 diff --git a/czech-file-knife/ci/.gitlab-ci.yml b/czech-file-knife/ci/.gitlab-ci.yml deleted file mode 100644 index 2dcfe10b8..000000000 --- a/czech-file-knife/ci/.gitlab-ci.yml +++ /dev/null @@ -1,154 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Primary CI/CD - GitLab is the source of truth - -stages: - - security - - lint - - test - - build -variables: - CARGO_HOME: ${CI_PROJECT_DIR}/.cargo -cache: - key: ${CI_COMMIT_REF_SLUG} - paths: - - .cargo/ - - target/ -# ================== -# Security Scanning -# ================== -trivy: - stage: security - image: aquasec/trivy:latest - script: - - trivy fs --exit-code 0 --severity HIGH,CRITICAL --format table . - - trivy fs --exit-code 1 --severity CRITICAL . - allow_failure: false -semgrep: - stage: security - image: returntocorp/semgrep - script: - - semgrep --config auto --error . - allow_failure: true -cargo-audit: - stage: security - image: rust:latest - script: - - cargo install cargo-audit - - cargo audit - rules: - - exists: - - Cargo.toml -cargo-deny: - stage: security - image: rust:latest - script: - - cargo install cargo-deny - - cargo deny --manifest-path Cargo.toml check --config .machine_readable/compliance/rust/deny.toml - rules: - - exists: - - Cargo.toml - allow_failure: true -mix-audit: - stage: security - image: elixir:latest - script: - - mix local.hex --force - - mix archive.install hex mix_audit --force - - mix deps.get - - mix deps.audit - rules: - - exists: - - mix.exs - allow_failure: true -# ================== -# Linting -# ================== -rustfmt: - stage: lint - image: rust:latest - script: - - rustup component add rustfmt - - cargo fmt -- --check - rules: - - exists: - - Cargo.toml -clippy: - stage: lint - image: rust:latest - script: - - rustup component add clippy - - cargo clippy -- -D warnings - rules: - - exists: - - Cargo.toml - allow_failure: true -mix-format: - stage: lint - image: elixir:latest - script: - - mix format --check-formatted - rules: - - exists: - - mix.exs -credo: - stage: lint - image: elixir:latest - script: - - mix local.hex --force - - mix deps.get - - mix credo --strict - rules: - - exists: - - mix.exs - allow_failure: true -# ================== -# Testing -# ================== -cargo-test: - stage: test - image: rust:latest - script: - - cargo test --all-features - rules: - - exists: - - Cargo.toml -mix-test: - stage: test - image: elixir:latest - script: - - mix local.hex --force - - mix deps.get - - mix test - rules: - - exists: - - mix.exs -# ================== -# Build -# ================== -cargo-build: - stage: build - image: rust:latest - script: - - cargo build --release - artifacts: - paths: - - target/release/ - expire_in: 1 week - rules: - - exists: - - Cargo.toml -mix-build: - stage: build - image: elixir:latest - script: - - mix local.hex --force - - mix deps.get - - MIX_ENV=prod mix compile - rules: - - exists: - - mix.exs -trufflehog: - stage: security - image: trufflesecurity/trufflehog:latest - script: - - trufflehog git file://. --only-verified --fail diff --git a/czech-file-knife/ci/.pre-commit-config.yaml b/czech-file-knife/ci/.pre-commit-config.yaml deleted file mode 100644 index 806916471..000000000 --- a/czech-file-knife/ci/.pre-commit-config.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hooks for hyperpolymath RSR repos. -# Install: pip install pre-commit && pre-commit install -# Run manually: pre-commit run --all-files - -repos: - # --- Standard hooks --- - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-json - - id: check-toml - - id: check-merge-conflict - - id: detect-private-key - - id: check-added-large-files - args: ['--maxkb=1024'] - - # --- Manifest validation (DEED grammar) --- - # The former provider repo (hyperpolymath/a2ml-pre-commit) was DELETED - # upstream in the A2ML retirement (standards #836 owner ruling; R-H3), - # leaving this pin as broken plumbing — pre-commit could not clone it. - # Replaced with a local hook calling .github/hooks/validate-deed.sh, - # verified exit-0 against all 123 manifests in this repo. Dual-accept: - # scans both .a2ml (legacy extension; migration = standards #837, the - # DEED conversion campaign) and .deed. When the grammar-faithful .deed - # validator is wired per owner ruling R-H2, this hook is its natural home. - - repo: local - hooks: - - id: validate-deed - name: Validate DEED manifests (dual-accept .a2ml/.deed) - entry: .github/hooks/validate-deed.sh - language: system - files: '\.(a2ml|deed)$' - pass_filenames: false - - # --- K9 contract validation --- - - repo: https://github.com/hyperpolymath/k9-pre-commit - rev: 9b82e1f7a6b6c0f99df72c145d7dee8851803c30 # k9-pre-commit @ main 2026-06-23 - hooks: - - id: validate-k9 - name: Validate K9 contracts - - # --- Shell linting --- - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.10.0.1 - hooks: - - id: shellcheck - - # --- EditorConfig --- - - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 3.2.1 - hooks: - - id: editorconfig-checker - exclude: '(\.git|node_modules|target|_build|deps|\.|external_corpora|\.lake)/' - - # --- Secret detection --- - - repo: https://github.com/gitleaks/gitleaks - rev: v8.24.3 - hooks: - - id: gitleaks - - # --- Arrival pack drift: CLAUDE.md must stay in sync with a2ml --- - - repo: local - hooks: - - id: validate-claude-md - name: CLAUDE.md arrival pack in sync with a2ml - entry: bash .machine_readable/arrival-pack/verify.sh - language: system - pass_filenames: false - files: '^(\.machine_readable/descriptiles/.*|CLAUDE\.md)$' diff --git a/czech-file-knife/ci/README.adoc b/czech-file-knife/ci/README.adoc deleted file mode 100644 index 933aae685..000000000 --- a/czech-file-knife/ci/README.adoc +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= CI configuration - -Non-GitHub CI configuration lives here. GitHub Actions workflows stay in -`.github/workflows/` because GitHub reads that path and no other. - -[cols="1,3",options="header"] -|=== -| File | Consumer - -| `.gitlab-ci.yml` -| GitLab CI. **Not** found automatically at this path — see below. - -| `.pre-commit-config.yaml` -| The `pre-commit` framework, when invoked with `--config`. -|=== - -== Required out-of-band setting (GitLab) - -GitLab looks for `.gitlab-ci.yml` at the repository root by default. Because the -file now lives in `ci/`, the project setting must be changed or **GitLab CI -silently stops running** — no pipeline, no error: - -* Project → Settings → CI/CD → General pipelines → *CI/CD configuration file* -* Set it to `ci/.gitlab-ci.yml` - -== Using pre-commit - -The config is no longer at the root, so pass it explicitly: - -[source,bash] ----- -pre-commit install --config ci/.pre-commit-config.yaml -pre-commit run --all-files --config ci/.pre-commit-config.yaml ----- - -[NOTE] -==== -This repository's own push-time gate does not use the `pre-commit` framework. -It is `.github/hooks/pre-push`, activated by `bash .github/hooks/install.sh`, which sets -`core.hooksPath` to `.github/hooks`. -==== diff --git a/czech-file-knife/coordination.k9.ncl b/czech-file-knife/coordination.k9.ncl deleted file mode 100644 index 6f4f9877b..000000000 --- a/czech-file-knife/coordination.k9.ncl +++ /dev/null @@ -1,49 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Thin coordination bindings for central session-management standards - -{ - pedigree = { - schema_version = "1.0.0", - metadata = { - name = "session-coordination", - version = "0.1.0", - }, - security = { - leash = 'Kennel, - }, - }, - - session_management = { - source_of_truth = "standards/3-practice/session-management-standards", - canonical_commands = [ - "intake repo ", - "checkpoint change ", - "verify maintenance ", - "verify substantial ", - "verify release ", - "close planned ", - "close urgent ", - "recover repo ", - "handover full ", - "handover split ", - "handover model ", - "handover human ", - ], - }, - - signals = [ - { name = "session.intake", command = "intake repo " }, - { name = "session.checkpoint", command = "checkpoint change " }, - { name = "session.verify.maintenance", command = "verify maintenance " }, - { name = "session.verify.substantial", command = "verify substantial " }, - { name = "session.verify.release", command = "verify release " }, - { name = "session.close.planned", command = "close planned " }, - { name = "session.close.urgent", command = "close urgent " }, - { name = "session.recover", command = "recover repo " }, - { name = "session.handover.full", command = "handover full " }, - { name = "session.handover.split", command = "handover split " }, - { name = "session.handover.model", command = "handover model " }, - { name = "session.handover.human", command = "handover human " }, - ], -} diff --git a/czech-file-knife/czech-file-knife_chora.deed b/czech-file-knife/czech-file-knife_chora.deed deleted file mode 100644 index 53e3716df..000000000 --- a/czech-file-knife/czech-file-knife_chora.deed +++ /dev/null @@ -1,152 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -; -; The repo deed of czech-file-knife: one-deed-per-repo doctrine (standards#837, -; owner ruling 2026-09-19, Option A). Folds the former ply manifest tree -; (0.N-AI-MANIFEST.a2ml, 82 directories) and the family-7 AI -; allocation manifest (root 0-AI-MANIFEST.a2ml) into deed clauses. -; Generated by the #837 pilot emitter; every byte gated by deed_lint.py. -(repo-deed - :schema-version "1.0.0" - :canonical-name "czech-file-knife" - :beholding-chora #u5"estate/chora" - :repo-uuid #u5"github.com/hyperpolymath/czech-file-knife" - (manifest - :version "0.1.0" - :last-updated "2026-09-28" - :project "Czech File Knife" - :purpose "Canonical RSR (Rhodium Standard Repository) template — governance, CI/CD, machine-readable metadata, ABI/FFI seam and formal-verification scaffolding that hyperpolymath projects are instantiated from." - (agent :name CLAUDE :role "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") - (agent :name CHATGPT :role "prose, papers, publication review, standards docs, outreach drafts, letters") - (agent :name GEMINI :role "estate audits, cross-repo sweeps, long-context triage, pattern detection") - (agent :name VIBE :role "UI/frontend, PanLL panels, components, theming, rapid prototyping") - (policy :rules ("Do not duplicate tasks across sections. If a task needs multiple LLMs, note the handoff." "Update THIS file during sessions. Do NOT recreate per-repo TODO files.")) - (work :agent CLAUDE :task "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") - (work :agent CHATGPT :task "prose, papers, publication review, standards docs, outreach drafts, letters") - (work :agent GEMINI :task "estate audits, cross-repo sweeps, long-context triage, pattern detection") - (work :agent VIBE :task "UI/frontend, PanLL panels, components, theming, rapid prototyping") - ) - (ply - (directory :ply 1 :path ".github") - (directory :id "machine-readable-pillar" :ply 1 :path ".machine_readable" :description "Registry for all machine-readable metadata, policies, and internal\nautomation scripts." - (canonical-locations :agentic "descriptiles/AGENTIC.a2ml" :ai_configs "ai/" :anchors "descriptiles/anchors/" :clade "descriptiles/CLADE.a2ml" :compliance "compliance/" :ecosystem "descriptiles/ECOSYSTEM.a2ml" :meta "descriptiles/META.a2ml" :neurosym "descriptiles/NEUROSYM.a2ml" :playbook "descriptiles/PLAYBOOK.a2ml" :policies "policies/" :scripts "scripts/" :state "descriptiles/STATE.a2ml") - :invariants ("Metadata files MUST follow a2ml format" "Internal automation MUST live in scripts/ subfolder") - (directory :id "ai-registry" :ply 2 :path ".machine_readable/ai" :description "Sub-registry for ai metadata.") - (directory :id "configs-registry" :ply 2 :path ".machine_readable/configs" :description "Sub-registry for configs metadata.") - (directory :ply 0 :path ".machine_readable/descriptiles" :description "This manifest declares the AI-assistant context for the descriptiles machine-readable metadata directory." - :invariants ("No duplicate files in root directory" "Single source of truth: this directory is authoritative" "No stale metadata") - (directory :ply 0 :path ".machine_readable/descriptiles/anchors" :description "This manifest declares the AI-assistant context for the anchor machine-readable metadata directory." - :invariants ("Multiple versions with different dates are permitted" "No other A2ML files in this directory" "Single source of truth for anchor documents"))) - (directory :id "policies-registry" :ply 2 :path ".machine_readable/policies" :description "Sub-registry for policies metadata.") - (directory :id "automation-scripts-unit" :ply 2 :path ".machine_readable/scripts" :description "Internal automation logic for the project lifecycle, forge sync,\nverification triggers, and maintenance." - (canonical-locations :forge "forge/" :lifecycle "lifecycle/" :maintenance "maintenance/" :verification "verification/") - (directory :id "automation-unit-forge" :ply 3 :path ".machine_readable/scripts/forge" :description "Internal automation logic for project forge.") - (directory :id "automation-unit-lifecycle" :ply 3 :path ".machine_readable/scripts/lifecycle" :description "Internal automation logic for project lifecycle.") - (directory :id "automation-unit-verification" :ply 3 :path ".machine_readable/scripts/verification" :description "Internal automation logic for project verification."))) - (directory :id "container-templates" :ply 1 :path "build/container" :version "1.0.0" :description "Container templates for the stapeln container ecosystem. This directory\nprovides Podman-Chainguard-stapeln templates that are customised via\n`just container-init` or `just repo-init` during project bootstrap.\n\nAll files use {{PLACEHOLDER}} tokens that are substituted with project-\nspecific values during initialisation." :purpose "Provide a complete, security-first container deployment story for any\nRSR-compliant repository. The templates cover the full lifecycle:\nbuild, sign, verify, deploy, monitor, and govern." :overview "The stapeln container ecosystem comprises six tools:\n\nselur — Container orchestration with zero-copy IPC. Reads compose.toml.\ncerro-torre — Verified container packaging (.ctp bundles), Ed25519 signing.\nsvalinn — Policy-driven edge gateway (auth, rate limits, CORS, trust).\nvordr — Runtime monitoring (health, crashes, resources, logs).\nrokur — Secrets management (runtime injection, no baked secrets).\nk9-svc — Nickel deployment components (Kennel/Yard/Hunt trust levels)." - :context ("https://a2ml.org/ns/v2" "https://stapeln.dev/ns/v1") - (canonical-locations :build_pipeline "build/container/stapeln/ct-build.sh" :compose "build/container/stapeln/compose.toml" :compose_example "build/container/stapeln/compose.example.toml" :compose_portable "build/container/compose.yaml" :compose_portable_example "build/container/compose.example.yaml" :containerfile "build/container/Containerfile" :containerignore "build/container/.containerignore" :deployment "build/container/stapeln/deploy.k9.ncl" :entrypoint "build/container/entrypoint.sh" :gatekeeper "build/container/stapeln/.gatekeeper.yaml" :just_module "build/just/container.just" :manifest "build/container/stapeln/manifest.toml" :monitoring "build/container/stapeln/vordr.toml" :secrets_gate "build/container/stapeln/rokur.toml") - :invariants ("Base images MUST be cgr.dev/chainguard/wolfi-base or cgr.dev/chainguard/static" "Container runtime is Podman — never Docker" "Containerfile — never Dockerfile" "All images run as non-root (appuser or project-specific user)" ".ctp bundles are signed with Ed25519 via cerro-torre" "Health endpoints (/health, /ready) must always be public (no auth)") - (file-relationships - (file :name "compose.toml" :role "Orchestration" :description "selur-compose stack definition. Declares services, volumes, networks,\nand health checks. References the Containerfile for image builds and\n.gatekeeper.yaml for svalinn policy." :depends-on ("Containerfile" ".gatekeeper.yaml")) - (file :name "Containerfile" :role "Image Build (Tier A)" :description "Multi-stage OCI container build. Stage 1 compiles the application on\nwolfi-base; Stage 2 copies the binary into a minimal runtime image and\ncopies entrypoint.sh. Engine-agnostic (podman/nerdctl/docker). The\nstapeln files (.gatekeeper.yaml, manifest.toml) are referenced only as\nOCI labels and copied in only if you uncomment those COPY lines, so the\nimage has no hard dependency on tier C." :depends-on ("entrypoint.sh")) - (file :name "manifest.toml" :role "Bundle Metadata" :description "Cerro-torre .ctp bundle manifest. Describes provenance, dependencies,\nattestations, and runtime security profile. Used by `ct pack` and\n`ct verify`." :depends-on ()) - (file :name ".gatekeeper.yaml" :role "Gateway Policy" :description "Svalinn edge gateway policy. Controls authentication, rate limiting,\ncontainer trust, request validation, CORS, and audit logging." :depends-on ()) - (file :name "ct-build.sh" :role "Build Pipeline" :description "Shell script implementing the 5-stage pipeline: build (Podman),\npack (cerro-torre .ctp), sign (Ed25519), verify, push (optional).\nDegrades gracefully when cerro-torre tools are not installed." :depends-on ("Containerfile" "manifest.toml")) - (file :name "entrypoint.sh" :role "Container Entrypoint" :description "Startup script with signal handling (SIGTERM, SIGINT), logging, and\nexec into the main application process." :depends-on ()) - (file :name "vordr.toml" :role "Runtime Monitoring" :description "Vordr monitoring configuration. Health endpoint probing, crash\ndetection, resource thresholds, and structured log output." :depends-on ()) - (file :name "deploy.k9.ncl" :role "Deployment Component" :description "k9-svc deployment specification at Hunt trust level. Full pedigree\n(L1-L5), environment configs, container config, and rolling\ndeployment strategy." :depends-on ("compose.toml" "ct-build.sh")) - (file :name "compose.example.toml" :role "Example" :description "Fully-commented multi-service example (Rust API + Elixir worker +\nsvalinn gateway). Copy to compose.toml and customise." :depends-on ()) - )) - (directory :id "docs-pillar" :ply 1 :path "docs" :description "Technical documentation hub. The root contains high-level orientation\n(README, Quickstart, State-Visualizer). Specialized tracks live in\nsubdirectories." - (canonical-locations :architecture "architecture/" :attribution "attribution/" :decisions "decisions/" :developer "developer/" :governance "governance/" :legal "legal/" :practice "practice/" :quickstart "QUICKSTART.adoc" :reports "reports/" :standards "standards/" :state_visualizer "STATE-VISUALIZER.adoc" :theory "theory/" :whitepapers "whitepapers/" :wikis "wikis/") - :invariants ("Primary documentation format MUST be AsciiDoc (.adoc)" "Root docs/ MUST only contain pillar entry points") - (directory :id "architecture-track" :ply 2 :path "docs/architecture" :description "Documentation track for system architecture and threat models." - (canonical-locations :threat_model "THREAT-MODEL.adoc") - :invariants ("Visual diagrams MUST include ASCII or Mermaid representations")) - (directory :id "attribution-unit" :ply 2 :path "docs/attribution" :description "Sub-unit of the docs pillar focusing on attribution.") - (directory :id "decisions-unit" :ply 2 :path "docs/decisions" :description "Sub-unit of the docs pillar focusing on decisions.") - (directory :id "developer-unit" :ply 2 :path "docs/developer" :description "Sub-unit of the docs pillar focusing on developer.") - (directory :id "governance-pillar" :ply 1 :path "docs/governance" :description "Primary governance pillar implementing the Triaxial Software Development\nMethodology (TSDM). Contains planning, maintenance, and audit tracks." - (canonical-locations :approach "SOFTWARE-DEVELOPMENT-APPROACH.adoc" :audit "audit/" :checklist "MAINTENANCE-CHECKLIST.adoc" :crg "CRG-CRITERIA.adoc" :maintenance "maintenance/" :planning "planning/" :tsdm_spec "TSDM.adoc") - (directory :id "governance-axis-audit" :ply 2 :path "docs/governance/audit" :description "TSDM Audit track." - (directory :id "governance-unit-compliance" :ply 3 :path "docs/governance/audit/compliance" :description "TSDM compliance unit within the Audit axis.") - (directory :id "governance-unit-effects" :ply 3 :path "docs/governance/audit/effects" :description "TSDM effects unit within the Audit axis.") - (directory :id "governance-unit-systems" :ply 3 :path "docs/governance/audit/systems" :description "TSDM systems unit within the Audit axis.")) - (directory :id "governance-axis-maintenance" :ply 2 :path "docs/governance/maintenance" :description "TSDM Maintenance track." - (directory :id "governance-unit-adaptive" :ply 3 :path "docs/governance/maintenance/adaptive" :description "TSDM adaptive unit within the Maintenance axis.") - (directory :id "governance-unit-corrective" :ply 3 :path "docs/governance/maintenance/corrective" :description "TSDM corrective unit within the Maintenance axis.") - (directory :id "governance-unit-perfective" :ply 3 :path "docs/governance/maintenance/perfective" :description "TSDM perfective unit within the Maintenance axis.")) - (directory :id "governance-axis-planning" :ply 2 :path "docs/governance/planning" :description "TSDM Planning track." - (directory :id "governance-unit-could" :ply 3 :path "docs/governance/planning/could" :description "TSDM could unit within the Planning axis.") - (directory :id "governance-unit-must" :ply 3 :path "docs/governance/planning/must" :description "TSDM must unit within the Planning axis.") - (directory :id "governance-unit-should" :ply 3 :path "docs/governance/planning/should" :description "TSDM should unit within the Planning axis."))) - (directory :id "legal-track" :ply 2 :path "docs/legal" :description "Sub-unit for legal and licensing documentation. Contains framework\nexhibits and archival license texts." - (canonical-locations :exhibits "exhibits/" :texts "texts/")) - (directory :id "practice-unit" :ply 2 :path "docs/practice" :description "Sub-unit of the docs pillar focusing on practice.") - (directory :id "reports-unit" :ply 2 :path "docs/reports" :description "Documentation unit for all automated and manual audit reports. Classified\nby domain." - (canonical-locations :compliance "compliance/" :maintenance "maintenance/" :performance "performance/" :quality "quality/" :security "security/") - (directory :id "report-unit-compliance" :ply 3 :path "docs/reports/compliance" :description "Specialised repository for compliance findings and evidence.") - (directory :id "report-unit-maintenance" :ply 3 :path "docs/reports/maintenance" :description "Maintenance reports.") - (directory :id "report-unit-performance" :ply 3 :path "docs/reports/performance" :description "Specialised repository for performance findings and evidence.") - (directory :id "report-unit-quality" :ply 3 :path "docs/reports/quality" :description "Specialised repository for quality findings and evidence.") - (directory :id "report-unit-security" :ply 3 :path "docs/reports/security" :description "Specialised repository for security findings and evidence.")) - (directory :id "standards-unit" :ply 2 :path "docs/standards" :description "Standards unit for high-rigor verification.") - (directory :id "theory-track" :ply 2 :path "docs/theory" :description "Documentation track for domain-specific theory and research foundations.\nCategorised by discipline." - (canonical-locations :computing "computing/" :formalisms "formalisms/" :mathematics "mathematics/" :ontologies "ontologies/" :other "other/" :socio_technical "socio-technical/") - :invariants ("Theoretical claims MUST reference established academic or technical formalisms") - (directory :id "theory-unit-computing" :ply 3 :path "docs/theory/computing" :description "Theoretical foundation for computing.") - (directory :id "theory-unit-formalisms" :ply 3 :path "docs/theory/formalisms" :description "Theoretical foundation for formalisms.") - (directory :id "theory-unit-mathematics" :ply 3 :path "docs/theory/mathematics" :description "Theoretical foundation for mathematics.") - (directory :id "theory-unit-ontologies" :ply 3 :path "docs/theory/ontologies" :description "Theoretical foundation for ontologies.") - (directory :id "theory-unit-other" :ply 3 :path "docs/theory/other" :description "Theoretical foundation for other.") - (directory :id "theory-unit-socio-technical" :ply 3 :path "docs/theory/socio-technical" :description "Theoretical foundation for socio technical.")) - (directory :id "whitepapers-track" :ply 2 :path "docs/whitepapers" :description "Unit for strategic publications and whitepapers. Categorised by target\naudience: Academic, Industry, and Outreach." - (canonical-locations :academic "academic/" :industry "industry/" :outreach "outreach/") - :invariants ("Each sub-track MUST have a clear audience definition in its README") - (directory :id "academic-unit" :ply 3 :path "docs/whitepapers/academic" :description "Academic logic at level 3.") - (directory :id "industry-unit" :ply 3 :path "docs/whitepapers/industry" :description "Industry logic at level 3.") - (directory :id "whitepapers-track-outreach" :ply 3 :path "docs/whitepapers/outreach" :description "Documentation track for outreach, education, and general-audience\nengagement. Focuses on accessibility and high-level conceptual clarity." - :invariants ("Language MUST be accessible to non-technical audiences" "Avoid deep technical jargon without providing clear definitions"))) - (directory :id "wikis-track" :ply 2 :path "docs/wikis" :description "Long-form collaborative documentation and project knowledge base.\nThis directory mirrors the content structure of the project wiki." - :invariants ("Primary wiki format MUST be Markdown (.md) — owner ruling 2026-09-19: wikis are the .md home (berrywiki); everything outside wikis stays .adoc"))) - (directory :ply 1 :path "examples") - (directory :id "features-pillar" :ply 1 :path "features" :description "Optional project features and ecosystem integrations. Provides bootstrap\nguides for high-rigor tools (Panic-Attacker, BoJ-Server, SSGs)." - (canonical-locations :boj_server "boj-server/" :panic_attacker "panic-attacker/" :ssg "ssg/") - (directory :id "feature-unit-boj-server" :ply 2 :path "features/boj-server" :description "Bootstrap and integration logic for the boj-server ecosystem component.") - (directory :id "feature-unit-panic-attacker" :ply 2 :path "features/panic-attacker" :description "Bootstrap and integration logic for the panic-attacker ecosystem component.") - (directory :id "feature-unit-ssg" :ply 2 :path "features/ssg" :description "Bootstrap and integration logic for the ssg ecosystem component.")) - (directory :id "source-pillar" :ply 1 :path "src" :description "Primary source code directory. Organized by role and architectural\naspect." - (canonical-locations :aspects "aspects/" :bridges "bridges/" :contracts "contracts/" :core "core/" :definitions "definitions/" :errors "errors/" :interface "interface/") - :invariants ("Core logic MUST reside in core/" "Verified seams MUST reside in interface/" "Safety constraints MUST reside in contracts/" "Failure dictionaries MUST reside in errors/") - (directory :id "source-unit-aspects" :ply 2 :path "src/aspects" :description "Cross-cutting concerns and domain-specific aspects (Security,\nObservability, Integrity)." - (canonical-locations :integrity "integrity/" :observability "observability/" :security "security/") - (directory :id "aspect-unit-integrity" :ply 3 :path "src/aspects/integrity" :description "Implementation logic for the integrity aspect.") - (directory :id "aspect-unit-observability" :ply 3 :path "src/aspects/observability" :description "Implementation logic for the observability aspect.") - (directory :id "aspect-unit-security" :ply 3 :path "src/aspects/security" :description "Implementation logic for the security aspect.")) - (directory :id "source-unit-bridges" :ply 2 :path "src/bridges" :description "Integration logic for external systems (API, Database, RPC, etc.).") - (directory :id "source-unit-contracts" :ply 2 :path "src/contracts" :description "Contracts unit for high-rigor source code.") - (directory :id "source-unit-core" :ply 2 :path "src/core" :description "Primary application logic and core domain models.") - (directory :id "source-unit-definitions" :ply 2 :path "src/definitions" :description "Definitions unit for high-rigor source code.") - (directory :id "source-unit-errors" :ply 2 :path "src/errors" :description "Errors unit for high-rigor source code.") - (directory :id "interface-seams-unit" :ply 2 :path "src/interface" :description "Consolidated \"Verified Interface Seams\" unit. This directory unifies the\nformal specification (ABI), the bridge implementation (FFI), and the\nresulting artifacts (Generated)." - (canonical-locations :abi "abi/" :ffi "ffi/" :generated "generated/") - :invariants ("ABI MUST be Idris2 (.idr)" "FFI MUST be Zig (.zig)" "Generated artifacts MUST be C-compatible" "The 'Truth' lives in abi/; the 'Implementation' lives in ffi/") - (directory :id "abi-logic" :ply 3 :path "src/interface/Abi" :description "Specialised Level 3 logic for abi.") - (directory :id "ffi-logic" :ply 3 :path "src/interface/ffi" :description "Specialised Level 3 logic for ffi." - (directory :id "src-unit" :ply 4 :path "src/interface/ffi/src" :description "Src logic at level 4.") - (directory :id "test-unit" :ply 4 :path "src/interface/ffi/test" :description "Test logic at level 4.")) - (directory :id "generated-logic" :ply 3 :path "src/interface/generated" :description "Specialised Level 3 logic for generated." - (directory :id "abi-unit" :ply 4 :path "src/interface/generated/abi" :description "Abi logic at level 4.")))) - (directory :id "verification-pillar" :ply 1 :path "verification" :description "Primary verification pillar. Contains evidence for correctness,\nperformance, formal proofs, randomized testing, and aerospace-grade\nhigh-assurance metrics (MC/DC coverage, traceability, safety cases)." - (canonical-locations :benchmarks "benchmarks/" :coverage "coverage/" :fuzzing "fuzzing/" :proofs "proofs/" :safety_case "safety_case/" :simulations "simulations/" :tests "tests/" :traceability "traceability/") - :invariants ("Evidence MUST be reproducible and documented" "High-assurance deployments MUST satisfy traceability and safety_case requirements") - (directory :id "benches-pillar" :ply 2 :path "verification/benchmarks" :description "Benches pillar.") - (directory :id "verification-unit-coverage" :ply 2 :path "verification/coverage" :description "High-assurance verification unit for coverage. \nCritical for safety-of-life and aerospace-grade deployment standards.") - (directory :id "fuzzing-unit" :ply 2 :path "verification/fuzzing" :description "Fuzzing unit for high-rigor verification.") - (directory :id "verification-unit-proofs" :ply 2 :path "verification/proofs" :description "Sub-unit focusing on proofs.") - (directory :id "verification-unit-safety_case" :ply 2 :path "verification/safety_case" :description "High-assurance verification unit for safety case. \nCritical for safety-of-life and aerospace-grade deployment standards.") - (directory :id "simulations-unit" :ply 2 :path "verification/simulations" :description "Simulations unit for high-rigor verification.") - (directory :ply 2 :path "verification/tests") - (directory :id "verification-unit-traceability" :ply 2 :path "verification/traceability" :description "High-assurance verification unit for traceability. \nCritical for safety-of-life and aerospace-grade deployment standards.")) - ) -) diff --git a/czech-file-knife/docs/AFFIRMATION.adoc b/czech-file-knife/docs/AFFIRMATION.adoc deleted file mode 100644 index 7bb85b7bb..000000000 --- a/czech-file-knife/docs/AFFIRMATION.adoc +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= AFFIRMATION — Czech File Knife, as of -:toc: macro -:toclevels: 2 -:std-docs: https://github.com/hyperpolymath/standards/blob/main/docs - -_the No-Bullshit file: what we affirm was true and checkable at this moment._ - -[NOTE] -==== -*This file is a template.* Replace the `{{…}}` placeholders and the `<…>` anchor -fields, then re-run your project's own checks in the same session before -signing. It conforms to *profile A (evidential)* of the -link:{std-docs}/AFFIRMATION-STANDARD.adoc[AFFIRMATION authoring standard]. A -policy-surface repo should use profile B (MUST / INTEND / WISH) instead — see -that standard for the profile B skeleton. - -An *affirmation* is a solemn declaration of the truth of a statement, made by -someone who _declines to swear an oath_ — our truth-as-best-believed at a -stamped instant, binding on our honesty, not a claim of infallibility. It is the -third of the README / EXPLAINME / AFFIRMATION trio: - -[cols="1,3,2",options="header"] -|=== -| File | Answers | Tense -| `README.adoc` | _Where is this going, and why?_ — steering, intent, vision | future / aspirational -| `EXPLAINME.adoc` | _How is it built, and what's the evidence?_ — engineering | descriptive / mechanism -| *`AFFIRMATION.adoc`* (this file) | _What can we honestly affirm was *true and checkable* at a stamped moment?_ | a frozen instant, falsifiable -|=== - -This file is *optional*. Delete it rather than carry an affirmation you have not -re-verified — a stale affirmation is worse than none. -==== - -toc::[] - -== What this is, and how it works - -*What it is.* A short, dated, signed snapshot of what hyperpolymath can honestly and -verifiably claim about *Czech File Knife* at one exact commit. Nothing here is -marketing and nothing is a promise about the future — those live in the README. -This file is the receipt. - -*What the project is.* The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage. - -*How it stays trustworthy.* Three moving parts: - -. *Ground truth, not memory.* Every claim below must be produced by _running the - project's own checks_ in the session that writes this file (build, tests, - typecheck, `just audit`). Where a status doc, the `Justfile`, or memory says - otherwise, the live run wins and the contradiction is flagged here. -. *A frozen anchor.* The file names the exact commit SHA, branch, UTC timestamp, - working-tree delta and toolchain (see <>), so "true" always - means "true _at this point_". Move the SHA and this file is a draft until it - is re-run. -. *A real signature.* It is landed by a *signed git commit*; that signature over - this content at the anchored SHA is what makes the affirmation tamper-evident - and attributable — not the prose alone. - -*We are fallible.* This is our best honest belief, not a proof of its own -correctness. Treat it as a falsifiable claim, not gospel. - -== The epistemic contract (read this before you trust _or_ attack) - -This document records hyperpolymath's *best belief* at the timestamp below. It is -*not a guarantee of correctness.* The only guarantee is *no intentional -overclaim*: where something is proven we say "proven"; where it is a documented -trust boundary, an experiment, or an unwired module, we say so; where a claim is -the README's aspiration rather than a checked result, we say so. An honest claim -that later turns out false is an *error to be fixed* — not a lie. - -What you may conclude from this file: - -* Every claim below was produced by *running the tool* in the session that wrote - this file — not from memory, not copied forward from a previous affirmation, - and not read off a status document. -* Where a live run and a status document disagreed, the live run won and the - status document is named as stale in <>. - -What you may *not* conclude: - -* That anything is true *now*. This file describes the commit in - <> and nothing else. -* That unlisted things pass. *Silence is not a claim.* - -*Standing invitation to refute.* You are invited to bulldoze any claim in this -file. Bring a counter-example, a failing run, or a contradicting source. - -[#verifiable-anchor] -== Verifiable anchor - -[cols="1,3",options="header"] -|=== -| Field | Value - -| Project -| Czech File Knife - -| Repo -| `hyperpolymath/czech-file-knife` - -| Branch -| `main` - -| Commit (HEAD) -| `` - -| Permalink -| https://github.com/hyperpolymath/czech-file-knife/tree/ - -| Verified (UTC) -| `` - -| Working-tree delta at verification -| `clean`, or every modified and untracked file present when the checks ran, - with an explicit statement of whether it affects the results below. - -| Toolchain -| `` - -| Affirmed by -| Jonathan D.A. Jewell -|=== - -[IMPORTANT] -==== -*Never anchor to a tag.* Tags move, and a moved tag silently invalidates every -claim in this file. - -If you are reading this at a later commit, the claims may have drifted. Re-run -<> and write a fresh affirmation; do not trust a stale one. -==== - -== Companion documents and repo metadata (cross-check) - -The files a sceptic should read against this one — *including any that -contradict it*. A contradiction named here is honest; one the reader finds for -themselves is not. - -* `README.adoc` — the aspirational claims this file is measured against. -* `docs/EXPLAINME.adoc` — the mechanism. -* `docs/AUDIT.adoc` — the standing conformance audit. -* `*_chora.deed` (repo deed) — machine-readable repo metadata (AI allocation + ply tree). -* `` - -== The honest state (one breath) - -`` - -=== What is solid (and how we checked) - -[cols="2,1,3",options="header"] -|=== -| Claim | Status | Evidence (command, and what it printed) -| _e.g. The library builds clean_ | affirmed | `just build` at the anchor SHA — exit 0 -| _e.g. The ABI seam typechecks_ | affirmed | `idris2 --typecheck src/interface/abi.ipkg` — 0 errors -| _e.g. Feature X is complete_ | aspiration | README §… — *not checked*, do not read as affirmed -|=== - -=== The honest nuance you must not lose - -Where a true claim above is easily over-read. This section is what separates an -affirmation from marketing. - -* `` - -=== Known-incomplete but honestly fenced - -Gaps that fail *loudly* rather than silently. Name the guard that makes the -failure loud — a gap with no guard belongs in <> instead. - -* `` — fenced by `` - -[#outstanding] -=== Outstanding / weak / refuted (no spin) - -Known gaps, trust boundaries, postulates, unwired modules and stale docs. -*Silence is not affirmation* — name what you have not verified, and name -anything this session *refuted*. - -[IMPORTANT] -==== -Never delete a refuted claim. Deleting it is exactly the spin this genre exists -to prevent — mark it refuted and say what refuted it. -==== - -[#reproduce] -== Reproduce it yourself - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife -cd czech-file-knife -git checkout -# ----- - -== One-line characterisation (quote this) - -> `` - -== Joint attestation - -We, the undersigned, assert that *to the best of our joint belief at the -timestamp above, every claim in this file is true and was checked as described* -— with no intentional overclaim, and with the open gaps stated rather than -hidden. - -* *Engineering party (AI):* `` — ran the checks recorded - here at `` and stands behind the wording above as a faithful - report of those runs. -* *Owner / maintainer:* Jonathan D.A. Jewell — _signs by committing - this file with `-S`; the git commit signature over this content, at the - commit SHA recorded above, is the cryptographic form of this affirmation._ - -[source,bash] ----- -git commit -S -s docs/AFFIRMATION.adoc -m "docs: affirm state at " -git log --show-signature -1 ----- - -[WARNING] -==== -Do not use `--no-verify`. The pre-commit hook enforces the SPDX header, and an -affirmation landed past its own repo's gates is self-refuting. -==== - -_Landed by a signed git commit. Verify with `git log --show-signature` that the -anchor SHA above matches the parent of the commit that introduced this -affirmation. If they do not match, this file is a *draft* and must be read as -one._ diff --git a/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc b/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc deleted file mode 100644 index 19ed4632a..000000000 --- a/czech-file-knife/docs/AI-INSTALL-README-SECTION.adoc +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= AI-Install README Section (canonical snippet) - -The block below is the canonical `== AI-Assisted Installation` section. Paste it -into a repository's `README.adoc` verbatim; `just validate-ai-install` greps the -README for the heading text, so keep the heading exactly as written. - ----- -== AI-Assisted Installation - -If you are an AI agent installing this project, read -link:docs/AI_INSTALLATION_GUIDE.adoc[the AI installation guide] first — it gives -the orientation order, the full prompt sequence, and the privacy notice. - -The one trap worth stating up front: **do not set `RSR_NON_INTERACTIVE=1`**. It -stubs the shell builtin `read`, which also disables the loops that perform token -substitution — the run never terminates and substitutes nothing. Pipe answers to -stdin instead. ----- diff --git a/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc b/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc deleted file mode 100644 index 057fcd6a2..000000000 --- a/czech-file-knife/docs/AI_INSTALLATION_GUIDE.adoc +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= AI-Assisted Installation Guide -:toc: -:toclevels: 3 -:sectnums: - -This guide is for an **AI agent** instantiating this template into a new -repository. A human following it will not be misled, but the ordering and the -warnings are written for an agent working non-interactively. - -[[ai-implementation]] -== Implementation for an AI agent - -=== 1. Orient before acting - -Read, in this order, before running anything: - -. `*_chora.deed` (the repo deed) — the universal AI entry point at the repository root. -. `CLAUDE.md` — the generated arrival pack (estate doctrine + repo identity). -. `.machine_readable/descriptiles/STATE.a2ml` — current phase and completion. - -Do not infer the repository's purpose from its name. If a fact is not written -down, record it as `UNASSIGNED` rather than inventing it. - -=== 2. Obtain the template - -Always take the template from `origin/main`, never from a local working tree — -local checkouts across this estate are routinely stale or carry sweep debris. - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife.git my-project -cd my-project -rm -rf .git && git init ----- - -Alternatively, use GitHub's *Use this template* button, which produces a repo -with no git ancestry; `just repo-init` recovers the template tip via -`git ls-remote` and records it as the `parent-pin`. - -=== 3. Run the instantiation - -[source,bash] ----- -just repo-init # or: just repo-init ----- - -`just repo-init` renders every `{{TOKEN}}` in the tree, derives the repository -identity, writes the provenance files, and removes the template-only -directories. - -[WARNING] -==== -**Do not set `RSR_NON_INTERACTIVE=1`.** It stubs the shell builtin `read` -globally, which also disables the two `while read -r file` loops that drive -substitution. The result is an infinite loop that performs zero substitution — -it does not fail, it never terminates. - -Drive it non-interactively by **piping answers to stdin** instead. Real `read` -stays intact, so the data loops work. -==== - -The prompts, in order, are: - -[cols="1,3",options="header"] -|=== -| # | Prompt - -| 1 | Project name -| 2 | Repository slug — validated against `^[a-z0-9]+(-[a-z0-9]+)*$`; no capitals, spaces or underscores (it must be a valid Guix package name) -| 3 | Owner -| 4 | Author full name -| 5 | Author email -| 6 | Author organisation -| 7 | Previous/alternate email -| 8 | Project description -| 9 | Forge domain -| 10 | Security email -| 11 | Conduct email -| 12 | Project type -| 13 | Website URL (blank = default) -| 14 | OpenSSF Best Practices ID (blank = none) -| 15 | Service name (blank = default) -| 16 | Primary port (blank = 8080) -| 17 | Container registry (blank = default) -| 18 | Proceed? — answer `y` -|=== - -[IMPORTANT] -==== -Prompts 15–17 are gated on the presence of the container directory, **not** on -the project type. The template always ships one, so they always fire. Supplying -only 15 answers makes `read` hit EOF, which returns 1 and kills the recipe under -`set -e`. -==== - -=== 4. Verify the result - -[source,bash] ----- -just --list # every recipe resolves (imports are silent on failure) -just check-root-shape # root matches the allowlist -bash scripts/check-no-placeholders.sh . -just verify ----- - -A successful instantiation leaves **no** `{{TOKEN}}` anywhere in the tree, and -removes the template-only directories from the minted repository. If -`scripts/validate-template.sh` or the end-to-end instantiation test are still -present, the repository was not cured — they are the clearest single marker of -an un-instantiated repo. - -== Privacy - -This template performs no telemetry and transmits nothing during installation. - -Note what instantiation *records*, all of it locally and in your own git history: - -* The identity you supply at prompts 3–7 (owner, author name, both email - addresses, organisation) is written into source headers, `CITATION.cff`, - `.mailmap`, `CODEOWNERS` and the security contact. Treat the alternate email - as published data. -* `just repo-init` runs `git ls-remote` against the template's forge to resolve - the parent pin. This is a single outbound request; when it is unavailable the - pin is recorded as `UNASSIGNED` rather than guessed. -* The clade UUID is *derived*, never allocated: - `uuidgen --sha1 --namespace @url --name "//"`. It is a - function of the repository's name, so renaming the repository changes it and - registries must be regenerated rather than string-swapped. - -If you are an agent acting on someone else's behalf, do not invent identity -values to satisfy a prompt. Leave them `UNASSIGNED` and report the gap. diff --git a/czech-file-knife/docs/ARCHITECTURE.adoc b/czech-file-knife/docs/ARCHITECTURE.adoc deleted file mode 100644 index 44cce316e..000000000 --- a/czech-file-knife/docs/ARCHITECTURE.adoc +++ /dev/null @@ -1,48 +0,0 @@ -= Architecture - -== Overview - -This repository follows a modular, maintainable architecture designed for clarity, scalability, and long-term sustainability. - -== Directory Structure - -[source] ----- -. -├── src/ # Source code -├── tests/ # Test suites -├── docs/ # Documentation -├── scripts/ # Utility scripts -├── config/ # Configuration files -├── LICENSE # License file -├── LICENSES/ # Full license texts -└── README.adoc # Project documentation ----- - -== Design Principles - -* *Separation of Concerns*: Each module has a single responsibility -* *Testability*: Code is written to be easily testable -* *Documentation*: All public APIs are documented -* *Configuration*: Environment-specific settings are externalized - -== Dependencies - -* External dependencies are minimized and clearly declared -* Version pinning is used for reproducibility - -== Security Considerations - -* Sensitive data is never committed to the repository -* Secrets are managed through environment variables or secure vaults -* Regular dependency audits are performed - -== Maintainability - -* Code follows consistent style guidelines -* Pull requests require review and CI checks -* Issues and discussions are tracked transparently - ---- - -_Last updated: 2026-07-18_ diff --git a/czech-file-knife/docs/AUDIT.adoc b/czech-file-knife/docs/AUDIT.adoc deleted file mode 100644 index 04f5d5ede..000000000 --- a/czech-file-knife/docs/AUDIT.adoc +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Audit Gate -Codex -v1.1, 2026-04-07 -:toc: -:toclevels: 2 -:sectnums: - -== Purpose - -This root document exists so humans and bots can see the hard audit posture -without having to discover the standards repository first. - -Canonical source documents live in the `standards` repository. This file is a -repo-local audit gate summary for template users and automated agents. - -== Hard Rules - -* Do not call anything `stable`, `v1.0.0`, or full release unless the stable - release gate has been passed end to end. -* Do not publish implementation-facing work below `B` in CRG unless the work is - genuinely abstract and makes no implementation-readiness claim. -* `D` requires RSR compliance or a documented equivalent repository discipline. -* `C` requires deep code and folder annotation, not just local confidence. -* `B` means `beta-stable`: external breadth and safe broad trial, not merely - public visibility. -* Papers, whitepapers, release notes, and READMEs must not outrun the proofs, - tests, or artefacts that support their claims. -* Release paths must not ship with placeholders, stubs, `FIXME`, `XXX`, - template residue, fake fuzz, fake benches, or partial proof debt hidden as - if it were complete. - -== Canonical Standards - -Read these as the authoritative source: - -* `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` -* `standards/3-practice/release-pre-flight/V1-GATE.adoc` -* `standards/3-practice/publication-pre-flight/PREFLIGHT.adoc` -* `standards/3-practice/publication-pre-flight/ESTATE-AUDIT-BASELINE-2026-03-30.adoc` -* `standards/3-practice/session-management-standards/README.adoc` - -== Bot Requirement - -Bots operating in repositories derived from this template should treat this -document as a key root audit document and should not make optimistic release or -publication claims that conflict with it. diff --git a/czech-file-knife/docs/EXPLAINME.adoc b/czech-file-knife/docs/EXPLAINME.adoc deleted file mode 100644 index c8ca0f607..000000000 --- a/czech-file-knife/docs/EXPLAINME.adoc +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= RSR Template Repo - Explainme -image:https://img.shields.io/badge/License-MPL_2.0-blue.svg[License: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] - -:toc: -:icons: font - -This file explains how the key template claims map to real files. - -== Central Session Protocol Authority - -Claim: -Session protocols are centrally maintained and not duplicated in this template. - -How this is implemented: - -* The local dispatcher (`session/dispatch.sh`) maps canonical commands to central - protocol paths in `standards/3-practice/session-management-standards`. -* Local files (`session/custom-checks.k9`, `session/local-hooks.sh`, - `coordination.k9`) are integration-only. - -Caveat: - -* If `SESSION_STANDARDS_DIR` is unset and no adjacent standards checkout exists, - the dispatcher records the command but cannot resolve central checklist paths. - -== Canonical Command Surface - -Claim: -Template bindings align to one canonical command model. - -How this is implemented: - -* `Justfile` provides thin aliases (`intake-repo`, `checkpoint-change`, - `verify-maintenance`, `verify-substantial`, `verify-release`, `close-planned`, - `close-urgent`, `recover-repo`, `handover-*`). -* Every alias calls `session/dispatch.sh` with canonical verb-object pairs. - -Caveat: - -* Recipes are wrappers only. They do not replace protocol content from - the central standards repo. - -== Runtime State Is Local - -Claim: -Session state is per-repository runtime output, not standards text. - -How this is implemented: - -* `session/dispatch.sh` writes command and continuity-core capture stubs to - `.session/LAST-CANONICAL-COMMAND.md` in the target repository path. - -Caveat: - -* Runtime files are intentionally lightweight and require human/agent completion. - -== Template Token Policy - -Claim: -Placeholders are explicit template content until initialization. - -How this is implemented: - -* `README.adoc` and bootstrap recipes keep `{{TOKEN}}` placeholders visible. -* `just init` performs token replacement. - -Caveat: - -* Uninitialized placeholders must not be treated as project-specific truth. - -== Dependency Updates (Dependabot) - -Claim: -Dependency bumps land fast and safely, without manual chasing. - -How this is implemented: - -* `dependabot.yml` watches the Dependabot-supported ecosystems the estate - actually uses: `github-actions`, `cargo`, `mix` (Elixir), `docker`. -* `dependabot-automerge.yml` auto-merges *every* bump (patch/minor/major, - security or routine) **once the required checks are green** — a broken bump - fails CI and stays open (you get an email); it never lands on a red `main`. - -Caveat: - -* Dependabot has **no or Bun ecosystem**, and `pnpm` only rides under the - `npm` ecosystem (itself banned). For this -first estate Dependabot cannot - watch the runtime dependencies; the `npm`/`pip` entries are retained only for - transitional/legacy manifests and are otherwise inert. dependency - currency is managed via `.json`/`.lock`, not Dependabot. -* Do **not** add Dependabot as a ruleset *bypass* actor: that lets bumps skip - the required checks (secret-scanning, SAST), removing the safety gate and the - "it broke" signal. Auto-merge-on-green gives fast merges without it. - -== Julia Registry Packages — Standalone Repo Requirement - -Claim: -If this template is used to create a Julia package, it must remain a standalone repository registered with the Julia package registry. - -How this is implemented: - -* Julia's package registry (General.jl or other) expects each package to be a standalone GitHub repository with `Project.toml` at the repository root. -* Installation via `Pkg.add()`, dependency resolution, and automated CI/CD all depend on this canonical structure. - -Caveat: - -* Do NOT move this repository into a monorepo or subdirectory, as this breaks registry registration and package discoverability. -* Julia packages published to a registry must each remain a standalone top-level repository (registry registration and package discovery require it). -* Non-registry Julia packages can be organized differently if they are not published to any registry. - - -== License - -This project is licensed under the Mozilla Public License, v. 2.0. See the `LICENSE` file for details. - -SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/czech-file-knife/docs/GOVERNANCE.adoc b/czech-file-knife/docs/GOVERNANCE.adoc deleted file mode 100644 index ff82d8bdb..000000000 --- a/czech-file-knife/docs/GOVERNANCE.adoc +++ /dev/null @@ -1,65 +0,0 @@ -= Governance - -== Overview - -This project is governed by the following principles and structures to ensure transparent, inclusive, and effective decision-making. - -== Roles and Responsibilities - -=== Maintainers - -Maintainers are responsible for: - -* Reviewing and merging pull requests -* Managing releases and versioning -* Ensuring code quality and standards -* Triaging issues and bug reports -* Community engagement and support - -=== Contributors - -Contributors are expected to: - -* Follow the code of conduct -* Submit well-documented pull requests -* Write tests for new functionality -* Maintain existing tests -* Update documentation as needed - -== Decision Making - -=== Minor Changes - -* Can be made by any maintainer -* Include bug fixes, documentation updates, dependency updates - -=== Major Changes - -* Require discussion in issues or pull requests -* Include new features, architectural changes, API changes -* Need approval from at least 2 maintainers - -=== Breaking Changes - -* Require RFC (Request for Comments) process -* Need approval from majority of maintainers -* Must include migration guide - -== Code of Conduct - -All participants are expected to follow our Code of Conduct. Violations can be reported to the maintainers. - -== Communication - -* *Issues*: For bug reports and feature requests -* *Discussions*: For questions and general discussion -* *Pull Requests*: For code contributions - -== Licensing - -All contributions are made under the terms of the repository's LICENSE file. -By submitting a pull request, you agree to license your contributions accordingly. - ---- - -_Last updated: 2026-07-18_ diff --git a/czech-file-knife/docs/MAINTAINERS.adoc b/czech-file-knife/docs/MAINTAINERS.adoc deleted file mode 100644 index 3f1fad42d..000000000 --- a/czech-file-knife/docs/MAINTAINERS.adoc +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Maintainers -:toc: preamble - -This is the single maintainer roster for this repository. It supersedes the -earlier duplicates at `.github/MAINTAINERS` and `docs/attribution/MAINTAINERS.adoc`. - -== Current maintainers - -[cols="2,3,2",options="header"] -|=== -| Name | Role | Contact - -| Jonathan D.A. Jewell -| Lead Maintainer -| https://github.com/hyperpolymath[@hyperpolymath] -|=== - -== Responsibilities - -Maintainers are responsible for: - -* Reviewing and merging pull requests -* Triaging issues and feature requests -* Ensuring code quality and security standards -* Managing releases and versioning -* Upholding the project's Code of Conduct -* Maintaining documentation and examples -* Responding to security vulnerabilities - -== Contribution process - -Contributions are welcome via: - -. **Issues** — report bugs, request features, ask questions -. **Pull requests** — submit improvements for review -. **Discussions** — engage with the wider project - -== Decision making - -* Routine decisions (bug fixes, minor improvements) may be made by any maintainer. -* Significant changes require discussion and consensus among maintainers. -* Breaking changes should be discussed in an issue, with a migration path, before - implementation. - -== Becoming a maintainer - -Contributors who demonstrate consistent, high-quality contributions, an -understanding of the project's goals and standards, constructive participation, -and commitment to its long-term health may be invited to become maintainers at -the discretion of the existing maintainers. - -== Contact - -For questions about project governance, open an issue in this repository. - -== See also - -* link:GOVERNANCE.adoc[Governance model] -* link:../.github/CODE_OF_CONDUCT.md[Code of Conduct] -* link:../.github/CONTRIBUTING.md[Contributing guide] -* link:attribution/CODEOWNERS.adoc[Code owners] diff --git a/czech-file-knife/docs/QUICKSTART.adoc b/czech-file-knife/docs/QUICKSTART.adoc deleted file mode 100644 index 975cf0a37..000000000 --- a/czech-file-knife/docs/QUICKSTART.adoc +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Quickstart -:toc: preamble - -Get up and running in 60 seconds. - -== Prerequisites - -* Git 2.40+ -* just (command runner) -* Your language toolchain (see Justfile for details) - -== From Template (New Project) - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife my-project -cd my-project -rm -rf .git && git init -b main -just repo-init # interactive placeholder replacement ----- - -== Project Structure - -See README.adoc in the root for the Dual-Track architecture summary. diff --git a/czech-file-knife/docs/README.adoc b/czech-file-knife/docs/README.adoc deleted file mode 100644 index dc7274f0e..000000000 --- a/czech-file-knife/docs/README.adoc +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Documentation - -For the full directory map — every directory, what it is, and who reads it — -see link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc]. It is generated -and CI fails if it goes stale, so prefer it over any hand-written listing. - -== Start here - -* link:onboarding/QUICKSTART-USER.adoc[QUICKSTART-USER] — using a repo built from this template. -* link:onboarding/QUICKSTART-DEV.adoc[QUICKSTART-DEV] — developing in one. -* link:onboarding/QUICKSTART-MAINTAINER.adoc[QUICKSTART-MAINTAINER] — maintaining one. -* link:AI_INSTALLATION_GUIDE.adoc[AI_INSTALLATION_GUIDE] — for an AI agent instantiating the template. -* link:EXPLAINME.adoc[EXPLAINME] — how the pieces actually work. - -== The tracks - -[cols="1,3",options="header"] -|=== -| Directory | Holds - -| `architecture/` | Topology, threat model, and the generated repository map. -| `attribution/` | CODEOWNERS rationale and credit. -| `decisions/` | ADRs. `0000-template.adoc` is the template; number upwards from there. -| `developer/` | Developer-facing deep dives (ABI/FFI, tooling integrations). -| `governance/` | Governance model, maintenance checklist, development approach, audits, planning. -| `legal/` | Licence exhibits. -| `onboarding/` | Quickstarts and the LLM warm-up documents. -| `practice/` | Operational and implementation material, incl. AI conventions. -| `proposals/` | Proposals and RFCs, including the root-cleanup proposal this layout came from. -| `reports/` | Generated and periodic reports (audit, compliance, security, ...). -| `standards/` | Pointers to the estate canon in the `standards` repo. -| `status/` | READINESS, ROADMAP, TEST-NEEDS, PROOF-NEEDS, PROOF-STATUS. -| `theory/` | Formal and conceptual material. -| `whitepapers/` | Academic, industry and outreach whitepapers. -| `wikis/` | Long-form wiki material. -|=== - -== Core documents - -* link:governance/MAINTENANCE-CHECKLIST.adoc[governance/MAINTENANCE-CHECKLIST.adoc] -* link:governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc[governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc] -* link:MAINTAINERS.adoc[MAINTAINERS.adoc] — the single maintainer roster. -* link:GOVERNANCE.adoc[GOVERNANCE.adoc] — the governance model. - -[NOTE] -==== -`.md` files are not permitted under `docs/`; AsciiDoc is the estate standard and -`scripts/check-no-md-in-docs.sh` enforces it. The previous version of this file -pointed at `maintenance/MAINTENANCE-CHECKLIST.md` — a directory that does not -exist, holding a file with an extension this tree forbids. -==== diff --git a/czech-file-knife/docs/RSR-PHILOSOPHY.adoc b/czech-file-knife/docs/RSR-PHILOSOPHY.adoc deleted file mode 100644 index ef3b6cabe..000000000 --- a/czech-file-knife/docs/RSR-PHILOSOPHY.adoc +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= RSR Philosophy — How Work Is Done Here -:toc: -:icons: font - -[.lead] -The RSR standard is not only a set of files to scaffold; it is a way of working. -This document states the operating principles an agent or maintainer is expected -to hold while doing work in any hyperpolymath repository. They are deliberately -few, deliberately blunt, and meant to be applied — not admired. - -These principles are the human-readable home of the *Doctrine* that the estate -arrival-pack projects into every repository's `CLAUDE.md`. The Doctrine list is -the terse machine-facing summary; this file is the reasoning behind it. Where the -two differ, the canon (`hyperpolymath/standards`) and the owner's `manifesto` -prevail. - -== The load-bearing four - -These four are named together because they describe the *order*, *manner*, *locus* -and *standard* of the work undertaken, and because each is a standing trap that -fluent, plausible work falls into. - -=== Holes before goals - -Fix soundness holes before you build features, optimise, or polish documentation. -A hole is anywhere the system can be wrong without saying so: an unproven seam, an -unchecked input, a `TODO` that load-bearing code depends on, a claim no tool -establishes. Goals are everything you would rather be doing. The discipline is to -let the holes set the agenda, not the goals — because a goal reached on top of a -hole is not reached. - -=== Always fail loudly - -No silent green. A check that cannot fail is not a check; a fallback that hides a -broken precondition is a forged result. When something is wrong, the system must -say so — visibly, early, and in a way that stops the line — rather than degrade -quietly into a plausible-looking success. Seams (ABI / FFI boundaries) are sealed -and proven, not assumed. Prefer a build that breaks to a build that lies. - -=== Solutions at source - -Fix the canonical, upstream origin of a problem — never patch the downstream -symptom. When a defect, a drift, or a wrong setting appears in many places, it is -almost never many problems; it is one problem at a source, expressed many times. -Remediating the copies without fixing the source guarantees the problem returns. - -Two obligations follow from this, and they are not optional: - -* *Find the source.* Before acting, trace the thing back to where it is actually - defined — the template, the generator, the canon, the single point that - everything else inherits from. The estate's structure is - `standards → czech-file-knife → (every repo)`; a fix that belongs at the - template does not belong in 380 leaves. -* *Be mindful of every up- and down-stream.* A change at a source propagates. - Before you make it, know what feeds into the thing you are changing (upstream) - and what depends on it (downstream), and make sure the change is safe across - all of them. A correct fix that breaks a downstream consumer is not yet a fix. - -When the source genuinely cannot be reached in this pass — an upstream you do not -own, a fix gated on owner ratification — remediate the downstream *and* record the -source fix as the real work still owed. Patching the symptom silently, as if it -were the cure, is itself a hole (see _always fail loudly_). - -=== Elegance by default - -Treat the most elegant and correct long-term solution as the default choice — and -say which option that is, every time you put a choice to the owner. This is not a -preference for tidiness; it is a refusal to let the judgment be made silently. - -A set of options offered as merely _different_ is not neutral. Whichever one is -listed first, or described most fluently, becomes the recommendation whether you -intended it or not — and the option that reads most fluently is usually the one you -found quickest to write. So an unlabelled list quietly substitutes your convenience -for the standard this estate is held to, which is the same error as patching a -symptom in place of a source: a choice backed by authority rather than justified by -the construction that produced it. - -Three obligations follow, and they are not optional: - -* *Label it.* Exactly one option is marked as the most elegant and correct in the - long run. Judge that on long-run grounds alone — correctness, no deferred - breakage, no special cases, a fix at the generator rather than at the instance — - and never on effort, speed, or convenience. If two options genuinely tie, say so; - silence is not a tie. -* *Justify any departure.* If your recommendation is not the elegant arm, name both - arms and state, in the offer itself, why you are departing on this occasion — an - irreversible step already taken, a live outage, a precondition still gated. An - unexplained departure is a defect in the question, not a matter of style. Never - merge the two labels to avoid having to write the explanation. -* *It binds unasked decisions too.* This is a methodology, not a formatting rule - for questions. Where you take the non-elegant arm without asking, report it - rather than absorb it. - -The default is a *starting point, not a prediction*. The owner may take the other -arm with full information, and often will; what may not happen is an expedient -choice made in ignorance that it was the expedient one. - -== The full Doctrine - -The four above are the principles most often abused, but they sit inside the -estate's full operating Doctrine. The canonical, always-current list is projected -into the top of every repository's `CLAUDE.md` from -`.machine_readable/arrival-pack/`. In summary it also holds: ground-truth by -running the tool, not trusting status docs; distrust the neural for exactness -(licences / invariants / equivalence belong to PLASMA, not an LLM); squabble, -don't bypass (reach green by satisfying the gate, never by admin-override); no -automated licence edits; no deletion by access-recency; wire first; always sign; -report faithfully (no overclaim); stop-first on costly or outward-facing actions; -boundaries are real; and equivalence as identity. - -== Status - -* *Licence:* CC-BY-SA-4.0 (documentation). -* *Canon:* `hyperpolymath/standards` is the source of truth for these principles; - `hyperpolymath/manifesto` states the doctrine in the owner's voice. This file - operationalises them for the RSR template and its descendants. diff --git a/czech-file-knife/docs/RSR_OUTLINE.adoc b/czech-file-knife/docs/RSR_OUTLINE.adoc deleted file mode 100644 index 9bbe401e5..000000000 --- a/czech-file-knife/docs/RSR_OUTLINE.adoc +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= RSR Template Repository - -image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] -:toc: -:sectnums: - -// Badges -image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] -image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] -image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] - -[IMPORTANT] -==== -*Superseded as an overview.* The root `README.adoc` is the entry point, and the -authoritative directory map is generated at -link:architecture/REPOSITORY-MAP.adoc[architecture/REPOSITORY-MAP.adoc]. - -This document is kept for its longer-form rationale, not as a second README. -Its hand-written directory tree was removed in 2026-08 after being found stale -in six places at once (a2ml files listed outside `descriptiles/`, `Trustfile.hs` -for `Trustfile.a2ml`, `docs/CITATIONS.adoc` and `docs/TOPOLOGY-GUIDE.adoc` which -do not exist, and `src/interface/Abi/` which was renamed to lowercase). -==== - -== Overview - -**The canonical template for RSR (Rhodium Standard Repository) projects.** - -This repository provides the standardized structure, configuration, and tooling for all RSR-compliant repos. Use it to: - -* Bootstrap new projects with RSR compliance -* Reference the standard directory structure -* Copy configuration templates (Justfile, STATE.a2ml, etc.) - -== Quick Start - -[source,bash] ----- -# Clone the template -git clone https://github.com/hyperpolymath/RSR-template-repo my-project -cd my-project - -# Remove template git history -rm -rf .git -git init - -# Interactive bootstrap — replaces all placeholders -just repo-init - -# Enter development environment -guix shell -D -f build/guix.scm - -# Validate compliance -just validate-rsr ----- - -== What's Included - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|`.editorconfig` -|Editor configuration (indent, charset) - -|`.gitignore` -|Standard ignore patterns - -|`.gitattributes` -|Line endings, diff drivers, binary detection - -|`.guix-channel` -|Guix channel definition - -|`www/` -|Site-operations bundle; RFC-compliant metadata at `www/.well-known/` (security.txt, ai.txt, humans.txt) - -|`.machine_readable/` -|All machine-readable content: state files (6 a2ml), `bot_directives/`, `contractiles/` - -|`docs/` -|Documentation directory - -|`build/guix.scm` -|Guix package definition (canon 1.2.1 guix-primary names `build/`) - -|`Justfile` -|Task runner with 40+ recipes - -|`Containerfile` -|Container build (Wolfi base, Podman) - -|`LICENSE` -|MPL-2.0 (code) / CC-BY-SA-4.0 (docs) - -|`EXHIBIT-A-ETHICAL-USE.txt` -|MPL-2.0 source-code-form license notice (LICENSE Exhibit A) - -|`EXHIBIT-B-QUANTUM-SAFE.txt` -|Quantum-safe provenance spec (LICENSE Exhibit B) - -|`README.adoc` -|Project overview - -|`TOPOLOGY.md` -|Architecture diagram and completion dashboard - -|`PLACEHOLDERS.md` -|Template variable reference and replacement guide - -|`*_chora.deed` (repo deed) -|Universal AI agent entry point - -|`AI.a2ml` -|Claude-specific instructions - -|`src/interface/Abi/` -|Idris2 ABI definitions (Types, Layout, Foreign) - -|`ffi/zig/` -|Zig FFI implementation - -|`generated/abi/` -|Auto-generated C headers from Idris2 ABI -|=== - -== Justfile Features - -The template Justfile provides: - -* **Combinatoric matrix recipes** for build, test, container, CI -* **Cookbook generation**: `just cookbook` -> `docs/just-cookbook.adoc` -* **Man page generation**: `just man` -> `docs/man/project.1` -* **RSR validation**: `just validate-rsr` -* **STATE.a2ml management**: `just state-touch`, `just state-phase` -* **Container support**: `just container-build`, `just container-push` -* **CI matrix**: `just ci-matrix [stage] [depth]` - -=== Key Recipes - -[source,bash] ----- -just # Show all recipes -just help # Detailed help -just info # Project info -just combinations # Show matrix options - -just build # Build (debug) -just test # Run tests -just quality # Format + lint + test -just ci # Full CI pipeline - -just validate # RSR + STATE validation -just docs # Generate all docs -just cookbook # Generate Justfile docs - -just guix-shell # Guix dev environment -just container-build # Build container ----- - -== Directory Structure - -[source] -See link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc] for the -directory map. It is generated from the tree and diffed in CI, so unlike the -hand-written tree that used to sit here it cannot go stale. - - -== RSR Compliance - -=== Language Tiers - -* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, , Gleam -* **Tier 2** (Silver): Nickel, Guile Scheme, Idris2, OCaml -* **Infrastructure**: Guix channels, derivations, Julia batch scripts - -=== Required Files - -* `.editorconfig` -* `.gitignore` -* `Justfile` -* `README.adoc` -* `LICENSE` (MPL-2.0) -* `.machine_readable/descriptiles/STATE.a2ml` -* `www/.well-known/security.txt` -* `www/.well-known/ai.txt` -* `www/.well-known/humans.txt` -* `build/guix.scm` - -=== Prohibited - -* Python outside `salt/` directory -* /JavaScript (use ) -* CUE (use Guile/Nickel) -* `Dockerfile` (use `Containerfile`) -* npm, Bun, pnpm, yarn (use ) -* Go (use Rust) - -== STATE.a2ml - -The STATE.a2ml file tracks project state: - -[source] ----- -# STATE — Project State Checkpoint -# Format: a2ml (AI-readable markup) - -project: v-graphql -version: 0.1.0 -last-updated: 2026-02-14 -status: active - -phase: implementation -maturity: beta - -ecosystem: - part-of: RSR Framework - depends-on: [] - -milestones: - - name: Initial setup - completion: 100 - - name: Core implementation - completion: 0 ----- - -== Badge Schema - -Generate badges from STATE.a2ml: - -[source,bash] ----- -just badges standard ----- - -See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. - -== Ecosystem Integration - -This template is part of: - -* **STATE.a2ml Ecosystem**: Conversation checkpoints -* **RSR Framework**: Repository standards -* **Consent-Aware-HTTP**: .well-known compliance -* **Hypatia**: Neurosymbolic security scanning -* **gitbot-fleet**: Bot orchestration - -== License - -SPDX-License-Identifier: CC-BY-SA-4.0 - -== Links - -* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE tooling -* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement -* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/czech-file-knife/docs/STATE-VISUALIZER.adoc b/czech-file-knife/docs/STATE-VISUALIZER.adoc deleted file mode 100644 index 2e23ca3ec..000000000 --- a/czech-file-knife/docs/STATE-VISUALIZER.adoc +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Project State Visualizer - -[source] ----- - - - - -# RSR Template Repo — Project Topology - -## System Architecture - -``` - ┌─────────────────────────────────────────┐ - │ NEW REPOSITORY │ - │ (Consumer of this Template) │ - └───────────────────┬─────────────────────┘ - │ Scaffolding - ▼ - ┌─────────────────────────────────────────┐ - │ RSR TEMPLATE HUB │ - │ │ - │ ┌───────────┐ ┌───────────────────┐ │ - │ │ AI Gate- │ │ ABI / FFI │ │ - │ │ keeper │ │ Standard │ │ - │ │ (0-AI-M) │ │ (Idris2/Zig) │ │ - │ └─────┬─────┘ └────────┬──────────┘ │ - │ │ │ │ - │ ┌─────▼─────┐ ┌────────▼──────────┐ │ - │ │ Topology │ │ SCM / 6SCM │ │ - │ │ Guide │ │ Metadata │ │ - │ │ (Visual) │ │ (machine_read) │ │ - │ └─────┬─────┘ └────────┬──────────┘ │ - │ │ │ │ - │ ┌─────▼─────────────────▼──────────┐ │ - │ │ CONTAINER ECOSYSTEM │ │ - │ │ ┌──────────┐ ┌───────────────┐ │ │ - │ │ │ Podman / │ │ selur-compose │ │ │ - │ │ │ OCI │ │ cerro-torre │ │ │ - │ │ │ Build │ │ svalinn/vordr │ │ │ - │ │ └──────────┘ └───────────────┘ │ │ - │ │ ct-build.sh deploy.k9.ncl │ │ - │ └──────────────────────────────────┘ │ - └────────│─────────────────│──────────────┘ - │ │ - ▼ ▼ - ┌─────────────────────────────────────────┐ - │ PLATFORM INTEGRATION │ - │ ┌───────────┐ ┌───────────┐ ┌───────┐│ - │ │ GitHub │ │ GitLab │ │ Nix / ││ - │ │ Workflows │ │ CI/CD │ │ Guix ││ - │ └───────────┘ └───────────┘ └───────┘│ - └─────────────────────────────────────────┘ - - ┌─────────────────────────────────────────┐ - │ REPO INFRASTRUCTURE │ - │ Justfile / Mustfile .machine_readable/ │ - │ Codeowners / Reuse *_chora.deed (root) │ - └─────────────────────────────────────────┘ -``` - -## Completion Dashboard - -``` -COMPONENT STATUS NOTES -───────────────────────────────── ────────────────── ───────────────────────────────── -CORE STANDARDS - ABI/FFI Standard (Idris2/Zig) ██████████ 100% Universal interface stable - AI Gatekeeper (repo deed) ██████████ 100% Universal entry point active - TOPOLOGY.md Standard ██████████ 100% Visual summary guide active - 6SCM Metadata Structure ██████████ 100% Machine-readable state stable - -INFRASTRUCTURE - Justfile Automation ██████████ 100% Standard build/verify tasks - CI/CD Workflow Templates ██████████ 100% GH/GL scaffolding verified - Multi-Forge Sync ██████████ 100% Hub-and-spoke mirroring stable - -CONTAINER ECOSYSTEM (Phase 2) - Containerfile (OCI build) ██████████ 100% Multi-stage Chainguard base - selur-compose orchestration ██████████ 100% Template + concrete example - cerro-torre manifest ██████████ 100% Bundle metadata & signing - svalinn gateway policy ██████████ 100% .gatekeeper.yaml active - vordr runtime monitoring ██████████ 100% Runtime config template - k9-svc deployment (Nickel) ██████████ 100% Hunt-level deploy descriptor - ct-build.sh pipeline ██████████ 100% Build/sign/verify script - Justfile container-* recipes ██████████ 100% 8 recipes integrated - Trustfile CONTAINER_SUPPLY_CHAIN ██████████ 100% Supply chain section added - -REPO INFRASTRUCTURE - .machine_readable/ ██████████ 100% STATE/META/ECOSYSTEM active - Governance & License ██████████ 100% MPL-2.0 & Ethical use verified - Development Shells (Guix) ██████████ 100% Reproducible env stable - -───────────────────────────────────────────────────────────────────────────── -OVERALL: ██████████ 100% RSR Template Stable & Certified -``` - -## Key Dependencies - -``` -Philosophy ──────► RSR Standard ──────► Template Scaffolding ──► New Repo - │ │ │ │ - ▼ ▼ ▼ ▼ -CCCP Policy ─────► repo deed ────────────► Justfile ──────────► Compliance - │ - ▼ - Container Ecosystem - ┌──────────┼──────────┐ - ▼ ▼ ▼ - selur-compose cerro- svalinn/ - (orchestrate) torre vordr - (sign) (monitor) - │ - ▼ - k9-svc deploy -``` - -## Update Protocol - -This file is maintained by both humans and AI agents. When updating: - -1. **After completing a component**: Change its bar and percentage -2. **After adding a component**: Add a new row in the appropriate section -3. **After architectural changes**: Update the ASCII diagram -4. **Date**: Update the `Last updated` comment at the top of this file - -Progress bars use: `█` (filled) and `░` (empty), 10 characters wide. -Percentages: 0%, 10%, 20%, ... 100% (in 10% increments). ----- diff --git a/czech-file-knife/docs/architecture.adoc b/czech-file-knife/docs/architecture.adoc deleted file mode 100644 index a61ac86a4..000000000 --- a/czech-file-knife/docs/architecture.adoc +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= Architecture — Czech File Knife -:revdate: 2026-MM-DD - -== System overview - -One paragraph: what this project does and what it does not do. State the -*invariant* — the property that, if violated, would make the whole project -pointless. Future maintainers will read this paragraph first. - -== Component diagram - -Replace this section with an ASCII or Mermaid diagram. Keep it under 20 -lines — anything bigger belongs in `architecture/`. - -[source] ----- -+------------------+ +------------------+ -| Component A | ---> | Component B | -+------------------+ +------------------+ - | - v -+------------------+ -| Component C | -+------------------+ ----- - -== Data flow - -For each external input, describe: - -* **Source**: where it comes from. -* **Validation**: what guarantees we enforce on entry. -* **Transformation**: high-level processing stages. -* **Sink**: where the result goes. - -== Key invariants - -Enumerate the load-bearing invariants of the system. Each should have: - -. A one-line statement. -. The code location(s) that enforce it. -. The failure mode if the invariant is violated. - -Example: - -[cols="1,2,2,2", options="header"] -|=== -| # | Invariant | Enforced at | Failure mode - -| 1 -| All HTTP requests carry a valid `X-Request-ID`. -| `src/request_id.rs` -| Logs become unjoinable; correlation breaks. - -| 2 -| The output buffer is always flushed before exit. -| `src/main.rs:88-92` (Drop impl) -| Last ~16KB of log lost on crash. -|=== - -== Dependencies - -* **Internal**: list other hyperpolymath repos this depends on. -* **External**: SHA-pinned (see `Cargo.lock` / `.lock` / etc.). -* **Build-time**: tools required to build (just, , cargo, …). - -== Out of scope - -Explicit non-goals. Things we deliberately do *not* do, with a one-line -reason for each. This section saves more time than the rest combined. - -== See also - -* link:./usage.adoc[Usage] — consumer perspective. -* link:./contributing.adoc[Contributing] — developer setup. -* link:./decisions/[ADRs] — historical record of why this shape. diff --git a/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc b/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc deleted file mode 100644 index 67ae12a1b..000000000 --- a/czech-file-knife/docs/architecture/DISTRIBUTED_FILESYSTEMS.adoc +++ /dev/null @@ -1,380 +0,0 @@ -== Distributed Filesystems Support - -Czech File Knife (CFK) provides unified access to various distributed -and network filesystems through its provider abstraction layer. - -=== Supported Filesystems - -==== Network File Systems - -===== NFS (Network File System) - -* *Module*: `+cfk-providers/src/nfs.rs+` -* *Feature*: `+nfs+` -* *Versions*: NFSv3, NFSv4, NFSv4.1 -* *Status*: Stub (uses system mount) - -[source,rust] ----- -use cfk_providers::nfs::{NfsBackend, NfsConfig, NfsVersion}; - -let config = NfsConfig { - server: "nas.local".into(), - export_path: "/exports/data".into(), - version: NfsVersion::V4, - ..Default::default() -}; - -let backend = NfsBackend::new("my-nfs", config); ----- - -===== SMB/CIFS (Server Message Block) - -* *Module*: `+cfk-providers/src/smb.rs+` -* *Feature*: `+smb+` -* *Versions*: SMB2, SMB3, SMB3.1.1 -* *Status*: Stub (uses system mount or libsmbclient) - -[source,rust] ----- -use cfk_providers::smb::{SmbBackend, SmbConfig, SmbVersion}; - -let config = SmbConfig { - server: "fileserver".into(), - share: "Documents".into(), - username: Some("user".into()), - password: std::env::var("CFK_SMB_PASSWORD").ok(), - version: SmbVersion::Smb3, - ..Default::default() -}; - -let backend = SmbBackend::new("my-smb", config); ----- - -===== SFTP (SSH File Transfer Protocol) - -* *Module*: `+cfk-providers/src/sftp.rs+` -* *Feature*: `+sftp+` -* *Status*: Stub (requires ssh2 or russh crate) - -[source,rust] ----- -use cfk_providers::sftp::{SftpBackend, SftpConfig, SftpAuth}; - -let config = SftpConfig { - host: "server.example.com".into(), - port: 22, - auth: SftpAuth::Agent { username: "user".into() }, - ..Default::default() -}; - -let backend = SftpBackend::new("my-sftp", config); ----- - -==== Plan 9 Protocol - -===== 9P (Plan 9 File Protocol) - -* *Module*: `+cfk-providers/src/ninep.rs+` -* *Feature*: `+ninep+` -* *Versions*: 9P2000, 9P2000.L, 9P2000.u -* *Use Cases*: WSL2 file sharing, QEMU virtio-9p, Plan 9 systems - -[source,rust] ----- -use cfk_providers::ninep::{NinePBackend, NinePConfig, NinePVersion}; - -// WSL2 connection -let backend = NinePBackend::wsl2("my-9p", "/mnt/c"); - -// QEMU virtio-9p -let config = NinePConfig { - transport: "virtio".into(), - aname: "shared".into(), - version: NinePVersion::L, - ..Default::default() -}; -let backend = NinePBackend::new("qemu-9p", config); ----- - -==== Distributed Storage Systems - -===== Ceph - -* *Module*: `+cfk-providers/src/ceph.rs+` -* *Feature*: `+ceph+` -* *Interfaces*: RADOS, CephFS, RGW (S3-compatible) - -[source,rust] ----- -use cfk_providers::ceph::{CephBackend, CephConfig, CephMode}; - -// CephFS (POSIX-like) -let config = CephConfig { - monitors: vec!["mon1:6789".into(), "mon2:6789".into()], - mode: CephMode::Fs { - fs_name: "cephfs".into(), - mount_point: "/mnt/ceph".into(), - }, - user: "admin".into(), - keyring_path: Some("/etc/ceph/ceph.client.admin.keyring".into()), - ..Default::default() -}; - -let backend = CephBackend::new("my-ceph", config); - -// RGW (S3-compatible) -let rgw_backend = CephBackend::rgw( - "my-rgw", - "https://rgw.example.com", - &std::env::var("CFK_S3_ACCESS_KEY_ID")?, - &std::env::var("CFK_S3_SECRET_ACCESS_KEY")?, - "my-bucket" -); ----- - -===== IPFS (InterPlanetary File System) - -* *Module*: `+cfk-providers/src/ipfs.rs+` -* *Feature*: `+ipfs+` -* *Features*: Content-addressing, MFS, Pinning, IPNS - -[source,rust] ----- -use cfk_providers::ipfs::{IpfsBackend, IpfsConfig}; - -let config = IpfsConfig { - api_url: "http://localhost:5001".into(), - gateway_url: Some("http://localhost:8080".into()), - use_mfs: true, // Mutable File System - mfs_root: "/cfk".into(), - ..Default::default() -}; - -let backend = IpfsBackend::new("my-ipfs", config); ----- - -===== AFS (Andrew File System) - -* *Module*: `+cfk-providers/src/afs.rs+` -* *Feature*: `+afs+` -* *Features*: Kerberos auth, ACLs, distributed cells - -[source,rust] ----- -use cfk_providers::afs::{AfsBackend, AfsConfig}; - -let config = AfsConfig { - cell: "example.edu".into(), - realm: Some("EXAMPLE.EDU".into()), - cache_dir: Some("/var/cache/openafs".into()), - afs_mount: "/afs".into(), - ..Default::default() -}; - -let backend = AfsBackend::new("my-afs", config); ----- - -==== Cloud Storage - -===== S3-Compatible - -* *Module*: `+cfk-providers/src/s3.rs+` -* *Feature*: `+s3+` -* *Providers*: AWS, MinIO, Cloudflare R2, Backblaze B2, DigitalOcean -Spaces, Wasabi - -[source,rust] ----- -use cfk_providers::s3::{S3Backend, S3Config}; - -// AWS S3 -let backend = S3Backend::aws( - "my-s3", - "my-bucket", - "us-west-2", - "AKIAIOSFODNN7EXAMPLE", - "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" -); - -// MinIO -let backend = S3Backend::minio( - "my-minio", - "http://localhost:9000", - "my-bucket", - "minioadmin", - "minioadmin" -); - -// Cloudflare R2 -let backend = S3Backend::cloudflare_r2( - "my-r2", - "account-id", - "my-bucket", - &std::env::var("CFK_S3_ACCESS_KEY_ID")?, - &std::env::var("CFK_S3_SECRET_ACCESS_KEY")? -); ----- - -===== WebDAV - -* *Module*: `+cfk-providers/src/webdav.rs+` -* *Feature*: `+webdav+` -* *Servers*: NextCloud, ownCloud, Apache mod_dav, nginx - -[source,rust] ----- -use cfk_providers::webdav::{WebDavBackend, WebDavConfig, WebDavAuth}; - -// Generic WebDAV -let config = WebDavConfig { - base_url: "https://dav.example.com/files/".into(), - auth: Some(WebDavAuth::Basic { - username: "user".into(), - password: std::env::var("CFK_WEBDAV_PASSWORD")?, - }), - ..Default::default() -}; - -let backend = WebDavBackend::new("my-webdav", config); - -// NextCloud -let backend = WebDavBackend::nextcloud( - "my-nextcloud", - "https://cloud.example.com", - "username", - &std::env::var("CFK_NEXTCLOUD_APP_PASSWORD")? -); ----- - -=== Feature Comparison - -[cols=",,,,,,,,,",options="header",] -|=== -|Feature |NFS |SMB |SFTP |9P |Ceph |IPFS |AFS |S3 |WebDAV -|Read |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ -|Write |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ -|Delete |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ -|Rename |✓ |✓ |✓ |✓ |✓ |✗ |✓ |✗¹ |✓ -|Copy |✓ |✓ |✗ |✓ |✓ |✗ |✓ |✓² |✓ -|List |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ -|Streaming |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ |✓ -|Resume |✗ |✗ |✓ |✗ |✗ |✓ |✗ |✓ |✓ -|Versioning |✗ |✓³ |✗ |✗ |✗ |✓⁴ |✗ |✓ |✓ -|Watch |✗ |✓ |✗ |✗ |✗ |✗ |✗ |✗ |✗ -|ACLs |✓ |✓ |✓ |✗ |✓ |✗ |✓ |✓ |✗ -|=== - -¹ S3 rename is copy+delete ² S3 copy is server-side for same bucket ³ -SMB Previous Versions ⁴ IPFS content is immutable; IPNS provides -mutability - -=== Performance Considerations - -==== Latency - -[cols=",,",options="header",] -|=== -|Protocol |Typical Latency |Best For -|NFS v4 |1-10ms (LAN) |Enterprise storage -|SMB 3 |1-10ms (LAN) |Windows environments -|SFTP |10-100ms |Secure remote access -|9P |<1ms (virtio) |VM/container sharing -|Ceph |1-5ms |Scalable storage -|IPFS |Variable |Content distribution -|S3 |50-200ms |Object storage -|WebDAV |50-500ms |Web-based access -|=== - -==== Caching Strategy - -CFK automatically uses the caching layer (`+cfk-cache+`) to optimize -performance: - -[arabic] -. *Metadata Cache*: TTL-based caching of file/directory metadata -. *Content Cache*: Content-addressed blob storage with LZ4 compression -. *Eviction Policies*: LRU, LFU, FIFO, or adaptive policies - -[source,rust] ----- -use cfk_cache::{CachePolicy, PolicyConfig, EvictionPolicy}; - -let policy = CachePolicy::new(PolicyConfig { - max_size: 10 * 1024 * 1024 * 1024, // 10 GB - max_entries: 100_000, - eviction_policy: EvictionPolicy::Adaptive, - ..Default::default() -}); ----- - -=== Security - -==== Authentication Methods - -[cols=",",options="header",] -|=== -|Protocol |Methods -|NFS |Kerberos (sec=krb5), AUTH_SYS -|SMB |NTLM, Kerberos, Guest -|SFTP |Password, Public Key, Agent -|9P |None (transport security), Custom -|Ceph |Cephx, None -|IPFS |None (public), Key-based -|AFS |Kerberos -|S3 |AWS Sig V4, IAM -|WebDAV |Basic, Digest, OAuth -|=== - -==== Encryption - -[cols=",,",options="header",] -|=== -|Protocol |In-Transit |At-Rest -|NFS v4 |Optional (krb5p) |No -|SMB 3 |Yes (AES-128-GCM) |No -|SFTP |Yes (SSH) |No -|9P |No (use TLS wrapper) |No -|Ceph |Optional |Optional -|IPFS |Optional |No -|AFS |Yes |No -|S3 |Yes (HTTPS) |Optional (SSE) -|WebDAV |Yes (HTTPS) |No -|=== - -=== Enabling Features - -Add the desired features to your `+Cargo.toml+`: - -[source,toml] ----- -[dependencies] -cfk-providers = { path = "../cfk-providers", features = [ - "nfs", - "smb", - "sftp", - "ninep", - "ceph", - "ipfs", - "afs", - "s3", - "webdav", -] } ----- - -=== Architecture - -.... -┌─────────────────────────────────────────────────────────────────────┐ -│ Application Layer │ -│ (cfk-cli, cfk-vfs, cfk-tui) │ -├─────────────────────────────────────────────────────────────────────┤ -│ StorageBackend Trait │ -│ (unified interface for all filesystem operations) │ -├───────┬───────┬───────┬───────┬───────┬───────┬───────┬────────────┤ -│ NFS │ SMB │ SFTP │ 9P │ Ceph │ IPFS │ AFS │ S3/WebDAV │ -├───────┴───────┴───────┴───────┴───────┴───────┴───────┴────────────┤ -│ Cache Layer (cfk-cache) │ -│ (metadata cache, blob store, eviction) │ -└─────────────────────────────────────────────────────────────────────┘ -.... diff --git a/czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc b/czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc deleted file mode 100644 index 33aaaf3d0..000000000 --- a/czech-file-knife/docs/architecture/HYBRID-OPERATIONS.adoc +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -= Hybrid-machine operations: design notes -:toc: - -CFK's goal is to be the "Swiss File Knife" of the hybrid machine: one tool -that works on local disks and cloud storage alike and picks the cheapest -correct way to do each job. This page covers two example jobs and how they -rely on the reversible journal (`cfk-core::reversible`). - -== 1. Reversible journal (implemented) - -`ReversibleBackend` wraps any backend. Before each write, delete, move -or copy it saves the prior content in a SHA-256 content store and appends -the inverse operation to an append-only `oplog.jsonl` (the JanusKey model). - -* `cfk history` shows the log, `cfk undo [id]` rolls back the latest operation. -* State lives in `$CFK_JOURNAL_DIR`, or `$XDG_STATE_HOME/cfk/journal`, or `~/.local/state/cfk/journal`. -* The capture limit defaults to 1 GiB (`CFK_JOURNAL_MAX_BYTES`). Bigger - operations are *refused*, not silently made irreversible. - `CFK_NO_JOURNAL=1` opts out. -* Captured priors also back `get_versions`, so every backend gets version - history. - -== 2. Cloud-side operations without a local round trip - -Rule: *never route bytes through the local machine when the provider can -do the job itself.* When the provider can't, stream the bytes through -memory and never stage them on disk. - -[cols="1,2"] -|=== -| Situation | Strategy - -| Same provider (Drive→Drive, S3→S3 in the same region) -| Server-side calls: Drive `files.copy` / `files.update(addParents)`, S3 - `CopyObject` / `UploadPartCopy`, Dropbox `copy_v2`, OneDrive `copy`. - No bytes cross the local link. - -| Converting formats inside a provider -| Drive `files.export` / `files.copy` with a target mimeType (e.g. Doc→PDF) - keeps the work on Google's side. - -| Different providers (Drive→S3) -| Pipe a streamed download into a resumable or multipart upload through a - bounded memory buffer. Local disk use is zero. Bandwidth still flows - through this machine unless a relay (such as a cloud VM running `cfk`) - is configured. -|=== - -Proposed trait extension: `async fn server_side_copy(&self, src, dest) -> -CfkResult>`, where `None` means "can't do it, fall back to -streaming". The planner tries it first. - -== 3. Compressing a file in place when disk space is short - -Example: a 25 GB file, 8 GB free, 4 GB target output, with no extra local -or cloud storage allowed. - -The naive approach (write the compressed file, then delete the original) -needs roughly 25 GB + 4 GB. You can't delete first either, so a normal -compressor can never produce the output. - -=== Algorithm: forward compress plus hole punching - -. Read chunk *i* (for example 256 MiB) of the input. -. Compress it into its own zstd frame and append the frame to `out.zst`. - Concatenated zstd frames form a valid zstd stream, so the result is a - normal `.zst` file. -. `fsync` the output, then record `{chunk: i, in_off, out_off}` in the - journal and `fsync` the journal. -. Only then call `fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)` on - the input range just consumed. The filesystem frees those blocks. -. Repeat. At the end, unlink the (now sparse, empty) input. - -Peak extra space is about one compressed chunk plus the journal, not the -full output. Each chunk frees about 256 MiB and consumes about 40 MiB, so -free space *grows* as the job runs. - -=== Crash safety - -A range is punched only after the frame that replaces it is durable and -journaled. On restart, the job truncates `out.zst` to the last journaled -`out_off` and resumes from `in_off`. No data exists in zero copies at any -point. - -The job is also reversible: decompressing frames `0..k` and writing them -back into the punched ranges restores the original, using the same -hole-filling logic in reverse. - -=== Portability and fallbacks - -* Hole punching works on Linux ext4, XFS, btrfs and tmpfs, and on macOS APFS - via `fcntl(F_PUNCHHOLE)`. Windows NTFS supports it with - `FSCTL_SET_ZERO_DATA` on sparse files. -* Without hole punching, process the file *from the end*: compress the last - chunk into a frame, then `ftruncate` the input to free it. The frames come - out in reverse order, so the output is a small indexed container (or a - seekable zstd file with a frame table) rather than a plain stream. - Reordering it in place is a later step. -* A CoW filesystem with active snapshots won't free blocks. Detect this with - a `statvfs` check after the first chunk and abort cleanly. -* Proof target: frame-then-punch ordering keeps "every input byte is either - still present or in a durable frame" as an invariant. This is a natural - obligation for the absolute-zero and januskey proofs. - -Proposed CLI: `cfk squeeze [--codec zstd] [--level 19] [--chunk 256M] [--resume]`. diff --git a/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc b/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc deleted file mode 100644 index e40745b0a..000000000 --- a/czech-file-knife/docs/architecture/REPOSITORY-MAP.adoc +++ /dev/null @@ -1,267 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// GENERATED by scripts/gen-repo-map.sh - do not hand-edit. -// Regenerate with `just repo-map`. CI fails if this file is stale. -= Repository map -:toc: - -The single authoritative map of this repository. It is generated from the -tree and from the annotations in `.machine_readable/root-allow.txt`, and CI -fails if it drifts, so it cannot rot the way its five hand-written -predecessors did. - -== Root - -[cols="2,1,4",options="header"] -|=== -| Path | Required | What it is, and who reads it - -| `.cicd-hygiene-allow` -| yes -| code-hygiene gate allowlist; template scaffolds are seams, not debt - -| `.clinerules` -| yes -| AI editor rules. Cline/Cursor/Windsurf read these from the project - -| `.clusterfuzzlite/` -| optional -| ClusterFuzzLite reads its build config from the repo root only - -| `.cursorrules` -| yes -| ROOT, so the copies formerly under .machine_readable/ai/ were inert. - -| `.devcontainer/` -| optional -| VS Code dev container spec; present only where the container capability is declared - -| `.editorconfig` -| yes -| - - -| `.envrc` -| yes -| - - -| `.gitattributes` -| yes -| - - -| `.github/` -| yes -| community health + workflows (GitHub reads this path and no other) + versioned git hooks (.github/hooks/, wired via core.hooksPath) - -| `.gitignore` -| yes -| - - -| `.gitleaksignore` -| yes -| exact fingerprints for reviewed historical false positives - -| `.gitmessage` -| yes -| git commit template; wired by .github/hooks/install.sh (git config commit.template) - -| `.hypatia-ignore` -| yes -| the Hypatia scanner reads it from the repo root - -| `.machine_readable/` -| yes -| manifests, contractiles, policies. Renamed back from machine-readable/ 2026-09-17 by owner ruling, for one canonical spelling estate-wide (census at the 2026-08 divergence: 48 dotted vs 9 hyphenated — the majority was already dotted). See docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc - -| `.mailmap` -| yes -| git reads .mailmap from the worktree root only - -| `.tool-versions` -| yes -| - - -| `.windsurfrules` -| yes -| - - -| `CHANGELOG.adoc` -| yes -| AsciiDoc is the estate-standard documentation format - -| `CITATION.cff` -| yes -| citation metadata; GitHub reads it from the root of the default branch only - -| `CLAUDE.md` -| yes -| generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) - -| `CONTRIBUTING.adoc` -| optional -| estate docs gate (standards scripts/check-docs-presence.sh) requires CONTRIBUTING at the ROOT; this is the copy it verifies - -| `Cargo.lock` -| optional -| rust capability; lives beside Cargo.toml - -| `Cargo.toml` -| optional -| rust capability (template-capability-gates.toml); cargo requires the workspace manifest at root - -| `GEMINI.md` -| optional -| pointer to CLAUDE.md for repos without AGENTS.md yet - -| `Justfile` -| yes -| thin; delegates phases to build/just/*.just - -| `LICENSE` -| yes -| - - -| `LICENSES/` -| yes -| REUSE licence texts, dual-licence model (code MPL-2.0 / docs CC-BY-SA-4.0) - -| `README.adoc` -| yes -| - - -| `benches/` -| optional -| Cargo-conventional at package root - -| `build/` -| yes -| build orchestration: just/ phase modules, container/, docs-seed/, templates/, guix.scm (canon 1.2.1 guix-primary template_ref = build/) - -| `ci/` -| yes -| .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require - -| `coordination.k9.ncl` -| yes -| repo-local session binding (template-mandated) - -| `czech-file-knife_chora.deed` -| yes -| the repo deed: universal AI entry point; family-7 allocation manifest + ply tree folded in (standards#837 pilot). Filename carries the repo slug (deed dispatch _chora.deed), so repo-init renames it at mint - -| `docs/` -| yes -| human documentation - -| `examples/` -| optional -| Cargo-conventional at package root - -| `features/` -| optional -| optional feature packs - -| `mise.toml` -| yes -| pinned toolchains - -| `scripts/` -| yes -| repo helper scripts - -| `session/` -| yes -| dispatch.sh, custom-checks.k9.ncl, local-hooks.sh - -| `sonar-project.properties` -| optional -| only where the repo is analysed by SonarCloud - -| `src/` -| yes -| - - -| `tests/` -| yes -| - - -| `verification/` -| optional -| proofs; only where formal-proofs is declared - -| `www/` -| yes -| site-operations bundle; canonical .well-known/ lives at www/.well-known/ (issue #53) - -|=== - -== Declared structure not yet filled - -These directories currently hold only a stub `README.adoc` and a level -manifest. That is deliberate. They declare the shape a repository minted -from this template is expected to grow into; they are *intended -structure, not abandoned work*. Add content, or delete the directory in -your own repo - but do not read their emptiness as neglect here. - -[cols="1"] -|=== -| `.machine_readable/scripts/verification/` -| `docs/governance/audit/` -| `docs/governance/audit/compliance/` -| `docs/governance/audit/effects/` -| `docs/governance/audit/systems/` -| `docs/governance/maintenance/` -| `docs/governance/maintenance/adaptive/` -| `docs/governance/maintenance/corrective/` -| `docs/governance/maintenance/perfective/` -| `docs/governance/planning/` -| `docs/governance/planning/could/` -| `docs/governance/planning/must/` -| `docs/governance/planning/should/` -| `docs/reports/compliance/` -| `docs/reports/maintenance/` -| `docs/reports/performance/` -| `docs/reports/security/` -| `docs/standards/` -| `docs/theory/` -| `docs/theory/computing/` -| `docs/theory/formalisms/` -| `docs/theory/mathematics/` -| `docs/theory/ontologies/` -| `docs/theory/other/` -| `docs/theory/socio-technical/` -| `docs/whitepapers/` -| `docs/whitepapers/academic/` -| `docs/whitepapers/industry/` -| `docs/whitepapers/outreach/` -| `features/boj-server/` -| `features/panic-attacker/` -| `src/aspects/` -| `src/aspects/integrity/` -| `src/aspects/observability/` -| `src/aspects/security/` -| `src/bridges/` -| `src/contracts/` -| `src/core/` -| `src/definitions/` -| `src/errors/` -| `verification/benchmarks/` -| `verification/coverage/` -| `verification/fuzzing/` -| `verification/safety_case/` -| `verification/simulations/` -| `verification/tests/` -| `verification/traceability/` -|=== - -== Where things are enforced - -* Root shape - `scripts/check-root-shape.sh` against - `.machine_readable/root-allow.txt`, run by `.github/workflows/estate-rules.yml`. - The check is bidirectional: unlisted entries fail, and required entries - that are absent also fail. -* This map - `just repo-map` must produce no diff. -* Community health - GitHub reads `.github/` and no other path. -* Variant divergence - `scripts/check-variant-drift.sh` compares a child - to its parent BY PATH, so any move here invalidates every child's - declared path lists until they are re-anchored. diff --git a/czech-file-knife/docs/architecture/THREAT-MODEL.adoc b/czech-file-knife/docs/architecture/THREAT-MODEL.adoc deleted file mode 100644 index 645147802..000000000 --- a/czech-file-knife/docs/architecture/THREAT-MODEL.adoc +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Threat Model -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -== Threat Model: Czech File Knife - -=== Document Info - -|=== -| Field | Value - -| Project | Czech File Knife -| Version | 1.0 -| Last Reviewed | 2026-09-28 -| Author | Jonathan D.A. Jewell -| Methodology | STRIDE -|=== - - -=== Scope - -==== In Scope - -- Application source code and build pipeline -- CI/CD workflows (GitHub Actions) -- Container images and runtime environment -- Secrets and credential management -- Dependencies (direct and transitive) -- Deployment artifacts (binaries, containers, SBOM) - -==== Out of Scope - -- Physical security of hosting infrastructure -- GitHub/GitLab platform-level vulnerabilities -- End-user device security -- Social engineering attacks against maintainers (handled by org policy) - -=== System Overview - -Brief description of Czech File Knife and its architecture. - -NOTE: See link:../STATE-VISUALIZER.adoc[STATE-VISUALIZER.adoc] for the full architecture diagram and completion dashboard. - -=== Assets - -|=== -| Asset | Classification | Owner | Notes - -| Source code | Internal | Maintainers | Public repos are still internal-integrity -| Signing keys | Restricted | Release lead | Signing keys (e.g., Ed25519), GPG keys -| CI/CD secrets | Restricted | Maintainers | GITHUB_TOKEN, deploy tokens, PATs -| User/contributor data | Confidential | Org | Emails, contributor identity -| Build artifacts | Internal | CI pipeline | Binaries, WASM bundles -| Container images | Internal | CI pipeline | Chainguard-based, signed via image signing tool -| SBOM / provenance | Public | CI pipeline | SLSA attestations -| Dependencies | Public | Lockfile | Cargo.lock, .lock, gleam.toml -| Infrastructure config | Confidential | Maintainers | Containerfiles, compose files, orchestration config -|=== - - -=== Trust Boundaries - -|=== -| Boundary | From (Lower Trust) | To (Higher Trust) - -| Pull request submission | External contributor | Repository codebase -| CI/CD workflow execution | Workflow definition | Runner with secrets access -| Container build boundary | Build stage | Runtime stage -| External API calls | Third-party service | Application internals -| User input (CLI/Web) | End user | Application logic -| Dependency resolution | Package registry | Build environment -| Forge mirroring | GitHub | GitLab / Bitbucket -|=== - - -=== Threat Actors - -|=== -| Actor | Motivation | Capability - -| Script kiddie | Vandalism, clout | Low -| Disgruntled contributor | Sabotage, backdoor insertion | Medium -| Supply chain attacker | Wide-impact compromise | High -| Nation state | Espionage, disruption | Very High -| Automated bot | Credential stuffing, spam PRs | Low-Medium -|=== - - -=== STRIDE Analysis - -==== Spoofing - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| Unsigned commits impersonate maintainer | Source code | Medium | High | High | Require GPG-signed commits; vigilant code review -| Forged bot actions (automated agents) | CI/CD pipeline | Low | High | Medium | Bot tokens scoped minimally; audit bot activity -| Spoofed package registry identity | Dependencies | Low | High | Medium | Pin dependencies by hash; verify provenance -|=== - - -==== Tampering - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| Malicious pull request | Source code | Medium | High | High | Branch protection; required reviews; CodeQL -| Dependency poisoning (typosquat) | Dependencies | Medium | High | High | Lockfiles; secret-scanner; security scans -| Tampered container base image | Container images | Low | High | Medium | Chainguard images; image signing verification -| Workflow file modification | CI/CD pipeline | Low | High | Medium | CODEOWNERS on .github/; workflow-linter -|=== - - -==== Repudiation - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| Unlogged deployment | Build artifacts | Medium | Medium | Medium | SLSA provenance; deployment audit trail -| Denied merge of vulnerable code | Source code | Low | Medium | Low | Git history is immutable; signed commits -| Secret rotation without record | CI/CD secrets | Low | Low | Low | Secret rotation logged in STATE.a2ml -|=== - - -==== Information Disclosure - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| Secrets leaked in git history | CI/CD secrets | Medium | High | High | TruffleHog in CI; secret-scanner workflow -| Verbose error messages in prod | Application logic | Medium | Medium | Medium | Sanitize outputs; structured logging -| SBOM reveals internal structure | Infrastructure | Low | Low | Low | Accepted risk; SBOM is intentionally public -|=== - - -==== Denial of Service - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| CI resource exhaustion (fork bomb in PR) | CI/CD pipeline | Medium | Medium | Medium | Concurrency limits; timeout on workflows -| Spam issues/PRs flooding triage | Maintainer time | Medium | Low | Low | GitHub rate limits; bot auto-close stale -| Large binary commits bloating repo | Source code | Low | Medium | Low | .gitattributes LFS policy; pre-commit hooks -|=== - - -==== Elevation of Privilege - -|=== -| Threat | Affected Asset | Likelihood | Impact | Risk | Mitigation - -| Workflow injection via PR title/body | CI/CD pipeline | Medium | High | High | Never interpolate PR fields in `run:`; use env vars -| GITHUB_TOKEN over-scoped | CI/CD secrets | Medium | High | High | `permissions: read-all` default; per-job scoping -| Container escape | Runtime environment | Low | High | Medium | Hardened container runtime; read-only rootfs; no-new-privileges -| Compromised action dependency | CI/CD pipeline | Medium | High | High | SHA-pin all actions; never use `@latest` tags -|=== - - -=== Mitigations in Place - -- **SLSA Provenance**: Build attestations via slsa-github-generator -- **Secret Scanning**: TruffleHog + secret-scanner workflow on every push -- **Static Analysis**: CodeQL on supported languages -- **Supply Chain**: OpenSSF Scorecard (scorecard.yml + scorecard-enforcer.yml) -- **Container Signing**: Ed25519 signatures on all published images (optional: use your signing tool) -- **Container Runtime**: Hardened container runtime with formal verification (optional) -- **Dependency Pinning**: All GitHub Actions SHA-pinned; lockfiles committed -- **Workflow Validation**: workflow-linter.yml checks all workflow changes -- **Security Scanning**: Neurosymbolic scanning (hypatia-scan.yml, optional) -- **Bot Governance**: Bot orchestration with confidence thresholds (optional) -- **Edge Security**: Gateway with policy enforcement (optional, where applicable) -- **SBOM**: Generated and published with releases - -=== Residual Risks - -|=== -| Risk | Accepted Because | Review Trigger - -| Zero-day in GitHub Actions runner | Platform responsibility; no feasible mitigation | GitHub advisory -| Maintainer account compromise | Mitigated by 2FA requirement; residual remains | Any suspicious activity -| Transitive dependency vulnerability (0-day) | Lockfiles limit blast radius; scanning catches known CVEs | CVE database update -| SBOM exposes internal component names | Transparency is a design goal | Policy change -|=== - - -=== Review Schedule - -This threat model should be reviewed: - -- **Quarterly** as a standing item -- **When architecture changes** (new services, new trust boundaries, new deployment targets) -- **Before major releases** (v1.0, v2.0, etc.) -- **After any security incident** affecting this project or its dependencies - -Reviewer should update the "Last Reviewed" date and version in Document Info above. diff --git a/czech-file-knife/docs/architecture/TOPOLOGY.adoc b/czech-file-knife/docs/architecture/TOPOLOGY.adoc deleted file mode 100644 index dd802fe59..000000000 --- a/czech-file-knife/docs/architecture/TOPOLOGY.adoc +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Last updated: 2026-04-04 -= Architecture Topology - -== System Overview - -RSR (Rhodium Standard Repository) template provides the canonical scaffold for all hyperpolymath projects, with integrated CI/CD, documentation, and service discovery patterns. - -== Component Overview - -[cols="1,1,2", options="header"] -|=== -| Component | Language | Purpose - -| dogfood-gate workflow | YAML | Quality checks (CRG, security, linting) -| eclexiaiser-validate job | YAML | Resource cost awareness scoring -| Groove discovery | JSON | Service endpoint registration -|=== - -== Data Flow - ----- -[Code Push] → [GitHub Actions] → [hypatia scan] → [eclexiaiser validate] → [Results] ----- - -== Integration Points - -* *Upstream*: Hypatia (neurosymbolic CI/CD), eclexiaiser (resource scoring) -* *Downstream*: All RSR-based repositories (500+ instances) - -== Deployment - -* Container: Stapeln Six ecosystem -* CI/CD: GitHub Actions → Hypatia scan → eclexiaiser-validate (6 scorecard dimensions) → Mirror -* Service Discovery: Groove protocol (www/.well-known/groove/manifest.json) diff --git a/czech-file-knife/docs/attribution/CFK-CITATIONS.adoc b/czech-file-knife/docs/attribution/CFK-CITATIONS.adoc deleted file mode 100644 index 9905364fb..000000000 --- a/czech-file-knife/docs/attribution/CFK-CITATIONS.adoc +++ /dev/null @@ -1,36 +0,0 @@ -= czech-file-knife - Citation Guide -:toc: - -== BibTeX - -[source,bibtex] ----- -@software{czech-file-knife_2025, - author = {Polymath, Hyper}, - title = {czech-file-knife}, - year = {2025}, - url = {https://github.com/hyperpolymath/czech-file-knife}, - license = {MPL-2.0} -} ----- - -== Harvard Style - -Polymath, H. (2025) _czech-file-knife_ [Computer software]. Available at: https://github.com/hyperpolymath/czech-file-knife - -== OSCOLA - -Hyper Polymath, 'czech-file-knife' (2025) - -== MLA - -Polymath, Hyper. "czech-file-knife." 2025, github.com/hyperpolymath/czech-file-knife. - -== APA 7 - -Polymath, H. (2025). _czech-file-knife_ [Computer software]. GitHub. https://github.com/hyperpolymath/czech-file-knife - -== See Also - -* link:../CITATION.cff[CITATION.cff] -* link:../codemeta.json[codemeta.json] diff --git a/czech-file-knife/docs/attribution/CITATIONS.adoc b/czech-file-knife/docs/attribution/CITATIONS.adoc deleted file mode 100644 index c87330b4a..000000000 --- a/czech-file-knife/docs/attribution/CITATIONS.adoc +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Czech File Knife - Citation Guide -:toc: - -== BibTeX - -[source,bibtex] ----- -@software{Czech File Knife_2026, - author = {Jewell, Jonathan D.A.}, - title = {Czech File Knife}, - year = {2026}, - url = {https://github.com/hyperpolymath/Czech File Knife}, - license = {MPL-2.0} -} ----- - -== Harvard Style - -Jewell, J. (2026) _Czech File Knife_ [Computer software]. Available at: https://github.com/hyperpolymath/Czech File Knife - -== OSCOLA - -Jonathan D.A. Jewell, 'Czech File Knife' (2026) - -== MLA - -Jewell, Jonathan D.A. "Czech File Knife." 2026, github.com/hyperpolymath/Czech File Knife. - -== APA 7 - -Jewell, J. (2026). _Czech File Knife_ [Computer software]. GitHub. https://github.com/hyperpolymath/Czech File Knife - -== See Also - -* link:CITATION.cff[CITATION.cff] diff --git a/czech-file-knife/docs/attribution/CODEOWNERS.adoc b/czech-file-knife/docs/attribution/CODEOWNERS.adoc deleted file mode 100644 index ffd88f1f0..000000000 --- a/czech-file-knife/docs/attribution/CODEOWNERS.adoc +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Code Ownership -:icons: font - -This project utilizes a formally defined code ownership structure to ensure that specific components are reviewed by domain experts. - -== Authority Model - -Our ownership model is based on the "Perimeter" architecture: -* **Perimeter 1 (Core):** Strictly controlled by Lead Maintainers. -* **Perimeter 2 (Extensions):** Maintained by component owners. -* **Perimeter 3 (Community):** Open for broader community participation. - -== Automated Enforcement - -The technical rules for automatic review assignments are maintained in the machine-readable link:../../.github/CODEOWNERS[.github/CODEOWNERS] file. GitHub uses this to automatically notify owners when changes are proposed to their sections. - -== Component Owners - -A full list of maintainers and their contact information can be found in link:../MAINTAINERS.adoc[MAINTAINERS.adoc]. diff --git a/czech-file-knife/docs/attribution/README.adoc b/czech-file-knife/docs/attribution/README.adoc deleted file mode 100644 index c6c3b5e7e..000000000 --- a/czech-file-knife/docs/attribution/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= attribution Unit diff --git a/czech-file-knife/docs/contributing.adoc b/czech-file-knife/docs/contributing.adoc deleted file mode 100644 index f6efed341..000000000 --- a/czech-file-knife/docs/contributing.adoc +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= Contributing — Czech File Knife -:revdate: 2026-MM-DD - -== Audience - -Developers working *on* `Czech File Knife`. For consumers (people calling -or depending on it) see link:./usage.adoc[usage.adoc]. - -== Local-dev setup - -Prerequisites — the minimum versions and where to get them: - -* `` v`` — ``. -* `` v`` — ``. -* GPG signing key configured (estate policy — all commits must be - signed). See - link:https://github.com/hyperpolymath/standards/blob/main/docs/secure-coding-training.md[standards/docs/secure-coding-training.md]. - -One-shot setup: - -[source,bash] ----- -git clone git@github.com:hyperpolymath/Czech File Knife.git -cd Czech File Knife -just setup # installs deps, sets up hooks -just test # runs the full test suite ----- - -== Running tests - -* **Unit**: `just test-unit` — fast, no I/O. -* **Integration**: `just test-int` — uses real services (database, - HTTP, etc.). Estate policy: prefer real over mocked - (see `feedback_integration_tests_real_db` in maintainer's memory). -* **Property**: `just test-prop` — randomised, slower; budget - documented in `docs/proof-debt.md` if applicable. -* **Full**: `just test` — runs all of the above. - -== Code style - -We enforce style via CI (governance-reusable.yml from hyperpolymath/standards). -Locally: - -[source,bash] ----- -just fmt # auto-format -just lint # static checks ----- - -* All commits must be **GPG-signed** (CI enforces; see - link:https://github.com/hyperpolymath/standards[standards]). -* All source files must carry an **SPDX-License-Identifier** header - (CI enforces). -* Conventional commits — `feat`, `fix`, `chore`, `refactor`, `docs`, - `test`, `ci`, `revert` (CHANGELOG is auto-generated from these - via link:https://github.com/hyperpolymath/standards/blob/main/.github/workflows/changelog-reusable.yml[`changelog-reusable.yml`]). - -== Branching & PR workflow - -. Branch off `main` as `claude/` (for AI agents) or - `/` (for humans). -. Make focused, narrow commits — one logical change per commit. -. Open a PR against `main`. -. **Enable auto-merge immediately** on every PR you open - (`gh pr merge --auto --squash`) — estate standing policy - (see standards#196 audit and policies). -. CI must be green. The PR auto-merges when checks pass + reviews land. - -== Adding a new dependency - -. State the *why* in the PR body — what does this dependency unlock? -. Check provenance (maintained, audited, no malicious history). -. Pin to a SHA, not a tag. -. Update `docs/architecture.adoc#Dependencies`. - -== Adding an ADR - -When you make a non-obvious design decision, write it down: - -. Copy `docs/decisions/0001-template.adoc` → `0002-.adoc`. -. Fill in: Context, Decision, Consequences, Alternatives. -. Link the ADR from the README or relevant code as a comment. - -== Reporting issues - -* Bugs in `Czech File Knife`: file at `hyperpolymath/Czech File Knife/issues`. -* Estate-wide concerns (policy, conventions, CI): file at - `hyperpolymath/standards/issues`. diff --git a/czech-file-knife/docs/decisions/0000-template.adoc b/czech-file-knife/docs/decisions/0000-template.adoc deleted file mode 100644 index 0025b64e4..000000000 --- a/czech-file-knife/docs/decisions/0000-template.adoc +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Architecture Decision Record: 0000-template -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -== [NUMBER]. [TITLE] - -Date: YYYY-MM-DD - -=== Status - -link:NNNN-title.md[Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN] | Rejected] - -=== Context - -What is the issue that we're seeing that is motivating this decision or change? - -=== Decision - -What is the change that we're proposing and/or doing? - -=== Consequences - -What becomes easier or more difficult to do because of this change? - -==== Positive - -- ... - -==== Negative - -- ... - -==== Neutral - -- ... diff --git a/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc b/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc deleted file mode 100644 index 1d7c2601d..000000000 --- a/czech-file-knife/docs/decisions/0001-adopt-rsr-standard.adoc +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Architecture Decision Record: 0001-adopt-rsr-standard -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -== 1. Adopt Rhodium Standard Repository (RSR) Template - -Date: 2026-02-14 - -=== Status - -Accepted - -=== Context - -Managing multiple repositories with an ad-hoc approach led to significant -inconsistencies across the ecosystem. Common problems included: - -- Missing or incomplete configuration files (SECURITY.md, CONTRIBUTING.md, - .editorconfig, etc.) -- State files (STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml) placed in the repository - root instead of the canonical `.machine_readable/` directory -- Duplicate or conflicting workflow definitions across repos -- No standardized entry point for AI agents interacting with repositories -- Inconsistent bot directive configurations leading to unreliable automation -- No contractile enforcement or Justfile automation - -Without a single source of truth for repository structure, each new repo -required manual setup and inevitably drifted from best practices over time. - -=== Decision - -Adopt the Rhodium Standard Repository (RSR) template (`rsr-template-repo`) as -the canonical starting point for all new repositories. Existing repositories -will migrate incrementally as they receive active development. - -The RSR template provides: - -- **Machine-readable state files** in `.machine_readable/` (STATE.a2ml, - ECOSYSTEM.a2ml, META.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml) -- **AI manifest** (`0-AI-MANIFEST.a2ml`) as a universal entry point for all - AI agents -- **Bot directives** in `.machine_readable/bot_directives/` for bot orchestration integration -- **Contractiles** in `.machine_readable/contractiles/` (k9, dust, intend, must, trust) for - policy enforcement -- **Standardized workflows** (16+ GitHub Actions workflows, all SHA-pinned) -- **Justfile automation** with standard recipes for common tasks -- **Security and governance files**: SECURITY.md, CONTRIBUTING.md, - CODE_OF_CONDUCT.md, LICENSE (MPL-2.0) -- **Architecture Decision Records** in `docs/decisions/` - -New repositories are created by cloning the template: - -[source,bash] ----- -git clone https://github.com/hyperpolymath/rsr-template-repo new-repo-name -cd new-repo-name -rm -rf .git && git init ----- - -=== Consequences - -==== Positive - -- Consistency across all repositories, enforced from creation -- Automated compliance checking via `rsr-antipattern.yml` workflow -- Bot fleet can operate reliably across all repos with predictable structure -- AI agents (Claude, Gemini, etc.) have a standardized entry point via - `0-AI-MANIFEST.a2ml` -- New contributors can onboard faster with familiar, documented structure -- Reduced maintenance burden: fix once in template, propagate to all repos -- Machine-readable state enables tooling and automation pipelines - -==== Negative - -- Migration effort for existing repos requires time and attention -- Learning curve for contributors unfamiliar with RSR conventions -- Template updates need propagation mechanism to existing repos -- Some repos may have unique needs that do not fit the standard template - without customization - -==== Neutral - -- Existing CI/CD pipelines continue to work; RSR workflows are additive -- Third-party dependencies retain their original licenses regardless of - repo structure -- ADR process itself is part of the template, enabling future decisions - to be recorded consistently diff --git a/czech-file-knife/docs/decisions/0001-template.adoc b/czech-file-knife/docs/decisions/0001-template.adoc deleted file mode 100644 index b4070a83d..000000000 --- a/czech-file-knife/docs/decisions/0001-template.adoc +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= ADR-0001 — Use Architecture Decision Records -:revdate: 2026-MM-DD -:status: ACCEPTED - -== Context - -We need a lightweight way to record significant architectural decisions -and the reasoning behind them, so future maintainers (and AI agents) -can understand *why* the project is shaped as it is. - -== Decision - -Adopt link:https://adr.github.io/[Architecture Decision Records (ADRs)] -in `docs/decisions/`, numbered sequentially (0001, 0002, ...). - -* Each ADR is a single `.adoc` file. -* The first ADR (this one) records the decision to use ADRs. -* Status values: PROPOSED, ACCEPTED, DEPRECATED, SUPERSEDED. -* When a decision is overturned, the new ADR records the supersession - and updates the old one's status to `SUPERSEDED by 00NN`. - -== Consequences - -. **Positive**: future readers see *why* without git-archaeology. -. **Positive**: design alternatives are documented, not just the - winning choice. -. **Positive**: ADRs are reviewable in PRs — the design discussion - happens alongside the code that implements it. -. **Negative**: maintenance burden — every non-obvious decision now - warrants an ADR. (We mitigate by keeping ADRs short; "non-obvious" - is a judgment call.) -. **Negative**: ADRs can rot. We accept this; the SUPERSEDED chain - is the recovery mechanism. - -== Alternatives considered - -. **Comments in code** — too local; doesn't capture cross-cutting - decisions. -. **Wiki / external doc** — drifts from code; not in PR review. -. **Commit messages** — too transient and not discoverable. -. **No record** — discarded; this is how every project ends up with - "I don't know why we do it this way" debt. - -== Companion ADRs - -* (None yet — this is the first ADR.) - -== References - -* MADR template — `https://adr.github.io/madr/` -* Estate convention — `hyperpolymath/standards/docs/RSR_OUTLINE.adoc` diff --git a/czech-file-knife/docs/decisions/0002-variant-contract.adoc b/czech-file-knife/docs/decisions/0002-variant-contract.adoc deleted file mode 100644 index dfed0ec50..000000000 --- a/czech-file-knife/docs/decisions/0002-variant-contract.adoc +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Architecture Decision Record: 0002-variant-contract - -== 2. Variant templates declare a machine-readable contract with their parent - -Date: 2026-07-07 - -=== Status - -Accepted (ratified by owner 2026-07-07; first instance live in -rsr-julia-library-template-repo) - -=== Context - -The RSR template family grows variants where the spine genuinely diverges — -build system, test/docs toolchain, ABI story (first: the Julia library -template; candidates: Rust+SPARK paired-lib). Hand-maintained variant repos -silently drift from the canonical template: the parent gains fixes the -variant never receives, the variant fixes defects the parent cannot see -(especially while the parent's own CI is red), and nothing records which -differences are intentional. - -Anti-proliferation doctrine requires that variation be *declared, bounded and -enforceable*, and the long-term plan (Scaffoldia as composer; template repos -becoming generated artifacts with a maintenance sync loop until cut-off) -needs variant deltas to exist as data, not tribal knowledge. - -=== Decision - -Every variant template carries a **variant contract** at -`.machine_readable/descriptiles/VARIANT.a2ml` declaring: - -- **parent** (owner/repo) and **parent-pin** — the last reconciled parent - commit. Bumping the pin after reconciling is the template-maintenance sync - operation. -- **normalisation rules** separating per-repo operational state from spine - content: 40-hex action/reusable pin SHAs (dependabot cadence differs per - repo) and self-name folding (both repo names → `SELF` on both sides). -- **path lists**, verified against the parent at the pin: - `added`, `removed`, `diverged` (permanently variant-owned), - `diverged-pending-upstream` (fixes made variant-first; doubles as the - upstream-promotion worklist), and `operational-state` (excluded from - comparison, e.g. coaptation receipts). - -A **drift gate** (`scripts/check-variant-drift.sh` + the Variant Drift Gate -workflow: push / PR / weekly cron / manual, no `paths:` filter) enforces the -contract: any tracked file not declared variant-owned must be identical to -the parent at the pin modulo normalisation, and declared additions/removals -are asserted in both directions. - -CLADE stays identity-only: `[lineage]` records *who* the parent is and gains -a one-line `variant-contract` pointer; the operational mechanics (pin, path -lists) live in VARIANT.a2ml so pin bumps never churn the identity file. - -The contract is **direction-agnostic**: today the parent is the source and -the variant a hand-maintained specialisation (`direction = -"template-is-source"`); after the Scaffoldia inversion the same lists -describe how to regenerate the variant from core + variant pack -(`direction = "generated-from-core"`). - -=== Consequences - -- Variant drift becomes a red check instead of an archaeology project. -- The `diverged-pending-upstream` list is a standing, exact worklist for - upstream promotion (first use: rsr-template-repo PR #137). -- Scaffoldia can consume variant contracts as data when composing or - regenerating repos; GitHub's "Use this template" path and the composer - path cannot silently diverge because both are checked against the same - contract. -- New variants must justify spine divergence in the contract's `[variant]` - block; anything expressible as a feature or profile should not become a - variant repo (anti-proliferation). -- Reference implementation: rsr-julia-library-template-repo (VARIANT.a2ml, - scripts/check-variant-drift.sh, .github/workflows/variant-drift-gate.yml). diff --git a/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc b/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc deleted file mode 100644 index 9726e04b6..000000000 --- a/czech-file-knife/docs/decisions/0003-forge-and-sustain-lifecycle.adoc +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Architecture Decision Record: 0003-forge-and-sustain-lifecycle - -== 3. One lifecycle, one contract: Forge (mint → provision → configure → harness) and Sustain (corrective / adaptive / perfective / preventive) - -Date: 2026-07-21 - -=== Status - -Accepted (drafted 2026-07-21; ratified by owner ruling 2026-09-19). The normative -statement lives in `hyperpolymath/standards` -`0-canon/rsr/SCAFFOLD-LIFECYCLE.adoc` (relocated from `rhodium-standard-repositories/spec/` in the September 2026 reorg); this ADR records -the decision and its mechanics in the spine. - -=== Context - -The scaffolding machinery exists but is disconnected, and each disconnection -has produced a measured incident: - -- `just repo-init` renders 34 substitutions and derives identity, but leaves 12 - tokens `UNASSIGNED`, performs no platform configuration, and — decisive — - records **no provenance**. A minted repo does not know which template - commit it came from, so template improvements cannot be propagated and - drift cannot be detected. The external audit - (rsr-template-repo-experiment, 05-ZIGZAG §2.2) named this the single - highest-leverage gap: Copier/Cruft solved it with an answer-file + - `update` + `check`; the estate hand-simulates it as recurring - standardisation-PR campaigns. -- `rsr-profile.a2ml` — the file that makes hypatia's implemented - `rsr-conformance` oracle able to score a repo at all — exists in ~2 of - ~300 repos, because nothing mints it. -- Identity in the probot-managed `settings.yml` renamed this very repo to - `-REPO-` on every push (the 2026-07-20 incident). Identity must be set - out of band, but no stage of the lifecycle owned that step, and this - file's own header previously claimed `just repo-init` did it via `gh` — it - never has. -- ADR-0002's variant contract + drift gate work (reference: - rsr-julia-library-template-repo) but apply only to variant *templates*, - not to the ~200 minted repos that need the same mechanism. - -=== Decision - -**One lifecycle.** A repository is *forged* in four ordered stages — exit -criteria in SCAFFOLD-LIFECYCLE.adoc: - -1. **Mint** — tree exists, placeholders rendered, identity derived, - archetype overlay applied, `rsr-profile.a2ml` and provenance written, - and **no template identity survives in the child**: the spine's own - name is a literal rather than a token, so rendering placeholders is not - sufficient — a self-name pass must rewrite it everywhere it denotes - *this* repo, while leaving every line that names the parent *as* a - parent untouched. A minted repo that still declares - `sonar.projectKey=…_rsr-template-repo` has not been minted; it has been - copied. -2. **Provision** — the recorded `BUILD_CMD`/`TEST_CMD` actually run - (echoed advice is not provisioning). -3. **Configure** — identity, visibility, branch protection set out of band - via `gh`/UI, once per repo; required contexts equal emitted job ids; - repo registered in `gv-clade-index`. -4. **Harness** — CI green or accounted for, drift gate armed against the - recorded pin, oracle able to score. - -It is then *sustained* in four modes: **corrective** (fleet `fix-*` on -broken invariants), **adaptive** (parent-pin bumps + fan-out campaigns), -**perfective** (tier climbing via `mix hypatia.rsr_score`; promotion of -`diverged-pending-upstream` fixes), **preventive** (canary-tested gates, -placeholder guard, drift-gate cron). - -**One contract.** The ADR-0002 `VARIANT.a2ml` shape is extended in *use*, -not in format, to minted repos: `just repo-init` writes -`.machine_readable/descriptiles/VARIANT.a2ml` with `direction = -"generated-from-core"`, `parent = hyperpolymath/rsr-template-repo`, -`parent-pin` = the template tip at mint (resolved via `git ls-remote`; -`UNASSIGNED` when offline — honest, never guessed), and empty path lists -that grow as the repo legitimately diverges. A variant template, a minted -repo, and a retrofitted repo are the same object — a child of the spine at -a pin with declared divergences — so `scripts/check-variant-drift.sh` -(promoted into the spine by this ADR, verbatim from the Julia variant — it -was already contract-driven) is the estate's re-templating check for all -three populations. - -**Archetypes are data.** `archetypes//ARCHETYPE.a2ml` carries a -capability `preset` (defined in standards `template-capability-gates.toml`) -plus a `[tokens]` table filling the previously-`UNASSIGNED` init tokens -that are archetype-determined (`PROJECT_KIND`, `LANG_STACK`, `BUILD_CMD`, -`TEST_CMD`, …). Tokens that are genuine per-repo judgement -(`PROJECT_UNIQUE_STRENGTH`, `MUST_INVARIANTS`, …) stay `UNASSIGNED` — an -archetype must not write fiction. `just repo-init ` applies the -overlay; the `archetypes/` directory is template-only and removed from the -minted tree (like `build/templates/`). First archetype: `julia-library`, -harvested from the Julia variant's contract. Declared next (content to -follow, not stubbed empty): `wordpress-plugin`, `zotero-plugin`, -`userscript` — presets already reserved in the canon. - -**Scaffoldia consumes, never redefines.** The composer's pack axis is the -archetype/variant contract; its profile axis is the capability preset. Its -GitLab-lineage implementation's registry is harvested as input data; the -engine stays dormant until the composer ADR (scaffoldia Phase 1) chooses -its implementation. - -=== Consequences - -- Every newly minted repo is born sustainable: scorable by the oracle - (profile), re-templatable (provenance), and drift-checkable (gate) — the - three artefacts whose absence created the manual-campaign treadmill. -- The 12 `UNASSIGNED` tokens split honestly: archetype-determined ones get - real values; judgement ones stay loud. -- The `settings.yml` header's false claim about `gh` automation is - corrected: Configure is an operator stage today, printed as exact - commands by `just repo-init`; automating it is future work and must not be - described as existing. -- Retrofit of already-minted repos = writing the provenance contract into - them (megasweep can detect which repos lack one; mutation stays - owner-gated, audit-first). -- The dogfood loop closes: the template ships the contract format, the - canon defines its lifecycle meaning, the oracle scores its presence, the - fleet propagates its updates. diff --git a/czech-file-knife/docs/decisions/README.adoc b/czech-file-knife/docs/decisions/README.adoc deleted file mode 100644 index cb9c6d848..000000000 --- a/czech-file-knife/docs/decisions/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= decisions Unit diff --git a/czech-file-knife/docs/developer/ABI-FFI-README.adoc b/czech-file-knife/docs/developer/ABI-FFI-README.adoc deleted file mode 100644 index 041e140d0..000000000 --- a/czech-file-knife/docs/developer/ABI-FFI-README.adoc +++ /dev/null @@ -1,405 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= ABI/FFI Standards -{{~ Aditionally delete this line and fill out the template below ~}} - -== CZECH_FILE_KNIFE ABI/FFI Documentation - -=== Overview - -This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: - -- **ABI (Application Binary Interface)** defined in **Idris2** with formal proofs -- **FFI (Foreign Function Interface)** implemented in **Zig** for C compatibility -- **Generated C headers** bridge Idris2 ABI to Zig FFI -- **Any language** can call through standard C ABI - -=== Architecture - ----- -┌─────────────────────────────────────────────┐ -│ ABI Definitions (Idris2) │ -│ src/interface/Abi/ │ -│ - Types.idr (Type definitions) │ -│ - Layout.idr (Memory layout proofs) │ -│ - Foreign.idr (FFI declarations) │ -└─────────────────┬───────────────────────────┘ - │ - │ generates (at compile time) - ▼ -┌─────────────────────────────────────────────┐ -│ C Headers (auto-generated) │ -│ generated/abi/czech_file_knife.h │ -└─────────────────┬───────────────────────────┘ - │ - │ imported by - ▼ -┌─────────────────────────────────────────────┐ -│ FFI Implementation (Zig) │ -│ ffi/zig/src/main.zig │ -│ - Implements C-compatible functions │ -│ - Zero-cost abstractions │ -│ - Memory-safe by default │ -└─────────────────┬───────────────────────────┘ - │ - │ compiled to libczech_file_knife.so/.a - ▼ -┌─────────────────────────────────────────────┐ -│ Any Language via C ABI │ -│ - Rust, , Julia, Python, etc. │ -└─────────────────────────────────────────────┘ ----- - -=== Directory Structure - ----- -czech_file_knife/ -├── src/ -│ ├── abi/ # ABI definitions (Idris2) -│ │ ├── Types.idr # Core type definitions with proofs -│ │ ├── Layout.idr # Memory layout verification -│ │ └── Foreign.idr # FFI function declarations -│ └── lib/ # Core library (any language) -│ -├── ffi/ -│ └── zig/ # FFI implementation (Zig) -│ ├── build.zig # Build configuration -│ ├── build.zig.zon # Dependencies -│ ├── src/ -│ │ └── main.zig # C-compatible FFI implementation -│ ├── test/ -│ │ └── integration_test.zig -│ └── include/ -│ └── czech_file_knife.h # C header (optional, can be generated) -│ -├── generated/ # Auto-generated files -│ └── abi/ -│ └── czech_file_knife.h # Generated from Idris2 ABI -│ -└── bindings/ # Language-specific wrappers (optional) - ├── rust/ - ├── / - └── julia/ ----- - -=== Why Idris2 for ABI? - -==== 1. **Formal Verification** - -Idris2's dependent types allow proving properties about the ABI at compile-time: - -[source,idris] ----- --- Prove struct size is correct -public export -exampleStructSize : HasSize ExampleStruct 16 - --- Prove field alignment is correct -public export -fieldAligned : Divides 8 (offsetOf ExampleStruct.field) - --- Prove ABI is platform-compatible -public export -abiCompatible : Compatible (ABI 1) (ABI 2) ----- - -==== 2. **Type Safety** - -Encode invariants that C/Zig cannot express: - -[source,idris] ----- --- Non-null pointer guaranteed at type level -data Handle : Type where - MkHandle : (ptr : Bits64) -> {auto 0 nonNull : So (ptr /= 0)} -> Handle - --- Array with length proof -data Buffer : (n : Nat) -> Type where - MkBuffer : Vect n Byte -> Buffer n ----- - -==== 3. **Platform Abstraction** - -Platform-specific types with compile-time selection: - -[source,idris] ----- -CInt : Platform -> Type -CInt Linux = Bits32 -CInt Windows = Bits32 - -CSize : Platform -> Type -CSize Linux = Bits64 -CSize Windows = Bits64 ----- - -==== 4. **Safe Evolution** - -Prove that new ABI versions are backward-compatible: - -[source,idris] ----- --- Compiler enforces compatibility -abiUpgrade : ABI 1 -> ABI 2 -abiUpgrade old = MkABI2 { - -- Must preserve all v1 fields - v1_compat = old, - -- Can add new fields - new_features = defaults -} ----- - -=== Why Zig for FFI? - -==== 1. **C ABI Compatibility** - -Zig exports C-compatible functions naturally: - -[source,zig] ----- -export fn library_function(param: i32) i32 { - return param * 2; -} ----- - -==== 2. **Memory Safety** - -Compile-time safety without runtime overhead: - -[source,zig] ----- -// Null check enforced at compile time -const handle = init() orelse return error.InitFailed; -defer free(handle); ----- - -==== 3. **Cross-Compilation** - -Built-in cross-compilation to any platform: - -[source,bash] ----- -zig build -Dtarget=x86_64-linux -zig build -Dtarget=aarch64-macos -zig build -Dtarget=x86_64-windows ----- - -==== 4. **Zero Dependencies** - -No runtime, no libc required (unless explicitly needed): - -[source,zig] ----- -// Minimal binary size -pub const lib = @import("std"); -// Only includes what you use ----- - -=== Building - -==== Build FFI Library - -[source,bash] ----- -cd ffi/zig -zig build # Build debug -zig build -Doptimize=ReleaseFast # Build optimized -zig build test # Run tests ----- - -==== Generate C Header from Idris2 ABI - -[source,bash] ----- -cd src/abi -idris2 --cg c-header Types.idr -o ../../generated/abi/czech_file_knife.h ----- - -==== Cross-Compile - -[source,bash] ----- -cd ffi/zig - -# Linux x86_64 -zig build -Dtarget=x86_64-linux - -# macOS ARM64 -zig build -Dtarget=aarch64-macos - -# Windows x86_64 -zig build -Dtarget=x86_64-windows ----- - -=== Usage - -==== From C - -[source,c] ----- -#include "czech_file_knife.h" - -int main() { - void* handle = czech_file_knife_init(); - if (!handle) return 1; - - int result = czech_file_knife_process(handle, 42); - if (result != 0) { - const char* err = czech_file_knife_last_error(); - fprintf(stderr, "Error: %s\n", err); - } - - czech_file_knife_free(handle); - return 0; -} ----- - -Compile with: -[source,bash] ----- -gcc -o example example.c -lczech_file_knife -L./zig-out/lib ----- - -==== From Idris2 - -[source,idris] ----- -import CZECH_FILE_KNIFE.ABI.Foreign - -main : IO () -main = do - Just handle <- init - | Nothing => putStrLn "Failed to initialize" - - Right result <- process handle 42 - | Left err => putStrLn $ "Error: " ++ errorDescription err - - free handle - putStrLn "Success" ----- - -==== From Rust - -[source,rust] ----- -#[link(name = "czech_file_knife")] -extern "C" { - fn czech_file_knife_init() -> *mut std::ffi::c_void; - fn czech_file_knife_free(handle: *mut std::ffi::c_void); - fn czech_file_knife_process(handle: *mut std::ffi::c_void, input: u32) -> i32; -} - -fn main() { - unsafe { - let handle = czech_file_knife_init(); - assert!(!handle.is_null()); - - let result = czech_file_knife_process(handle, 42); - assert_eq!(result, 0); - - czech_file_knife_free(handle); - } -} ----- - -==== From Julia - -[source,julia] ----- -const libczech_file_knife = "libczech_file_knife" - -function init() - handle = ccall((:czech_file_knife_init, libczech_file_knife), Ptr{Cvoid}, ()) - handle == C_NULL && error("Failed to initialize") - handle -end - -function process(handle, input) - result = ccall((:czech_file_knife_process, libczech_file_knife), Cint, (Ptr{Cvoid}, UInt32), handle, input) - result -end - -function cleanup(handle) - ccall((:czech_file_knife_free, libczech_file_knife), Cvoid, (Ptr{Cvoid},), handle) -end - -# Usage -handle = init() -try - result = process(handle, 42) - println("Result: $result") -finally - cleanup(handle) -end ----- - -=== Testing - -==== Unit Tests (Zig) - -[source,bash] ----- -cd ffi/zig -zig build test ----- - -==== Integration Tests - -[source,bash] ----- -cd ffi/zig -zig build test-integration ----- - -==== ABI Verification (Idris2) - -[source,idris] ----- --- Compile-time verification -%runElab verifyABI - --- Runtime checks -main : IO () -main = do - verifyLayouorrect - verifyAlignmenorrect - putStrLn "ABI verification passed" ----- - -=== Contributing - -When modifying the ABI/FFI: - -1. **Update ABI first** (`src/interface/Abi/*.idr`) - - Modify type definitions - - Update proofs - - Ensure backward compatibility - -2. **Generate C header** - ```bash - idris2 --cg c-header src/interface/Abi/Types.idr -o generated/abi/czech_file_knife.h - ``` - -3. **Update FFI implementation** (`ffi/zig/src/main.zig`) - - Implement new functions - - Match ABI types exactly - -4. **Add tests** - - Unit tests in Zig - - Integration tests - - ABI verification tests - -5. **Update documentation** - - Function signatures - - Usage examples - - Migration guide (if breaking changes) - -=== License - -MPL-2.0 - -=== See Also - -- https://idris2.readthedocs.io[Idris2 Documentation] -- https://ziglang.org/documentation/master/[Zig Documentation] -- https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium Standard Repositories] diff --git a/czech-file-knife/docs/developer/IOS_INTEGRATION.adoc b/czech-file-knife/docs/developer/IOS_INTEGRATION.adoc deleted file mode 100644 index 3c8031aac..000000000 --- a/czech-file-knife/docs/developer/IOS_INTEGRATION.adoc +++ /dev/null @@ -1,394 +0,0 @@ -== iOS Integration Guide - -Czech File Knife (CFK) provides native iOS integration through Apple’s -File Provider framework, allowing cloud storage providers to appear in -the iOS Files app. - -=== Architecture - -.... -┌─────────────────────────────────────────────────────────────────┐ -│ iOS Files App │ -├─────────────────────────────────────────────────────────────────┤ -│ File Provider Framework │ -│ (NSFileProviderExtension) │ -├─────────────────────────────────────────────────────────────────┤ -│ Swift Wrapper Layer │ -│ (CfkFileProviderItem, CfkFileProviderExtension) │ -├─────────────────────────────────────────────────────────────────┤ -│ C FFI Bridge │ -│ (CfkBridge.h, ffi.rs) │ -├─────────────────────────────────────────────────────────────────┤ -│ Rust Core Library │ -│ (cfk-ios, cfk-core, cfk-providers) │ -└─────────────────────────────────────────────────────────────────┘ -.... - -=== Components - -==== cfk-ios Crate - -The `+cfk-ios+` crate provides: - -* *error.rs*: iOS-specific error types mapping to -`+NSFileProviderError+` -* *domain.rs*: File Provider domain management -* *item.rs*: `+NSFileProviderItem+` representation -* *provider.rs*: Main provider manager coordinating backends -* *ffi.rs*: C FFI layer for Swift interop - -==== Swift Integration - -Swift files in `+cfk-ios/swift/+`: - -* *CfkBridge.h*: C header for bridging -* *CfkFileProviderItem.swift*: `+NSFileProviderItem+` implementation -* *CfkFileProviderExtension.swift*: -`+NSFileProviderReplicatedExtension+` implementation - -=== Building for iOS - -==== Prerequisites - -[arabic] -. Xcode 14+ with iOS 16+ SDK -. Rust with iOS targets: - -[source,bash] ----- -rustup target add aarch64-apple-ios -rustup target add aarch64-apple-ios-sim # For simulator ----- - -[arabic, start=3] -. `+cargo-lipo+` for universal binaries (optional): - -[source,bash] ----- -cargo install cargo-lipo ----- - -==== Build Static Library - -[source,bash] ----- -# For device -cargo build --release --target aarch64-apple-ios -p cfk-ios - -# For simulator -cargo build --release --target aarch64-apple-ios-sim -p cfk-ios - -# Universal binary (both architectures) -cargo lipo --release -p cfk-ios ----- - -The static library will be at: - Device: -`+target/aarch64-apple-ios/release/libcfk_ios.a+` - Simulator: -`+target/aarch64-apple-ios-sim/release/libcfk_ios.a+` - -=== Xcode Project Setup - -==== 1. Create File Provider Extension - -[arabic] -. In Xcode, File → New → Target -. Select "`File Provider Extension`" -. Name it (e.g., "`CfkFileProvider`") - -==== 2. Add Static Library - -[arabic] -. Drag `+libcfk_ios.a+` into your project -. In target settings → Build Phases → Link Binary: -* Add `+libcfk_ios.a+` -* Add `+libresolv.tbd+` (for networking) - -==== 3. Configure Bridging Header - -[arabic] -. Create `+YourExtension-Bridging-Header.h+` -. Add: - -[source,objc] ----- -#import "CfkBridge.h" ----- - -[arabic, start=3] -. In Build Settings → Swift Compiler → Objective-C Bridging Header: -* Set to `+$(SRCROOT)/YourExtension/YourExtension-Bridging-Header.h+` - -==== 4. Add Swift Files - -Copy the Swift files from `+cfk-ios/swift/+` into your extension: - -`+CfkFileProviderItem.swift+` - `+CfkFileProviderExtension.swift+` - -==== 5. Configure Info.plist - -[source,xml] ----- -NSExtension - - NSExtensionFileProviderDocumentGroup - group.com.yourcompany.cfk - NSExtensionPointIdentifier - com.apple.fileprovider-nonui - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).CfkFileProviderExtension - NSExtensionFileProviderSupportsEnumeration - - ----- - -==== 6. Configure Entitlements - -[source,xml] ----- -com.apple.developer.fileprovider.testing-mode - -com.apple.security.application-groups - - group.com.yourcompany.cfk - ----- - -=== Usage - -==== Register Domains - -In your main app, register file provider domains: - -[source,swift] ----- -import FileProvider - -class StorageManager { - func addDropboxAccount() async throws { - // Initialize CFK - let container = FileManager.default.containerURL( - forSecurityApplicationGroupIdentifier: "group.com.yourcompany.cfk" - )! - - try initializeCfk( - storagePath: container.appendingPathComponent("storage"), - cachePath: container.appendingPathComponent("cache"), - tempPath: container.appendingPathComponent("temp") - ) - - // Add domain via FFI - let result = "dropbox-main".withCString { id in - "Dropbox".withCString { name in - "dropbox".withCString { backend in - cfk_domain_add(id, name, backend, nil) - } - } - } - - guard result == CFK_ERROR_SUCCESS else { - throw CfkError.from(code: result) - } - - // Register with system - let domain = NSFileProviderDomain( - identifier: NSFileProviderDomainIdentifier("dropbox-main"), - displayName: "Dropbox" - ) - - try await NSFileProviderManager.add(domain) - } -} ----- - -==== Custom Extension Class - -Subclass `+CfkFileProviderExtension+` for customization: - -[source,swift] ----- -@available(iOS 16.0, *) -class MyFileProviderExtension: CfkFileProviderExtension { - - override func item( - for identifier: NSFileProviderItemIdentifier, - request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void - ) -> Progress { - // Custom handling - return super.item(for: identifier, request: request, completionHandler: completionHandler) - } -} ----- - -=== Handling Authentication - -==== OAuth Flow - -For cloud providers requiring OAuth: - -[source,swift] ----- -import AuthenticationServices - -class AuthManager { - func authenticateDropbox() async throws -> String { - // Build OAuth URL - let authURL = URL(string: "https://www.dropbox.com/oauth2/authorize?...")! - - // Present auth session - let callbackURL = try await withCheckedThrowingContinuation { continuation in - let session = ASWebAuthenticationSession( - url: authURL, - callbackURLScheme: "cfk" - ) { url, error in - if let error = error { - continuation.resume(throwing: error) - } else if let url = url { - continuation.resume(returning: url) - } - } - session.presentationContextProvider = self - session.start() - } - - // Extract token from callback - let token = extractToken(from: callbackURL) - - // Store token and configure backend - let config = """ - {"access_token": "\(token)"} - """ - - let result = "dropbox-main".withCString { id in - "Dropbox".withCString { name in - "dropbox".withCString { backend in - config.withCString { cfg in - cfk_domain_add(id, name, backend, cfg) - } - } - } - } - - return token - } -} ----- - -=== File Coordination - -For proper file coordination with other apps: - -[source,swift] ----- -func coordinatedRead(at url: URL) async throws -> Data { - let coordinator = NSFileCoordinator() - var error: NSError? - var data: Data? - - coordinator.coordinate(readingItemAt: url, options: [], error: &error) { coordURL in - data = try? Data(contentsOf: coordURL) - } - - if let error = error { - throw error - } - - return data ?? Data() -} ----- - -=== Thumbnails - -Implement thumbnail provider for preview support: - -[source,swift] ----- -@available(iOS 16.0, *) -class CfkThumbnailProvider: NSFileProviderThumbnailRequest { - - func fetchThumbnails( - for itemIdentifiers: [NSFileProviderItemIdentifier], - requestedSize size: CGSize, - perThumbnailCompletionHandler: @escaping ( - NSFileProviderItemIdentifier, - Data?, - Error? - ) -> Void, - completionHandler: @escaping (Error?) -> Void - ) -> Progress { - // Fetch thumbnails from backend - // ... - } -} ----- - -=== Testing - -==== Simulator Testing - -Enable File Provider testing in simulator: - -[arabic] -. Build and run extension -. In Simulator → Features → Enable File Provider Testing - -==== Device Testing - -[arabic] -. Enable Developer Mode on device -. Install provisioning profile with File Provider entitlement -. Build and run - -==== Debug Logging - -Enable verbose logging: - -[source,swift] ----- -#if DEBUG -cfk_ios_init() // Enables tracing in debug builds -#endif ----- - -=== Troubleshooting - -==== Common Issues - -[arabic] -. *Extension not appearing in Files* -* Check entitlements -* Verify NSExtension configuration in Info.plist -* Ensure domain is registered -. *Authentication failures* -* Check OAuth callback URL scheme -* Verify token storage -. *Crashes on launch* -* Verify static library is linked -* Check bridging header path -* Ensure `+cfk_ios_init()+` is called first -. *Slow enumeration* -* Enable caching -* Implement pagination properly - -==== Memory Considerations - -File Provider extensions have limited memory. Best practices: - -* Use streaming for large files -* Implement proper pagination -* Release cached items when memory warnings occur - -[source,swift] ----- -override func didReceiveMemoryWarning() { - // Release non-essential cached data -} ----- - -=== Resources - -* https://developer.apple.com/documentation/fileprovider[Apple File -Provider Documentation] -* https://developer.apple.com/videos/play/wwdc2017/243/[WWDC 2017: File -Provider Enhancements] -* https://developer.apple.com/videos/play/wwdc2021/10182/[WWDC 2021: -Meet the File Provider Replicated Extension] diff --git a/czech-file-knife/docs/developer/README.adoc b/czech-file-knife/docs/developer/README.adoc deleted file mode 100644 index 8d0a28367..000000000 --- a/czech-file-knife/docs/developer/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= developer Unit diff --git a/czech-file-knife/docs/developer/invariant-path.adoc b/czech-file-knife/docs/developer/invariant-path.adoc deleted file mode 100644 index 040d7a233..000000000 --- a/czech-file-knife/docs/developer/invariant-path.adoc +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Invariant Path Integration (RSR Template) - -Run Invariant Path from this repository root: - -[source,bash] ----- -./scripts/invariant-path.sh scan --file ./README.adoc --artifact-uri repo://README.adoc --write ----- - -Or through Just: - -[source,bash] ----- -just invariant-path scan --file ./README.adoc --artifact-uri repo://README.adoc --write ----- - -This wrapper points to the shared workspace at `/var/mnt/eclipse/repos/invariant-path` -and defaults to the `generic` profile. diff --git a/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc b/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc deleted file mode 100644 index c04cfd1ea..000000000 --- a/czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Czech File Knife — CRG Audit (2026-09-28) -Jonathan D.A. Jewell -2026-09-28 -:toc: -:sectnums: - -// USAGE -// ----- -// Copy this file to `docs/governance/CRG-AUDIT-YYYY-MM-DD.adoc` in the target -// repo, replace the {{PLACEHOLDER}} tokens, and fill every section with -// *verified-today* evidence. Do not leave "TODO" or "tbd" in a finished audit. -// Worked example of a completed audit: boj-server/docs/governance/CRG-AUDIT-2026-04-18.adoc -// -// The audit must grade the repo *as-is today*, not aspirationally. Per CRG v2.0: -// "honest D > dishonest B". - -== Scope and Standard - -Evaluates the `czech-file-knife` repository against -*Component Readiness Grades v2.0 (STRICT)*, 2026-03-30 revision. - -- Standard: `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` -- Template (criteria boilerplate): `czech-file-knife/docs/governance/CRG-CRITERIA.adoc` -- Self-declared grade (prior art): - * `.machine_readable/descriptiles/STATE.a2ml` → `grade = ""` - * `docs/READINESS.md` (if present) → per-component grades - * Third-party badges (Glama, OpenSSF, etc.) if any — note date and scope -- Audit date: 2026-09-28 -- Auditor: Jonathan D.A. Jewell - -The audit grades the repo *as-is today*, not aspirationally. - -== v2.0 Strictness Recap - -[cols="1,3,4"] -|=== -| Grade | Release Stage | Hard Requirement (v2.0) - -| X | None | Untested. -| F | Reject | Harmful, wasteful, or superseded. -| E | Pre-alpha | >=1 test, failures documented. -| D | Alpha | Test matrix + scope documented + *RSR compliance mandatory*. -| C | Alpha-stable | Dogfooded, CI green, *deep per-file + per-directory annotation*. -| B | Beta | 6+ *diverse* external targets, issues fed back. -| A | Stable | Real-world external feedback confirms value; no harm. -|=== - -Publication requires B+. Long alpha is discipline, not shame. - -== Evidence Inventory (verified 2026-09-28) - -=== Structural compliance (Grade D floor) - -List every RSR-mandated artefact with PRESENT/ABSENT and the concrete source. -Do *not* mark PRESENT without citing the path. - -[cols="2,1,3"] -|=== -| Item | State | Source - -| RSR mandatory workflows (17+) -| PRESENT / PARTIAL (count) / ABSENT -| `.github/workflows/` - -| `.machine_readable/descriptiles/` canonical A2ML -| PRESENT (STATE, META, ECOSYSTEM, AGENTIC, NEUROSYM, PLAYBOOK) / ABSENT -| Layout matches CLAUDE.md invariant - -| `*_chora.deed` (repo deed) -| PRESENT / ABSENT -| Root - -| `docs/EXPLAINME.adoc`, `docs/READINESS.md`, `docs/RSR_OUTLINE.adoc` -| PRESENT / PARTIAL / ABSENT -| `docs/` - -| `build/guix.scm` -| PRESENT (primary + fallback) / PARTIAL / ABSENT -| build/ - -| `www/.well-known/` (security.txt, ai.txt, humans.txt) -| PRESENT / ABSENT -| Per RSR_OUTLINE.adoc; a root `.well-known/` is the legacy location (migration window, issue #53) - -| SPDX headers on source -| PRESENT on sampled files (N/N) / PARTIAL / ABSENT -| `LICENSE`, `LICENSE-MPL-2.0` fallback text - -| `Containerfile` (not `Dockerfile`) -| PRESENT / N/A (no container) / VIOLATION (Dockerfile found) -| Container policy - -| Contractile trident -| PRESENT (`INTENT.contractile`, `TRUST.contractile`, `MUST.contractile`, `ADJUST.contractile`) / PARTIAL / ABSENT -| `.machine_readable/` - -| `Justfile` + `Mustfile` (no `Makefile`) -| PRESENT / VIOLATION (Makefile found) -| Build-system policy (RSR R-020) - -| Remote -| `git@github.com:hyperpolymath/czech-file-knife.git` (origin only) / drift -| `git remote -v` -|=== - -RSR compliance verdict: *met* / *partial* / *not met* — Grade D floor satisfied? Yes/No. - -=== Code and proofs - -[cols="2,1,3"] -|=== -| Item | Count | Notes - -| Idris2 ABI modules (`src/interface/Abi/**/*.idr`) -| N files, N LOC -| Note any proof modules that typecheck green. - -| Zig FFI modules (`ffi/zig/src/*.zig`) -| N files, N LOC -| Call out largest modules. - -| <> (e.g. cartridges, panels, plugins) -| N directories/files -| Link to manifest. - -| Axiomatic `believe_me` sites -| N irreducible -| Each must have a file-header note justifying it as a documented primitive. - -| Non-axiomatic `believe_me` -| N (should be 0 for C) -| Reference the sweep commit and date. - -| Dangerous-pattern scan (`assert_total`, `Admitted`, `sorry`, `unsafeCoerce`, `Obj.magic`) -| N -| Grepped 2026-09-28. -|=== - -=== Test matrix - -Cite *from authoritative source* (STATE.a2ml or CI log). Do not re-count by -filesystem grep — grade the evidence that already exists. - -- `total-tests = N` (from `.machine_readable/descriptiles/STATE.a2ml` or CI). -- Breakdown by suite: list each named suite and its count. -- CI: `.github/workflows/.yml` runs on push / tag / schedule. -- Last green run: date + commit SHA. - -=== Annotation depth - -v2.0 C requires **per-file and per-directory orientation**. Evidence checklist: - -- Per-directory orientation docs: which `docs/` subtrees exist - (`docs/architecture/`, `docs/decisions/`, `docs/integration/`, - `docs/specification/`, `docs/wiki/`) and their file counts. -- Per-<> README coverage: *M of N* (percentage). M of N that lack - a README is the single biggest tell for C-readiness. Anything below - ~90% coverage fails the "deep per-file annotation" clause. -- Per-module inline docs: sample-audit the largest/most-important modules - and note whether purpose comments, invariants, and boundary conditions - are documented. -- Per-subtree README: which non-trivial source subtrees lack an orienting - README (e.g. `/`, `.machine_readable/`, `ffi/zig/src/`). - -=== Dogfooding / home-stable evidence - -Cite STATE.a2ml `[dogfooding-status]`. For each line item: -- WHAT was dogfooded (capability), -- HOW (concrete use-path), -- WHEN (date completed), -- WHERE (link to commit / artefact / deployment). - -- <> — <>. DONE / IN-PROGRESS / PLANNED. -- <> — … -- <> — … -- LIVE deployment (if any): URL, health signal, last verified. - -=== External validation (Grade B / A) - -Distinguish carefully — v2.0 B requires **real external users with feedback**, -not catalogue listings. Populate the canonical STATE.a2ml sections named -below; internal-capability items belong under `[grade-b-status]` (legacy) or -a roadmap section, *not* under `[external-targets]`. - -- STATE.a2ml `[external-targets]` — at least 6 diverse entries for B. - Target identity + date + outcome for each. If absent, grade is capped at C. -- STATE.a2ml `[issues-fed-back]` — closed-issue trail with external-reporter - label for B. -- STATE.a2ml `[field-signal]` — multi-source external confirmation for A. -- Awesome-list PRs merged (N). *Catalogue listings, not dogfooders.* -- Directory/registry listings (MCP directory, Glama, etc.) with automated - assessment badges — *third-party automated assessment, not external usage*. -- Seeded/reference nodes: configured but not yet run by third parties. - -NOTE: Legacy `[grade-b-status]` sections (pre-2026-04-18) often mix internal -capabilities with external targets. During audit, re-classify each entry -and either move it to `[external-targets]` or drop it. See the boj-server -audit for a worked example (six items all re-classified as internal). - -== Grade x Evidence Matrix - -[cols="1,1,3,3"] -|=== -| Grade | Met? | Evidence supporting | Evidence against / gaps - -| X -| n/a -| Tests exist -> not X -| — - -| F -| n/a -| Not superseded; no harm signal -| — - -| E -| ✓/✗ -| ... -| ... - -| D -| ✓/✗ -| All RSR structural requirements met; N tests; scope documented in STATE.a2ml, ROADMAP.adoc -| ... - -| C -| ✓/✗ -| Home-stable; CI green; core dogfood demonstrated across N capability lines; deep annotation present for core and architecture docs. -| Per-<> README coverage M/N. Per-directory orientation gaps. READINESS.md schema version. Dogfood-sweep log. - -| B -| ✓/✗ -| — -| N external targets (need 6+). External users. Issues-fed-back pipeline. - -| A -| ✓/✗ -| — -| Field signal of external confirmation. -|=== - -== Verdict - -*Current CRG grade: **<> (<>)**.* - -State how this matches / diverges from the self-declared grade. v2.0 -strictness clause: does stricter evidence standard change the outcome? - -The grade is earned, not conservative-by-default — list the three strongest -*positive* signals: - -1. <> -2. <> -3. <> - -What blocks *immediate* promotion to <>: - -1. <> -2. <> -3. <> - -What blocks promotion to the grade after that: - -1. <> -2. <> - -== Notes on Non-Negotiables Respected by this Audit - -List irreducible / intentional exceptions so future audits don't retread -them. Examples: - -- Documented axiomatic `believe_me` sites at `` are backend primitives, - not proof debt. -- Proof closures declared "done" (e.g. credential-isolation modules) are not - re-audited here. -- Standing rules (e.g. januskey, private-repo exceptions) are respected. - -== Related Files - -- `docs/READINESS.md` — per-component table. Note v1.0-vs-v2.0 schema alignment. -- `.machine_readable/descriptiles/STATE.a2ml` — authoritative self-declared state. -- `docs/governance/CRG-LIFT-PLAN-2026-09-28.adoc` — companion plan for - D→C (and medium-term C→B) lifts. -- `docs/governance/CRG-CRITERIA.adoc` — boilerplate criteria doc. - -_End of audit._ diff --git a/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml b/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml deleted file mode 100644 index 616258594..000000000 --- a/czech-file-knife/docs/governance/CRG-CRITERIA.a2ml +++ /dev/null @@ -1,108 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -; Component Readiness Grades (CRG) — Machine-readable specification -; Format: A2ML (AI-to-Machine Language) -; Standard: CRG v1.0 - -(standard - (name "Component Readiness Grades") - (abbreviation "CRG") - (version "1.0") - (date "2026-02-28") - (author "Jonathan D.A. Jewell ") - (license "MPL-2.0") - (family "RSR")) - -(grades - (grade - (code X) - (name "Untested") - (release-stage #f) - (ordinal 0) - (description "No testing has been performed. Status unknown.") - (evidence-required "none") - (minimum-for #f)) - (grade - (code F) - (name "Harmful / Wasteful") - (release-stage #f) - (ordinal 1) - (description "Actively harmful, wasteful, or better handled externally. Reject, deprecate, or delegate.") - (evidence-required "documented test results showing harm, waste, or redundancy; comparison with alternatives") - (minimum-for #f)) - (grade - (code E) - (name "Minimal / Salvageable") - (release-stage "pre-alpha") - (ordinal 2) - (description "Does something slight. Barely functional. Needs redesign or major work.") - (evidence-required "at least one successful test case; documented failures and limitations") - (minimum-for #f)) - (grade - (code D) - (name "Partial / Inconsistent") - (release-stage "alpha") - (ordinal 3) - (description "Works on some things but not systematically.") - (evidence-required "matrix of tested scenarios; documented scope vs actual capabilities") - (minimum-for "alpha")) - (grade - (code C) - (name "Self-Validated") - (release-stage "beta") - (ordinal 4) - (description "Tested on the tool/project itself (dogfooding). Reliable in home context.") - (evidence-required "active dogfooding; CI integration or equivalent; no known failures in home context") - (minimum-for "beta")) - (grade - (code B) - (name "Broadly Validated") - (release-stage "release-candidate") - (ordinal 5) - (description "Tested on at least 6 disparate, unrelated targets.") - (evidence-required "list of 6+ diverse targets with test results; evidence of feedback incorporation") - (minimum-for "release-candidate")) - (grade - (code A) - (name "Field-Proven") - (release-stage "stable") - (ordinal 6) - (description "Real-world external feedback confirms value. Does no harm in the wild.") - (evidence-required "real-world usage data; feedback incorporation evidence; no unresolved harm reports") - (minimum-for "stable"))) - -(transitions - (promotion - (from X) (to E) (requirement "Run at least one test. Document results.")) - (promotion - (from X) (to F) (requirement "Evaluate and determine harmful or wasteful.")) - (promotion - (from E) (to D) (requirement "Fix critical failures. Document scope.")) - (promotion - (from D) (to C) (requirement "Dogfood on own project. Fix what breaks.")) - (promotion - (from C) (to B) (requirement "Test on 6+ diverse external targets. Fix what breaks.")) - (promotion - (from B) (to A) (requirement "Ship. Collect external feedback. Demonstrate no harm.")) - (demotion - (from A) (to B) (trigger "External feedback dries up or reveals no longer useful.")) - (demotion - (from A) (to F) (trigger "External feedback reveals component causes harm.")) - (demotion - (from B) (to C) (trigger "Broad validation reveals unfixed failures.")) - (demotion - (from C) (to D) (trigger "Home context changes and component no longer reliable.")) - (demotion - (from C) (to F) (trigger "Dogfooding reveals net negative.")) - (demotion - (from D) (to E) (trigger "Scope narrows to barely functional.")) - (demotion - (from any) (to F) (trigger "Better external alternative makes this pure opportunity cost."))) - -(conformance - (rule "Each assessable component MUST have a grade from {X, F, E, D, C, B, A}.") - (rule "Each grade above X MUST be supported by evidence per section 4.") - (rule "Assessments MUST be recorded in a version-controlled location.") - (rule "Assessments MUST be reviewed at least once per release cycle.") - (rule "Release stages MUST respect minimum grade thresholds.")) diff --git a/czech-file-knife/docs/governance/CRG-CRITERIA.adoc b/czech-file-knife/docs/governance/CRG-CRITERIA.adoc deleted file mode 100644 index e37dce111..000000000 --- a/czech-file-knife/docs/governance/CRG-CRITERIA.adoc +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Component Readiness Grades (CRG) Criteria -:toc: preamble -:icons: font - -This document defines the quality assessment criteria for individual project components. - -== Grade Definitions - -[cols="1,2,3,4",options="header"] -|=== -| Grade | Name | Release Stage | Meaning - -| **A** | Field-Proven | Stable | Real-world feedback amassed; no harm in wild. -| **B** | Broadly Validated | Release Candidate | Tested on 6+ diverse external targets. -| **C** | Self-Validated | Beta | Reliable in home context (dogfooded). -| **D** | Partial | Alpha | Works on some inputs/cases but not systematically. -| **E** | Minimal | Pre-alpha | Barely functional; needs major work. -| **F** | Harmful/Wasteful | Reject/Delegate | Redundant or negative value. -| **X** | Untested | — | Status completely unknown. -|=== - -== Core Principles - -1. **Assess components, not projects:** Each feature gets its own grade. -2. **Evidence over intuition:** Every grade above X requires documented evidence. -3. **Honest assessment:** Grade the component as it is today, not as you hope it will be. -4. **Grades are earned and can be lost:** Regressions lead to demotion. - -== Assessment Checklist - -1. Has it been tested at all? (No → **X**) -2. Does it cause harm or duplicate something better? (Yes → **F**) -3. Does it do something, however slight? (Barely → **E**) -4. Does it work on some things but not others? (Partial → **D**) -5. Does it work reliably on our own project? (Dogfooded → **C**) -6. Has it been tested on 6+ diverse external targets? (Broad → **B**) -7. Do external users confirm it works and is useful? (Field-proven → **A**) - -See link:READINESS.adoc[READINESS.adoc] for the current project assessment. diff --git a/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc b/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc deleted file mode 100644 index 48149d86d..000000000 --- a/czech-file-knife/docs/governance/MAINTENANCE-CHECKLIST.adoc +++ /dev/null @@ -1,571 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Maintenance Checklist -# Maintenance Checklist (Cross-Repo) - -Use this as a repeatable maintenance runbook for any repo. - -Companion policy: - -- `docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` (human-readable) -- `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` (machine-readable) - -## Canonical Repo Baseline (Final) - -Apply this baseline to every repo unless an explicit exception is recorded. - -### Three-Axis Default Model - -- [ ] Axis 1 (scope priority, runs first): `must > intend > like` -- [ ] Axis 2 (maintenance priority): `corrective > adaptive > perfective` -- [ ] Axis 3 (audit priority): `systems > compliance > effects` -- [ ] Perfective items are derived from Axis 1 honest state (not started independently). - -### Axis 1 Scoping Pass (Mandatory) - -Before Axis 2/3 execution, assemble a scoped worklist from evidence: - -- [ ] Read and reconcile: `README`, roadmap, status docs, maintenance checklist, and current CI/security docs. -- [ ] Scan for unfinished markers: `TODO`, `FIXME`, `XXX`, `HACK`, `STUB`, `PARTIAL`. -- [ ] If Idris is present, scan unsoundness markers: `believe_me`, `assert_total`. -- [ ] Identify declared intent vs actual implementation (docs honesty check). -- [ ] Produce a scope assembly artifact with prioritized entries under: - - `must` (release blockers / safety / correctness) - - `intend` (planned near-term) - - `like` (nice-to-have) - -### Axis 2 Maintenance Execution Rules - -- [ ] Corrective first: fix breakage, defects, regressions, safety issues. -- [ ] Adaptive second: reconcile changed scope, remove stale references, cull no-longer-relevant work. -- [ ] Perfective third: only from current honest state established by Axis 1 and updated by corrective/adaptive actions. - -### Axis 3 Audit Rules - -- [ ] Verify systems are in place and actually operating. -- [ ] Verify documentation explains the real/current state (not aspirational-only), including documented exceptions. -- [ ] Verify safety and security controls are present, active, and evidenced. -- [ ] Verify observed effects/impacts are captured and reviewed. -- [ ] Effects audit includes: - - benchmark execution and recorded results (with before/after where relevant) - - explicit maintainer dialogue/status review on what changed, why, and next risks -- [ ] Audit compliance seams/compromises explicitly: - - policy exceptions are recorded with rationale, scope, and expiry/review - - exception does not silently broaden into general policy drift - - language-policy contamination checks run (example: a single TS exception must not trigger broad conversion) - - run `panic-attack` as the compliance-audit scanner - - run ecological checking under effects (using sustainabot guidance as current baseline) - -### Generic Cleanup And Finish-Off Pass - -Run this pass at the end of a corrective/adaptive/perfective cycle: - -- [ ] Root cleanup: - - keep only required control/entry files in root - - move non-essential docs/reports/fixtures to canonical folders -- [ ] Remove or archive stale work: - - close out completed TODO/STUB/PARTIAL items - - cull obsolete references, dead files, and superseded plans -- [ ] Documentation finish-off: - - ensure README, roadmap, status, and wiki match actual implementation state - - ensure machine-readable policy/state files match human docs -- [ ] Security/compliance finish-off: - - run compliance scanner (`panic-attack`) and resolve high-priority findings - - verify exception register and seams/compromises are explicitly bounded -- [ ] Effects finish-off: - - run benchmark/effects checks and record evidence - - conduct explicit maintainer review dialogue (what changed, why, remaining risks) -- [ ] Release-prep finish-off: - - produce Must/Should/Could summary - - produce immediate corrective/adaptive/perfective next-actions list - -### Must - -- [ ] Keep required control files at repository root: - - `.gitignore`, `.gitattributes`, `.editorconfig`, `.tool-versions` - - `Containerfile` - - `.containerignore` (or `.dockerignore` only when required for compatibility) - - `CNAME` and `.nojekyll` when using GitHub Pages/custom domain - - `Justfile` (root by convention) -- [ ] Keep ownership/governance files present: - - `MAINTAINER` in root - - `.github/CODEOWNERS` -- [ ] Keep machine-readable canonical structure under `.machine_readable/`: - - state/meta/ecosystem files (`*.a2ml` or repo standard) - - `anchors/ANCHOR.a2ml` - - `contractiles/` (`must`, `trust`, `intend`, and related) - - `ai/` for AI guidance files - - `bot_directives/` for bot control files -- [ ] Keep contractiles/invariants present and wired: - - root `Mustfile` (or equivalent) with enforceable checks - - `Trustfile` and `Intentfile` present -- [ ] Keep security metadata present: - - `www/.well-known/security.txt` and relevant policy metadata - - CI security scanning configured and runnable -- [ ] Keep docs and navigation coherent: - - single navigation entry point in root (`NAVIGATION.adoc` or equivalent) - - no duplicate conflicting docs for same purpose (for example both `.md` and `.adoc` in root unless intentionally required) -- [ ] Enforce ABI/FFI purity where the policy applies: - - ABI definitions in Idris2 (`src/interface/Abi/*.idr`) - - FFI implementations in Zig (`ffi/**/*.zig`) -- [ ] Ensure quality gate includes: formatting, lint, unit/integration tests, p2p/e2e checks, benchmark smoke, docs checks, security scan. - -### Should - -- [ ] Keep human docs primarily in AsciiDoc (`.adoc`) except where ecosystem rules require other formats (GitHub/community health, legal text, tool-specific files). -- [ ] Keep non-essential root files moved into structured folders: - - `docs/` (theory/practice/whitepapers/proofs/reports) - - `tests/` (fixtures/outputs) - - `docs/legal/` (while retaining root `LICENSE` when forge detection needs it) -- [ ] Maintain `www/.well-known/` for public metadata where applicable (`security.txt`, `humans.txt`, `ads.txt` mirrors if used); a root `.well-known/` is legacy (issue #53). -- [ ] Keep CI policy checks for doc-format conventions and canonical file placement. -- [ ] Keep roadmap/status docs honest with dated evidence. - -### Could - -- [ ] Maintain both human and machine views of maintenance policy from a single source (generate one from the other). -- [ ] Add policy bots for corrective/adaptive/perfective/audit modes. -- [ ] Add repo-level architecture map (`TOPOLOGY.md`) and release-readiness dashboards. -- [ ] Add per-repo exception registry for approved policy deviations. - -### Explicit Root-Placement Rule - -Do **not** move the following out of root if you want default tool behavior: - -- `.gitignore`, `.gitattributes`, `.editorconfig`, `.tool-versions` -- `Containerfile` and ignore file (`.containerignore`/`.dockerignore`) -- `CNAME` and `.nojekyll` for GitHub Pages -- `Justfile` - -## Quick Automated Run (Script) - -Use the helper script first, then use the checklist for deeper/manual follow-up. - -Script locations: -- `${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/run-maintenance.sh` -- `~/Desktop/run-maintenance.sh` - -```bash -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --output /tmp/maintenance-report.json -jq . /tmp/maintenance-report.json -``` - -Useful flags: - -```bash -# Strict mode: fail process on failed checks -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --strict - -# Skip expensive checks when needed -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --skip-panic - -# Explicit language selection -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --rust --python - -# Release hard-pass mode (fails on warnings or failures) -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --fail-on-warn -``` - -Permission policy in script: -- Flags `g+w/o+w` files/dirs -- Flags suspicious executable files -- Flags shebang scripts missing executable bit -- Supports repo-local exceptions via `.maintenance-perms-ignore` (regex per line) -- **Audit-first by default** (non-mutating) -- `--fix-perms` is explicit opt-in only (never implicit) -- For reversible local hardening, pair snapshot/restore scripts where available: - - `scripts/maintenance/perms-state.sh snapshot` - - `scripts/maintenance/perms-state.sh lock` - - `scripts/maintenance/perms-state.sh restore` - -Important git behavior: -- Git generally tracks execute bit, not full UNIX mode matrix. -- Permission hardening audits do not force collaborators to re-unlock every file on pull. -- Keep lock mode opt-in, with restore path documented. - -```bash -# Audit-only (recommended default) -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo - -# Opt-in permission fixes (review output before commit) -~/Desktop/run-maintenance.sh --repo /absolute/path/to/repo --fix-perms -``` - -## 0) Setup - -```bash -REPO="/absolute/path/to/repo" -cd "$REPO" -``` - -```bash -date -u -git rev-parse --abbrev-ref HEAD -git rev-parse HEAD -git status --porcelain -``` - -## 1) Preflight - -- [ ] Confirm clean intent: note existing unrelated dirty files before edits. -- [ ] Confirm runtime/toolchain versions. -- [ ] Confirm container mode expectation (`podman`/`podman-compose`) if required. - -```bash -command -v rg git jq || true -command -v podman podman-compose || true -``` - -## 2) Dependency/Env Prereqs - -- [ ] Python deps in active interpreter (for Python paths). -- [ ] Language-specific tooling installed. - -```bash -python -c "import sys; print(sys.executable)" -python -c "import pydantic; print(pydantic.__version__)" || echo "pydantic missing" -``` - -## 3) Corrective Maintenance First - -- [ ] Fix regressions, runtime errors, panics, broken commands, failing tests. -- [ ] Re-run failing checks immediately after each fix. - -## 4) Code Health Scans - -- [ ] `TODO/FIXME/XXX/HACK/STUB/PARTIAL` scan. -- [ ] Permission policy scan (`g+w/o+w`, executable hygiene). -- [ ] ABI/FFI policy scan (if applicable: Idris2 ABI, Zig FFI). - -```bash -rg -n "TODO|FIXME|XXX|HACK|STUB|PARTIAL" -g '!**/.git/**' -g '!**/target/**' . -``` - -```bash -# Optional per-repo exceptions (regex per line): -# .maintenance-perms-ignore -# ^vendor/ -# ^third_party/ -``` - -```bash -# Adjust paths for your repo layout -find . -type f \( -name '*.idr' -o -name '*.idris2' -o -name '*.zig' \) -``` - -## 5) Panic/Safety/Security Pass - -- [ ] Run `panic-attacker` assail/assault. -- [ ] Triage findings by severity. -- [ ] Fix high first, then medium. -- [ ] Re-run until acceptable. - -```bash -PANIC_BIN="${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/panic-attacker/target/release/panic-attack" -"$PANIC_BIN" assail "$REPO" --output /tmp/assail.json --output-format json --quiet -jq -r '.weak_points | length' /tmp/assail.json -jq -r '.weak_points[] | "\(.severity)|\(.location)|\(.description)"' /tmp/assail.json -``` - -```bash -# If repo has production-only source builder, prefer this for baseline checks: -./scripts/ci/build-panic-assail-source.sh /tmp/panic-src -"$PANIC_BIN" assail /tmp/panic-src --output /tmp/assail-prod.json --output-format json --quiet -``` - -## 6) Language-Specific Validation - -### Rust - -- [ ] Format -- [ ] Lint -- [ ] Tests -- [ ] Doc tests -- [ ] Benches (where relevant) - -```bash -cargo fmt --all --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace -cargo test --workspace --doc -# Optional targeted benchmarks: -cargo bench -``` - -### Python - -- [ ] Format/lint -- [ ] Type check -- [ ] Tests - -```bash -ruff check . -ruff format --check . -mypy . -pytest -q -``` - -### Elixir - -- [ ] Format check -- [ ] Lint/static checks -- [ ] Tests - -```bash -mix format --check-formatted -mix credo --strict -mix test -``` - -## 7) Container/Runtime Checks (Podman) - -- [ ] Build container path. -- [ ] Run smoke tests inside containerized flow. -- [ ] Compare host vs container behavior for parity. - -```bash -podman --version -podman compose version || podman-compose --version -``` - -## 8) Benchmark + Regression Check - -- [ ] Capture before/after metrics for touched hot paths. -- [ ] Record command + sample size + output. -- [ ] Fail change if critical path regresses beyond threshold. - -## 9) Adaptive and Perfective Maintenance - -- [ ] Adaptive: compatibility updates (tooling/API/deprecations/config flags). -- [ ] Perfective: clarity, docs parity, developer workflow improvements. -- [ ] Update roadmap/checklist/docs to match actual implementation state. - -## 10) Final QA and Release Hygiene - -- [ ] Re-run full relevant checks one final time. -- [ ] Confirm no unintended file changes. -- [ ] Commit scoped changes with clear message. -- [ ] Push and capture commit SHA. - -```bash -git status --short -git diff --stat -git add -git commit -m "maint: " -git push -``` - -## 11) Maintenance Report Template - -Copy this block per repo run: - -```text -Repo: -Branch: -Start UTC: -End UTC: - -Scope: -- Corrective: -- Adaptive: -- Perfective: - -Checks Run: -- TODO/FIXME scan: -- Panic-attacker: -- Rust/Python/Elixir checks: -- Container checks: -- Benchmark checks: - -Findings: -- High: -- Medium: -- Low: - -Fixes Applied: -1. -2. -3. - -Validation Results: -- Tests: -- Benchmarks: -- Panic-attacker rerun: - -Artifacts: -- assail report: -- benchmark output: -- logs: - -Commit(s): -- SHA: - -Remaining Risks / Follow-ups: -1. -2. -``` - -## 12) Language-Repo Additions (Eclexia-Specific) - -Add these checks for language/compiler repositories with formal ABI/FFI constraints: - -- [x] README structure restored (index/TOC, audience paths, quickstart sanity). -- [x] Wiki split by audience (laypeople/users/developers) and linked from docs index. -- [x] Root-level clutter reduced (archive, analysis, reports relegated to `docs/` subtrees). -- [x] Machine-readable docs synchronized (`STATE.a2ml`, `META.a2ml`, `ECOSYSTEM.a2ml`, contractiles). -- [x] Human-readable docs synchronized (`README`, `QUICK_STATUS`, roadmap, wiki home). -- [x] `Mustfile` invariants present and enforceable in CI. -- [x] `Trustfile` and `Intentfile` present and complete. -- [x] FFI/ABI purity policy enforced (`*.zig` for FFI, `*.idr`/Idris2 for ABI). -- [x] `panic-attack` findings triaged with explicit severity budget for release. -- [x] Point-to-point, end-to-end, and benchmark checks wired in one quality gate. -- [x] CI workflows include quality + security + docs checks with explicit policy. -- [x] Release audit includes corrective/adaptive/perfective + Must/Should/Could. -- [x] Roadmap/status honesty pass completed (dates and current evidence updated). - -## 13) Latest Execution Record (Eclexia, 2026-02-24) - -Repo: `/tmp/eclexia-releaseprep` (branch `release-prep`, base `533ec9e9447f374135cc9e2e81021624ddb3c0ad`) - -### 13.1 Setup/Preflight - -- [x] Captured UTC timestamp and git state. -- [x] Tooling presence verified (`rg`, `git`, `jq`, `cargo`, `rustc`, `just`). -- [x] Runtime/toolchain versions captured. -- [x] Container tooling checked (`podman`, `podman-compose`). - -### 13.2 Corrective Maintenance - -- [x] Fixed `panic-attack` script path handling (`mktemp` output + local fallback binary detection). -- [x] Removed Idris `believe_me` usage from ABI wrappers. -- [x] Fixed conformance crash-noise path by skipping known intentional stack-overflow case in default runner. -- [x] Re-ran affected checks after each fix. - -### 13.3 Code-Health Scans - -- [x] TODO/FIXME/STUB/PARTIAL scan run on active code paths. -- [x] ABI/FFI file inventory run (`*.idr`, `*.zig`). -- [x] Active-code marker count reduced/triaged; remaining items tracked in release audit. - -### 13.4 Security/Panic Pass - -- [x] `panic-attack` run and triaged. -- [x] Critical findings cleared (Idris unsoundness markers removed). -- [x] Current baseline: 0 weak points (Critical 0, High 0, Medium 0, Low 0). -- [x] High/Medium backlog fully eliminated. - -### 13.5 Language Validation - -- [x] Final `just quality-gate` pass completed (docs, fmt, lint, unit, conformance, integration, p2p, e2e, bench). -- [x] Additional targeted reruns completed (`just test-conformance`, `just panic-attack`, `just docs-check`). - -### 13.6 Adaptive/Perfective/Docs - -- [x] README/wiki/docs structure and indexing restored. -- [x] Root tidy/relegation pass executed. -- [x] Roadmap/status honesty update performed with current date and evidence links. -- [x] Release audit created with corrective/adaptive/perfective + Must/Should/Could. -- [x] Full quality-gate rerun passed after hardening updates. -- [x] ABI/FFI extension lane added without breaking stable symbols (`ecl_abi_get_info`, `ecl_tracker_create_ex`, `ecl_tracker_snapshot`). -- [x] CI quality workflow now validates sibling `proven` repo presence and critical binding files. -- [x] Proven roadmap now includes explicit "critical core, not full rewrite" adoption guidance and flowchart. - -### 13.7 Outstanding Items (Explicit) - -- [x] Stable `v1.0.0` technical gate readiness met (quality + panic scan clean). -- [x] Parser/codegen/runtime panic-path hardening completed for scanner-flagged paths. -- [x] Non-eclexia `proven` library checked: already Idris2-first with Zig ABI bridge; no additional integration changes required in this run. -- [ ] Remote push blocked by token scope: GitHub rejected branch updates (`release-prep`, `release-prep-pushable`) due missing `workflow` OAuth scope. - -### 13.8 Artifacts - -- Release audit: `docs/reports/V1-READINESS-AUDIT-2026-02-24.md` -- Panic report: `/tmp/eclexia-panic-attack.KZ1jpC.json` (0 weak points) -- Final quality gate log: `/tmp/eclexia-quality-gate-final2.log` (plus post-change reruns via terminal sessions) -- Local commits: `88fa2af` (`release-prep`), `baa3d1c` (`release-prep-pushable`) + pending new commit from this pass - -## 12) LLM Operator Instructions - -Use this prompt with an LLM agent when you want the process run end-to-end: - -```text -Run the maintenance workflow for this repo using MAINTENANCE-CHECKLIST.md. - -Required behavior: -1. Run ~/Desktop/run-maintenance.sh first and collect the JSON report. -2. Triage report results by severity: fail > warn > pass. -3. Execute corrective maintenance first (fix regressions, panics, broken tests/commands). -4. Run TODO/FIXME/stub scan and address relevant items. -5. Run panic-attacker and fix findings in priority order; rerun to confirm. -6. Run language-specific checks (Rust/Python/Elixir) relevant to this repo. -7. Run benchmark/regression checks for touched hot paths. -8. Enforce permission policy: - - no group/world writable source files unless justified - - executable bit only where intended - - use .maintenance-perms-ignore for justified exceptions -9. Update docs/roadmap/checklist entries to reflect actual state. -10. Produce a final report using the template in MAINTENANCE-CHECKLIST.md. - -Constraints: -- Do not revert unrelated existing dirty changes. -- Stage and commit only scoped intended files. -- If blocked, state exactly what is blocked and why. -``` - -## 13) AI Execution Integrity Contract (Mandatory) - -Use this when delegating maintenance to any AI (Gemini/Claude/ChatGPT/etc.). - -```text -You must execute this maintenance run with strict integrity. - -Non-negotiable rules: -1. Do not claim any step is complete unless you actually ran it. -2. Do not silently skip checklist items. If skipped, state SKIPPED + exact reason. -3. For every check, provide evidence: - - command executed - - pass/fail/warn - - key output summary - - artifact/log path -4. If a command fails, stop claiming success and report the failure clearly. -5. After each fix, re-run the relevant failing check and report the rerun result. -6. Do not hide uncertainty. If unsure, say so and run additional verification. -7. Never mark “all done” while any fail/warn remains unexplained. -8. Do not make destructive or broad permission changes by default. - - permission changes must be audit-first - - use --fix-perms only with explicit intent -9. Final output must include: - - checklist coverage matrix (each item: PASS/FAIL/WARN/SKIPPED) - - unresolved risks - - exact next actions -10. Prioritize user safety and reputation: no “looks fine” claims without evidence. -``` - -Recommended enforcement line for AI prompts: - -```text -Fail closed: if evidence is missing for any checklist item, treat that item as NOT DONE. -``` - -## 14) Fleet Enrollment Automation (Gitbot + Hypatia) - -For centralized coverage across existing and new repos: - -```bash -cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet -just enroll-repos -``` - -Optional directive write-back to repos that already have `.machine_readable/`: - -```bash -cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet -just enroll-repos /var$REPOS_DIR true -``` - -Release hard gate from fleet: - -```bash -cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet -just maintenance-hard-pass /absolute/path/to/repo -``` diff --git a/czech-file-knife/docs/governance/README.adoc b/czech-file-knife/docs/governance/README.adoc deleted file mode 100644 index 3031484bc..000000000 --- a/czech-file-knife/docs/governance/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Governance Pillar (TSDM) diff --git a/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc b/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc deleted file mode 100644 index 7c43d5622..000000000 --- a/czech-file-knife/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Software Development Approach (General) -:toc: left -:toclevels: 2 - -This is the general operating policy for software development across repositories. - -== Core Sequence - -Always run work in this order: - -1. Scope first (Axis 1) -2. Maintenance second (Axis 2) -3. Audit third (Axis 3) - -== Axis Definitions - -=== Axis 1: Scope - -Priority order: `must > intend > like` - -Axis 1 output is a scoped assembly of work based on: - -* README, roadmap, status, CI/security docs -* marker scans (`TODO`, `FIXME`, `XXX`, `HACK`, `STUB`, `PARTIAL`) -* Idris unsoundness scan when Idris exists (`believe_me`, `assert_total`) -* docs honesty check (intent vs actual implementation) - -=== Axis 2: Maintenance - -Priority order: `corrective > adaptive > perfective` - -* Corrective: fix defects, regressions, breakage, security/safety failures -* Adaptive: reconcile scope changes, remove stale references, cull obsolete work -* Perfective: improve quality/clarity/performance only from the honest Axis 1 state - -=== Axis 3: Audit - -Priority order: `systems > compliance > effects` - -* Systems: required mechanisms exist and are operating -* Compliance: seams/compromises/exceptions are explicit, bounded, and do not drift -* Effects: benchmark and operational impact evidence is captured and reviewed - -Compliance scanner baseline: `panic-attack` + -Effects/ecological baseline: sustainabot-guided ecological checking - -== Generic Cleanup And Finish-Off - -At cycle end: - -* reduce root clutter to required control/entry files -* archive/remove stale or superseded work -* synchronize human and machine docs -* run compliance and effects audits with evidence capture -* produce Must/Should/Could summary and immediate next-actions list - -== Collaboration Rule - -Effects review must include explicit maintainer dialogue: - -* what changed -* why it changed -* what risks remain diff --git a/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc b/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc deleted file mode 100644 index 963167e26..000000000 --- a/czech-file-knife/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -= Template Lineage Audit — self-identity drift in minted repositories -:toc: macro -:toclevels: 3 -:sectnums: - -*Measured 2026-09-15 against the live estate.* This document records what the -template's self-identity repair passes do, what they provably did *not* do to -repositories minted before they existed, and — just as importantly — which of -the measurements behind those claims are reliable and which are not. - -This file is deliberately placed in `docs/governance/` alongside -`TEMPLATE-STANDARDS-AUDIT.adoc`, and is deliberately added to the self-name -pass's skip list in `build/just/repo-init.just`. A document *about* the -literal `rsr-template-repo` must not have that literal rewritten inside it -when a child is minted, or every minted copy becomes nonsense. - -toc::[] - -== Summary - -Two mint-time repair passes in `build/just/repo-init.just` exist to stop a -freshly minted repository from declaring that it *is* the template. Both work. -Neither has ever been applied retroactively, so repositories minted before the -passes landed still carry the template's identity. - -The live, directly verified population is *small* — eight repositories, in two -overlapping fault classes — and is a *floor*, not a total. An earlier estimate -of seventeen affected repositories was produced by trusting a code-search -result and is *wrong*; see <>. - -== Root cause: a literal that cannot be a token - -The template's own comment states the mechanism, and it is worth quoting -because it is more precise than any paraphrase: - -[quote, 'build/just/repo-init.just, Self-name pass (ADR-0003)'] -____ -The template's own name is written as a LITERAL, not as a placeholder token, -so the substitution loop above never touched it. Measured on a scratch mint of -346ae56: a repo minted as `mint-check-lib` still carried -`project := "rsr-template-repo"`, `REPO := "rsr-template-repo"`, -`sonar.projectKey=hyperpolymath_rsr-template-repo`, -`STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo"` … i.e. it declared -that it WAS the template, and would have reported its code analysis into the -TEMPLATE's SonarCloud project. -____ - -The ordinary mint path rewrites `hyperpolymath`, `czech-file-knife` and friends. Those -tokens *are* consumed widely — 56 files in this repository, including the -README badge block, `CITATION.cff`, `.github/CODEOWNERS`, -`.github/settings.yml` and the three AI-assistant rule files. The problem is -confined to files that cannot carry a `{{...}}` token, which is why it looks -arbitrary from the outside. - -== The two repair passes, and the exact limit of each - -Both live in `build/just/repo-init.just` and both run *only at mint time*. - -=== Self-name pass - -Guarded by `if [ "$REPO" != "rsr-template-repo" ]`. Rewrites the template -literal to the child's name across the tree, with two protections: - -* Paths that are *about* the template are skipped wholesale — `.git`, - `build/just/repo-init.just` itself, `docs/decisions/*`, - `.machine_readable/descriptiles/{CLADE,VARIANT}.a2ml`, `CHANGELOG.md`, - `TEMPLATE-STANDARDS-AUDIT.adoc`, and binary extensions. -* Within every other file, any line naming the parent *as a parent* survives - byte-for-byte, matched by - `instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: `. - -It uses `sed -i` deliberately — the comment records why: ``sed -i` preserves -the mode bit; rewriting via a temp file would not.` - -=== Self-OWNER pass - -Guarded by `if [ "$OWNER" != "hyperpolymath" ]`, and *deliberately narrow* — -exactly three lines: the `Justfile` `OWNER :=` declaration, -`sonar.organization` and `sonar.projectKey`. The reasoning is sound and should -not be "improved" into a blanket rewrite: - -[quote, 'build/just/repo-init.just'] -____ -A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: -`hyperpolymath/standards`, `hyperpolymath/proven` and the other estate -dependencies are real repos every child genuinely points at. -____ - -== Measured: what is actually wrong in the estate today - -All rows below were read *directly* from each repository's -`sonar-project.properties` via the contents API, not inferred from search. - -=== Class 1 — self-name pass never applied (4 repositories) - -These declare the template's project key, so their analyses are configured to -land in the template's own SonarCloud project. - -[cols="2,3", options="header"] -|=== -| Repository | `sonar.projectKey` -| `hyperpolymath/first-post` | `hyperpolymath_rsr-template-repo` -| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` -| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` -| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` -|=== - -NOTE: "configured to report into the template's project" is what has been -measured. No analysis has been *observed* landing there; the SonarCloud side -was not inspected. - -=== Class 2 — self-OWNER pass never applied (7 repositories) - -`metadatastician`-owned repositories carrying `sonar.organization=hyperpolymath`. - -[cols="2,3", options="header"] -|=== -| Repository | `sonar.projectKey` -| `metadatastician/cadastra` | `hyperpolymath_cadastra` -| `metadatastician/chronicles-of-slavia` | `hyperpolymath_chronicles-of-slavia` -| `metadatastician/harvard-dehallucinator`| `hyperpolymath_harvard-dehallucinator` -| `metadatastician/IDApTIK` | `hyperpolymath_IDApTIK` -| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` -| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` -| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` -|=== - -The last three are also Class 1. The union is *eight distinct repositories*: -one Class-1-only, four Class-2-only, three in both. - -*Negative control:* `metadatastician/pong-ping` reads -`sonar.organization=metadatastician` / `sonar.projectKey=metadatastician_pong-ping` -— correct on both axes, and correctly absent from both tables. - -[[method]] -== Method, and three ways the measurement lied - -This section exists because the first version of this audit was wrong, and the -way it was wrong is reusable. - -=== Failure 1 — a false zero on punctuation - -`gh search code --owner hyperpolymath --owner metadatastician 'REPO := "rsr-template-repo"' --filename Justfile` -returns *zero repositories*. That string had already been read directly at -`rsr-julia-library-template-repo/Justfile:24`. GitHub code search does not do -exact substring matching across `:=`, spaces and quotes. - -⇒ *The `Justfile`-level population is unmeasured, not zero.* No count of it -appears in this document. - -=== Failure 2 — false positives from the recipe's own comment - -Searching for `sonar.projectKey=hyperpolymath_rsr-template-repo` returned 18 -repositories. Most matched in `build/just/repo-init.just` — the mint recipe, -which quotes that exact key *inside the comment documenting this defect*, and -which every minted child carries. Only five matched in an actual -`sonar-project.properties`, and one of those is the template itself. - -Two repositories the search listed (`metadatastician/pong-ping`, -`metadatastician/enaction-engine`) have entirely correct keys today. - -⇒ *A search hit is a hit on a byte sequence, not on a fact.* Every row in the -tables above was re-read from the file that would actually be consumed. - -=== Failure 3 — counts that are not defect counts - -A case-insensitive search for banned runtimes (`rescript`, `deno`, -`typescript`) in `rsr-julia-library-template-repo` returns 39 files; with word -boundaries, 38. *This is not a defect count.* At least 17 of those files -contain ban/forbid language — they name the runtime precisely in order to -prohibit it, `.github/workflows/runtime-policy.yml` most obviously. Files that -look like genuine *use* rather than prohibition, and so need individual -judgement, are `examples/web-project-deno.json`, `mise.toml`, -`container/compose.yaml` and `.github/workflows/release.yml`. - -⇒ Recorded here as a pointer for a human, deliberately without a headline -number. - -== Related findings - -=== `rsr-julia-library-template-repo` misreports its own name - -Both `Justfile:24` and `.machine_readable/contractiles/Justfile:24` declare -`REPO := "rsr-template-repo"`, while -`.machine_readable/descriptiles/CLADE.a2ml:9` correctly says -`canonical-name = "rsr-julia-library-template-repo"`. `OWNER :=` is correct in -both files. - -*Severity is lower than it appears.* Across every `Justfile` and `*.just` in -that repository there are *zero* `hyperpolymath` or `czech-file-knife` interpolations, so -neither variable is consumed by any recipe. This is a wrong self-description -with no runtime effect — worth fixing because a template propagates its own -mistakes, not because anything currently misbehaves. - -=== Layout migration lag, not divergence - -This template used `machine-readable/`; `rsr-julia-library-template-repo` and -much of the estate use `.machine_readable/`. Commit `a983c00` ("chore(shape): -remake the repository layout for human legibility", #43, 2026-08-26, 219 files) -renamed `.machine_readable/` to `machine-readable/` here, on a legibility -argument. That rename was **reversed on 2026-09-17 by owner ruling**: dotted is -the standard everywhere. The direction of "lag" therefore inverted — the -repositories still on the hyphenated spelling are now the ones that have not -followed, and `scripts/check-root-shape.sh` continues to resolve both spellings -so those repositories keep working rather than exiting 2. - -Estate floors at the time of the 2026-08 rename, from code search and therefore -subject to <>: 48 repositories on `.machine_readable/`, 9 on -`machine-readable/`. The majority was already dotted when this template -diverged from it. - -== What this document deliberately does not do - -* *It changes no other repository.* Rewriting `sonar-project.properties` - across eight repositories in two organizations is a fleet action, and fleet - campaigns are parked by standing decision. The tables above are the work - order if and when that is unparked. -* *It does not touch `.a2ml` manifests, `.deed` grammar or `k9` contracts.* -* *It does not widen the self-OWNER pass*, for the reason the recipe already - gives. -* *It does not claim the passes are buggy.* They are correct; they are simply - mint-time-only, and nothing has ever backfilled. - -== Provenance - -Measured 2026-09-15 against the live GitHub estate by direct contents-API -reads, with a stated negative control (`metadatastician/pong-ping`) and three -recorded search failures. Template state as of `cc76f4e`. diff --git a/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc b/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc deleted file mode 100644 index fcfba12b1..000000000 --- a/czech-file-knife/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Template Standards Audit -Codex -v1.0, 2026-04-07 -:toc: -:toclevels: 3 -:sectnums: - -== Scope And Inventory - -Audit scope: - -* Repository: `rsr-template-repo` -* Focus: root authority docs/manifests, Justfile integration, session bindings -* Recursive inventory method: `rg --files -g '!.git' | sort` - -Inventory snapshot at audit time: - -* `rsr-template-repo` total tracked files discovered: `240` -* Session-local binding files discovered: `4` under `session/` plus `coordination.k9` - -== Claim Vs Actual - -[cols="2,2,3,1,3,3",options="header"] -|=== -| Claimed item | Claimed by | Expected path | Actual status | Notes | Recommended action - -| Placeholder badge tokens in README -| `README.adoc` -| placeholder -| Template placeholders are intentional pre-bootstrap content. -| Keep; document clearly as template tokens. - -| Docs links used `.adoc` files not present -| legacy `README.adoc` (pre-migration) -| `CONTRIBUTING.adoc`, `GOVERNANCE.adoc`, `SECURITY.adoc` -| outdated -| Actual files are `CONTRIBUTING.md`, no root `GOVERNANCE.adoc`, `SECURITY.md`. -| Fixed README links to existing files. - -| ABI path lower-case only -| legacy `README.adoc` + legacy checks -| `src/interface/abi/*.idr` -| outdated -| Tree previously shipped `src/interface/Abi/*.idr` (uppercase A); now renamed. -| Renamed to canonical lowercase `src/interface/abi/*.idr` (matches `validate-template.sh` + gossamer). Checks remain tolerant of either case. - -| Root manifest structure with non-existent files -| legacy `0-AI-MANIFEST.a2ml` (pre-migration) -| `GOVERNANCE.adoc`, several strict root assumptions -| outdated -| Manifest claims did not match actual template contents. -| Replaced with accurate authority split + startup checklist. - -| Source-human references maintenance/practice docs -| legacy Justfile + policy A2ML (pre-migration) -| `docs/maintenance/...`, `docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` -| outdated -| Those paths did not exist in template. -| Updated to `docs/governance/...` paths. - -| Session-management local binding files -| target architecture -| `session/README.md`, `session/custom-checks.k9`, `session/local-hooks.sh`, `coordination.k9` -| exists -| Added as thin integration layer without protocol duplication. -| Keep. - -| Canonical command mapping in local automation -| target architecture -| `Justfile` session aliases + `session/dispatch.sh` -| exists -| All canonical commands map to dispatcher. -| Keep. - -| Central protocols are authoritative -| `README.adoc`, `0-AI-MANIFEST.a2ml`, `AUDIT.adoc` -| `standards/3-practice/session-management-standards/` -| exists -| Local docs now explicitly defer protocol authority to standards repo. -| Keep. - -| Runtime session artifacts stay per-repo -| `session/README.md`, `session/dispatch.sh` -| `.session/` in target repo path -| exists -| Dispatcher records canonical commands into runtime `.session/` files. -| Keep runtime artifacts out of authoritative standards docs. - -| Local policy hooks remain local -| `session/local-hooks.sh`, `session/custom-checks.k9` -| local session binding layer -| exists -| Policy/hook logic separated from central protocol definitions. -| Keep local-only. -|=== - -== Classification - -=== Authoritative Shared Standard - -* External to this repo: `standards/3-practice/session-management-standards/*` - -=== Repo-Local Binding/Integration - -* `Justfile` canonical session aliases -* `session/dispatch.sh` -* `session/custom-checks.k9` -* `session/local-hooks.sh` -* `session/README.md` -* `coordination.k9` -* `AUDIT.adoc` (local gate summary) - -=== Generated Runtime Artifact - -* `.session/*` outputs created by local dispatcher per repository path - -=== Obsolete/Duplicate/Drifted - -* Legacy path assumptions (`docs/maintenance/*`, `docs/practice/*` for governance policy references) -* Legacy root-doc claims to files not present in this template -* Legacy lower-case-only ABI path references - -== Move/Stay/Delete Guidance - -=== Move Into `standards` - -* Any future full protocol text drafts should move to - `standards/3-practice/session-management-standards/`. - -=== Stay In `rsr-template-repo` - -* Local aliases, hooks, coordination wiring, and repo-local checks. - -=== Delete/Archive - -* Archive or remove legacy references to non-existent docs/paths if reintroduced. -* Avoid reintroducing full duplicated protocol definitions. - -== Missing-But-Expected Session Files (Template Repo) - -Template repo expected only thin local integration files. - -Current status: - -* No mandatory thin-binding files are missing. -* Full protocol directories/files are intentionally absent here by design. - -== Proposed Final Directory Map - -[source,text] ----- -rsr-template-repo/ - README.adoc - AUDIT.adoc - 0-AI-MANIFEST.a2ml - EXPLAINME.adoc - Justfile - coordination.k9 - session/ - README.md - dispatch.sh - custom-checks.k9 - local-hooks.sh - docs/ - ... (repo-local human docs) - .machine_readable/ - ... (repo-local machine-readable policy/state) ----- - -== Maintenance Model Note - -* Protocols central: maintained in `standards/3-practice/session-management-standards/`. -* Policy local: maintained in template/downstream repos (`session/*.k9`, hooks, - coordination bindings). -* State per-repo: generated during execution (`.session/*`) in the active - working repository. diff --git a/czech-file-knife/docs/governance/TSDM.a2ml b/czech-file-knife/docs/governance/TSDM.a2ml deleted file mode 100644 index f27036cc7..000000000 --- a/czech-file-knife/docs/governance/TSDM.a2ml +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [TSDM_SPEC] -id: "tsdm-standard" -version: "1.0.0" - -axes: - axis_1: - name: "Planning" - levels: ["must", "should", "could"] - axis_2: - name: "Maintenance" - levels: ["corrective", "adaptive", "perfective"] - axis_3: - name: "Audit" - levels: ["systems", "compliance", "effects"] - -invariants: - - "Every task MUST map to at least one TSDM coordinate" - - "Axis 1 priority governs resource allocation" - - "Axis 2 type governs commit categorisation" - - "Axis 3 focus governs audit depth" diff --git a/czech-file-knife/docs/governance/TSDM.adoc b/czech-file-knife/docs/governance/TSDM.adoc deleted file mode 100644 index 42899ec6b..000000000 --- a/czech-file-knife/docs/governance/TSDM.adoc +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Triaxial Software Development Methodology (TSDM) -:toc: preamble -:icons: font - -TSDM is a three-dimensional governance framework designed for high-assurance, long-lived software systems. It ensures that every project decision is mapped across three critical axes: Planning, Maintenance, and Audit. - -== The Three Axes - -=== Axis 1: Planning (Scope Priority) -* **Must:** Non-negotiable core invariants and safety requirements. -* **Should:** Essential features and planned improvements. -* **Could:** Desired enhancements and future-proofing. - -=== Axis 2: Maintenance (Execution Type) -* **Corrective:** Fixing bugs, vulnerabilities, and failures. -* **Adaptive:** Responding to environment or dependency changes. -* **Perfective:** Improving performance, refactoring, and documentation. - -=== Axis 3: Audit (Verification Focus) -* **Systems:** Integrity of tools, infrastructure, and automation. -* **Compliance:** Adherence to standards, licenses, and verified seams. -* **Effects:** Real-world impact, ecological footprint, and user feedback. - -== Integration - -TSDM is the operational core of the Rhodium Standard. Every task in the `Justfile` and every state change in `STATE.a2ml` should be justifiable within this framework. diff --git a/czech-file-knife/docs/governance/audit/README.adoc b/czech-file-knife/docs/governance/audit/README.adoc deleted file mode 100644 index a0b0b256f..000000000 --- a/czech-file-knife/docs/governance/audit/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Audit Axis diff --git a/czech-file-knife/docs/governance/audit/compliance/README.adoc b/czech-file-knife/docs/governance/audit/compliance/README.adoc deleted file mode 100644 index aa01a9afb..000000000 --- a/czech-file-knife/docs/governance/audit/compliance/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Compliance Unit diff --git a/czech-file-knife/docs/governance/audit/effects/README.adoc b/czech-file-knife/docs/governance/audit/effects/README.adoc deleted file mode 100644 index f34e583c6..000000000 --- a/czech-file-knife/docs/governance/audit/effects/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Effects Unit diff --git a/czech-file-knife/docs/governance/audit/systems/README.adoc b/czech-file-knife/docs/governance/audit/systems/README.adoc deleted file mode 100644 index 70ef18cc3..000000000 --- a/czech-file-knife/docs/governance/audit/systems/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Systems Unit diff --git a/czech-file-knife/docs/governance/maintenance/README.adoc b/czech-file-knife/docs/governance/maintenance/README.adoc deleted file mode 100644 index 2cb875f11..000000000 --- a/czech-file-knife/docs/governance/maintenance/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Maintenance Axis diff --git a/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc b/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc deleted file mode 100644 index ea4269e37..000000000 --- a/czech-file-knife/docs/governance/maintenance/adaptive/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Adaptive Unit diff --git a/czech-file-knife/docs/governance/maintenance/corrective/README.adoc b/czech-file-knife/docs/governance/maintenance/corrective/README.adoc deleted file mode 100644 index 7a045985a..000000000 --- a/czech-file-knife/docs/governance/maintenance/corrective/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Corrective Unit diff --git a/czech-file-knife/docs/governance/maintenance/perfective/README.adoc b/czech-file-knife/docs/governance/maintenance/perfective/README.adoc deleted file mode 100644 index 1bf8f4288..000000000 --- a/czech-file-knife/docs/governance/maintenance/perfective/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Perfective Unit diff --git a/czech-file-knife/docs/governance/planning/README.adoc b/czech-file-knife/docs/governance/planning/README.adoc deleted file mode 100644 index 676460466..000000000 --- a/czech-file-knife/docs/governance/planning/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Planning Axis diff --git a/czech-file-knife/docs/governance/planning/could/README.adoc b/czech-file-knife/docs/governance/planning/could/README.adoc deleted file mode 100644 index 06863c897..000000000 --- a/czech-file-knife/docs/governance/planning/could/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Could Unit diff --git a/czech-file-knife/docs/governance/planning/must/README.adoc b/czech-file-knife/docs/governance/planning/must/README.adoc deleted file mode 100644 index 990c563de..000000000 --- a/czech-file-knife/docs/governance/planning/must/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Must Unit diff --git a/czech-file-knife/docs/governance/planning/should/README.adoc b/czech-file-knife/docs/governance/planning/should/README.adoc deleted file mode 100644 index 8c9ea245f..000000000 --- a/czech-file-knife/docs/governance/planning/should/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Should Unit diff --git a/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt b/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt deleted file mode 100644 index b873155f0..000000000 --- a/czech-file-knife/docs/legal/EXHIBIT-A-ETHICAL-USE.txt +++ /dev/null @@ -1,20 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 - -================================================================================ -EXHIBIT A — SOURCE CODE FORM LICENSE NOTICE -Mozilla Public License Version 2.0 -================================================================================ - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file) where -a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -================================================================================ -END OF EXHIBIT A -================================================================================ diff --git a/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt b/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt deleted file mode 100644 index 81f928878..000000000 --- a/czech-file-knife/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt +++ /dev/null @@ -1,104 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 - -================================================================================ -QUANTUM-SAFE PROVENANCE SPECIFICATION -Rhodium Standard Repository (RSR) — Exhibit B -================================================================================ - -1. PURPOSE - - This exhibit specifies the cryptographic algorithms and procedures for - quantum-safe provenance of contributions in an RSR-compliant repository. - It is a standalone, licence-agnostic specification: it imposes no licence - terms of its own and may be referenced by software under any licence - (this repository is MPL-2.0 for code, CC-BY-SA-4.0 for prose). - -2. APPROVED ALGORITHMS - - The following post-quantum cryptographic algorithms are approved for - signing Provenance Metadata: - - 2.1. Digital Signatures - - ML-DSA (FIPS 204, formerly CRYSTALS-Dilithium) - Recommended: ML-DSA-65 (security level 3) or ML-DSA-87 (level 5) - - SLH-DSA (FIPS 205, formerly SPHINCS+) - Recommended: SLH-DSA-SHA2-256f or SLH-DSA-SHAKE-256f - - FALCON (NIST Round 3 finalist) - Recommended: FALCON-1024 - - 2.2. Key Encapsulation (for encrypted provenance) - - ML-KEM (FIPS 203, formerly CRYSTALS-Kyber) - Recommended: ML-KEM-1024 - - 2.3. Hash Functions - - SHA-3 (FIPS 202) - Recommended: SHA3-256 or SHA3-512 - - SHAKE (FIPS 202 extendable output) - Recommended: SHAKE-256 - - 2.4. Key Derivation - - Argon2id (RFC 9106) - Parameters: t=3, m=65536, p=4 (minimum) - -3. PROVENANCE METADATA FORMAT - - Provenance Metadata should include: - - 3.1. Required Fields - - author-identity: Contributor name and contact - - timestamp: ISO 8601 with timezone - - content-hash: SHA3-256 hash of the contribution - - signature: Quantum-safe signature over all fields - - 3.2. Optional Fields - - parent-hash: Hash of the previous contribution in the chain - - context-notes: Narrative context markers - - platform: Build/development environment - - witnesses: Third-party attestation signatures - -4. SIGNATURE PROCEDURE - - 4.1. Signing - a. Compute SHA3-256 hash of the contribution content - b. Construct metadata record with all required fields - c. Serialize metadata in canonical JSON form - d. Sign with ML-DSA-65 (or approved alternative) - e. Attach signature to distribution - - 4.2. Verification - a. Extract metadata and signature from distribution - b. Verify signature against contributor's public key - c. Verify content hash matches actual content - d. Verify timestamp is within acceptable range - e. Verify parent-hash chain if present - -5. KEY MANAGEMENT - - 5.1. Contributors should publish quantum-safe public keys via: - - OpenPGP keyservers (with PQ algorithm support) - - Repository www/.well-known/keys/ directory (served at /.well-known/keys/) - - Contributor's personal website - - 5.2. Key rotation should occur: - - At least annually - - When algorithm recommendations change - - When key compromise is suspected - -6. TRANSITION PERIOD - - During the transition to quantum-safe cryptography: - - 6.1. Classical signatures (Ed25519, RSA) remain valid - 6.2. Hybrid signatures (classical + PQ) are encouraged - 6.3. Pure PQ signatures are preferred for new contributions - 6.4. Classical-only signatures will be deprecated in a future version - -7. COMPLIANCE - - Quantum-safe provenance is OPTIONAL. When present, it must follow this - specification, and quantum-safe signatures should not be stripped from - distributions that carry them. - -================================================================================ -END OF EXHIBIT B -================================================================================ diff --git a/czech-file-knife/docs/legal/PALIMPSEST.adoc b/czech-file-knife/docs/legal/PALIMPSEST.adoc deleted file mode 100644 index e9d2df3a4..000000000 --- a/czech-file-knife/docs/legal/PALIMPSEST.adoc +++ /dev/null @@ -1,41 +0,0 @@ -= Palimpsest License -:toc: -:toc-placement!: - -image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: PMPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] -image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] - -toc::[] - -== Legal Status - -This project is licensed under the **Palimpsest-MPL License 1.0 (PMPL-1.0)**. -For SPDX and tooling, use **PMPL-1.0-or-later**. - -PMPL-1.0 incorporates the Mozilla Public License 2.0 by reference and adds -ethical-use, provenance, and lineage requirements. - -== What PMPL Adds - -* **Emotional Lineage** - preserve narrative intent and cultural context -* **Provenance Integrity** - retain attribution and lineage metadata -* **Ethical Use Constraints** - explicit consent for non-interpretive AI training -* **Quantum-Safe Provenance (optional)** - post-quantum signature support - -== How to Adopt - -1. Include the PMPL-1.0 license text in `LICENSE`. -2. Add SPDX headers to source files: - `SPDX-License-Identifier: CC-BY-SA-4.0` -3. Add a Palimpsest badge to your README (see `assets/badges/` and `embed/license-blocks/`). - -== Versioning - -See `VERSIONING.adoc` for the release process and the "-or-later" model. -The current legal text is PMPL-1.0. - -== References - -* `legal/README.adoc` -* `assets/badges/README.md` -* `embed/license-blocks/README.md` diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc deleted file mode 100644 index 2dc2c0df3..000000000 --- a/czech-file-knife/docs/onboarding/QUICKSTART-DEV.adoc +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-DEV.adoc — clone → build → test → PR -= czech-file-knife — Quick Start for Developers -:toc: -:toclevels: 2 - -== Tech Stack - -Rust (2021 edition, MSRV 1.75) Cargo workspace under `src/cfk-*`; tokio async runtime; -Swift bridge for the iOS File Provider (`src/cfk-ios`); Ada TUI prototype (`src/cfk-tui`). - -== Set Up Development Environment - -=== Option A: Guix (preferred) - -[source,bash] ----- -guix shell ----- - -=== Option B: Manual - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife -just setup-dev ----- - -== Build - -[source,bash] ----- -just build # cargo build --workspace ----- - -== Test - -[source,bash] ----- -just test # cargo test --workspace ----- - -== Project Structure - -[source] ----- -czech-file-knife/ -├── src/cfk-*/ # Cargo workspace crates (core, cli, providers, cache, search, vfs, ...) -├── tests/ # Test suite -├── docs/ # Documentation -├── .machine_readable/ # Checkpoint files (STATE, META, ECOSYSTEM) -├── Justfile # Task runner recipes -├── build/ # Build orchestration (just/, guix.scm, container/) -└── *_chora.deed # repo deed: AI entry point (allocation + ply tree) ----- - -== Key Recipes - -[source,bash] ----- -just build # Build the project -just test # Run tests -just doctor # Self-diagnostic -just lint # Lint and format -just panic-scan # Security scan via panic-attacker -just tour # Guided tour of the codebase ----- - -== Before Submitting a PR - -[source,bash] ----- -just lint # Format and lint -just test # All tests pass -just panic-scan # No new security issues ----- - -== Contractile Invariants - -Read `.machine_readable/MUST.contractile` before making changes. -Key invariants that must never be violated: - -* No destructive operation without a recorded inverse, unless the user explicitly opts out (`CFK_NO_JOURNAL=1`). -* Never stage cloud-to-cloud transfers on local disk; prefer provider-side operations. -* Space-constrained transforms never free an input range before its replacement is durable. - -== LLM/AI Agent Development - -If using an AI assistant, load the warmup context first: - -[source,bash] ----- -just llm-context # Outputs role-appropriate context ----- - -Or read the repo deed (`*_chora.deed` at root) and `.claude/CLAUDE.md` directly. - -== Get Help - -* **Architecture**: link:EXPLAINME.adoc[EXPLAINME.adoc] -* **Wiki**: https://github.com/hyperpolymath/czech-file-knife/wiki -* **Report issue**: `just help-me` diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc deleted file mode 100644 index c1be86aed..000000000 --- a/czech-file-knife/docs/onboarding/QUICKSTART-MAINTAINER.adoc +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-MAINTAINER.adoc — packaging, deploying, and maintaining -= czech-file-knife — Quick Start for Platform Maintainers -:toc: -:toclevels: 2 - -== Overview - -This guide covers packaging, deploying, and maintaining czech-file-knife for -distribution on your platform. - -== Runtime Dependencies - -None for the core CLI. Optional: `libfuse3` (FUSE mounting, `cfk-vfs`), `aria2c`, `agrep`, `pandoc` (integrations). - -== Build from Source - -[source,bash] ----- -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife -just build-release ----- - -Output: `target/release/cfk` - -== Packaging - -=== Guix - -[source,bash] ----- -guix build -f build/guix.scm ----- - -=== Container (Stapeln) - -[source,bash] ----- -just stapeln-export # Generates Containerfile -podman build -t czech-file-knife . ----- - -=== Manual Package - -[source,bash] ----- -just install --prefix=/usr/local ----- - -Files installed: - -[cols="1,2"] -|=== -| Path | Contents - -| `$PREFIX/bin/` -| Executables - -| `$PREFIX/share/cfk/` -| Data files, assets - -| `$PREFIX/share/doc/cfk/` -| Documentation - -| `$PREFIX/share/applications/` -| .desktop file (Linux, if GUI) - -| `$PREFIX/share/man/man1/` -| Man pages -|=== - -== Configuration - -Default config location: `$XDG_CONFIG_HOME/cfk/config.toml` - -Fallback: `$HOME/.config/cfk/config.toml` - -== Health Checks - -[source,bash] ----- -just doctor # Full diagnostic -just run --version # Version check -just run --selftest # Built-in self-test ----- - -== Updating - -[source,bash] ----- -git pull -just build-release -just install --prefix=/usr/local ----- - -Or via OPSM: `opsm update cfk` - -== Security Notes - -* License: MPL-2.0 (code) / CC-BY-SA-4.0 (docs) -* All dependencies SHA-pinned -* `panic-attacker` scan results: link:INSTALL-SECURITY-REPORT.adoc[] -* OpenSSF Scorecard: see badge in README - -== Multi-Instance Deployment - -For deploying multiple instances (e.g., different users or tenants): - -[source,bash] ----- -just install --prefix=/opt/cfk-instance1 --config=/etc/cfk/instance1.toml -just install --prefix=/opt/cfk-instance2 --config=/etc/cfk/instance2.toml ----- - -Each instance has isolated config, data, and logs. - -== Reporting Issues - -* Upstream: https://github.com/hyperpolymath/czech-file-knife/issues -* With diagnostic: `just help-me` (pre-fills context) diff --git a/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc b/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc deleted file mode 100644 index 8d9759cdd..000000000 --- a/czech-file-knife/docs/onboarding/QUICKSTART-USER.adoc +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-USER.adoc — 5-minute path to working software -// Replace czech-file-knife, Rsr Template Repo — See README.adoc for details., just run, Rsr Template Repo started successfully. with actuals -= czech-file-knife — Quick Start for Users -:toc: -:toclevels: 2 - -== What is czech-file-knife? - -Rsr Template Repo — See README.adoc for details. - -== Prerequisites - -Before you begin, ensure you have: - -* **just** — task runner (https://github.com/casey/just[install guide]) -* Platform-specific requirements listed below - -[cols="1,3"] -|=== -| Platform | Additional Requirements - -| Linux -| See README.adoc - -| macOS -| See README.adoc - -| Windows -| See README.adoc -|=== - -== Install - -=== Option 1: Standard Install (recommended) - -[source,bash] ----- -# Clone and set up -git clone https://github.com/hyperpolymath/czech-file-knife.git -cd czech-file-knife -just setup ----- - -The setup script will: - -* Detect your platform and shell -* Install missing dependencies (with your permission) -* Configure the application -* Offer install location choices -* Run a self-diagnostic to verify everything works - -=== Option 2: Container (via Stapeln) - -[source,bash] ----- -just stapeln-run ----- - -=== Option 3: Portable (no system changes) - -[source,bash] ----- -just install --portable --prefix=./czech-file-knife-portable ----- - -== First Run - -[source,bash] ----- -just run ----- - -Expected output: - -[source] ----- -Rsr Template Repo started successfully. ----- - -== Self-Diagnostic - -If something isn't working: - -[source,bash] ----- -just doctor ----- - -This checks all dependencies, permissions, paths, and connectivity. -If it finds issues, it will suggest fixes. - -To attempt automatic repair: - -[source,bash] ----- -just heal ----- - -== Get Help - -* **In-app**: `just run --help` -* **Guided tour**: `just tour` -* **Report a problem**: `just help-me` (pre-fills diagnostic context) -* **Wiki**: https://github.com/hyperpolymath/czech-file-knife/wiki - -== Uninstall - -[source,bash] ----- -just uninstall ----- - -You will be asked: - -1. Which uninstall tier (Bennett reversible, parameter-based, standard, or secure) -2. Whether to include or exclude your data -3. Whether to clear caches and LLM models - -== Next Steps - -* Read the link:README.adoc[README] for full feature overview -* Read the link:EXPLAINME.adoc[EXPLAINME] for architecture and design decisions -* Try `just tour` for a guided walkthrough diff --git a/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc b/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc deleted file mode 100644 index 00dc863b3..000000000 --- a/czech-file-knife/docs/onboarding/llm-warmup-dev.adoc +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= LLM Warmup — czech-file-knife (Developer) - -== What is czech-file-knife? - -See `README.adoc` for overview. - -== Key Commands - -* `just setup` — set up development environment -* `just build` — build the project -* `just test` — run tests -* `just doctor` — diagnose issues -* `just heal` — attempt auto-repair - -== Quick Context - -* License: MPL-2.0 -* Part of hyperpolymath ecosystem -* See `EXPLAINME.adoc` for architecture diff --git a/czech-file-knife/docs/onboarding/llm-warmup-user.adoc b/czech-file-knife/docs/onboarding/llm-warmup-user.adoc deleted file mode 100644 index 1c2d69661..000000000 --- a/czech-file-knife/docs/onboarding/llm-warmup-user.adoc +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= LLM Warmup — czech-file-knife (User) - -== What is czech-file-knife? - -See `README.adoc` for overview. - -== Key Commands - -* `just setup` — set up development environment -* `just build` — build the project -* `just test` — run tests -* `just doctor` — diagnose issues -* `just heal` — attempt auto-repair - -== Quick Context - -* License: MPL-2.0 -* Part of hyperpolymath ecosystem -* See `EXPLAINME.adoc` for architecture diff --git a/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc b/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc deleted file mode 100644 index 467e0abee..000000000 --- a/czech-file-knife/docs/practice/AI-CONVENTIONS.adoc +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AI Conventions -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -== AI Conventions (Authoritative Source) - -All AI coding agents working in this repository MUST follow these rules. -Per-tool config files (.cursorrules, .clinerules, etc.) reference this document. - -=== Session Startup - -1. Read the repo deed (`*_chora.deed` at root) FIRST (mandatory gatekeeper). -2. Read `.machine_readable/descriptiles/STATE.a2ml` for current status and blockers. -3. Read `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` for canonical authority boundaries. -4. Read `.machine_readable/policies/MAINTENANCE-AXES.a2ml` for maintenance/audit sequencing. -5. Read `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` for baseline controls. -6. Read `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` for execution order. -7. Read `.machine_readable/descriptiles/AGENTIC.a2ml` for agent constraints. - -=== License - -Per estate policy (hyperpolymath/standards `LICENCE-POLICY.adoc` Rule 1 + Addendum A8): - -- Code / config / scripts: `SPDX-License-Identifier: MPL-2.0`. -- Prose docs (`*.adoc`, `*.md` narrative): `SPDX-License-Identifier: CC-BY-SA-4.0`. -- GitHub shows only one repo licence: the root `LICENSE` is MPL-2.0 (so the - sidebar reads MPL-2.0); **both** canonical texts live in `LICENSES/` - (`MPL-2.0.txt` + `CC-BY-SA-4.0.txt`) and prose is CC-BY-SA-4.0 by its - per-file header, not a second root licence file. -- Fallback (platform-required only): MPL-2.0 with a comment explaining why. -- NEVER use AGPL-3.0 (son-shared repos are the only exception, per Rule 3 — not this template). -- Preserve third-party licenses verbatim; never relicense. -- Licence-header remediation on existing files is manual, owner-only — no - automated/bulk SPDX edits (Addendum A2). New files may carry the correct - SPDX from birth. - -=== Author Attribution - -- Name: **Jonathan D.A. Jewell** -- Email: **j.d.a.jewell@open.ac.uk** -- Copyright: `Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) ` - -=== State Files - -State/metadata files, anchors, and policies (.a2ml) belong in `.machine_readable/` ONLY. -NEVER create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, -NEUROSYM.a2ml, PLAYBOOK.a2ml, ANCHOR.a2ml, MAINTENANCE-AXES.a2ml, -MAINTENANCE-CHECKLIST.a2ml, or SOFTWARE-DEVELOPMENT-APPROACH.a2ml in the repository root. - -=== Banned Patterns - -|=== -| Language | Banned | Reason - -| Idris2 | `believe_me`, `assert_total` | Unsound escape hatches -| Haskell | `unsafeCoerce`, `unsafePerformIO` | Breaks type safety -| OCaml | `Obj.magic`, `Obj.repr`, `Obj.obj` | Unsafe casting -| Coq | `Admitted` | Unproven assumption -| Lean | `sorry` | Unproven assumption -| Rust | `transmute` (unless FFI + SAFETY:) | Unsound reinterpret -|=== - - -=== Banned Languages - -|=== -| Banned | Use Instead - -| | -| Node.js / npm / bun | -| Go | Rust -| Python | Julia / Rust -|=== - - -=== Container Standard - -- Runtime: **Podman** (never Docker). -- File: **Containerfile** (never Dockerfile). -- Base images: `cgr.dev/chainguard/wolfi-base:latest` or `cgr.dev/chainguard/static:latest`. - -=== ABI/FFI Standard - -- ABI definitions: **Idris2** with dependent types (`src/interface/Abi/`). -- FFI implementation: **Zig** with C ABI compatibility (`ffi/zig/`). -- Generated C headers: `generated/abi/`. - -=== Build System - -Use `just` (Justfile) for all build, test, lint, and format tasks. - -=== References - -- `*_chora.deed` (repo deed) -- universal AI entry point -- `.machine_readable/descriptiles/AGENTIC.a2ml` -- agent permissions and constraints -- `.machine_readable/descriptiles/STATE.a2ml` -- current project state -- `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` -- canonical authority and policy boundary -- `.machine_readable/policies/MAINTENANCE-AXES.a2ml` -- canonical axis sequencing and audit requirements -- `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` -- baseline maintenance checklist policy -- `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -- axis execution approach policy diff --git a/czech-file-knife/docs/practice/README.adoc b/czech-file-knife/docs/practice/README.adoc deleted file mode 100644 index 117fa892f..000000000 --- a/czech-file-knife/docs/practice/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= practice Unit diff --git a/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc b/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc deleted file mode 100644 index 02bff1b67..000000000 --- a/czech-file-knife/docs/practice/STATE-VISUALIZER-GUIDE.adoc +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= TOPOLOGY.md — Generation Guide -Jonathan D.A. Jewell (hyperpolymath) -:toc: -:sectnums: - -== What Is TOPOLOGY.md? - -A single-file visual map of any project's architecture and completion status. -It lives in the repo root and contains: - -1. **ASCII architecture diagram** — the full system as it will look when complete -2. **Completion dashboard** — every component with a progress bar and status note -3. **Dependency graph** — what blocks what (the critical path) -4. **Update protocol** — how to keep it current - -It is designed to be readable by humans, AI agents, and rendered cleanly on any -forge (GitHub, GitLab, Codeberg, Bitbucket). - -== Why - -- Gives any contributor (human or AI) an instant picture of the whole project -- Replaces "read 20 files to understand the architecture" with one glance -- The completion dashboard makes project health visible without running anything -- Works offline, no tooling required, just a text file - -== How To Generate One - -=== Option 1: Ask an AI agent - -Use this prompt (works with Claude, Gemini, ChatGPT, or any LLM with repo access): - -[source,text] ----- -Read the entire repository and produce a TOPOLOGY.md file for the repo root. - -The file must contain exactly three sections: - -1. **System Architecture** — An ASCII box diagram showing the complete system - as it will look when finished. Use Unicode box-drawing characters - (┌ ┐ └ ┘ │ ─ ├ ┤ ┬ ┴ ┼), arrows (▲ ▼ ◄ ► → ←), and double lines - (═ ║) for boundaries. Show: - - All external services (DNS, CDN, gateways) at the top - - Application components in the middle - - Data layer (databases, caches, queues) below - - Repo infrastructure (CI, contractiles, SCM files) at the bottom - - Every box labelled, every connection labelled or obvious from context - - The diagram should be BESPOKE to this project, not generic - -2. **Completion Dashboard** — A table in a code block listing every component - from the diagram. For each component show: - - Name (left-aligned, padded to 35 chars) - - Progress bar: 10 characters using █ (done) and ░ (remaining) - - Percentage (0% to 100% in 10% increments) - - A short note explaining the status - Group components by layer/concern. End with an OVERALL summary line. - -3. **Key Dependencies** — An ASCII arrow diagram showing the critical path. - What must finish before what else can start. - -Add a header comment with SPDX-License-Identifier and Last updated date. -End with an "Update Protocol" section explaining how to maintain the file. - -Use the template at TOPOLOGY.md in czech-file-knife as a structural reference, -but make the content completely specific to THIS project. ----- - -=== Option 2: Copy the template and fill it in - -[source,bash] ----- -cp /path/to/czech-file-knife/TOPOLOGY.md ./TOPOLOGY.md -# Then edit: replace placeholders, draw the real architecture, fill the dashboard ----- - -=== Option 3: Batch generation across all repos - -[source,bash] ----- -# From the repos root, generate for every repo that lacks one -for repo in /path/to/your/repos/*/; do - if [ ! -f "$repo/TOPOLOGY.md" ]; then - echo "NEEDS TOPOLOGY: $(basename $repo)" - fi -done ----- - -Then feed each repo to an AI agent with the prompt above. Claude Code can do -this with a session per repo, or you can batch it. - -== Conventions - -=== Box-drawing characters - -Use Unicode, not ASCII art. This renders correctly everywhere. - -[cols="1,1", options="header"] -|=== -| Character | Use -| `┌ ┐ └ ┘` | Box corners -| `│ ─` | Vertical / horizontal lines -| `├ ┤ ┬ ┴ ┼` | T-junctions and crosses -| `═ ║` | Double lines for major boundaries -| `▲ ▼ ◄ ►` | Directional arrows -| `→ ← ↑ ↓` | Thin arrows (alternative) -|=== - -=== Progress bars - -Always 10 characters wide. Use full blocks only (no half-blocks). - -[source,text] ----- -░░░░░░░░░░ 0% Not started -█░░░░░░░░░ 10% Stub/skeleton exists -██░░░░░░░░ 20% Early work -███░░░░░░░ 30% Foundation laid -████░░░░░░ 40% Core logic started -█████░░░░░ 50% Half done -██████░░░░ 60% Most logic complete -███████░░░ 70% Working but rough -████████░░ 80% Needs polish/docs -█████████░ 90% Nearly done -██████████ 100% Complete and tested ----- - -=== Component naming - -- Use the actual names from the codebase (file names, service names, tool names) -- Group by architectural layer, not alphabetically -- Include repo infrastructure (CI, contractiles, SCM files) as a layer - -=== When to update - -- After completing a component → change bar + percentage -- After adding a component → add row -- After architectural change → redraw diagram -- After major milestone → update overall percentage -- Always update the `Last updated` date - -== Integration With Other RSR Files - -TOPOLOGY.md complements but does not replace: - -- **STATE.a2ml** — machine-readable state (tasks, blockers, next actions) -- **ECOSYSTEM.a2ml** — position in the wider project ecosystem -- **META.a2ml** — architecture decisions and design rationale -- ***_chora.deed (repo deed)** — AI agent entry point: allocation policy and directory invariants - -TOPOLOGY.md is the _visual summary_ for humans; the a2ml files are the -_structured data_ for tooling. Both should agree. - -== Copyright - -Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) diff --git a/czech-file-knife/docs/practice/ci-cost-reduction.adoc b/czech-file-knife/docs/practice/ci-cost-reduction.adoc deleted file mode 100644 index 99a2a6239..000000000 --- a/czech-file-knife/docs/practice/ci-cost-reduction.adoc +++ /dev/null @@ -1,278 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= CI Cost Reduction — RSR Estate Spec -:toc: left -:toclevels: 3 - -Estate-wide playbook for reducing GitHub Actions minutes consumption -without sacrificing CI coverage. Born out of an incident where the -`hyperpolymath` Actions billing tripped and every runner-based workflow -in the estate went dark. The blocker was external, but the exposure — -how much we spend — was self-inflicted. This document captures the -knobs that exist, which ones are worth pulling, and in what order. - -Priority sort applied: *dependability > security > interop > usability -> performance > versatility > functional extension*. Cost reduction is -a dependability/performance concern; nothing here trades security for -savings. - -== Why this is worth doing - -A representative RSR repo (007) runs ~26 active workflows. On a single -push: - -* ~10-15 workflows fire in parallel. -* Several (scorecard, codeql, hypatia-scan) also fire on a schedule. -* `oracle-fuzz.yml` runs a 10-minute differential job every hour. -* Multiple workflows do `fetch-depth: 0` full-history clones. -* There are meaningful overlaps (trufflehog + gitleaks, multiple - security-gate jobs, codeql + hypatia-scan). - -Conservative saving potential from the five highest-leverage patterns -below: *60–80%* of current Actions-minutes, with no coverage loss. - -== Priority-ordered patterns - -=== 1. Concurrency kills (one-line, estate-wide) - -Every push-triggered workflow should cancel its superseded runs when -a rapid-fire commit lands: - -[source,yaml] ----- -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true ----- - -Add this at the workflow top level. Exceptions: release workflows, -deploy workflows, scheduled workflows — those need to finish. - -*Impact:* cancels obsolete runs immediately. On active branches with -many commits, easily 20–40% of current minutes. - -=== 2. Path filters on heavy workflows - -Rust CI, CodeQL, E2E, and language-specific test suites should not run -on docs-only or comment-only changes. - -[source,yaml] ----- -on: - push: - paths: - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/rust-ci.yml' - pull_request: - paths: - - 'src/**' - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' ----- - -For workflows whose scope is "the whole repo" (scorecard, codeql, -hypatia-scan): leave unfiltered. For language-specific jobs: filter. - -*Impact:* single largest saving on repos that get a lot of README or -issue-template churn. - -=== 3. Reduce schedule frequency - -Scheduled workflows should run as often as the decision they inform -actually changes — not more. - -[cols="2,1,1,2",options="header"] -|=== -| Workflow | Current | Recommended | Rationale - -| `oracle-fuzz.yml` (differential fuzz) -| Every 10 min -| Every 6h or nightly -| TRG §5.7.4 90-day differential-fuzzing clock measures *total time*, - not *frequency*. A nightly 10-min slice gives the same statistical - coverage at 1/144 the cost. - -| `scorecard.yml` -| Daily -| Weekly -| OSSF's own recommendation is weekly for stable repos. - -| `codeql.yml` (schedule branch) -| Daily -| Weekly, or remove schedule (relies on push trigger) -| CodeQL on every push already covers PR and main commits. A weekly - sweep catches newly-disclosed CVEs that affect existing code. - -| `hypatia-scan.yml` -| Weekly -| Keep weekly -| Correct cadence. Don't change. - -| `parser-fuzz.yml` (if present) -| Nightly, 5 min per target × 3 targets -| Keep -| Already minimal. -|=== - -=== 4. Overlap consolidation - -Several workflows cover overlapping ground. Consolidate where the -substitution is near-free: - -[cols="2,2,2",options="header"] -|=== -| Overlap | Resolution | Notes - -| `trufflehog` + `gitleaks` in `secret-scanner.yml` -| Keep one (gitleaks preferred for CVE coverage; trufflehog for - verified-only mode) -| Running both is belt-and-braces with ~90% coverage overlap. One is - enough for prevention; both only for quarterly history-sweeps. - -| `rsr-antipattern.yml` + `scorecard-enforcer.yml` + - `static-analysis-gate.yml` -| Merge into a single composite `rsr-gate.yml` with three steps -| They already run in the same CI slot; merging deduplicates - setup/checkout/clone. - -| `codeql.yml` + `hypatia-scan.yml` -| Keep both, but drop Hypatia from on-push; let it run only on - schedule -| Hypatia is the slower of the two, and its neurosymbolic analysis - doesn't need sub-minute latency on every commit. - -| `codeql.yml` (on-push) + `codeql.yml` (scheduled) -| Keep on-push, drop scheduled -| Redundant unless the repo rarely gets commits. -|=== - -=== 5. Shallow clones where full-history isn't needed - -Full-history clones (`fetch-depth: 0`) are expensive on large repos. -Required for: - -* Scorecard (history-based metrics). -* Gitleaks history-sweep mode. -* TruffleHog history-sweep mode. - -Not required for: - -* PR-event secret scanning (limit to the PR delta). -* Rust CI, language-specific tests. -* CodeQL (shallow is fine). -* Hypatia-scan (shallow is fine for pattern detection). - -Pattern for secret-scanner: - -[source,yaml] ----- -on: - pull_request: # shallow clone, just the diff - push: - branches: [main] - schedule: - - cron: '0 4 * * 1' # weekly history sweep - -jobs: - pr-scan: - if: github.event_name == 'pull_request' - # shallow — don't need history for delta scanning - steps: - - uses: actions/checkout@... - full-scan: - if: github.event_name != 'pull_request' - steps: - - uses: actions/checkout@... - with: - fetch-depth: 0 # only for push-to-main + scheduled ----- - -=== 6. Job-level timeouts - -Cap every job so a hung runner doesn't burn the budget: - -[source,yaml] ----- -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 # fail-fast instead of 360 default ----- - -Recommendations: - -* Setup / lint / format jobs: 5 min. -* Build jobs: 15–20 min. -* Test jobs: 20–30 min. -* E2E / integration: 45 min max. -* Fuzz: exact target time + 2 min tolerance. - -=== 7. Reusable workflow (longer-term) - -Ship the above patterns in a single reusable workflow under -`czech-file-knife/.github/workflows/rsr-ci-defaults.yml`, and adopt -it estate-wide via `uses: hyperpolymath/czech-file-knife/.github/workflows/rsr-ci-defaults.yml@main` -in downstream repos. Single PR propagates improvements. - -=== 8. Self-hosted runner for heavy work (long-term) - -Oracle-fuzz, E2E+Conformance+Bench, Hypatia-scan: these are the -expensive jobs. Moving them to a self-hosted runner on the Eclipse -host gets the cost to zero ongoing. Setup: ~1 day. Security: run in -a rootless Podman container with the `ubuntu-22.04` runner image. -Not urgent; track as future work. - -=== 9. Dependabot noise - -`.github/dependabot.yml` in this template uses -`open-pull-requests-limit: 0` on cargo to suppress routine patch PRs -while keeping security PRs flowing. That's the correct pattern — do -NOT revert to the previous `ignore: "*" patch` rule, which also -silences security PRs (see -007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md). - -Paired with `.github/workflows/dependabot-automerge.yml`, security -PRs for low/moderate patches+minors auto-merge after CI, leaving -humans to review only HIGH+CRITICAL security updates and all -non-security bumps. - -== Rollout plan - -. *Week 1:* Apply patterns 1–2 (concurrency + path filters) to every - active RSR repo. Reusable template update; downstream propagation - is a find-and-replace pass. -. *Week 2:* Apply patterns 3–4 (schedule frequency + overlap - consolidation). Audit one repo at a time; check ~30 days of runs - before concluding an overlap is safe to drop. -. *Week 3:* Apply patterns 5–6 (shallow clone + timeouts). Low-risk. -. *Week 4+:* Pattern 7 (reusable workflow) — single PR, big payoff. -. *When scope allows:* Pattern 8 (self-hosted runner). - -== Measurement - -Before/after: `gh api /users//settings/billing/actions` shows -current minutes usage. Diff after each rollout wave. Target a 60% -reduction by end of week 4 on the three highest-consumption repos -(boj-server, hypatia, 007 — based on workflow count × schedule -frequency × job count). - -== Out of scope - -* Reducing CI coverage (not on the table). -* Disabling security workflows to save minutes. -* Skipping CI on "trivial" commits via commit-message tags - (gameable, easy to abuse). -* Switching to a paid Actions tier before exhausting these patterns. - -== Cross-references - -* `007-lang/audits/audit-rsr-workflows-2026-04-17.md` — billing - incident that motivated this spec. -* `007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md` — - separate defect stack also addressed in the same session. -* `czech-file-knife/.github/workflows/dependabot-automerge.yml` — - canonical pattern for auto-merge pattern referenced above. diff --git a/czech-file-knife/docs/proposals/root-cleanup.adoc b/czech-file-knife/docs/proposals/root-cleanup.adoc deleted file mode 100644 index 84548e343..000000000 --- a/czech-file-knife/docs/proposals/root-cleanup.adoc +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Root Cleanup Proposal — Reconcile Template With Its Own Audit -:toc: -:toclevels: 3 -:sectnums: - -== Motivation - -`TEMPLATE-STANDARDS-AUDIT.adoc` ships a "Proposed Final Directory Map" for the -template root. The tracked root violates that map by roughly 5x: - -[cols="1,1,1",options="header"] -|=== -| Bucket | Audit-mandated count | Actual count - -| Root files (visible) -| 6 -| 36 - -| Root directories (visible) -| 3 -| 17 -|=== - -Downstream repositories (e.g. `the-nash-equilibrium`) inherit the violation -verbatim. This proposal describes the relocations that bring the template back -into compliance with its own map, and adds an automated guard -(`scripts/check-root-shape.sh` against `.machine_readable/root-allow.txt`) so -the violation cannot silently return. - -NOTE: This is a template-side change. Downstream repos pick it up via a -one-shot relocation PR per project once the template lands. - -== Allowlist (canonical root) - -The full enumeration lives in `.machine_readable/root-allow.txt`. Summary: - -* *Authority files (root):* `README.adoc`, `AUDIT.adoc`, `EXPLAINME.adoc`, - `0-AI-MANIFEST.a2ml` (thin pointer), `LICENSE`, `CHANGELOG.md`. -* *Build entry points (root):* `Justfile`, `coordination.k9`. -* *Tool-required dotfiles (root):* `.editorconfig`, `.envrc`, `.gitattributes`, - `.gitignore`, `.tool-versions`. -* *Directories (root):* `.git/`, `.github/`, `.machine_readable/`, - `.well-known/`, `build/`, `ci/`, `docs/`, `session/`, plus the conventional - source/test trees (`src/`, `tests/`, `benches/`, `examples/`, `features/`, - `scripts/`, `verification/`, `build/container/`). - -== Relocation plan - -Each line is a `git mv` (or delete) plus the references that need updating. -Run from the template repo root. - -=== Onboarding prose → `docs/onboarding/` - -[source,bash] ----- -mkdir -p docs/onboarding -git mv QUICKSTART-DEV.adoc docs/onboarding/ -git mv QUICKSTART-USER.adoc docs/onboarding/ -git mv QUICKSTART-MAINTAINER.adoc docs/onboarding/ -git mv llm-warmup-dev.md docs/onboarding/ -git mv llm-warmup-user.md docs/onboarding/ ----- - -References to update: - -* `README.adoc` — any `link:QUICKSTART-*.adoc[…]`. -* `Justfile` — any `cat QUICKSTART-*` echoes in `init`/`help`. - -=== Status/roadmap docs → `docs/status/` - -[source,bash] ----- -mkdir -p docs/status docs/architecture -git mv READINESS.md docs/status/ -git mv ROADMAP.adoc docs/status/ -git mv TEST-NEEDS.md docs/status/ -git mv PROOF-NEEDS.md docs/status/ -git mv PROOF-STATUS.md docs/status/ -git mv TOPOLOGY.md docs/architecture/ # validate-template.sh already accepts this path ----- - -References to update: - -* `README.adoc` — `link:ROADMAP.adoc[…]` and similar. -* `Justfile` — `readiness:` recipe (currently reads `READINESS.md`). - -=== Health files → `.github/` - -GitHub auto-discovers these under `.github/`, so the move is transparent to -contributors and tools. - -[source,bash] ----- -git mv CONTRIBUTING.md .github/ -git mv CODE_OF_CONDUCT.md .github/ -git mv SECURITY.md .github/ ----- - -=== Build orchestration → `build/` - -`Justfile` stays at root (just convention) but becomes thin: it imports -phase-specific just files from `build/`. - -[source,bash] ----- -mkdir -p build -git mv contractile.just build/ -git mv setup.sh build/ -git mv guix.scm build/ -git mv .guix-channel build/ -# Containerfile may already live in build/container/ — keep whichever the project uses. -[ -f Containerfile ] && git mv Containerfile build/ ----- - -References to update in `Justfile`: - -[source,diff] ----- -- import? "contractile.just" -+ import? "build/contractile.just" ----- - -The current Justfile already supports `build/container/Containerfile` *or* root -`Containerfile` — extend that to also accept `build/Containerfile`. - -The 62 KB monolithic `Justfile` is a separate smell. A follow-up should split -it under `build/just/{init,verify,test,docs,container,security}.just` with -`import?` lines from the thin root file. - -=== CI configs → `ci/` - -Most CI tools accept a custom config path; where they don't, a one-line root -shim file is acceptable. - -[source,bash] ----- -mkdir -p ci -git mv .gitlab-ci.yml ci/ -git mv .pre-commit-config.yaml ci/ ----- - -Updates required: - -* GitLab CI: project setting "CI/CD configuration file" → `ci/.gitlab-ci.yml`. -* pre-commit: invoke as `pre-commit run --config ci/.pre-commit-config.yaml`, - or leave a one-line root shim. - -=== Custom-format configs → `.machine_readable/configs/` - -[source,bash] ----- -git mv eclexiaiser.toml .machine_readable/configs/ -git mv selur-compose.toml .machine_readable/configs/ -git mv stapeln.toml .machine_readable/configs/ ----- - -References to update wherever any tool reads them (grep for the filenames -across `Justfile`, `scripts/`, `.machine_readable/`). - -=== AI manifest deduplication - -`.machine_readable/0.1-AI-MANIFEST.a2ml` is the canonical AI manifest in both -the template and downstream repos (e.g. `the-nash-equilibrium` README line -244). The root `0-AI-MANIFEST.a2ml` becomes a thin pointer: - -[source,a2ml] ----- -# 0-AI-MANIFEST.a2ml — pointer file -authority = ".machine_readable/0.1-AI-MANIFEST.a2ml" -note = "AI agents MUST read the canonical manifest at the path above." ----- - -Once the canonical version is stable, decide whether to keep this pointer at -root or delete it entirely. - -=== Template-only relocations - -A dry-run of `check-root-shape.sh` against the unmodified template flagged -three template-only extras that aren't drift in downstream repos but should -move regardless: - -[source,bash] ----- -# The audit doc itself is drift — it doesn't apply at root. -git mv TEMPLATE-STANDARDS-AUDIT.adoc docs/governance/ - -# `tools/` and `scripts/` overlap; pick one (proposal: keep `scripts/`). -# Inspect tools/ contents and either merge into scripts/ or rename and document -# the split (e.g. `scripts/` = repo-local helpers, `tools/` = bundled binaries). ----- - -=== Hygiene: case collisions in `affinescript/stdlib/` - -Cloning the template on a case-insensitive filesystem (Windows, default macOS) -silently drops files because of pairs like `Math.affine` vs `math.affine`. - -Pick one canonical case per name and `git mv` the duplicates away. Keeping -both is not portable. - -== Justfile recipe - -[source,just] ----- -# Verify root layout against the canonical allowlist. -check-root-shape: - ./scripts/check-root-shape.sh - -# Add to the existing aggregate `verify` target. -verify: ... check-root-shape ... ----- - -A pre-commit hook (in `ci/.pre-commit-config.yaml`) and a CI job -(`ci/.gitlab-ci.yml`) should both call `just check-root-shape`. - -== Downstream rollout - -For each downstream repo (start with `the-nash-equilibrium`): - -1. Apply the same `git mv` set (skip moves whose source doesn't exist locally). -2. Copy the new `.machine_readable/root-allow.txt` and adapt comments/extras. -3. Update internal links (README, docs, Justfile recipes). -4. Run `just check-root-shape` and `scripts/validate-template.sh` to confirm - shape parity with the template. - -== Out of scope (mentioned for follow-up) - -* Splitting the 62 KB monolithic `Justfile`. -* Migrating from GitLab CI to GitHub Actions parity (or vice versa). -* Reworking the `0.1-` / `0.2-` manifest versioning convention. diff --git a/czech-file-knife/docs/standards/README.adoc b/czech-file-knife/docs/standards/README.adoc deleted file mode 100644 index b31c112c7..000000000 --- a/czech-file-knife/docs/standards/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Standards Unit diff --git a/czech-file-knife/docs/status/PROOF-NEEDS.adoc b/czech-file-knife/docs/status/PROOF-NEEDS.adoc deleted file mode 100644 index 9a0e7ee12..000000000 --- a/czech-file-knife/docs/status/PROOF-NEEDS.adoc +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: rsr-template-repo/docs/status/PROOF-NEEDS.adoc -// Authoritative master list: ~/Desktop/PROOF-REQUIREMENTS-MASTER.adoc -= Proof Requirements — CZECH_FILE_KNIFE - -== Proof Tier - -// Assign one: T1 (Critical), T2 (High), T3 (Standard), T4 (Light), T5 (Exempt) -*Tier*: T3 — Standard - -== Proof Categories - -[cols="1,3,1", options="header"] -|=== -| Code | Meaning | Applies? - -| *TP* | Typing Proofs (type soundness, type safety) | Yes -| *INV* | Invariant Proofs (state machines, monotonicity, bounds) | -| *SEC* | Security Proofs (crypto, injection freedom, access control) | -| *CONC* | Concurrency Proofs (linearizability, deadlock freedom) | -| *ALG* | Algorithm Proofs (termination, correctness, bounds) | -| *ABI* | ABI/FFI Proofs (memory layout, pointer safety, platform compat) | Yes -| *DOM* | Domain-Specific Proofs (bespoke to this project) | -|=== - -== Mandatory Proofs (All RSR Repos) - -These proofs come from the czech-file-knife and MUST be present in every repo: - -=== ABI/FFI Boundary Proofs (Idris2) - -[cols="1,3,1,3", options="header"] -|=== -| # | Proof | Status | File - -| ABI-1 | Non-null pointer proofs (`So (ptr /= 0)`) | Needed | `verification/proofs/idris2/ABI/Pointers.idr` -| ABI-2 | Memory layout correctness (`HasSize`, `HasAlignment`) | Needed | `verification/proofs/idris2/ABI/Layout.idr` -| ABI-3 | Platform type size proofs (per platform) | Needed | `verification/proofs/idris2/ABI/Platform.idr` -| ABI-4 | FFI function return type proofs | Needed | `verification/proofs/idris2/ABI/Foreign.idr` -| ABI-5 | C ABI compliance (`CABICompliant`, `FieldsAligned`) | Needed | `verification/proofs/idris2/ABI/Compliance.idr` -|=== - -=== Typing Proofs (Prover Varies) - -[cols="1,3,1,3", options="header"] -|=== -| # | Proof | Status | File - -| TP-1 | Core data type well-formedness | Needed | `verification/proofs/idris2/Types.idr` -| TP-2 | Public API type safety (exported functions) | Needed | `verification/proofs/lean4/ApiTypes.lean` -|=== - -== Project-Specific Proofs - -[cols="1,3,1,1,1,2", options="header"] -|=== -| # | Proof Needed | Category | Prover | Priority | File(s) - -| P1 | Journal reversibility: for every recorded op `o` on state `s`, `undo(o(s)) = s` (content-level; metadata out of scope until implemented) | Invariant | Lean 4 / Coq (shared with januskey) | High | `src/cfk-core/src/reversible.rs` -| P2 | `op ; undo` is a Certified Null Operation (absolute-zero CNO) | Invariant | Coq / Lean 4 (absolute-zero) | High | `src/cfk-core/src/reversible.rs` -| P3 | Latest-only undo never clobbers a later write (ordering safety) | Invariant | TLA+ | Medium | `src/cfk-core/src/reversible.rs` -| P4 | `cfk squeeze` frame-then-punch: every input byte is at all times present in the input or in a durable output frame (crash-safe at every step) | Invariant | TLA+ | High | `docs/architecture/HYBRID-OPERATIONS.adoc` (planned) -| P5 | Content store integrity: `get(put(x)) = x` and a mismatched object is rejected | Typing | Idris2 | Medium | `src/cfk-core/src/reversible.rs` -|=== - -== Dangerous Patterns (BANNED) - -The following MUST NOT appear anywhere in proof files: - -[cols="2,2,3", options="header"] -|=== -| Pattern | Language | Meaning - -| `believe_me` | Idris2 | Unsafe cast / trust-me -| `assert_total` | Idris2 | Skip totality check -| `postulate` | Idris2/Agda | Unproven axiom -| `sorry` | Lean4 | Incomplete proof -| `Admitted` | Coq | Incomplete proof -| `unsafeCoerce` | Haskell | Unsafe type cast -| `Obj.magic` | OCaml/ | Unsafe type cast -| `unsafe` (unaudited) | Rust | Unsafe block without safety comment -|=== - -CI will reject any PR introducing these patterns (enforced by `panic-attack assail`). - -== Prover Selection Guide - -[cols="2,2,3", options="header"] -|=== -| Use Case | Recommended Prover | Why - -| ABI/FFI boundaries | *Idris2* | Dependent types model layouts precisely -| Type system proofs | *Coq* or *Lean4* | Mature proof assistants for metatheory -| Algebraic properties | *Lean4* | Good mathlib support -| Inductive/coinductive | *Agda* | Native support for (co)induction -| Distributed systems | *TLA+* | Model checking for protocols -| Numerical properties | *Isabelle* | Strong real analysis library -|=== - -== Proof File Locations - ----- -verification/proofs/ -├── idris2/ # Idris2 proofs (ABI, dependent types) -│ ├── ABI/ # ABI-specific proofs -│ └── *.idr # Project-specific Idris2 proofs -├── lean4/ # Lean4 proofs (algebra, lattices) -│ └── *.lean -├── agda/ # Agda proofs (induction, metatheory) -│ └── *.agda -├── coq/ # Coq proofs (type systems, compilation) -│ └── *.v -└── tlaplus/ # TLA+ specs (distributed protocols) - └── *.tla ----- - -== References - -* Master list: `~/Desktop/PROOF-REQUIREMENTS-MASTER.adoc` -* Proof status tracking: `PROOF-STATUS.adoc` (this repo) -* Proven library: `proven` repo (Idris2 verified foundations) -* Template: `rsr-template-repo/docs/status/PROOF-NEEDS.adoc` diff --git a/czech-file-knife/docs/status/PROOF-STATUS.adoc b/czech-file-knife/docs/status/PROOF-STATUS.adoc deleted file mode 100644 index 47c857af9..000000000 --- a/czech-file-knife/docs/status/PROOF-STATUS.adoc +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: rsr-template-repo/docs/status/PROOF-STATUS.adoc -// Tracks proof completion. Requirements defined in PROOF-NEEDS.adoc -= Proof Status — CZECH_FILE_KNIFE - -== Summary - -[cols="2,1,1,1,1,1", options="header"] -|=== -| Category | Total | Done | In Progress | Blocked | Remaining - -| ABI/FFI (ABI) | 5 | 0 | 0 | 0 | 5 -| Typing (TP) | 2 | 0 | 0 | 0 | 2 -| Invariant (INV) | 0 | 0 | 0 | 0 | 0 -| Security (SEC) | 0 | 0 | 0 | 0 | 0 -| Concurrency (CONC) | 0 | 0 | 0 | 0 | 0 -| Algorithm (ALG) | 0 | 0 | 0 | 0 | 0 -| Domain (DOM) | 0 | 0 | 0 | 0 | 0 -| *Total* | *7* | *0* | *0* | *0* | *7* -|=== - -*Overall*: 0% proven - -== Proofs Done - -// Format: -// | ID | Proof | Prover | File | Date | Verified By | -// | ABI-1 | Non-null pointer proofs | Idris2 | verification/proofs/idris2/ABI/Pointers.idr | 2026-XX-XX | idris2 --check | - -[cols="1,3,1,3,1,2", options="header"] -|=== -| ID | Proof | Prover | File | Date | Verified By - -| — | No proofs completed yet | — | — | — | — -|=== - -== Proofs In Progress - -[cols="1,3,1,2,1,2", options="header"] -|=== -| ID | Proof | Prover | Assignee | Started | Blocker - -| — | — | — | — | — | — -|=== - -== Proofs Blocked - -[cols="1,3,2,3", options="header"] -|=== -| ID | Proof | Blocked By | Notes - -| — | — | — | — -|=== - -== Proofs Remaining - -[cols="1,3,1,1,1,1", options="header"] -|=== -| ID | Proof | Category | Prover | Priority | Est. Effort - -| ABI-1 | Non-null pointer proofs | ABI | Idris2 | P1 | 2h -| ABI-2 | Memory layout correctness | ABI | Idris2 | P1 | 4h -| ABI-3 | Platform type size proofs | ABI | Idris2 | P1 | 2h -| ABI-4 | FFI function return type proofs | ABI | Idris2 | P1 | 2h -| ABI-5 | C ABI compliance | ABI | Idris2 | P1 | 4h -| TP-1 | Core data type well-formedness | TP | Idris2 | P1 | 4h -| TP-2 | Public API type safety | TP | Lean4 | P2 | 4h -|=== - -== Verification Commands - -[source,bash] ----- -# Check all Idris2 proofs -just proof-check-idris2 - -# Check all Lean4 proofs -just proof-check-lean4 - -# Check all Agda proofs -just proof-check-agda - -# Check all Coq proofs -just proof-check-coq - -# Run all proof checks -just proof-check-all - -# Scan for dangerous patterns -panic-attack assail --proofs-only ----- - -== Changelog - -[cols="1,3,1", options="header"] -|=== -| Date | Change | By - -| 2026-04-04 | Initial proof status tracking | Template -|=== diff --git a/czech-file-knife/docs/status/READINESS.adoc b/czech-file-knife/docs/status/READINESS.adoc deleted file mode 100644 index bb706ce16..000000000 --- a/czech-file-knife/docs/status/READINESS.adoc +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Czech File Knife Component Readiness Assessment - -*Standard:* link:https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades[Component Readiness Grades (CRG) v2.0 STRICT] + -*Assessed:* 2026-09-28 + -*Assessor:* Jonathan D.A. Jewell + -*Previous assessment:* __ - -*Current Grade:* X - -This line is parsed by `just crg-grade` / `just crg-badge`. The grade above is -the worst-graded in-scope component — *the project's weakest link sets its -grade*. See the per-component table in §3. - -[NOTE] -==== -*Honest grading.* Per CRG v2.0 Principle 4: grade as-is today, not -aspirationally. Long alpha is discipline, not shame. Demote immediately if -evidence doesn't support the claim. -==== - -''' - -== 1. CRG v2.0 Grade Reference - -[cols="1,2,2,3,2", options="header"] -|=== -| Grade | Name | Release Stage | Stability Posture | Shorthand - -| X | Untested | — | — | — -| F | Harmful / Wasteful | — | — | reject/delegate -| E | Minimal / Salvageable | Pre-alpha | Unstable | `pre-alpha` -| D | Partial / Inconsistent| Alpha | Unstable | `alpha-unstable` -| C | Self-Validated | Alpha | Stable in home context | `alpha-stable` -| B | Broadly Validated | Beta | Stable for broad trial | `beta-stable` -| A | Field-Proven | Stable | Stable | `stable` -|=== - -*Evidence gates (v2.0 is stricter than v1.0):* - -* *D*: RSR-compliant + per-capability tests + documented scope. Test matrix - must cite counts and live in `.machine_readable/descriptiles/STATE.a2ml` or CI. -* *C*: All of D, plus active dogfooding in home context with *no known - home-context failures over an evidence window*, plus *deep per-file and - per-directory annotation* (purpose, boundaries, invariants, - execution/test/proof surfaces, per-directory orientation READMEs). - STATE.a2ml `[dogfooding-status]` populated with concrete "done — " - entries. -* *B*: All of C, plus *six genuinely diverse external targets* with - feedback fed back. STATE.a2ml `[external-targets]` holds target identities - + dates + outcomes; `[issues-fed-back]` holds the closed-issue trail. - Internal-capability items do *not* count. -* *A*: All of B, plus multi-source real-world external feedback confirming - value, no harm in the wild. STATE.a2ml `[field-signal]` populated. - -*Publication gate:* non-abstract implementation claims require *B+*. -Below B, any publication must be explicitly abstract or provisional. - -''' - -== 2. Headline Evidence (as of 2026-09-28) - -[cols="2,3,3", options="header"] -|=== -| Metric | Value | Source - -| Test count | __ | `.machine_readable/descriptiles/STATE.a2ml` or CI -| Formal-verification posture | __ | grep of proof dirs -| Dangerous patterns (`assert_total`, `unsafeCoerce`, `Obj.magic`) | __ | grep 2026-09-28 -| Per-unit README coverage | __ | filesystem scan of unit dirs -| CI status | __ | `.github/workflows/` -| RSR mandatory workflows | __ | `.github/workflows/` -| Third-party badges (if any) | __ | external -| LIVE deployment (if any) | __ | production -|=== - -''' - -== 3. Component Assessment - -All components graded *as-is today*, per CRG v2.0 Principle 4. Stability -posture reflects the component's state *within its home context only* -unless noted. - -[cols="2,1,1,3,3,2", options="header"] -|=== -| Component | Grade | Posture | Evidence Summary | Promotion blocker | Last Assessed - -| __ | X/F/E/D/C/B/A | __ | __ | __ | 2026-09-28 -| __ | … | … | … | … | 2026-09-28 -| __ | … | … | … | … | 2026-09-28 -|=== - -*Rules of thumb for populating this table:* - -* One row per independently-gradable unit. If a subsystem can ship or break - independently, it gets its own row. -* Evidence Summary must cite *numbers* (test counts, LOC, file counts) or - *paths* (e.g. `src/interface/Abi/Module.idr`), never vague claims. -* Promotion blocker must be *actionable* — "add external consumer in home - context, then annotate" beats "needs more validation". -* Re-assess every release cycle; date-stamp each row. - -''' - -== 4. What's Needed for D → C - -CRG v2.0 requires two new pieces of evidence on top of D: - -. *Active dogfooding in home context with no known home-context failures.* -** Start date: __. -** Home context: __. -** "No known failures" is a moving claim — must hold continuously across - the evidence window (recommended: 4 weeks, daily use). -** Populate `STATE.a2ml [dogfooding-status]` with one entry per capability: - `capability = "done — "`. - -. *Deep code and folder annotation.* -** Per-directory orientation READMEs in every non-trivial source subtree. -** Per-file header comments: purpose, boundaries, invariants, - execution/test/proof surface. -** Per-unit (cartridge/panel/plugin/module) README covering: purpose, - tools, architecture-at-a-glance, build steps. Overview-level alone is - not sufficient — depth is required where a reviewer would otherwise - have to read source to orient. - -*Minimum first-ring targets for C promotion:* list the trunk components -here. If these aren't C, nothing else can be. - -''' - -== 5. What's Needed for C → B - -Six *genuinely diverse* external targets with feedback fed back into the -component. Candidate diversity axes: - -* Different language runtime (not just variants of the same one). -* Different OS family (at least one must not be Linux). -* Different hardware class (cloud / server / desktop / embedded / mobile). -* Different auth posture (unauthenticated, API-key, vault-brokered, mTLS). -* Different topology (star, mesh, peer-to-peer). -* Different trust model (same-org, federated, adversarial). - -Populate `STATE.a2ml`: - -* `[external-targets]` — target-id → `" — — "`. -* `[issues-fed-back]` — issue-id → `" — — "`. - -Six variations of the same use case do *not* count. Re-classify any items -currently under `[grade-b-status]` (legacy) and move external-eligible ones -into `[external-targets]`. - -''' - -== 6. What's Needed for B → A - -Real-world external feedback confirming value. Populate -`STATE.a2ml [field-signal]` with multi-source entries: - -* External users beyond the six B-targets. -* Third-party writeups, talks, papers, or testimonials. -* No unresolved safety / correctness incidents in the last 90 days. - -''' - -== 7. Summary (2026-09-28) - -* Project grade: __. Why: _<1-2 line justification>_. -* Delta since last assessment: __. -* Next milestone: __. -* Machine-readable grade line present (§ header) for `just crg-grade` / `just crg-badge`. - -''' - -== 8. Companion Artefacts - -* `docs/governance/CRG-CRITERIA.adoc` — grade definitions (boilerplate). -* `docs/governance/CRG-AUDIT-.adoc` — formal audit (populate from - `czech-file-knife/docs/governance/CRG-AUDIT-TEMPLATE.adoc`). -* `docs/practice/DOGFOOD-LOG.adoc` — dated dogfood evidence (required for C). -* `.machine_readable/descriptiles/STATE.a2ml` — authoritative state (canonical keys - `[dogfooding-status]`, `[external-targets]`, `[issues-fed-back]`, - `[field-signal]`). - -_Run `just crg-badge` to generate the shields.io badge for your README._ diff --git a/czech-file-knife/docs/status/ROADMAP.adoc b/czech-file-knife/docs/status/ROADMAP.adoc deleted file mode 100644 index 5c7821a5e..000000000 --- a/czech-file-knife/docs/status/ROADMAP.adoc +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Czech File Knife Roadmap -:toc: - -The machine-readable source of truth is -`.machine_readable/descriptiles/STATE.a2ml` `[route-to-mvp]`; this page is the -human view of it. - -== v0.1.0 — Foundation (current) -* [x] Core traits (`StorageBackend`, `VirtualPath`, `Entry`), local backend -* [x] CLI: `ls cat cp mv rm mkdir stat backends df` -* [x] Reversible journal: `ReversibleBackend`, `cfk history`, `cfk undo`, CAS-backed `get_versions` -* [x] Standalone RSR repository (extracted from developer-ecosystem) -* [ ] First green CI run of `just build test e2e` - -== v0.2.0 — Hybrid-machine essentials -* [ ] `cfk squeeze` — compress in place within free space (hole punching; tail-truncation fallback) -* [ ] `StorageBackend::server_side_copy` + planner that prefers provider-side ops -* [ ] Google Drive and S3 backends (server-side copy/move/export) -* [ ] Undo restores metadata; selective undo with conflict detection -* [ ] `--plan` / dry-run mode (absolute-zero: certified no-op) - -== v0.3.0 — Scale out -* [ ] Remaining cloud backends (Dropbox, OneDrive, Box), network (SFTP, SMB, WebDAV, NFS, 9P) -* [ ] `cfk-cache` on the shared content store; Tantivy search -* [ ] FUSE mounting (`cfk-vfs`); Windows via WinFsp -* [ ] Cross-backend loss warnings from `StorageCapabilities` (echo-types) -* [ ] Cost-aware transfer planning (tropical-types) - -== v1.0.0 — Stable -* [ ] Mechanised reversibility proof for the journal (see link:PROOF-NEEDS.adoc[PROOF-NEEDS]) -* [ ] iOS File Provider extension (`cfk-ios`) -* [ ] Packaging across the targets in `build/packaging/` diff --git a/czech-file-knife/docs/status/TEST-NEEDS.adoc b/czech-file-knife/docs/status/TEST-NEEDS.adoc deleted file mode 100644 index 50e301a5d..000000000 --- a/czech-file-knife/docs/status/TEST-NEEDS.adoc +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= TEST-NEEDS: czech-file-knife - -== CRG Grade: C — ACHIEVED 2026-04-04 - -== Current State (Updated 2026-04-04) - -[cols="2,1,4", options="header"] -|=== -| Category | Count | Details - -| *Source modules* | 6 | 3 Idris2 ABI (Foreign, Layout, Types), 2 Zig FFI (build, main), 1 Zig integration test template -| *Unit tests* | 0 | None in main source (inline tests in main.zig) -| *Integration tests* | 1 | `test/integration_test.zig` (documented template, 1 placeholder test) -| *E2E tests* | 1 | `tests/e2e/template_instantiation_test.sh` (full instantiation + validation) -| *Workflow tests* | 1 | `tests/workflows/validate_workflows_test.sh` (21 workflows validated) -| *Validation tests* | 1 | `scripts/validate-template.sh` (8-phase comprehensive validation) -| *Benchmarks* | 5 | `benches/template_bench.sh` (validation, Zig build, tests, workflows, instantiation) -| *Fuzz tests* | 0 | `README.adoc` scaffold with harness instructions -|=== - -== Completed Work (CRG C - Testing & Benchmarking) - -=== Template Validation Script ✅ - -* [x] `scripts/validate-template.sh` — 8-phase validation -** Phase 1: Core repository structure (root files, directories) -** Phase 2: Machine-readable metadata (`.machine_readable/`) -** Phase 3: GitHub Actions workflows (17 required + all present) -** Phase 4: Idris2 ABI and Zig FFI source files -** Phase 5: Placeholder token replacement (skipped in template) -** Phase 6: SPDX license headers (100% coverage, 6/6 files) -** Phase 7: Build system verification (`zig build` + `idris2` syntax check) -** Phase 8: Documentation requirements (TOPOLOGY, ABI-FFI-README, etc) -* Status: *PASSING* (0 errors, 3 warnings about template placeholders) - -=== E2E Template Instantiation Test ✅ - -* [x] `tests/e2e/template_instantiation_test.sh` — full workflow -** Clones template to temp directory -** Runs the real `just repo-init` (never a second copy of its substitution list) -** Validates resulting structure with `scripts/validate-template.sh` -** Verifies Zig build works after instantiation -** Checks no placeholder survives, via `scripts/check-no-placeholders.sh` -** Cleans up temp directory - -NOTE: this entry read `[x]` from the day it was written until 2026-07-17, while -the test aborted at its first substitution line (`file: unbound variable`) on -every invocation. Nothing noticed: CI ran `tests/e2e.sh`, which did not call it, -and its only caller — `benches/template_bench.sh` — pipes it to `/dev/null` and -appends `|| true`, so it was timing a script that died instantly. The tick was -copied from the test's intent, not from a run. Tick these boxes from observed -output only. -* Status: *READY TO TEST* (can be verified by CI) - -=== Workflow Validation Test ✅ - -* [x] `tests/workflows/validate_workflows_test.sh` -** Validates all 21 workflows exist and have proper structure -** Checks SPDX headers, `name` field -** Verifies all 15 required workflows present -* Status: *PASSING* (0 errors, 15/15 required workflows found) - -=== Zig FFI Tests ✅ - -* [x] `src/interface/ffi/test/integration_test.zig` — template with examples -** Converted from `czech_file_knife` placeholders to "template" namespace -** Added comprehensive comments for how to instantiate -** Tests grouped by category (lifecycle, operations, strings, errors, version, memory safety, threading) -** Compiles and passes placeholder test -* Status: *PASSING* (1 test: `placeholder_test_implementation_required` passes) - -=== Benchmarks ✅ - -* [x] `benches/template_bench.sh` — 5 benchmark suites -** Validation script: ~5.8s average (3 runs) -** Zig build: ~19ms (clean build) -** Zig tests: ~20ms -** Workflow validation: ~117ms -** Template instantiation: ~427ms -* Formats: human, json, csv -* Status: *PASSING* (all benchmarks execute) - -=== Build System ✅ - -* [x] `src/interface/ffi/build.zig` — updated for Zig 0.15.2 -** Simplified to test-only configuration -** Supports both unit tests and integration tests -** Works with `zig build` without errors -* Status: *PASSING* (builds successfully) - -== Test Results Summary - ----- -Validation Script: PASS (0 errors, 3 warnings) -Workflow Validation: PASS (21/21 workflows valid) -Integration Tests: PASS (1/1 placeholder test) -E2E Instantiation: READY (needs CI confirmation) -Benchmarks: PASS (5/5 benchmark suites) -Build System: PASS (zig build succeeds) ----- - -== CRG C Compliance - -* *Coverage*: 6/6 test categories (unit, integration, E2E, workflow, validation, benchmarks) -* *Documentation*: All test files have SPDX headers + inline documentation -* *Author Attribution*: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> -* *License*: MPL-2.0 on all new files -* *Automation*: All scripts executable + working - -== FLAGGED ISSUES - ALL RESOLVED - -* [.line-through]#*Template repo used by ALL new repos has 0 validation tests*# → FIXED: 4 test suites + validation script -* [.line-through]#*fuzz/placeholder.txt*# → FIXED: replaced with `README.adoc` containing real harness instructions -* [.line-through]#*No E2E tests for template instantiation*# → FIXED: full E2E test suite -* [.line-through]#*Zig FFI integration tests are placeholders*# → FIXED: converted to documented template format - -== Next Steps (Future Sessions) - -* [ ] Integrate test scripts into CI/CD workflows -* [ ] Generate test coverage reports -* [ ] Add more specialized benchmarks (memory, threading stress) -* [ ] Document test instantiation patterns for new repos - -== Priority: P0 (COMPLETE) ✅ diff --git a/czech-file-knife/docs/theory/README.adoc b/czech-file-knife/docs/theory/README.adoc deleted file mode 100644 index 20ef5b6da..000000000 --- a/czech-file-knife/docs/theory/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= theory Unit diff --git a/czech-file-knife/docs/theory/computing/README.adoc b/czech-file-knife/docs/theory/computing/README.adoc deleted file mode 100644 index be73e7351..000000000 --- a/czech-file-knife/docs/theory/computing/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Computing Theory diff --git a/czech-file-knife/docs/theory/formalisms/README.adoc b/czech-file-knife/docs/theory/formalisms/README.adoc deleted file mode 100644 index fcbc818ed..000000000 --- a/czech-file-knife/docs/theory/formalisms/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Formalisms Theory diff --git a/czech-file-knife/docs/theory/mathematics/README.adoc b/czech-file-knife/docs/theory/mathematics/README.adoc deleted file mode 100644 index f4653d155..000000000 --- a/czech-file-knife/docs/theory/mathematics/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Mathematics Theory diff --git a/czech-file-knife/docs/theory/ontologies/README.adoc b/czech-file-knife/docs/theory/ontologies/README.adoc deleted file mode 100644 index 2008f2abc..000000000 --- a/czech-file-knife/docs/theory/ontologies/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Ontologies Theory diff --git a/czech-file-knife/docs/theory/other/README.adoc b/czech-file-knife/docs/theory/other/README.adoc deleted file mode 100644 index 16ed2b165..000000000 --- a/czech-file-knife/docs/theory/other/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Other Theory diff --git a/czech-file-knife/docs/theory/socio-technical/README.adoc b/czech-file-knife/docs/theory/socio-technical/README.adoc deleted file mode 100644 index 91fa67014..000000000 --- a/czech-file-knife/docs/theory/socio-technical/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Socio technical Theory diff --git a/czech-file-knife/docs/troubleshooting.adoc b/czech-file-knife/docs/troubleshooting.adoc deleted file mode 100644 index f9d3b17bc..000000000 --- a/czech-file-knife/docs/troubleshooting.adoc +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= Troubleshooting — Czech File Knife -:revdate: 2026-MM-DD - -This file is a *living FAQ* — known failure modes and recovery paths. -Add a new entry every time you debug something that took more than 15 -minutes; future-you (and other maintainers) will thank you. - -== Known failure modes - -=== `` - -**Symptom**: short reproduction. What does the user see? - -**Cause**: root cause (one or two sentences). - -**Fix**: -[source,bash] ----- - ----- - -**Avoidance**: how to not hit this again (config setting, doc link, etc.). - ---- - -=== `` - -(Replace this and the above example with real entries as you encounter -them.) - -== Diagnostic toolkit - -When something is wrong, run these first: - -[source,bash] ----- -just doctor # runs all available self-checks -just version # prints the version & build hash -just log-tail # last N lines of logs ----- - -== When to escalate - -. Filed an issue with: version, OS, exact command, full error output, - and the symptom in plain English. -. Linked from the issue: the relevant ADR(s) and any related issues. -. For *security*-relevant findings, see - link:./../SECURITY.md[SECURITY.md] — do **not** file public issues. - -== Where to look for more help - -* `docs/architecture.adoc` — internals. -* `docs/decisions/` — historical record of why things are this shape. -* `https://github.com/hyperpolymath/Czech File Knife/issues?q=is%3Aissue+` — has anyone hit this before? -* `https://github.com/hyperpolymath/standards/issues` — for estate-wide problems. diff --git a/czech-file-knife/docs/usage.adoc b/czech-file-knife/docs/usage.adoc deleted file mode 100644 index a36ef7bbe..000000000 --- a/czech-file-knife/docs/usage.adoc +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -= Usage — Czech File Knife -:revdate: 2026-MM-DD - -== Audience - -This document is for *consumers* of `Czech File Knife` — people who depend -on it, call it, or include it. For developers working *on* it, see -link:./contributing.adoc[contributing.adoc]. - -== Quickstart - -The shortest path from zero to a working call: - -[source,bash] ----- -# 1. Install -just install # or: cargo install --path . / task install - -# 2. Configure -cp examples/config.example.toml ./config.toml -# Edit minimal required fields. - -# 3. Run -just run # or the equivalent for your language ----- - -Expected output: - -[source] ----- -Czech File Knife v0.0.0 -Listening on 127.0.0.1:8080 ----- - -== Common use cases - -For each canonical use case, give: - -. A one-line statement of the goal. -. The minimal invocation. -. Expected output / side effect. - -=== Use case 1: - -(Replace with real content.) - -=== Use case 2: - -== Configuration reference - -Document every configurable field. Keep this section authoritative — if -the code grows a new option, this table must grow too (CI can be wired -to enforce this). - -[cols="1,1,1,3", options="header"] -|=== -| Field | Type | Default | Meaning - -| `` | `` | `` | -|=== - -== Stability guarantees - -Be explicit: - -* **Stable**: API surfaces that follow SemVer (breaking change = major bump). -* **Unstable**: behind a flag / pre-1.0 / explicitly marked. -* **Internal**: documented for reference but no compatibility promise. - -== Limits & known constraints - -* Maximum supported ``: ``. -* `` is not yet implemented; track at issue `#`. -* On ``, `` behaves differently because ``. - -== See also - -* link:./architecture.adoc[Architecture] — how it works internally. -* link:./troubleshooting.adoc[Troubleshooting] — when things go wrong. diff --git a/czech-file-knife/docs/whitepapers/README.adoc b/czech-file-knife/docs/whitepapers/README.adoc deleted file mode 100644 index 36d9f9061..000000000 --- a/czech-file-knife/docs/whitepapers/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= whitepapers Unit diff --git a/czech-file-knife/docs/whitepapers/academic/README.adoc b/czech-file-knife/docs/whitepapers/academic/README.adoc deleted file mode 100644 index a050acd3c..000000000 --- a/czech-file-knife/docs/whitepapers/academic/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Academic Logic diff --git a/czech-file-knife/docs/whitepapers/industry/README.adoc b/czech-file-knife/docs/whitepapers/industry/README.adoc deleted file mode 100644 index ca743a10c..000000000 --- a/czech-file-knife/docs/whitepapers/industry/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Industry Logic diff --git a/czech-file-knife/docs/whitepapers/outreach/README.adoc b/czech-file-knife/docs/whitepapers/outreach/README.adoc deleted file mode 100644 index 6ccc80bee..000000000 --- a/czech-file-knife/docs/whitepapers/outreach/README.adoc +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Outreach & Education -:toc: preamble -:icons: font - -This directory contains whitepapers, guides, and presentations tailored for a general audience, including schools, corporate partners, and special interest groups. - -== Target Audiences - -* **Schools & Education:** Introductory material on formal verification and sovereign systems. -* **Corporate:** High-level business value and compliance summaries. -* **Special Interest Groups:** Community-specific impact and ethical use cases. - -== Goals - -* De-mystify high-rigor engineering. -* Promote the adoption of the Rhodium Standard. -* Provide accessible entry points for non-technical stakeholders. diff --git a/czech-file-knife/docs/wikis/README.md b/czech-file-knife/docs/wikis/README.md deleted file mode 100644 index f15c7cfed..000000000 --- a/czech-file-knife/docs/wikis/README.md +++ /dev/null @@ -1,17 +0,0 @@ - -# Project Wikis - -This directory contains the source files for the project wiki. It is intended for long-form documentation, deep-dives, and community-maintained knowledge. - -## Structure - -- **Core Concepts:** Fundamental architectural ideas. -- **Workflows:** Step-by-step guides for contributors. -- **Glossary:** Definitions of project-specific terminology. - -## Wiki Synchronization - -Changes made here should be synchronised with the forge-hosted wiki (GitHub/GitLab) using the project's sync scripts. diff --git a/czech-file-knife/examples/README.adoc b/czech-file-knife/examples/README.adoc deleted file mode 100644 index f0bf52ef8..000000000 --- a/czech-file-knife/examples/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= examples Pillar diff --git a/czech-file-knife/examples/sample-manifest.ncl b/czech-file-knife/examples/sample-manifest.ncl deleted file mode 100644 index 92a8961e3..000000000 --- a/czech-file-knife/examples/sample-manifest.ncl +++ /dev/null @@ -1,37 +0,0 @@ -{ - meta = { - manifestVersion = "1.0.0", - owner = "Hyperpolymath", - runLabel = "secure-archive-pilot-2025-10", - }, - - providers = [ - { name = "gdrive-main", type = "gdrive", authRef = "secret:gdrive-pilot", root = "root" }, - { name = "dropbox-secure", type = "dropbox", authRef = "secret:dropbox-archive", root = "/secure/archive" } - ], - - selection = { - provider = "gdrive-main", - query = { pathPrefix = "Sensitive_Docs", mimeTypes = ["application/pdf", "text/plain"] } - }, - - flow = { - files = select(selection), - - steps = [ - step "hash" = { plugin = "Hasher", config = { algo = "sha256" } }.map(files), - step "search" = { plugin = "Search", config = { regex = "SSN|CONFIDENTIAL|DOB" } }.map(hash), - step "xfer" = { plugin = "Transfer", config = { from = "gdrive-main", to = "dropbox-secure", mode = "copy" } }.map(search), - step "verify" = { plugin = "Verify", config = { method = "hash", algo = "sha256" } }.map(xfer) - ], - - assertions = [ - assert "hash_present" on hash: "ctx.hash != null" - ] - }, - - policies = { - idempotency = true, - dryRun = true, - } -} diff --git a/czech-file-knife/examples/web-project-deno.json b/czech-file-knife/examples/web-project-deno.json deleted file mode 100644 index eaa39d95c..000000000 --- a/czech-file-knife/examples/web-project-deno.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "// NOTE": "Example deno.json for ReScript web projects", - "tasks": { - "build": "deno run -A npm:rescript", - "clean": "deno run -A npm:rescript clean", - "watch": "deno run -A npm:rescript -w", - "serve": "deno run -A jsr:@std/http/file-server .", - "test": "deno test --allow-all" - }, - "imports": { - "rescript": "npm:rescript@^12.1.0", - "@rescript/core": "npm:@rescript/core@^1.6.0", - "safe-dom/": "https://raw.githubusercontent.com/hyperpolymath/rescript-dom-mounter/main/src/", - "proven/": "../proven/bindings/rescript/src/" - }, - "compilerOptions": { - "allowJs": true, - "checkJs": false - } -} diff --git a/czech-file-knife/features/README.adoc b/czech-file-knife/features/README.adoc deleted file mode 100644 index 5c24f38bf..000000000 --- a/czech-file-knife/features/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Project Features diff --git a/czech-file-knife/features/boj-server/README.adoc b/czech-file-knife/features/boj-server/README.adoc deleted file mode 100644 index 25f08a148..000000000 --- a/czech-file-knife/features/boj-server/README.adoc +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= BoJ Server Integration -:icons: font - -This unit provides a "starting hand" for integrating with the **BoJ-Server** (Box of Justice) ecosystem — a high-rigor, verified server infrastructure. - -== Integration Options - -* **Core:** Use BoJ-Server as the primary verified backend for this project. -* **Bridge:** Utilize the BoJ-Server IPC bridge for cross-boundary communication. - -== Related Repository - -For the full specification and source, visit: -https://github.com/hyperpolymath/boj-server diff --git a/czech-file-knife/features/panic-attacker/README.adoc b/czech-file-knife/features/panic-attacker/README.adoc deleted file mode 100644 index deff9f173..000000000 --- a/czech-file-knife/features/panic-attacker/README.adoc +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Panic Attacker Feature -:icons: font - -This unit integrates the **Panic-Attacker** high-rigor stress testing tool into the project lifecycle. - -== Value Proposition - -Panic-Attacker goes beyond unit testing by applying: -* **Static Analysis (Assail):** Detecting logic-based bug signatures. -* **Multi-Axis Dynamic Attacks (Assault):** Stressing CPU, Memory, Disk, and Network boundaries. - -== Usage in this Template - -This template includes a pre-configured maintenance trigger: - -[source,bash] ----- -just maint-assault ----- - -This runs a medium-intensity assault on the project binary and emits a report to `docs/reports/security/`. - -== Related Repository - -https://github.com/hyperpolymath/panic-attacker diff --git a/czech-file-knife/features/ssg/README.adoc b/czech-file-knife/features/ssg/README.adoc deleted file mode 100644 index a6063e142..000000000 --- a/czech-file-knife/features/ssg/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Ssg Feature diff --git a/czech-file-knife/features/ssg/ssg-bootstrap.sh b/czech-file-knife/features/ssg/ssg-bootstrap.sh deleted file mode 100755 index 25d23cbdd..000000000 --- a/czech-file-knife/features/ssg/ssg-bootstrap.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# ssg-bootstrap.sh — Universal SSG Initialisation Helper -# -# Provides a starting hand for creating a documentation site or blog. -# Options 1-2 are hyperpolymath-maintained SSGs; options 3-5 are popular -# third-party choices. Use whichever fits your project. - -set -euo pipefail - -DEST="${1:-docs/site}" - -echo "═══════════════════════════════════════════════════" -echo " SSG BOOTSTRAP HELPER" -echo " Target directory: $DEST" -echo "═══════════════════════════════════════════════════" -echo "" -echo "Select an SSG to initialize in this project:" -echo " [1] Casket-SSG (Haskell) — hyperpolymath, pretty-formal" -echo " [2] Ddraig-SSG (Idris2) — hyperpolymath, dependent-type proofed" -echo " [3] Serum (Elixir) — BEAM-based, concurrent" -echo " [4] Zola (Rust) — Fast, standalone, standard" -echo " [5] Custom Git URL — Any SSG from a git repository" -echo "" - -read -rp "Enter choice [1-5]: " choice - -case "$choice" in - 1) - echo "Selected: Casket-SSG" - echo "Run: git clone https://github.com/hyperpolymath/casket-ssg $DEST" - ;; - 2) - echo "Selected: Ddraig-SSG" - echo "Run: git clone https://github.com/hyperpolymath/ddraig-ssg $DEST" - ;; - 3) - echo "Selected: Serum" - echo "Run: mix serum.new $DEST" - ;; - 4) - echo "Selected: Zola" - echo "Run: zola init $DEST" - ;; - 5) - read -rp "Git URL: " custom_url - echo "Run: git clone $custom_url $DEST" - ;; - *) - echo "Invalid selection. Aborting." - exit 1 - ;; -esac - -echo "═══════════════════════════════════════════════════" -echo " SCAFFOLDING SECURITY & RESOURCE DIRECTORIES" -echo "═══════════════════════════════════════════════════" -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" -WWW="$REPO_ROOT/www" - -mkdir -p "$DEST/security_headers" -mkdir -p "$DEST/.well-known" -mkdir -p "$DEST/resource_records" - -# Canonical sources live in www/ (issue #53). Copy from there when present; -# the inline defaults below remain for repositories without the bundle. -if [ -d "$WWW/.well-known" ]; then - cp -r "$WWW/.well-known/." "$DEST/.well-known/" - echo " .well-known: copied from canonical www/.well-known/" -fi -if [ -d "$WWW/dns/records" ]; then - cp -r "$WWW/dns/records/." "$DEST/resource_records/" - echo " resource records: copied from www/dns/records/" -fi -if [ -f "$WWW/security_headers/csp.conf" ]; then - cp "$WWW/security_headers/csp.conf" "$DEST/security_headers/csp.conf" - echo " security headers: copied from www/security_headers/csp.conf" -else -cat << 'EOF' > "$DEST/security_headers/csp.conf" -Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests; -X-Content-Type-Options: nosniff -X-Frame-Options: DENY -X-XSS-Protection: 1; mode=block -Referrer-Policy: strict-origin-when-cross-origin -Strict-Transport-Security: max-age=31536000; includeSubDomains; preload -EOF -fi - -echo "Scaffolding complete. Please ensure these directories are copied to your site's output root." diff --git a/czech-file-knife/mise.toml b/czech-file-knife/mise.toml deleted file mode 100644 index 77e4f75e0..000000000 --- a/czech-file-knife/mise.toml +++ /dev/null @@ -1,67 +0,0 @@ -# Every tool name below was checked against `mise registry` AND resolved with -# `mise ls-remote` before being written. The previous version of this file named -# 13 tools that do not exist in the registry, so mise emitted ~25 lines of -# warnings on every shell entry while installing none of them. -# -# This file is a TEMPLATE. Repos minted from it inherit it verbatim, so add -# your project's actual language toolchain here after minting — pinned to the -# same versions your CI uses, so local and CI agree. - -[tools] -# Czech File Knife needs only these. (The template's multi-language list was -# trimmed at extraction: estate policy bans Python/Go/TypeScript toolchains, -# and nothing here uses node/java/bun.) -rust = "stable" -just = "latest" # the estate's task runner (Justfile) -shfmt = "latest" # tests/*.sh, scripts/*.sh - -# --------------------------------------------------------------------------- -# REMOVED — none of these resolve in the mise registry, so they installed -# nothing and only produced warnings. Grouped by why they were wrong: -# -# Already provided by a tool that IS listed above: -# cargo -> ships with `rust` -# gofmt -> ships with `go` -# pip -> ships with `python` -# -# Project-level dependencies, not system tools. These belong in -# package.json / pyproject.toml / mix.exs, not in a toolchain manager: -# vitest, jest, pytest, isort -# -# Base system binaries that mise should not be shadowing on Linux: -# git, gnu-sed, gnu-grep, gnu-tar -# (If a GNU-vs-BSD coreutils difference ever actually bites on macOS, add -# `coreutils`, which does exist in the registry, rather than these names.) -# -# Task runner that never installed: -# go-task -> not a registry name. NB the bare name `task` does not work -# either: `mise registry` LISTS it as `aqua:go-task/task`, but resolving -# `task@latest` still fails. Appearing in `mise registry` output is NOT -# sufficient to conclude a name resolves — check with `mise ls-remote`. -# `just` is listed above instead. -# --------------------------------------------------------------------------- - -[env] -NODE_ENV = "development" -PYTHONDONTWRITEBYTECODE = "1" -PYTHONUNBUFFERED = "1" - -# --------------------------------------------------------------------------- -# REMOVED — an `[alias]` section holding what were clearly meant to be tasks: -# -# [alias] -# task = "go-task" -# build = "cargo build --release || npm run build || go build" -# test = "cargo test || npm test || go test ./..." -# lint = "ruff check . || prettier --check . || black --check ." -# fmt = "ruff format . || prettier --write . || black ." -# -# mise's `[alias]` maps an alias to a TOOL PLUGIN — it does not define tasks, -# so none of these ever ran. mise also warns that `[alias]` is deprecated in -# favour of `[tool_alias]`. -# -# They are not re-added as `[tasks]`: the estate drives builds through the -# Justfile, and a second task runner sharing the verbs `build`/`test`/`lint`/ -# `fmt` — one of which silently falls through `||` chains into a different -# language's build — is worse than one. Use `just `. -# --------------------------------------------------------------------------- diff --git a/czech-file-knife/scripts/campaigns/www-bundle.campaign b/czech-file-knife/scripts/campaigns/www-bundle.campaign deleted file mode 100644 index 046639b75..000000000 --- a/czech-file-knife/scripts/campaigns/www-bundle.campaign +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# www-bundle.campaign — propagate the canonical www/ test suite and runbooks. -# -# THE GAP THIS CLOSES -# -# Issue #119 stage 5 moved every estate repository's root .well-known/ to -# www/.well-known/. What it could not do was bring the rest of the bundle: -# -# "no www/tests bundle" — all 262 sweep results -# 0/269 estate repositories carry any www/ tree -# -# so repositories ended up with a www/.well-known/ directory and nothing that -# knows how to check it. This campaign carries the checks and the runbooks that -# operate them. -# -# WHY IT IS SCOPED TO TWO DIRECTORIES AND NOT ALL OF www/ -# -# Measured 2026-09-18 by applying the suite to a real post-migration checkout -# (Axiom.jl at chore/well-known-to-www): -# -# www/tests/ alone -> 3 of 6 checks FAIL -# www/tests/ + guards -> 0 failures, 3 explicit SKIPs -# -# The failures were checks whose INPUTS the bundle does not supply: -# check-publication-boundary.sh wants www/schemas/ + www/webservers/, -# check-aibdp.sh wants www/.well-known/aibdp.json, check-migration.sh wants the -# template-side migrator. Those guards now exist, so the suite states that it -# does not apply instead of failing where it does not apply. -# -# The rest of www/ is deliberately NOT propagated: -# -# www/public/ per-repo placeholders (Czech File Knife and friends) -# www/policies/ per-repo placeholders -# www/dns/ a DNS scaffold most repositories do not operate -# www/tls/ as above -# www/webservers/ as above -# www/schemas/ only meaningful alongside the servers above -# www/profiles/ as above -# www/.well-known/ per-repo CONTENT, already migrated by stage 5 — the -# spine's copy holds placeholders and would overwrite -# every repository's real security.txt contact -# -# Carrying those would re-plant template identity in 262 repositories at once. -# They are a separate campaign with per-repo substitution, and they need their -# own decision and their own review. -# -# The two directories here contain no non-meta {{TOKEN}} at all, so every file -# travels verbatim. rsr-campaign.sh still checks, so this manifest cannot -# silently become untrue. - -[meta] -name = chore/rsr-www-bundle -description = canonical www/ test suite + operational runbooks (issue #119 follow-on) - -[paths] -www/tests/ -www/runbooks/ diff --git a/czech-file-knife/scripts/check-action-pinning.js b/czech-file-knife/scripts/check-action-pinning.js deleted file mode 100644 index 80533f64b..000000000 --- a/czech-file-knife/scripts/check-action-pinning.js +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env bun -// SPDX-License-Identifier: MPL-2.0 -// -// Assert every workflow action ref is pinned — inline SHA, or via actions.lock. -// -// What counts as "pinned" changed when GitHub introduced workflow lockfiles. A -// ref is pinned if EITHER it is an inline 40-hex SHA, OR -// .github/workflows/actions.lock resolves it to one. The previous inline-only -// rule rejected all 40 of this repo's own refs, so `Workflow Security Linter` -// was red on main permanently — and every repo minted from this template -// inherited both the tag-form workflows and the gate that rejects them. -// -// Runtime: Bun — the estate's first-choice runtime per LANGUAGE-POLICY.adoc §1 -// (Bun > Deno > pnpm > npm). Plain JavaScript, not TypeScript: TypeScript is -// banned estate-wide (owner ruling; CLAUDE.md banned-languages table). Note -// LANGUAGE-POLICY.adoc §1.2 currently claims TS is "permitted under Bun" — that -// line is wrong and should be corrected; the ban stands. -// -// Python was the first draft of this script and is banned with no exceptions. -// -// Three details are load-bearing, each of them a defect found by testing: -// -// `-?` in the pattern. `- uses:` is the list-item form and by far the most -// common; a `\s+uses:` pattern silently misses every one of them. Estate -// memory records this exact failure — "green linter != full SHA-pinning". A -// gate that cannot see the common case reads as coverage and enforces nothing. -// -// Case-insensitive comparison. Workflows write `SonarSource/...`; the lockfile -// records `sonarsource/...`. Action refs are case-insensitive in practice, so -// a case-sensitive match reports a false positive on a correctly-pinned action. -// -// Only *.yml / *.yaml, depth 1. A bare recursive grep also reads actions.lock -// (whose `uses:` keys are lockfile entries, not refs) and *.yml.template -// (whose tag ref is deliberate and resolved at mint time). Both were reported -// as unpinned, which made the gate unsatisfiable the moment a lockfile existed. -// -// Exit 0 if every ref is pinned, 1 otherwise, listing file:line: ref. - -import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; -import { join } from "node:path"; - -const WF = ".github/workflows"; -const LOCK = join(WF, "actions.lock"); -// `-?` matches the list-item form; see the header. -const USES = /^\s*-?\s*uses:\s*([^\s#]+)/; -const SHA = /@[a-f0-9]{40}$/; -const EXEMPT_PREFIX = ["./", "$", "docker://"]; - -function workflowFiles() { - if (!existsSync(WF) || !statSync(WF).isDirectory()) return []; - return readdirSync(WF) - .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")) - .sort() - .map((f) => join(WF, f)); -} - -function main() { - if (!existsSync(WF)) { - console.log("no .github/workflows — nothing to check"); - return 0; - } - - let known = new Set(); - if (existsSync(LOCK)) { - const lock = readFileSync(LOCK, "utf8"); - known = new Set( - [...lock.matchAll(/'([^']+@[^']+)'/g)].map((m) => m[1].toLowerCase()), - ); - console.log(`lockfile present: ${known.size} ref(s) resolvable through it`); - } else { - console.log("no lockfile — every ref must be an inline 40-character SHA"); - } - - const bad = []; - for (const wf of workflowFiles()) { - const lines = readFileSync(wf, "utf8").split("\n"); - lines.forEach((line, i) => { - const m = USES.exec(line); - if (!m) return; - const ref = m[1]; - if (EXEMPT_PREFIX.some((p) => ref.startsWith(p))) return; - if (ref.includes("actions/github-script")) return; - if (SHA.test(ref)) return; - const at = ref.lastIndexOf("@"); - const name = at === -1 ? ref : ref.slice(0, at); - const tag = at === -1 ? "" : ref.slice(at + 1); - // subpath actions key by repo root: github/codeql-action/init -> github/codeql-action - const root = name.split("/").slice(0, 2).join("/"); - if (known.has(ref.toLowerCase()) || known.has(`${root}@${tag}`.toLowerCase())) return; - bad.push(`${wf}:${i + 1}: ${ref}`); - }); - } - - if (bad.length) { - console.log("\nERROR: these action refs are neither SHA-pinned nor covered by the lockfile:"); - for (const b of bad) console.log(` ${b}`); - console.log("\nEither pin to a full commit SHA, or run `gh actions-lock` so the"); - console.log("lockfile resolves the ref."); - return 1; - } - console.log("all action refs are pinned"); - return 0; -} - -process.exit(main()); diff --git a/czech-file-knife/scripts/check-adoc-renders.sh b/czech-file-knife/scripts/check-adoc-renders.sh deleted file mode 100755 index 4f8fe4f82..000000000 --- a/czech-file-knife/scripts/check-adoc-renders.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Every tracked .adoc file must parse. Asciidoctor's --failure-level DEFAULTS TO -# FATAL, so a document that emits WARNING or ERROR to stderr still exits 0 -- -# which is why every gate in this estate that shelled out to asciidoctor was -# unfailable. The cure is the FLAG, not a wrapper, plus a three-way verdict: -# -# exit 0 + empty stderr -> the document rendered clean -# exit 0 + stderr output -> a finding (this is the case bare asciidoctor hides) -# non-zero -> a finding, or the run did not complete -# -# This is the same contract empty-linter ships. The two are complementary halves -# of document integrity: empty-linter's remit is invisible bytes, this gate's -# remit is whether the document parses at all. -# -# Exit codes: -# 0 all tracked .adoc rendered clean (or there are none) -# 1 at least one file emitted a diagnostic or failed to render -# 2 the check could not run (bad path, asciidoctor not installed) - -set -euo pipefail - -REPO_ROOT="${1:-.}" -if [ ! -d "$REPO_ROOT" ]; then - echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 - exit 2 -fi - -if ! command -v asciidoctor >/dev/null 2>&1; then - echo "ERROR: asciidoctor is not installed; cannot verify .adoc rendering." >&2 - echo " Install it with: gem install asciidoctor -v 2.0.26 --no-document" >&2 - echo " Failing closed: 'did not complete' is not 'clean'." >&2 - exit 2 -fi - -# Subject list read NUL-delimited so a path containing a space cannot split. -FILES=() -if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - while IFS= read -r -d '' f; do - FILES+=("$REPO_ROOT/$f") - done < <(git -C "$REPO_ROOT" ls-files -z -- '*.adoc' 2>/dev/null || true) -else - while IFS= read -r -d '' f; do - FILES+=("$f") - done < <(find "$REPO_ROOT" -type f -name '*.adoc' \ - -not -path '*/.git/*' -not -path '*/node_modules/*' -print0 2>/dev/null || true) -fi - -# An empty subject list is a PASS, not a failure. This is a TEMPLATE: a stripped -# mint may legitimately carry no .adoc yet, and the house idiom for an absent -# subject is check-no-md-in-docs.sh's "no docs/ directory (nothing to check)". -if [ "${#FILES[@]}" -eq 0 ]; then - echo "PASS: no tracked .adoc (nothing to check)" - exit 0 -fi - -FAILED=0 -FINDINGS="" -for f in "${FILES[@]}"; do - set +e - err="$(asciidoctor --failure-level=WARN --backend=html5 -o /dev/null "$f" 2>&1 >/dev/null)" - rc=$? - set -e - if [ "$rc" -ne 0 ] || [ -n "$err" ]; then - FAILED=$((FAILED + 1)) - first="$(printf '%s\n' "$err" | sed -n '1p')" - [ -n "$first" ] || first="(no diagnostic; exit $rc)" - FINDINGS="${FINDINGS} ${f#"$REPO_ROOT"/}: ${first}"$'\n' - fi -done - -if [ "$FAILED" -eq 0 ]; then - echo "PASS: all ${#FILES[@]} tracked .adoc file(s) render clean" - exit 0 -fi - -echo "FAIL: $FAILED of ${#FILES[@]} tracked .adoc file(s) did not render clean:" >&2 -printf '%s' "$FINDINGS" >&2 -echo >&2 -echo "Repair the document so asciidoctor emits nothing on stderr. Common causes:" >&2 -echo " * a Markdown body inside a .adoc: '# H' parses as a level-0 section" >&2 -echo " * an attribute line flush against '= Title', swallowed into the header" >&2 -echo " * a repeated [[anchor]] in prose, processed even inside backticks" >&2 -echo " * a stray '|' inside a table, shifting the cell count" >&2 -echo "Reproduce one file with:" >&2 -echo " asciidoctor --failure-level=WARN --backend=html5 -o /dev/null " >&2 -exit 1 diff --git a/czech-file-knife/scripts/check-invisible-characters.sh b/czech-file-knife/scripts/check-invisible-characters.sh deleted file mode 100755 index 8cc49fca1..000000000 --- a/czech-file-knife/scripts/check-invisible-characters.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Byte-safe scanner for invisible Unicode encodings and forbidden C0 controls. -set -u - -scan_root="${1:-}" -results_file="${2:-}" -blocking_results_file="${3:-}" -grep_bin="${INVISIBLE_GREP_BIN:-grep}" -find_bin="${INVISIBLE_FIND_BIN:-find}" - -if [[ -z "$scan_root" || ! -d "$scan_root" || -z "$results_file" ]]; then - echo "usage: $0 SCAN_ROOT RESULTS_FILE" >&2 - exit 2 -fi - -# Scan bytes under the C locale. This detects UTF-8 encodings even when another -# byte in the file is invalid UTF-8, while excluding permitted TAB/LF/CR bytes. -pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]|\xC2(?:\xA0|\xAD)|\xE2\x80[\x8B-\x8F\xAA-\xAF]|\xE2\x81(?:\xA0|[\xA6-\xA9])|\xEF\xBB\xBF' -blocking_pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]' -: > "$results_file" || exit 2 -if [[ -n "$blocking_results_file" ]]; then - : > "$blocking_results_file" || exit 2 -fi -scan_error=0 -enumeration_file="$(mktemp)" || exit 2 # TMPDIR-respecting; Hypatia hardcoded_tmp (alert #122) -# Invoked indirectly by the EXIT trap. -# shellcheck disable=SC2329 -cleanup() { - rm -f -- "$enumeration_file" -} -trap cleanup EXIT - -if ! "$find_bin" "$scan_root" \ - -not -path '*/.git/*' -not -path '*/node_modules/*' \ - -not -path '*/.deno/*' -not -path '*/target/*' \ - -not -path '*/_build/*' -not -path '*/deps/*' \ - -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ - -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ - -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ - -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ - -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ - -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \ - -o -name '*.a2ml' -o -name '*.txt' -o -name '*.just' \ - -o -name 'Justfile' -o -name 'Mustfile' -o -name 'Trustfile' -o -name 'Bustfile' \) \ - -print0 > "$enumeration_file"; then - echo "file enumeration failed: $scan_root" >&2 - exit 1 -fi - -while IFS= read -r -d '' filepath; do - LC_ALL=C "$grep_bin" -aPq "$pattern" "$filepath" - status=$? - case "$status" in - 0) - printf '%s\0' "$filepath" >> "$results_file" || scan_error=1 - if [[ -n "$blocking_results_file" ]]; then - LC_ALL=C "$grep_bin" -aPq "$blocking_pattern" "$filepath" - blocking_status=$? - case "$blocking_status" in - 0) printf '%s\0' "$filepath" >> "$blocking_results_file" || scan_error=1 ;; - 1) ;; - *) echo "blocking-classifier error ($blocking_status): $filepath" >&2; scan_error=1 ;; - esac - fi - ;; - 1) ;; - *) echo "scanner error ($status): $filepath" >&2; scan_error=1 ;; - esac -done < "$enumeration_file" - -exit "$scan_error" diff --git a/czech-file-knife/scripts/check-lock-sync.sh b/czech-file-knife/scripts/check-lock-sync.sh deleted file mode 100755 index 3088bbd5d..000000000 --- a/czech-file-knife/scripts/check-lock-sync.sh +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-lock-sync.sh — verify .github/workflows/actions.lock is in sync with the -# workflow YAML, in BOTH directions (including job-level reusable-workflow refs), -# AND that the lockfile is TRANSITIVELY CLOSED. -# -# Three clauses, each of which alone is insufficient: -# -# 1. every `uses:` in a workflow is locked under THAT workflow's own path; -# 2. every lockfile entry is still referenced by its workflow (no orphans); -# 3. every ref NAMED anywhere in the lockfile resolves to a top-level -# `dependencies:` record — the lockfile has no dangling edges. -# -# Clause 3 is not decoration. It is the clause that catches the failure mode that -# clauses 1 and 2 are structurally blind to, and it was added only after that -# blindness was measured. On hyperpolymath/cicd-squabbler, 2026-09-22: -# -# commit dangling-edge class result -# fe22bbc workflows: -> dependencies: (ref listed, no record) 4 workflows startup_failure, jobs=0 -# cfadcf9 dependencies: -> dependencies: (record added, its -# own nested uses: unrecorded) the same 4 still startup_failure -# 5286aa5 none - transitively closed 0 startup_failure, all 17 runs create jobs -# -# At fe22bbc AND cfadcf9 this script exited 0, `gh actions-lock --verify-local` -# exited 0, and the Lock Sync Gate reported green - while GitHub was refusing to -# start four workflows. Every local gate was green on a fatal commit. That is the -# guard/consumer trap: the gate asked "is every uses: locked?" and GitHub asks -# "is every locked ref RESOLVABLE?". -# -# The asymmetry that makes clause 3 mandatory, and counter-intuitive: -# * a job-level ref ABSENT from the lockfile entirely is HARMLESS; -# * a ref PRESENT in the lockfile but unresolvable is FATAL. -# So adding entries without closing them is strictly worse than adding nothing. -# Clause 1 demands entries be added; only clause 3 makes that demand safe. Shipping -# clause 1 without clause 3 actively steers a developer into the fatal state: -# Dependabot bumps a job-level ref -> clause 1 reds -> `gh actions-lock` is blind to -# job-level refs and will not backfill -> the developer hand-adds the workflows: -# entry to get green -> no dependencies: record -> CI dies silently, gate green. -# -# Exit 0 only when all three clauses hold. Any violation exits 1. There is no -# warn-only mode: a desync means GitHub refuses to start the run, so it must fail -# the job. A `::warning::` cannot fail a job and would be a vacuous gate. - -set -euo pipefail - -WF_DIR="${1:-.github/workflows}" -LOCK="$WF_DIR/actions.lock" - -# gawk is required: the parser uses 3-argument match(), a GNU extension. mawk -# (the Debian/Ubuntu default `awk`) does not support it, and a silent parse -# failure here would read as a clean pass - the exact failure mode this script -# exists to prevent. Probe it rather than trusting the name. -AWK="" -for cand in gawk awk; do - if command -v "$cand" >/dev/null 2>&1 \ - && echo x | "$cand" '{ if (match($0, /(x)/, m) && m[1] == "x") exit 0; exit 1 }' 2>/dev/null; then - AWK="$cand"; break - fi -done -if [ -z "$AWK" ]; then - echo "check-lock-sync: FATAL: no awk supporting 3-argument match() (need gawk)" >&2 - echo "check-lock-sync: install it with: sudo apt-get install -y gawk" >&2 - exit 1 -fi - -if [ ! -f "$LOCK" ]; then - echo "check-lock-sync: FATAL: no lockfile at $LOCK" >&2 - exit 1 -fi - -shopt -s nullglob -mapfile -t WORKFLOWS < <(printf '%s\n' "$WF_DIR"/*.yml "$WF_DIR"/*.yaml | sort -u) -if [ "${#WORKFLOWS[@]}" -eq 0 ]; then - echo "check-lock-sync: FATAL: no workflow files under $WF_DIR" >&2 - exit 1 -fi - -read -r -d '' PROG <<'AWK' || true -# owner/repo[/subpath...]@ref -> owner/repo@ref ("" if not an external ref) -function norm(r, at, path, ref, n, parts) { - at = 0 - for (n = length(r); n > 0; n--) { if (substr(r, n, 1) == "@") { at = n; break } } - if (at == 0) return "" - path = substr(r, 1, at - 1); ref = substr(r, at + 1) - if (path == "" || ref == "") return "" - if (substr(path, 1, 2) == "./" || substr(path, 1, 2) == "$/") return "" # local action - if (split(path, parts, "/") < 2) return "" - return parts[1] "/" parts[2] "@" ref -} - -# Fold case on the OWNER/REPO segment only, for comparison keys. GitHub resolves -# owner and repository names case-insensitively, and this is measured, not assumed: -# metadatastician/pong-ping's lockfile records sonarsource/sonarqube-scan-action@v8.2.1 -# while sonarqube.yml says SonarSource/..., and at commit cd5f90f that workflow ran -# SUCCESS while codeql.yml at the SAME commit was startup_failure. A same-commit -# control, so the case difference is provably not what kills a run. -# The REF is NOT folded: git tags and branch names are case-sensitive. -function ck(r, at, s) { - at = 0 - for (s = length(r); s > 0; s--) { if (substr(r, s, 1) == "@") { at = s; break } } - if (at == 0) return tolower(r) - return tolower(substr(r, 1, at - 1)) substr(r, at) -} - -# ---------- pass 1: the lockfile ---------- -FILENAME == lockfile { - if ($0 ~ /^workflows:[[:space:]]*$/) { inwf = 1; indep = 0; next } - if ($0 ~ /^dependencies:[[:space:]]*$/) { inwf = 0; indep = 1; next } - if ($0 ~ /^[a-z_]+:/) { inwf = 0; indep = 0; next } - - # --- the dependencies: section, for clause 3 --- - if (indep) { - # " 'owner/repo@ref':" -- a top-level dependency record - if (match($0, /^ '([^']+)':/, m)) { - depkey = m[1] - haverec[ck(depkey)] = 1; disp[ck(depkey)] = depkey - next - } - # " - 'owner/repo@ref'" -- a nested uses: of that record - if (match($0, /^ - '([^']+)'/, m) && depkey != "") { - r = ck(m[1]); disp[r] = m[1] - want[r] = 1 - wantsrc[r] = wantsrc[r] " dependencies:" depkey - next - } - next - } - - if (!inwf) next - - # " '.github/workflows/x.yml':" or "... : []" - if (match($0, /^ '([^']+)':/, m)) { - cur = m[1] - seen_path[cur] = 1 - next - } - if (match($0, /^ - '([^']+)'[[:space:]]*$/, m) && cur != "") { - lr = ck(m[1]); disp[lr] = m[1]; lock[cur, lr] = 1 - lockcount[cur]++ - want[lr] = 1 - wantsrc[lr] = wantsrc[lr] " " cur - next - } - next -} - -# ---------- pass 2: the workflow YAML ---------- -FNR == 1 { wf = FILENAME } -{ - line = $0 - sub(/[[:space:]]+#.*$/, "", line) # strip trailing comment - if (match(line, /^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(.+)$/, m)) { - raw = m[1] - gsub(/^["']|["']$/, "", raw) - gsub(/[[:space:]]+$/, "", raw) - if (raw ~ /^\$\//) { dollar[wf] = dollar[wf] " " raw; next } # known corruption - n = norm(raw) - if (n != "") { uses[wf, ck(n)] = 1; useslist[wf] = useslist[wf] " " n } - } -} - -END { - bad = 0 - for (i = 1; i < ARGC; i++) { - wf = ARGV[i] - if (wf == lockfile) continue - key = wf - sub(/.*\//, "", key) - key = ".github/workflows/" key # the lockfile always uses this canonical path - - if (dollar[wf] != "") { - printf "FAIL %s\n invalid local-action rewrite (uses: $/...):%s\n", key, dollar[wf] - bad = 1 - } - - # --- clause 1: every uses: must be locked under THIS path --- - nu = split(useslist[wf], u, " ") - delete uniq; missing = "" - for (j = 1; j <= nu; j++) { - if (u[j] == "" || (u[j] in uniq)) continue - uniq[u[j]] = 1 - if (!((key SUBSEP ck(u[j])) in lock)) missing = missing " " u[j] - } - if (missing != "") { - if (!(key in seen_path)) - printf "FAIL %s\n not onboarded: no lockfile entry for this path\n unlocked refs:%s\n", key, missing - else - printf "FAIL %s\n refs missing from the lockfile:%s\n", key, missing - bad = 1 - } - - # --- clause 2: every lock entry must be referenced by this workflow --- - orphan = "" - for (k in lock) { - split(k, kp, SUBSEP) - if (kp[1] != key) continue - if (!((wf SUBSEP kp[2]) in uses)) orphan = orphan " " (kp[2] in disp ? disp[kp[2]] : kp[2]) - } - if (orphan != "") { - printf "FAIL %s\n stale lockfile entries, no uses: references them:%s\n", key, orphan - bad = 1 - } - } - - # --- lockfile entries for workflow files that no longer exist --- - for (p in seen_path) { - found = 0 - for (i = 1; i < ARGC; i++) { - q = ARGV[i]; if (q == lockfile) continue - sub(/.*\//, "", q); q = ".github/workflows/" q - if (q == p) { found = 1; break } - } - if (!found) { printf "FAIL %s\n lockfile entry for a workflow file that does not exist\n", p; bad = 1 } - } - - # --- clause 4: COVERAGE. Every workflow FILE must have a key in the lockfile, - # including one with no uses: at all - the value is then an empty list. - # MEASURED 2026-09-22, single-variable flip on two independent repos: - # hyperpolymath/verisimdb's lock-sync-gate.yml was startup_failure 7 times - # running with ZERO uses: refs, and adding - # '.github/workflows/lock-sync-gate.yml': [] - # flipped it to success; reproduced on hyperpolymath/blocky-writer, 2 of 2. - # `gh actions-lock` already emits this empty-list form for other zero-uses: - # workflows (labels.yml), so it is the generator's own convention, not ours. - # Clauses 1-3 CANNOT catch this: they ask "is every uses: locked?", and a - # workflow with no uses: satisfies them vacuously while GitHub still refuses - # to start it. 13 repos passed clauses 1-3 with exactly this gap. - nunlisted = 0; unlisted = "" - for (i = 1; i < ARGC; i++) { - q = ARGV[i]; if (q == lockfile) continue - sub(/.*\//, "", q); q = ".github/workflows/" q - if (q in seen_path) continue - nunlisted++; unlisted = unlisted "\n " q - } - if (nunlisted > 0) { - printf "FAIL actions.lock: UNLISTED WORKFLOWS\n" - printf " %d workflow file(s) have no key in the lockfile. GitHub refuses such a\n", nunlisted - printf " run at startup (jobs=0) even when the workflow has no uses: at all.\n" - printf " The entry for a zero-uses: workflow is an empty list:%s\n", unlisted - bad = 1 - } - - # --- clause 3: TRANSITIVE CLOSURE. Every ref named anywhere in the lockfile - # must resolve to a top-level dependencies: record. A dangling edge makes - # GitHub refuse the run at startup with jobs=0. --- - ndang = 0; dang = "" - for (r in want) { - if (r !~ /^[^\/]+\/[^\/@]+@/) continue # not an OWNER/REPO@REF pin; not ours to resolve - if (r in haverec) continue - ndang++ - dang = dang sprintf("\n %s\n named by:%s", (r in disp ? disp[r] : r), wantsrc[r]) - } - if (ndang > 0) { - printf "FAIL actions.lock: DANGLING EDGES\n" - printf " %d ref(s) are named in the lockfile but have no top-level dependencies: record.%s\n", ndang, dang - bad = 1 - } - - # --- a dependencies: record nothing names is dead weight, not fatal: report only --- - nunref = 0 - for (d in haverec) if (!(d in want)) nunref++ - - if (bad) { - print "" - print "actions.lock is OUT OF SYNC with the workflow YAML, or is not transitively closed." - print "GitHub refuses such a run at startup: zero jobs are created and the run" - print "reports \"This run likely failed because of a workflow file issue.\"" - print "" - print "Fix, in this order:" - print " 1. `gh actions-lock --no-migrate-local-actions`, then review the diff. It does" - print " NOT handle job-level reusable-workflow refs and it can de-pin bare SHAs to" - print " floating tags - both must be corrected by hand." - print " 2. For any DANGLING EDGES above, add a top-level `dependencies:` record for each" - print " ref. A leaf record may legally omit the nested `uses:` key entirely, so adding" - print " leaves introduces no new dangling edges and closure terminates in one pass." - print " Keys are sorted with LC_ALL=C collation (ASCII '-' 0x2d sorts before '@' 0x40)." - print " 3. Nested `uses:` entries must be bare OWNER/REPO@REF. A subpath pin such as" - print " github/codeql-action/upload-sarif@ is REJECTED by the schema; collapse it" - print " to github/codeql-action@." - print " 4. For any UNLISTED WORKFLOWS above, add the path as a lockfile key. A workflow" - print " with no uses: takes an empty list: \x27.github/workflows/x.yml\x27: []" - print " `gh actions-lock` has been observed to OMIT such a workflow entirely; that" - print " omission is itself the defect, so re-running the tool may not add it." - exit 1 - } - printf "actions.lock is in sync and transitively closed:\n" - printf " * every uses: is locked under its own workflow path (job-level reusable refs included)\n" - printf " * every lockfile entry is still referenced\n" - printf " * every ref named in the lockfile resolves to a dependencies: record (0 dangling edges)\n" - printf " * every workflow file has a lockfile key (zero-uses: workflows included)\n" - if (nunref > 0) - printf " note: %d dependencies: record(s) are unreferenced - harmless, but prunable.\n", nunref -} -AWK - -"$AWK" -v lockfile="$LOCK" "$PROG" "$LOCK" "${WORKFLOWS[@]}" diff --git a/czech-file-knife/scripts/check-no-md-in-docs.sh b/czech-file-knife/scripts/check-no-md-in-docs.sh deleted file mode 100644 index 1658f935a..000000000 --- a/czech-file-knife/scripts/check-no-md-in-docs.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# check-no-md-in-docs.sh — enforce "AsciiDoc by default for general docs". -# -# Estate rule: .adoc for general docs (TOPOLOGY, READINESS, ROADMAP, etc.); -# .md only for files GitHub's community-health rules special-case by name -# (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CHANGELOG, etc.) — those live at -# root or in .github/, never under docs/. -# -# Fails if any .md files exist under docs/. Add justified entries to the -# ALLOWED list below if a docs/-rooted .md is genuinely needed (rare). -# -# Exit codes: -# 0 — no .md files under docs/ (or all matches are allow-listed) -# 1 — disallowed .md files found -# 2 — usage / setup error - -set -euo pipefail - -REPO_ROOT="${1:-.}" -DOCS_DIR="$REPO_ROOT/docs" - -# Justified exceptions, relative to repo root. Empty by default. -ALLOWED=() -# docs/berrywiki/ and docs/wikis/ are wiki-SYNC source trees: their content is -# mirrored to/from forge-hosted wikis (GitHub/GitLab), which are inherently -# Markdown. Converting them to AsciiDoc would break the sync contract, so the -# directories are allow-listed rather than the files. -ALLOWED_DIRS=("docs/berrywiki/" "docs/wikis/") - -if [ ! -d "$DOCS_DIR" ]; then - echo "PASS: no docs/ directory (nothing to check)" - exit 0 -fi - -mapfile -t HITS < <(find "$DOCS_DIR" -name '*.md' -type f 2>/dev/null | sort) - -EXTRAS=() -for hit in "${HITS[@]}"; do - rel="${hit#"$REPO_ROOT/"}" - skip=0 - for allowed in "${ALLOWED[@]}"; do - if [ "$rel" = "$allowed" ]; then skip=1; break; fi - done - for allowed_dir in "${ALLOWED_DIRS[@]}"; do - if [[ "$rel" == "$allowed_dir"* ]]; then skip=1; break; fi - done - if [ $skip -eq 0 ]; then EXTRAS+=("$rel"); fi -done - -if [ ${#EXTRAS[@]} -eq 0 ]; then - echo "PASS: no .md files under docs/ (${#HITS[@]} total found, ${#ALLOWED[@]} allow-listed)" - exit 0 -fi - -echo "FAIL: ${#EXTRAS[@]} .md files found under docs/ (estate rule: AsciiDoc by default):" >&2 -for e in "${EXTRAS[@]}"; do - echo " - $e" >&2 -done -echo "" >&2 -echo "Convert these to .adoc, or add a justified entry to the ALLOWED list" >&2 -echo "in scripts/check-no-md-in-docs.sh." >&2 -exit 1 diff --git a/czech-file-knife/scripts/check-no-placeholders.sh b/czech-file-knife/scripts/check-no-placeholders.sh deleted file mode 100755 index 6152cb899..000000000 --- a/czech-file-knife/scripts/check-no-placeholders.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# check-no-placeholders.sh — no repo may ship an unfilled {{PLACEHOLDER}}. -# -# Estate rule (methodology.a2ml: reject-if-contains): a token that `just repo-init` -# did not fill is debt, and in .github/settings.yml or SECURITY.md it is a -# defect with consequences — probot/settings applies settings.yml on every push, -# and a security policy that cites a key nobody holds is worse than one that -# says "email us". -# -# This is the single implementation of that rule, called from two places: -# * .github/workflows/openssf-compliance.yml — on the repo as committed -# * tests/e2e/template_instantiation_test.sh — on a freshly init'd repo, -# which is where a leak is still cheap to fix -# It exists as a script rather than inline shell in each caller because the -# previous split — a workflow that checked a hand-listed set of files, and an -# e2e test that re-implemented substitution with its own token list — let the -# two drift until the test passed while real instantiation leaked. -# -# Scans every text file and allow-lists the few legitimate carriers, rather -# than checking a list of files someone must remember to extend. The old -# required-files list omitted .github/settings.yml and ANCHOR.a2ml, which is -# precisely where the leaks were. -# -# Matches upper-snake brace tokens only. Justfiles are skipped entirely: an -# ARGS token there is just's own interpolation syntax, not a template token. -# GitHub Actions expressions are ${{ dotted.lower }} and do not match. -# -# Exit codes: -# 0 — no unfilled tokens (or this is a template repo, where tokens are the product) -# 1 — unfilled tokens found -# 2 — usage / setup error - -set -euo pipefail - -REPO_ROOT="${1:-.}" - -if [ ! -d "$REPO_ROOT" ]; then - echo "usage: $0 [repo-root]" >&2 - exit 2 -fi - -# ─── settings.yml identity guard — runs EVERYWHERE, template repos included ──── -# -# This check deliberately precedes the template exemption below. That exemption -# is why the original incident went unseen: the gate skipped `*-template-repo` -# entirely, so nobody noticed that .github/settings.yml shipped `name: "czech-file-knife"` -# — and .github/settings.yml is not inert content in a template. probot/settings -# applies it on every push to the default branch, in the template as much as in -# an instantiation. The template submitted the literal `czech-file-knife` as its own -# name; GitHub collapsed the illegal braces to dashes and renamed the repository -# to `-REPO-`, which then read as a deleted repo. -# -# So: in this one file, a placeholder is never "the product". Neither is an -# identity key with a real value — `name`/`private` cannot be inherited by a -# child repo without being wrong (see the header of .github/settings.yml). -SETTINGS="$REPO_ROOT/.github/settings.yml" -if [ -f "$SETTINGS" ]; then - settings_fail=0 - - # Comment-aware: the file's own header documents the incident and has to be - # able to quote the offending token. Prose about a token is not a token — - # the same distinction META_TOKENS draws below. Line numbers are preserved - # by filtering `grep -n` output rather than the file. - settings_tokens="$(grep -nE '\{\{' "$SETTINGS" | grep -vE '^[0-9]+:[[:space:]]*#' || true)" - if [ -n "$settings_tokens" ]; then - echo "FAIL: .github/settings.yml contains an unrendered {{ token." >&2 - printf '%s\n' "$settings_tokens" | sed 's/^/ /' >&2 - settings_fail=1 - fi - - # Keys of the `repository:` map sit at exactly two spaces of indent. Label - # and branch entries are list items (" - name:") and are not matched. - if grep -qE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS"; then - echo "FAIL: .github/settings.yml declares repository identity." >&2 - grep -nE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS" \ - | sed 's/^/ /' >&2 - settings_fail=1 - fi - - if [ "$settings_fail" -ne 0 ]; then - echo "" >&2 - echo "probot/settings applies this file on every push to the default branch," >&2 - echo "so these keys are enforced, not described. Repository identity and" >&2 - echo "visibility are set out of band at creation time — by \`just repo-init\` via" >&2 - echo "\`gh\` for minted repos, and deliberately by the owner for the template." >&2 - exit 1 - fi -fi - -# A template repo's placeholders ARE its product — they are what `just repo-init` -# consumes. Any other repo is an instantiation and is checked in full. -# -# Identity comes from the git remote, not the directory name. -# -# This used to be `basename "$(pwd)"`, which is right in CI — GITHUB_REPOSITORY -# is set to `owner/czech-file-knife` and matches — but wrong anywhere the -# checkout is not literally named `*-template-repo`. A git worktree is the common -# case: `git worktree add .claude/worktrees/defects` gives basename `defects`, -# the exemption misses, and the gate reports every one of the template's ~85 -# deliberate placeholder files as a failure. A clone into `czech-file-knife-2`, -# or any renamed directory, does the same. -# -# The remote URL is the repo's actual identity and survives all of that. The -# basename remains as the last fallback for a checkout with no remote. -REPO_NAME="${GITHUB_REPOSITORY:-}" -if [ -z "$REPO_NAME" ]; then - REPO_NAME="$(git -C "$REPO_ROOT" config --get remote.origin.url 2>/dev/null \ - | sed -E 's#(\.git)?/?$##; s#^.*[:/]([^/]+/[^/]+)$#\1#')" -fi -[ -z "$REPO_NAME" ] && REPO_NAME="$(cd "$REPO_ROOT" && basename "$(pwd)")" -case "$REPO_NAME" in - *-template-repo) - echo "PASS: $REPO_NAME is a template repo — unfilled tokens are intentional" - echo " (.github/settings.yml identity guard above still applied)" - exit 0 - ;; -esac - -# Files that legitimately contain tokens after instantiation. -ALLOWED=( - ".machine_readable/ai/PLACEHOLDERS.adoc" # the token vocabulary itself - "EXPLAINME.adoc" # prose explaining that tokens exist - "scripts/check-no-placeholders.sh" # this file (the pattern above) - "tests/e2e/template_instantiation_test.sh" # names tokens in its answer list -) - -is_allowed() { - local rel="$1" - for a in "${ALLOWED[@]}"; do - [ "$rel" = "$a" ] && return 0 - done - # just owns brace tokens inside justfiles — an ARGS token there is - # interpolation, not a placeholder. Justfiles are not only at the root: - # the contractiles ship one too. - case "$rel" in - Justfile|justfile|*/Justfile|*/justfile|*.just) return 0 ;; - esac - return 1 -} - -# Metasyntactic tokens: prose *about* tokens, not tokens. "Replace all -# {{PLACEHOLDER}} values" names the concept — there is no PLACEHOLDER variable -# for init to fill, so these can never be a leak, and flagging them would only -# teach people that this gate cries wolf. Real tokens name a real init variable. -META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' - -LEAKS=() -while IFS= read -r hit; do - rel="${hit#"$REPO_ROOT"/}" - is_allowed "$rel" && continue - # Re-check the file for at least one non-metasyntactic token. - if grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$hit" \ - | grep -qvE "^\{\{($META_TOKENS)\}\}$"; then - LEAKS+=("$rel") - fi -done < <(grep -rlE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT" \ - --exclude-dir=.git --binary-files=without-match 2>/dev/null | sort) - -if [ ${#LEAKS[@]} -eq 0 ]; then - echo "PASS: no unfilled {{PLACEHOLDER}} tokens" - exit 0 -fi - -echo "FAIL: ${#LEAKS[@]} file(s) contain unfilled {{PLACEHOLDER}} tokens:" >&2 -for leak in "${LEAKS[@]}"; do - tokens=$(grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT/$leak" \ - | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ') - echo " - $leak: $tokens" >&2 -done -echo "" >&2 -echo "Each token must either be filled by build/just/init.just's SED_ARGS, or" >&2 -echo "removed from the shipped file. A token with no possible value (a PGP key" >&2 -echo "the estate does not hold) makes this gate unsatisfiable — delete the" >&2 -echo "section instead of leaving the gate permanently red." >&2 -exit 1 diff --git a/czech-file-knife/scripts/check-no-vlang.sh b/czech-file-knife/scripts/check-no-vlang.sh deleted file mode 100755 index aeb62fcb8..000000000 --- a/czech-file-knife/scripts/check-no-vlang.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Enforce the estate ban on the V programming language. Zig is the supported -# systems/FFI language and must never be matched by this check. - -set -euo pipefail - -REPO_ROOT="${1:-.}" -if [ ! -d "$REPO_ROOT" ]; then - echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 - exit 2 -fi - -PATTERN='gen-v-connector|V-TRIPLE|v-triple|vlang|connectors/v-|import[[:space:]]+vweb' -HITS="" -V_MODS="" - -if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - # Search tracked content only. The exclusions are the policy and its - # enforcement/tests, which necessarily name the forbidden patterns. - HITS=$(git -C "$REPO_ROOT" grep -n -i -E "$PATTERN" -- \ - . \ - ':(exclude)affinescript/**' \ - ':(exclude)scripts/check-no-vlang.sh' \ - ':(exclude)tests/workflows/check_no_vlang_test.sh' \ - ':(exclude).github/workflows/estate-rules.yml' \ - ':(exclude).machine_readable/descriptiles/PLAYBOOK.a2ml' \ - 2>/dev/null || true) - V_MODS=$(git -C "$REPO_ROOT" ls-files -- 'v.mod' '**/v.mod' 2>/dev/null || true) -else - HITS=$(grep -rni -E "$PATTERN" "$REPO_ROOT" \ - --exclude-dir=.git \ - --exclude-dir=affinescript \ - --exclude-dir=node_modules \ - --exclude=check-no-vlang.sh \ - --exclude=check_no_vlang_test.sh \ - --exclude=estate-rules.yml \ - --exclude=PLAYBOOK.a2ml \ - 2>/dev/null || true) - V_MODS=$(find "$REPO_ROOT" -type f -name v.mod \ - -not -path '*/.git/*' -not -path '*/affinescript/*' \ - -printf '%P\n' 2>/dev/null || true) -fi - -# Drop self-references. A line whose only match is this checker's own file name -# is an INVOCATION, not a V-language artefact. The :(exclude) list above can -# only name call sites that already exist, so without this filter the gate -# false-positives the moment a repo invokes it from a new place -- a Justfile, a -# pre-push hook, a different workflow. Proven 2026-09-02: a Justfile line -# `bash scripts/check-no-vlang.sh .` was reported as a V-language reference. -SELF_REF='check[-_]no[-_]vlang(_test)?[.]sh' -if [ -n "$HITS" ]; then - HITS=$(printf '%s\n' "$HITS" | awk -v self="$SELF_REF" -v pat="$PATTERN" ' - { - line = tolower($0) - gsub(self, "", line) - if (line ~ tolower(pat)) { print } - }') -fi - -if [ -z "$HITS" ] && [ -z "$V_MODS" ]; then - echo "PASS: no V-language references in the inspected repository" - exit 0 -fi - -COUNT=0 -if [ -n "$HITS" ]; then - CONTENT_COUNT=$(printf '%s\n' "$HITS" | awk 'NF { count++ } END { print count + 0 }') - COUNT=$((COUNT + CONTENT_COUNT)) -fi -if [ -n "$V_MODS" ]; then - FILE_COUNT=$(printf '%s\n' "$V_MODS" | awk 'NF { count++ } END { print count + 0 }') - COUNT=$((COUNT + FILE_COUNT)) -fi - -echo "FAIL: $COUNT V-language reference(s) found (estate policy forbids V):" >&2 -if [ -n "$HITS" ]; then - printf '%s\n' "$HITS" | sed 's/^/ /' >&2 -fi -if [ -n "$V_MODS" ]; then - printf '%s\n' "$V_MODS" | sed 's/^/ tracked module file: /' >&2 -fi -echo >&2 -echo "Remove the V-language remnants; use the supported Zig adapter where an FFI/API bridge is needed." >&2 -exit 1 diff --git a/czech-file-knife/scripts/check-proofs.sh b/czech-file-knife/scripts/check-proofs.sh deleted file mode 100755 index 0135dfed8..000000000 --- a/czech-file-knife/scripts/check-proofs.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# check-proofs.sh prover in: idris2 | lean4 | agda | coq -# -# The single source of truth for "do the proofs in this repo compile?". -# build/just/proofs.just calls this (`just proof-check-`), and CI should -# too, so a green local run and a green CI run mean the same thing. -# -# It replaces four separate "checks that could not fail" that let non-compiling -# proofs sit in estate repos for months while every status file said "proved": -# -# 1. `command -v || { echo SKIP; exit 0; }` — a MISSING TOOLCHAIN -# reported success. A gate that cannot run must never report OK: it -# manufactures false confidence, which is worse than having no gate. -# 2. ` --check ` — Idris2 (and friends) derive the -# expected module name from the path they are handed, so a module checked -# from the wrong directory fails on a name mismatch rather than its real -# errors, and verdicts invert. Every module here is checked from its own -# SOURCE ROOT (declared in the MANIFEST). -# 3. Path-filtered CI that only looked at one directory — nothing checked the -# rest. Here, a proof file present on disk but absent from the MANIFEST is -# an ERROR, so new proofs are gated by default, not by remembering. -# 4. `idris2 --check X && ok` — `idris2 --check` EXITS 0 ON A MISSING IMPORT -# (verified against 0.7.0) while printing `Error: ...`. Testing the exit -# code alone is unsound; for idris2 we require exit 0 AND no `Error:` line. -# -# They share one shape: a null check that emits reassuring text. If you extend -# this script, the test to apply is not "does it pass?" but "have I watched it -# fail?". -# -# CONVENTION: proofs live under verification/proofs// ; the MANIFEST for -# a prover is verification/proofs//MANIFEST, one entry per line: -# -# ||gated|quarantine| -# -# source-root : directory the prover is invoked from, chosen so the module's -# declared name matches its path (getting this wrong is hole #2). -# gated : MUST compile. A failure fails this script and CI. -# quarantine : known-broken, tracked in STATE.a2ml. Must CONTINUE to fail; -# if one starts compiling the script fails and tells you to -# promote it, so the list cannot rot into a permanent excuse. -# -# Blank lines and lines starting with # are ignored. -# -# Exit: 0 = every gated module compiles AND every quarantined module still fails -# AND every proof file on disk is listed; 1 = otherwise; 2 = misuse. - -set -euo pipefail - -PROVER="${1:-}" -case "$PROVER" in - idris2|lean4|agda|coq) ;; - *) echo "usage: $(basename "$0") " >&2; exit 2 ;; -esac - -# This script lives in /scripts/ ; run everything from the repo root. -cd "$(dirname "${BASH_SOURCE[0]}")/.." -ROOT="$PWD" -PROOF_DIR="verification/proofs/$PROVER" -MANIFEST_FILE="$PROOF_DIR/MANIFEST" - -# --- per-prover configuration ------------------------------------------------- -# CMD : executable that must be on PATH (absent => FAIL, never skip). -# EXT : file extension, for the "unlisted proof" coverage scan. -# ERROR_RE : if non-empty, output must not match it even when the exit code is 0. -# Only idris2 needs this (its --check exits 0 on a missing import); -# for lean4 it is a harmless belt-and-braces guard. -case "$PROVER" in - idris2) CMD=idris2; EXT=idr; ERROR_RE='^Error:' ;; - lean4) CMD=lean; EXT=lean; ERROR_RE='error:' ;; - agda) CMD=agda; EXT=agda; ERROR_RE='' ;; - coq) CMD=coqc; EXT=v; ERROR_RE='' ;; -esac - -check_one() { - # $1 source-root (rel to ROOT), $2 module (rel to source-root). - # Sets LAST_OUT to the tool output on failure; returns 0 iff the module compiles. - local root="$1" rel="$2" out rc - set +e - case "$PROVER" in - idris2) out="$(cd "$ROOT/$root" && idris2 --check "$rel" 2>&1)"; rc=$? ;; - lean4) out="$(cd "$ROOT/$root" && lean "$rel" 2>&1)"; rc=$? ;; - agda) out="$(cd "$ROOT/$root" && agda --safe "$rel" 2>&1)"; rc=$? ;; - coq) out="$(cd "$ROOT/$root" && coqc "$rel" 2>&1)"; rc=$? ;; - esac - set -e - if [ "$rc" -eq 0 ] && { [ -z "$ERROR_RE" ] || ! grep -qE "$ERROR_RE" <<<"$out"; }; then - LAST_OUT=""; return 0 - fi - LAST_OUT="$out"; return 1 -} - -echo "=== $PROVER proof check ===" - -# --- toolchain: absent means FAIL, never skip --------------------------------- -if ! command -v "$CMD" >/dev/null 2>&1; then - { - echo "FAIL: '$CMD' not found on PATH." - echo - echo "This is deliberately fatal. The previous recipe did 'exit 0' here with" - echo "\"SKIP: $CMD not installed\", so every $PROVER proof reported green on any" - echo "machine that could not check it. Install the $PROVER toolchain, or run" - echo "this in CI where the workflow installs it." - } >&2 - exit 1 -fi -"$CMD" --version 2>/dev/null | head -1 || true -echo - -# --- a repo with proofs but no MANIFEST is itself a failure ------------------- -if [ ! -f "$MANIFEST_FILE" ]; then - if [ -d "$PROOF_DIR" ] && [ -n "$(find "$PROOF_DIR" -name "*.$EXT" 2>/dev/null)" ]; then - echo "FAIL: $PROOF_DIR contains .$EXT proofs but has no MANIFEST." >&2 - echo " Create $MANIFEST_FILE listing each as 'gated' or 'quarantine'." >&2 - exit 1 - fi - echo "no $PROOF_DIR/*.$EXT proofs and no MANIFEST — nothing to check." - exit 0 -fi - -fails=0 -unexpected_pass=0 -listed_tmp="$(mktemp)" -trap 'rm -f "$listed_tmp"' EXIT - -while IFS='|' read -r root rel status note; do - # skip blank lines and comments - [ -z "${root// }" ] && continue - case "${root#"${root%%[![:space:]]*}"}" in \#*) continue ;; esac - printf ' %-30s %-24s ' "$root" "$rel" - echo "$root/$rel" >>"$listed_tmp" - if check_one "$root" "$rel"; then - if [ "$status" = gated ]; then - echo "PASS" - else - echo "PASS -- UNEXPECTED (quarantined module now compiles)" - echo " Promote '$rel' to 'gated' in $MANIFEST_FILE and update STATE.a2ml." - unexpected_pass=$((unexpected_pass + 1)) - fi - else - if [ "$status" = gated ]; then - echo "FAIL" - printf '%s\n' "${LAST_OUT//$'\n'/$'\n '}" | sed '1s/^/ /' - fails=$((fails + 1)) - else - echo "fail (quarantined, expected)" - [ -n "${note// }" ] && echo " reason:$note" - fi - fi -done < "$MANIFEST_FILE" - -# --- coverage: every proof on disk must be listed ----------------------------- -# The anti-recurrence rule: proofs went unchecked for months because nothing -# forced them onto anyone's list. A file absent from the MANIFEST is an error. -echo -echo "=== manifest coverage ($PROOF_DIR) ===" -listed="$(sort -u "$listed_tmp")" -found="$(cd "$ROOT" && find "$PROOF_DIR" -name "*.$EXT" -not -path '*/build/*' 2>/dev/null | sort)" -unlisted="$(comm -13 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" -missing="$(comm -23 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" - -if [ -n "${unlisted//[[:space:]]/}" ]; then - echo "FAIL: .$EXT proofs on disk but absent from $MANIFEST_FILE:" - printf ' %s\n' $unlisted - echo " List each as 'gated' or 'quarantine'; new proofs are gated by default." - fails=$((fails + 1)) -fi -if [ -n "${missing//[[:space:]]/}" ]; then - echo "FAIL: MANIFEST lists modules that do not exist (stale entries):" - printf ' %s\n' $missing - fails=$((fails + 1)) -fi -[ -z "${unlisted//[[:space:]]/}${missing//[[:space:]]/}" ] && \ - echo " all $(printf '%s\n' "$found" | grep -c .) .$EXT file(s) accounted for" - -echo -if [ "$fails" -gt 0 ] || [ "$unexpected_pass" -gt 0 ]; then - echo "RESULT: FAIL ($fails failure(s), $unexpected_pass unexpected pass(es))" - exit 1 -fi -echo "RESULT: PASS -- gated modules compile; quarantined modules still fail as recorded" diff --git a/czech-file-knife/scripts/check-root-shape.sh b/czech-file-knife/scripts/check-root-shape.sh deleted file mode 100755 index 6ffdb7dad..000000000 --- a/czech-file-knife/scripts/check-root-shape.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# check-root-shape.sh — enforce the canonical root shape, in BOTH directions, -# against the repository root allowlist, under EITHER canonical spelling: -# .machine_readable/root-allow.txt (dotted, CANONICAL ESTATE-WIDE) -# machine-readable/root-allow.txt (hyphenated, accepted legacy) -# -# The dotted form is the standard (owner ruling 2026-09-17; estate census 48 -# repos dotted vs 9 hyphenated). The hyphenated form is still RESOLVED, not -# rejected, because the checker is shared with repos that have not migrated. -# Removing this branch would turn 9 working gates into exit-2 setup errors. -# -# * an entry at root that is not listed -> drift (extra) -# * a listed entry WITHOUT '?' that is missing -> drift (missing) -# -# The second direction was absent until 2026-08, and its absence is why the -# allowlist rotted: it accumulated 19 permissions for files the April root -# cleanup had already moved into docs/, and nothing could ever notice. A -# one-directional allowlist only ratchets open, so over time it licenses -# exactly the drift it was written to prevent. -# -# '?' marks an entry that is legitimately absent in some conforming repo — -# template-only material removed at mint, or a capability-gated module. -# -# Companion to scripts/validate-template.sh: that script enforces required -# files; this one enforces the shape as a whole. -# -# Exit codes: -# 0 — root matches allowlist -# 1 — drift (extras at root, or required entries missing) -# 2 — usage / setup error - -set -euo pipefail - -REPO_ROOT="${1:-.}" -REPO_ROOT_DOTTED="${REPO_ROOT}/.machine_readable/root-allow.txt" -REPO_ROOT_HYPHEN="${REPO_ROOT}/machine-readable/root-allow.txt" - -# Both spellings are estate contract. Resolve whichever exists; if BOTH exist -# that is itself drift (two sources of truth) and is refused. -if [ -f "$REPO_ROOT_DOTTED" ] && [ -f "$REPO_ROOT_HYPHEN" ]; then - echo "ERROR: both .machine_readable/ and machine-readable/ carry a root-allow.txt;" >&2 - echo " pick one spelling — two allowlists cannot both be canonical." >&2 - exit 2 -elif [ -f "$REPO_ROOT_DOTTED" ]; then - ALLOW_FILE="$REPO_ROOT_DOTTED" -elif [ -f "$REPO_ROOT_HYPHEN" ]; then - ALLOW_FILE="$REPO_ROOT_HYPHEN" -else - echo "ERROR: allowlist not found at either $REPO_ROOT_DOTTED or $REPO_ROOT_HYPHEN" >&2 - exit 2 -fi - -# Build the allow set: strip comments, trailing slashes, and blank lines. -# A leading '?' marks the entry optional; it is not part of the name. -mapfile -t ALLOW_RAW < <( - sed -E 's/[[:space:]]*#.*$//' "$ALLOW_FILE" \ - | sed -E 's|/$||' \ - | awk 'NF' \ - | sed -E 's/[[:space:]]+$//' -) - -declare -A ALLOW_SET=() -REQUIRED=() -ALLOW=() -for raw in "${ALLOW_RAW[@]}"; do - if [[ "$raw" == '?'* ]]; then - entry="${raw#\?}" - else - entry="$raw" - REQUIRED+=("$entry") - fi - ALLOW_SET["$entry"]=1 - ALLOW+=("$entry") -done - -# Enumerate everything at the repository root, EXCLUDING git-ignored entries. -# -# This was a bare `find`, which contradicted the contract root-allow.txt states -# ("Anything tracked at root that is not in this list is drift"): a plain -# filesystem scan also sees build output. Any repo with a root-level build -# directory -- `target/` for Cargo, `node_modules/`, `_build/` for Mix -- -# therefore failed this gate the moment someone built before running it, and -# the tempting "fix" was to allowlist an artifact directory that must never be -# committed. -# -# Filtering through `git check-ignore` makes the check mean what it says. The -# fallback keeps the script working outside a git worktree. -mapfile -t ACTUAL < <( - cd "$REPO_ROOT" && \ - find . -mindepth 1 -maxdepth 1 \ - ! -name '.' \ - -printf '%f\n' \ - | { if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - git check-ignore --stdin --non-matching --verbose 2>/dev/null \ - | sed -n 's/^::[[:space:]]//p' - else - cat - fi; } \ - | sort -) - -declare -A ACTUAL_SET=() -for entry in "${ACTUAL[@]}"; do - ACTUAL_SET["$entry"]=1 -done - -# Direction 1 — present at root but not permitted. -EXTRAS=() -for entry in "${ACTUAL[@]}"; do - if [ -z "${ALLOW_SET[$entry]+x}" ]; then - EXTRAS+=("$entry") - fi -done - -# Direction 2 — required by the allowlist but not present. -MISSING=() -for entry in "${REQUIRED[@]}"; do - if [ -z "${ACTUAL_SET[$entry]+x}" ]; then - MISSING+=("$entry") - fi -done - -if [ ${#EXTRAS[@]} -eq 0 ] && [ ${#MISSING[@]} -eq 0 ]; then - OPTIONAL_COUNT=$(( ${#ALLOW[@]} - ${#REQUIRED[@]} )) - echo "PASS: root matches allowlist (${#ACTUAL[@]} entries; ${#REQUIRED[@]} required, ${OPTIONAL_COUNT} optional)" - exit 0 -fi - -if [ ${#EXTRAS[@]} -gt 0 ]; then - echo "FAIL: ${#EXTRAS[@]} root entries are not on the allowlist:" >&2 - for e in "${EXTRAS[@]}"; do - if [ -d "$REPO_ROOT/$e" ]; then - echo " - $e/ (directory)" >&2 - else - echo " - $e" >&2 - fi - done - echo "" >&2 - echo "Either move them into the appropriate subdirectory, or add a justified" >&2 - echo "entry to ${ALLOW_FILE#"$REPO_ROOT"/}." >&2 -fi - -if [ ${#MISSING[@]} -gt 0 ]; then - echo "FAIL: ${#MISSING[@]} allowlist entries are required but absent:" >&2 - for e in "${MISSING[@]}"; do - echo " - $e" >&2 - done - echo "" >&2 - echo "Either restore them, or - if they are legitimately absent in this repo -" >&2 - echo "mark the entry optional with a leading '?' in root-allow.txt and say why." >&2 - echo "Do not mark an entry optional merely to silence this." >&2 -fi -exit 1 diff --git a/czech-file-knife/scripts/check-template-conformance.sh b/czech-file-knife/scripts/check-template-conformance.sh deleted file mode 100755 index 88345d5f4..000000000 --- a/czech-file-knife/scripts/check-template-conformance.sh +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# check-template-conformance.sh — the CHECK half of the template contract. -# -# ── Why this exists ────────────────────────────────────────────────────────── -# -# The estate's own comment in build/just/repo-init.just reads: -# -# "Copier/Cruft solved it with an answer-file + update + check; this estate -# hand-simulated it as recurring standardisation-PR campaigns. This is the -# answer-file." -# -# It built the answer-file — .machine_readable/PROVENANCE.a2ml — and then built -# neither the update nor the check. Nothing in this repository read that file. -# It was write-only. -# -# Four open defects are all the same missing half: -# -# #200 CONTRIBUTING.md shipped hardcoded template identity into children -# #201 the generated CLAUDE arrival pack retained the TEMPLATE's uuid, -# clade and "canonical template" purpose in a Julia child -# #203 a template work branch was copied into knot-knot with no common -# ancestor — git proved the trees byte-identical -# -# Each is a repo asserting provenance it does not have, and none could be -# detected because no check read the record. -# -# ── What it checks ─────────────────────────────────────────────────────────── -# -# Structural invariants (always, no network): -# T1 PROVENANCE.a2ml exists -# T2 it does not name THIS repo as its own template (self-parent) -# T3 template_branch / template_commit / template_tree are not UNASSIGNED -# T4 the repo carries no branches beyond its declared extra_branches -# -# T2 is the #200/#201 class. T4 is #203. -# -# Drift report (only with --template PATH, and only ADVISORY): -# D1 template-owned paths missing from this repo -# D2 template-owned paths that differ from the template checkout -# -# Drift is advisory on purpose. A child is SUPPOSED to diverge; that is what -# minting is for. Reporting divergence as failure is how a gate becomes -# unpassable and then gets `continue-on-error`-ed, which is how the estate's -# Hypatia gate ended up unable to fire at all (measured twice: #49, #64). -# -# ── Usage ──────────────────────────────────────────────────────────────────── -# bash scripts/check-template-conformance.sh [--repo PATH] [--template PATH] -# [--report-only] [--quiet] -# -# --repo PATH the minted repo to check (default: this script's repo) -# --template PATH a template checkout, to enable the advisory drift report -# --report-only never exit non-zero (for rollout onto existing children) -# --quiet suppress the per-check PASS lines -# -# Exit: 0 conforming (or report-only), 1 findings, 2 usage/environment error. - -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" - -TARGET="$REPO_DIR" -TEMPLATE="" -REPORT_ONLY=0 -QUIET=0 - -while [ $# -gt 0 ]; do - case "$1" in - --repo) TARGET="${2:-}"; shift 2 ;; - --template) TEMPLATE="${2:-}"; shift 2 ;; - --report-only) REPORT_ONLY=1; shift ;; - --quiet) QUIET=1; shift ;; - -h|--help) sed -n '2,60p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; - *) echo "unknown option: $1" >&2; exit 2 ;; - esac -done - -[ -d "$TARGET" ] || { echo "check-template-conformance: not a directory: $TARGET" >&2; exit 2; } -TARGET="$(cd "$TARGET" && pwd)" - -PASS=0; FAIL=0; WARN=0 -ok() { PASS=$((PASS+1)); [ "$QUIET" -eq 1 ] || printf ' \033[32mPASS\033[0m %s\n' "$1"; } -bad() { FAIL=$((FAIL+1)); printf ' \033[31mFAIL\033[0m %s\n' "$1"; } -warn() { WARN=$((WARN+1)); printf ' \033[33mWARN\033[0m %s\n' "$1"; } -note() { [ "$QUIET" -eq 1 ] || printf ' %s\n' "$1"; } - -PROV="$TARGET/.machine_readable/PROVENANCE.a2ml" - -finish() { - echo - if [ "$FAIL" -gt 0 ]; then - printf 'conformance: \033[31m%d failed\033[0m, %d passed, %d warnings\n' "$FAIL" "$PASS" "$WARN" - else - printf 'conformance: \033[32m%d passed\033[0m, %d warnings\n' "$PASS" "$WARN" - fi - if [ "$REPORT_ONLY" -eq 1 ]; then - echo "(report-only: not failing the run)" - exit 0 - fi - [ "$FAIL" -eq 0 ] -} - -# ── Self-skip: this IS the template ────────────────────────────────────────── -# Same convention as tests/e2e/template_instantiation_test.sh. archetypes/ and -# build/templates/ are template-only and are removed at mint, so their presence -# means this repo has not been instantiated and has no parent to conform to. -if [ -d "$TARGET/archetypes" ] || [ -d "$TARGET/build/templates" ]; then - echo "SKIP: $TARGET is a template (archetypes/ or build/templates/ present) — nothing to conform to." - exit 0 -fi - -echo "template conformance: $TARGET" -echo - -# ── T1: provenance exists ──────────────────────────────────────────────────── -if [ ! -f "$PROV" ]; then - bad "T1 no .machine_readable/PROVENANCE.a2ml — this repo cannot state what it was minted from" - note "A repo minted through the GitHub template UI, or by copying a tree, never" - note "runs repo-init and so never writes this file. That is the #203 mechanism." - note "Recreate it by hand from the template commit you actually took." - finish - exit $? -else - ok "T1 provenance present" -fi - -# a2ml is a flat key = "value" format as far as this check needs. -prov_get() { - sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"\(.*\)\"[[:space:]]*$/\1/p" "$PROV" | head -1 -} - -T_REPO="$(prov_get template_repo)" -T_BRANCH="$(prov_get template_branch)" -T_COMMIT="$(prov_get template_commit)" -T_TREE="$(prov_get template_tree)" - -# ── T2: no self-parent ─────────────────────────────────────────────────────── -SELF_SLUG="" -if command -v git >/dev/null 2>&1 && git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then - url="$(git -C "$TARGET" remote get-url origin 2>/dev/null || true)" - # normalise git@github.com:o/r.git and https://github.com/o/r(.git) to o/r. - # Strip the .git suffix FIRST: the previous single-sed form left it on, so - # SELF_SLUG became "owner/repo.git" and the T2 self-parent check silently - # never matched. Caught by its own negative control. - SELF_SLUG="$(printf '%s' "$url" \ - | sed -E 's|\.git$||; s|^[a-zA-Z][a-zA-Z0-9+.-]*://||; s|^git@||; s|:|/|' \ - | awk -F/ 'NF>=2 {print $(NF-1)"/"$NF}' || true)" -fi - -if [ -z "$T_REPO" ]; then - bad "T2 provenance has no template_repo" -elif [ -n "$SELF_SLUG" ] && [ "$T_REPO" = "$SELF_SLUG" ]; then - bad "T2 provenance names THIS repo as its own template ($T_REPO) — self-parent" - note "This is the #200/#201 class: the repo is asserting template identity." - note "A child must point at the template it came from, not at itself." -else - ok "T2 parent is external${SELF_SLUG:+ (self=$SELF_SLUG, parent=$T_REPO)}" -fi - -# ── T3: the pin is real ────────────────────────────────────────────────────── -for pair in "template_branch:$T_BRANCH" "template_commit:$T_COMMIT" "template_tree:$T_TREE"; do - key="${pair%%:*}"; val="${pair#*:}" - if [ -z "$val" ] || [ "$val" = "UNASSIGNED" ]; then - bad "T3 $key is ${val:-missing} — the parent pin is not resolvable" - note "UNASSIGNED is honest at mint time when offline, but it must be filled" - note "in before the repo is published, or drift can never be detected." - else - ok "T3 $key = $val" - fi -done - -# ── T4: branch contract (#203) ─────────────────────────────────────────────── -# The template must never leak its work branches into a child. knot-knot got a -# byte-identical copy of coderabbit/fix-hypatia-scan-failures/f36ac704 with no -# common ancestor; git proved the tree equality. -if git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then - DEFAULT="$(git -C "$TARGET" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')" - if [ -z "$DEFAULT" ]; then - for cand in main master; do - git -C "$TARGET" show-ref --verify --quiet "refs/heads/$cand" && { DEFAULT="$cand"; break; } - done - fi - - # Declared extras: parse the bracketed list on the extra_branches line. - DECLARED="$(sed -n 's/^[[:space:]]*extra_branches[[:space:]]*=[[:space:]]*\[\(.*\)\].*/\1/p' "$PROV" | head -1 | tr ',' '\n' | tr -d ' "' | grep -v '^$' || true)" - - UNEXPECTED="" - while IFS= read -r br; do - [ -z "$br" ] && continue - [ "$br" = "$DEFAULT" ] && continue - printf '%s\n' "$DECLARED" | grep -qxF "$br" && continue - UNEXPECTED="$UNEXPECTED $br" - done < <(git -C "$TARGET" for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null) - - if [ -n "$UNEXPECTED" ]; then - bad "T4 branch(es) not in the mint contract:${UNEXPECTED}" - note "Declared extra_branches: ${DECLARED:-}" - note "This is the #203 signature. A template work branch (coderabbit/," - note "chore/, bot-task) copied wholesale into a child. Review each: keep it" - note "deliberately, or delete it. Do not force unrelated histories together." - else - ok "T4 branch contract honoured (default=${DEFAULT:-?}, declared extras=${DECLARED:-none})" - fi - - # Byte-identical-tree detector: the cheapest proof of a leaked snapshot. - # If a non-default branch's tree equals an ancestor's tree exactly, it very - # likely arrived by copy rather than by work. - while IFS= read -r br; do - [ -z "$br" ] && continue - [ "$br" = "$DEFAULT" ] && continue - t="$(git -C "$TARGET" rev-parse "$br^{tree}" 2>/dev/null || true)" - p="$(git -C "$TARGET" merge-base "$br" "${DEFAULT:-HEAD}" 2>/dev/null || true)" - if [ -n "$t" ] && [ -z "$p" ]; then - warn "T4b '$br' has NO common ancestor with ${DEFAULT:-HEAD} (unrelated history)" - note "tree=$t — this is the exact knot-knot signature." - fi - done < <(git -C "$TARGET" for-each-ref --format='%(refname:short)' refs/heads/ 2>/dev/null) -else - warn "T4 skipped — not a git checkout" -fi - -# ── D1/D2: advisory drift against a template checkout ──────────────────────── -if [ -n "$TEMPLATE" ]; then - if [ ! -d "$TEMPLATE" ]; then - echo " (--template path not a directory: $TEMPLATE)"; TEMPLATE="" - elif [ ! -f "$TEMPLATE/Justfile" ]; then - echo " (--template path does not look like the template: no Justfile)"; TEMPLATE="" - fi -fi - -if [ -n "$TEMPLATE" ]; then - TEMPLATE="$(cd "$TEMPLATE" && pwd)" - echo - echo "advisory drift vs $TEMPLATE" - MISSING=0; DRIFTED=0 - - # Paths the template owns: everything it ships except what it deliberately - # drops at mint (archetypes/, build/, and the answer-file itself). - while IFS= read -r rel; do - case "$rel" in - archetypes/*|build/*|.git/*|.machine_readable/PROVENANCE.a2ml) continue ;; - esac - if [ ! -e "$TARGET/$rel" ]; then - MISSING=$((MISSING+1)); [ "$MISSING" -le 15 ] && echo " MISSING $rel" - elif ! cmp -s "$TEMPLATE/$rel" "$TARGET/$rel"; then - DRIFTED=$((DRIFTED+1)); [ "$DRIFTED" -le 15 ] && echo " differs $rel" - fi - done < <(cd "$TEMPLATE" && git ls-files 2>/dev/null || find . -type f | sed 's|^\./||') - - [ "$MISSING" -eq 0 ] && [ "$DRIFTED" -eq 0 ] && echo " (none — no template-owned path diverged)" - [ "$MISSING" -gt 0 ] && warn "D1 $MISSING template-owned path(s) missing from this repo" - [ "$DRIFTED" -gt 0 ] && warn "D2 $DRIFTED template-owned path(s) differ from the template" - note "Advisory only. A child is SUPPOSED to diverge — but a path that diverged" - note "once is never healed by a later template update, and copier's own 3-way" - note "merge carries such a difference forward silently, with no conflict marker." - note "Decide per path: conform it, or record why it is deliberately forked." -fi - -finish -exit $? diff --git a/czech-file-knife/scripts/check-variant-drift.sh b/czech-file-knife/scripts/check-variant-drift.sh deleted file mode 100755 index c8ada3c0a..000000000 --- a/czech-file-knife/scripts/check-variant-drift.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-variant-drift.sh — verify the shared RSR spine of this variant -# template stays convergent with its parent at the pinned commit. -# -# Reads the contract at .machine_readable/descriptiles/VARIANT.a2ml: -# - every tracked file NOT declared added/removed/diverged/pending/operational -# must be identical to the parent's copy at parent-pin, modulo the -# [normalise] rules (action-pin SHAs, self-name substitution); -# - declared additions must exist here and not in the parent; -# - declared removals must exist in the parent and not here. -# -# Usage: check-variant-drift.sh [self-dir] -# Exit: 0 = spine convergent; 1 = undeclared drift (listed on stdout). - -set -euo pipefail - -PARENT_DIR="${1:?usage: check-variant-drift.sh [self-dir]}" -SELF_DIR="${2:-.}" -CONTRACT="$SELF_DIR/.machine_readable/descriptiles/VARIANT.a2ml" - -[ -f "$CONTRACT" ] || { echo "FAIL: contract not found: $CONTRACT"; exit 1; } - -SELF_NAME=$(sed -n 's/^project = "\(.*\)"/\1/p' "$CONTRACT" | head -1) -PARENT_SLUG=$(sed -n 's/^parent = "\(.*\)"/\1/p' "$CONTRACT" | head -1) -PARENT_NAME="${PARENT_SLUG##*/}" -PIN=$(sed -n 's/^parent-pin = "\([0-9a-f]*\)".*/\1/p' "$CONTRACT" | head -1) - -# Extract the paths array of one [paths.
] block. -section_paths() { - awk -v sec="[paths.$1]" ' - $0 == sec { insec = 1; next } - insec && /^\[/ { insec = 0 } - insec && /^ *"/ { - line = $0 - sub(/^ *"/, "", line); sub(/".*$/, "", line) - print line - } - ' "$CONTRACT" -} - -ADDED=$(section_paths added) -REMOVED=$(section_paths removed) -SKIP=$(printf '%s\n' "$(section_paths diverged)" \ - "$(section_paths diverged-pending-upstream)" \ - "$(section_paths operational-state)") - -in_list() { # $1 = path, $2 = newline list (entries ending in / are prefixes) - local p="$1" e - while IFS= read -r e; do - [ -z "$e" ] && continue - case "$e" in - */) case "$p" in "$e"*) return 0;; esac ;; - *) [ "$p" = "$e" ] && return 0 ;; - esac - done <<< "$2" - return 1 -} - -# Fold operational state out of a file before comparison: action-pin SHAs, -# then BOTH repo names → SELF (variant name first — it does not contain the -# parent name as a substring, so order is safe). Folding both names on both -# sides keeps inherited files that legitimately mention the parent by name -# convergent, while still matching self-identity substitutions. -normalise() { # $1 = file - sed -E -e 's/@[0-9a-f]{40}[^ ]*( # v[^ ]*)?/@PIN/g' \ - -e "s/$SELF_NAME/SELF/g" -e "s/$PARENT_NAME/SELF/g" "$1" -} - -DRIFT=0 -report() { DRIFT=1; echo "DRIFT: $*"; } - -if [ -n "$PIN" ] && [ -d "$PARENT_DIR/.git" ]; then - ACTUAL=$(git -C "$PARENT_DIR" rev-parse HEAD) - [ "$ACTUAL" = "$PIN" ] || echo "WARN: parent checkout is $ACTUAL, contract pins $PIN" -fi - -# 1. Spine files must match, modulo normalisation. -while IFS= read -r f; do - in_list "$f" "$ADDED" && continue - in_list "$f" "$SKIP" && continue - if [ ! -f "$PARENT_DIR/$f" ]; then - report "$f exists here but not in parent (declare in paths.added or remove)" - continue - fi - if ! diff -q <(normalise "$PARENT_DIR/$f") \ - <(normalise "$SELF_DIR/$f") >/dev/null 2>&1; then - report "$f differs from parent (declare in paths.diverged or re-converge)" - fi -done < <(git -C "$SELF_DIR" ls-files) - -# 2. Parent files absent here must be declared removed. -while IFS= read -r f; do - [ -f "$SELF_DIR/$f" ] && continue - in_list "$f" "$REMOVED" && continue - in_list "$f" "$SKIP" && continue - report "parent has $f but it is absent here (declare in paths.removed)" -done < <(git -C "$PARENT_DIR" ls-files) - -# 3. Declared additions must exist (and not silently exist in parent). -while IFS= read -r e; do - [ -z "$e" ] && continue - case "$e" in - */) [ -d "$SELF_DIR/$e" ] || report "declared-added directory $e is missing" ;; - *) [ -f "$SELF_DIR/$e" ] || report "declared-added file $e is missing" - [ -e "$PARENT_DIR/$e" ] && report "declared-added $e also exists in parent (not an addition)" ;; - esac -done <<< "$ADDED" - -# 4. Declared removals must still exist in the parent. -while IFS= read -r e; do - [ -z "$e" ] && continue - [ -e "$PARENT_DIR/$e" ] || report "declared-removed $e no longer exists in parent (stale entry)" -done <<< "$REMOVED" - -if [ "$DRIFT" -eq 0 ]; then - echo "PASS: spine convergent with $PARENT_SLUG@${PIN:0:12} (modulo declared variant paths)" -else - echo "FAIL: undeclared drift against $PARENT_SLUG@${PIN:0:12} — update VARIANT.a2ml or re-converge" - exit 1 -fi diff --git a/czech-file-knife/scripts/gen-repo-map.sh b/czech-file-knife/scripts/gen-repo-map.sh deleted file mode 100755 index a9cdb64f6..000000000 --- a/czech-file-knife/scripts/gen-repo-map.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# gen-repo-map.sh — generate docs/architecture/REPOSITORY-MAP.adoc from the -# tree plus the annotations in .machine_readable/root-allow.txt. -# -# WHY THIS IS GENERATED. Before this existed the repo carried FIVE hand-written -# maps and not one was accurate: README's table omitted 12 directories, -# .github/DIRECTORY.adoc was a 3-line stub, .github/CONTRIBUTING.md drew a tree -# naming lib/, extensions/, plugins/ and spec/ (none of which exist), -# docs/README.adoc was wrong on every path it named, and docs/RSR_OUTLINE.adoc -# was a second whole-repo README contradicting the first. They rotted because -# nothing checked them. A map that is regenerated and diffed in CI cannot. -# -# Usage: bash scripts/gen-repo-map.sh [repo_root] -set -euo pipefail - -# Byte order, everywhere. `sort` is LOCALE-DEPENDENT: under en_US.UTF-8 it -# ignores leading punctuation, so dotfiles interleave with ordinary names; -# under LC_ALL=C (what CI runs) they sort first. The generator was therefore -# deterministic WITHIN an environment but not ACROSS environments, and the -# CI freshness check caught precisely that on its first real run. Running the -# generator twice in one shell cannot detect it - the check must vary the -# locale, which is what tests/shape/repo_map_determinism_test.sh now does. -export LC_ALL=C - -REPO_ROOT="${1:-.}" -cd "$REPO_ROOT" -ALLOW=".machine_readable/root-allow.txt" -OUT="docs/architecture/REPOSITORY-MAP.adoc" - -[ -f "$ALLOW" ] || { echo "ERROR: $ALLOW not found" >&2; exit 2; } -mkdir -p "$(dirname "$OUT")" - -# Root entries, tracked shape only (mirrors check-root-shape.sh). -mapfile -t ENTRIES < <(git ls-files | awk -F/ '{print $1}' | sort -u) - -# name -> annotation, harvested from the allowlist's own comments. -declare -A NOTE=() OPTIONAL=() -while IFS= read -r line; do - [[ "$line" =~ ^[[:space:]]*# ]] && continue - [[ -z "${line// }" ]] && continue - raw="${line%%#*}"; raw="$(echo "$raw" | xargs || true)" - [ -z "$raw" ] && continue - comment="${line#*#}"; [ "$comment" = "$line" ] && comment="" - comment="$(echo "$comment" | xargs || true)" - key="${raw%/}" - if [[ "$key" == '?'* ]]; then key="${key#\?}"; OPTIONAL["$key"]=1; fi - NOTE["$key"]="$comment" -done < "$ALLOW" - -# Directories whose own level holds nothing but a stub README (or .gitkeep). -# The owner ruling (2026-08-26) is that these stay: they are DECLARED STRUCTURE, -# a statement of intended shape for repos minted from this template, not -# abandoned work. Saying so explicitly is the difference between the two. -# Recursive: a directory counts as unfilled only when its WHOLE subtree holds -# no substantive file. Checking just its own level would flag archetypes/, whose -# julia-library/ child is real content. (The ply manifests that once padded -# these counts were folded into the repo deed — standards#837 pilot.) -declared_only() { - local d="$1" n - n=$(git ls-files "$d" | awk -F/ '{print $NF}' \ - | grep -vxE 'README\.adoc|\.gitkeep' | wc -l) - [ "$n" -eq 0 ] -} - -{ - echo "// SPDX-License-Identifier: CC-BY-SA-4.0" - echo "// Copyright (c) 2026 Jonathan D.A. Jewell " - echo "//" - echo "// GENERATED by scripts/gen-repo-map.sh - do not hand-edit." - echo "// Regenerate with \`just repo-map\`. CI fails if this file is stale." - echo "= Repository map" - echo ":toc:" - echo - echo "The single authoritative map of this repository. It is generated from the" - echo "tree and from the annotations in \`.machine_readable/root-allow.txt\`, and CI" - echo "fails if it drifts, so it cannot rot the way its five hand-written" - echo "predecessors did." - echo - echo "== Root" - echo - echo '[cols="2,1,4",options="header"]' - echo '|===' - echo "| Path | Required | What it is, and who reads it" - echo - for e in "${ENTRIES[@]}"; do - [ -d "$e" ] && disp="\`$e/\`" || disp="\`$e\`" - req="yes"; [ -n "${OPTIONAL[$e]+x}" ] && req="optional" - note="${NOTE[$e]:-}" - [ -z "$note" ] && note="-" - echo "| $disp" - echo "| $req" - echo "| $note" - echo - done - echo '|===' - echo - echo "== Declared structure not yet filled" - echo - echo "These directories currently hold only a stub \`README.adoc\` and a level" - echo "manifest. That is deliberate. They declare the shape a repository minted" - echo "from this template is expected to grow into; they are *intended" - echo "structure, not abandoned work*. Add content, or delete the directory in" - echo "your own repo - but do not read their emptiness as neglect here." - echo - first=1 - for d in $(git ls-files | grep '/' | sed 's|/[^/]*$||' | sort -u); do - if declared_only "$d"; then - [ $first -eq 1 ] && { echo "[cols=\"1\"]"; echo '|==='; first=0; } - echo "| \`$d/\`" - fi - done - [ $first -eq 0 ] && echo '|===' - echo - echo "== Where things are enforced" - echo - echo "* Root shape - \`scripts/check-root-shape.sh\` against" - echo " \`.machine_readable/root-allow.txt\`, run by \`.github/workflows/estate-rules.yml\`." - echo " The check is bidirectional: unlisted entries fail, and required entries" - echo " that are absent also fail." - echo "* This map - \`just repo-map\` must produce no diff." - echo "* Community health - GitHub reads \`.github/\` and no other path." - echo "* Variant divergence - \`scripts/check-variant-drift.sh\` compares a child" - echo " to its parent BY PATH, so any move here invalidates every child's" - echo " declared path lists until they are re-anchored." -} > "$OUT" - -echo "repo-map: wrote $OUT" diff --git a/czech-file-knife/scripts/invariant-path.sh b/czech-file-knife/scripts/invariant-path.sh deleted file mode 100755 index f46953400..000000000 --- a/czech-file-knife/scripts/invariant-path.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -set -euo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" -IP_ROOT="${REPO_ROOT}/../invariant-path" - -if [[ ! -f "${IP_ROOT}/Cargo.toml" ]]; then - echo "invariant-path workspace not found at ${IP_ROOT}" >&2 - exit 1 -fi - -if [[ $# -eq 0 ]]; then - set -- scan --profile generic --file "${REPO_ROOT}/README.adoc" --artifact-uri "repo://README.adoc" -elif [[ "$1" == "scan" ]]; then - shift - has_profile="false" - for arg in "$@"; do - if [[ "$arg" == "--profile" ]]; then - has_profile="true" - break - fi - done - if [[ "${has_profile}" == "true" ]]; then - set -- scan "$@" - else - set -- scan --profile generic "$@" - fi -fi - -exec cargo run --manifest-path "${IP_ROOT}/Cargo.toml" -p invariant-path-cli -- "$@" diff --git a/czech-file-knife/scripts/migrate-wellknown-to-www.sh b/czech-file-knife/scripts/migrate-wellknown-to-www.sh deleted file mode 100755 index aa9c2e245..000000000 --- a/czech-file-knife/scripts/migrate-wellknown-to-www.sh +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# migrate-wellknown-to-www.sh — conflict-safe migration of a repository-root -# .well-known/ tree into www/.well-known/ (czech-file-knife#53). -# -# Stage 1 established the canonical location. Stage 5 (#119) sweeps it across -# ~270 repositories, which changes what "conflict-safe" has to mean: a sweep -# is unattended, so a conflict may not halt it, and may not be silent either. -# -# Semantics, per the #53 migration requirements: -# * identical content in both locations -> root copy removed, www kept; -# * content only at the root -> moved into www/.well-known/; -# * content only under www/ -> no-op; -# * DIVERGENT content in both -> the ROOT copy is quarantined to -# www/.legacy-well-known-/ and the www/ copy is left untouched: -# never overwritten, never silent. Exit 0 by default so a batch sweep can -# continue; --strict turns a quarantine into a non-zero exit. -# -# --in-place restores the pre-stage-5 contract (both copies stay where they -# are, exit 1) for anyone who prefers to resolve divergence by hand before the -# tree is touched at all. -# -# Why the quarantine lives under www/ and not at the root: -# #106's commit message specifies `.legacy-well-known-YYYYMMDD/` at the -# repository root. The root allowlist (.machine_readable/root-allow.txt) is -# checked BIDIRECTIONALLY by scripts/check-root-shape.sh and matches entries -# literally, with no glob support — a root directory whose name carries a -# date can never be allowlisted, so every swept repository would report root -# drift the moment the migrator ran. Under www/ it is inside the site- -# operations bundle the allowlist already permits, and it is NOT under -# www/public/, so the publication boundary still holds. -# -# Run from the repository root. Uses `git mv`/`git rm` when the tree is a git -# checkout and the files are tracked, plain mv/rm otherwise, so it is safe on -# minted-but-uncommitted trees too. Propagation runners should combine this -# with a `git status --porcelain` / unpushed-commit check of their own; this -# script compares CONTENT, and content comparison is what "divergent" means -# here. -# -# Exit codes: -# 0 — clean migration (quarantines may have occurred; see the report) -# 1 — divergence left unresolved (--in-place), --strict and something was -# quarantined, or files unexpectedly remain at the root -# 2 — usage / setup error - -set -euo pipefail - -ROOT=".well-known" -DEST="www/.well-known" - -IN_PLACE=0 -STRICT=0 -DRY_RUN=0 -REPORT="" - -usage() { - cat <<'EOF' -Usage: scripts/migrate-wellknown-to-www.sh [options] - -Migrate a repository-root .well-known/ into www/.well-known/. - -Options: - --dry-run report what would change; write nothing (always exit 0) - --in-place divergent content stays put, both copies kept, exit 1 - (pre-stage-5 contract; for hand resolution) - --strict exit 1 if anything had to be quarantined - --report FILE write a TSV report: actionpathdetail - -h, --help this message - -Report actions: moved, deduped, quarantined, conflict, left. -EOF -} - -die() { echo "migrate-wellknown: ERROR — $*" >&2; exit 2; } - -while [ $# -gt 0 ]; do - case "$1" in - --in-place) IN_PLACE=1; shift ;; - --strict) STRICT=1; shift ;; - --dry-run) DRY_RUN=1; shift ;; - --report) [ $# -ge 2 ] || die "--report requires a path"; REPORT="$2"; shift 2 ;; - --report=*) REPORT="${1#--report=}"; shift ;; - -h|--help) usage; exit 0 ;; - *) die "unknown option: $1" ;; - esac -done - -if [ "$IN_PLACE" -eq 1 ] && [ "$STRICT" -eq 1 ]; then - die "--in-place and --strict are mutually exclusive (--in-place already fails on conflict)" -fi - -# The report is written under --dry-run too: a dry run that only prints is -# useless to a batch driver, which needs the planned actions in a form it can -# read. Under --dry-run the rows describe what WOULD happen. -if [ -n "$REPORT" ]; then - repdir="$(dirname "$REPORT")" - if [ ! -d "$repdir" ]; then mkdir -p "$repdir"; fi - printf 'action\tpath\tdetail\n' > "$REPORT" -fi - -rep() { - if [ -n "$REPORT" ]; then - printf '%s\t%s\t%s\n' "$1" "$2" "${3:-}" >> "$REPORT" - fi - return 0 -} - -if [ ! -d "$ROOT" ]; then - echo "migrate-wellknown: no $ROOT/ at repository root — nothing to migrate." - exit 0 -fi - -STAMP="$(date -u +%Y%m%d)" -QUARANTINE="www/.legacy-well-known-$STAMP" - -git_tracked() { git ls-files --error-unmatch "$1" >/dev/null 2>&1; } - -move_file() { # src dst — never overwrites; caller guarantees dst is free - local src="$1" dst="$2" - # The dry-run guard precedes every filesystem effect, mkdir included: - # an empty directory is invisible to `git status` but is still a change - # to the tree, and --dry-run promises to leave nothing behind. - if [ "$DRY_RUN" -eq 1 ]; then return 0; fi - mkdir -p "$(dirname "$dst")" - if git_tracked "$src"; then - git mv "$src" "$dst" - else - mv "$src" "$dst" - fi -} - -remove_file() { # src - local src="$1" - if [ "$DRY_RUN" -eq 1 ]; then return 0; fi - if git_tracked "$src"; then - git rm -q "$src" - else - rm "$src" - fi -} - -# A quarantine target is never overwritten: if the dated name is taken, the -# next free `.N` suffix is used instead. -quarantine_dest() { # rel -> path - # Declared one per line on purpose: in `local a="$1" b="$a"` every - # expansion happens before any assignment, so $a is still unset when it - # is read — fatal under `set -u`. - local rel="$1" - local base="$QUARANTINE/$rel" - local cand="$base" - local n=1 - while [ -e "$cand" ]; do - cand="$base.$n" - n=$((n + 1)) - done - printf '%s' "$cand" -} - -if [ "$DRY_RUN" -eq 0 ] && [ ! -d "$DEST" ]; then - mkdir -p "$DEST" -fi - -moved=0; deduped=0; conflicts=0; quarantined=0 - -while IFS= read -r -d '' src; do - rel="${src#"$ROOT"/}" - dst="$DEST/$rel" - - if [ -f "$dst" ]; then - if cmp -s "$src" "$dst"; then - # Identical: the duplicate root copy goes; www is canonical. - remove_file "$src" - deduped=$((deduped + 1)) - echo " identical, root copy removed: $rel" - rep deduped "$rel" "identical to $DEST/$rel; root copy removed" - elif [ "$IN_PLACE" -eq 1 ]; then - conflicts=$((conflicts + 1)) - echo " CONFLICT (both preserved): $rel differs between $ROOT/ and $DEST/" >&2 - echo " root sha256: $(sha256sum "$src" | cut -d' ' -f1)" >&2 - echo " www sha256: $(sha256sum "$dst" | cut -d' ' -f1)" >&2 - rep conflict "$rel" "divergent; both preserved in place" - else - qdst="$(quarantine_dest "$rel")" - if [ -e "$qdst" ]; then - die "refusing to overwrite quarantine target $qdst" - fi - # Hashes are read BEFORE the move: afterwards $src no longer - # exists, and a failing sha256sum inside $( ) would abort the - # script under `set -e` before the conflict was ever reported. - root_sha="$(sha256sum "$src" | cut -d' ' -f1)" - www_sha="$(sha256sum "$dst" | cut -d' ' -f1)" - move_file "$src" "$qdst" - quarantined=$((quarantined + 1)) - echo " DIVERGENT — root copy quarantined: $rel -> $qdst" >&2 - echo " root sha256: $root_sha" >&2 - echo " www sha256: $www_sha (kept, untouched)" >&2 - rep quarantined "$rel" "divergent; root copy -> $qdst" - fi - else - if [ -e "$dst" ]; then - die "refusing to overwrite existing $dst" - fi - move_file "$src" "$dst" - moved=$((moved + 1)) - echo " moved: $rel -> $dst" - rep moved "$rel" "root-only; -> $DEST/$rel" - fi -done < <(find "$ROOT" -type f -print0 | sort -z) - -# Drop the root directory only when it is fully empty of files. -left=0 -if [ -z "$(find "$ROOT" -type f -print -quit 2>/dev/null)" ]; then - if [ "$DRY_RUN" -eq 0 ]; then - find "$ROOT" -depth -type d -empty -delete 2>/dev/null || true - fi -else - left=$(find "$ROOT" -type f | wc -l | tr -d '[:space:]') -fi - -echo "migrate-wellknown: moved=$moved deduped=$deduped quarantined=$quarantined conflicts=$conflicts left_in_root=$left" - -if [ "$DRY_RUN" -eq 1 ]; then - echo "migrate-wellknown: dry run — nothing written." - exit 0 -fi - -if [ "$left" -gt 0 ]; then - echo "migrate-wellknown: WARNING — $left file(s) remain under $ROOT/ (unexpected)." >&2 - exit 1 -fi - -if [ "$conflicts" -gt 0 ]; then - echo "migrate-wellknown: DIVERGENT CONTENT — both locations preserved in place; resolve by hand." >&2 - exit 1 -fi - -if [ "$quarantined" -gt 0 ]; then - echo "migrate-wellknown: $quarantined divergent file(s) quarantined under $QUARANTINE/ —" >&2 - echo "migrate-wellknown: resolve by hand, then delete that directory. Nothing was overwritten." >&2 - if [ "$STRICT" -eq 1 ]; then - exit 1 - fi -fi - -exit 0 diff --git a/czech-file-knife/scripts/prune-dependabot-ecosystems.rs b/czech-file-knife/scripts/prune-dependabot-ecosystems.rs deleted file mode 100644 index fb0cacf39..000000000 --- a/czech-file-knife/scripts/prune-dependabot-ecosystems.rs +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// -// Keep only explicitly selected Dependabot ecosystems after project minting. -// -// Ported from prune-dependabot-ecosystems.rb: Ruby is not an estate-authorised -// language, and this runs on every mint. Single file, std only, no -// dependencies — compiled on demand by `just repo-init` (see the rust_tool -// helper in build/just/repo-init.just). -// -// Two rules that are easy to get wrong and are load-bearing: -// -// * Nix is not a valid Dependabot ecosystem, so it is dropped from the -// keep-list rather than matched against entries. -// * Pruning to an EMPTY updates list is refused. A dependabot.yml with no -// ecosystems is worse than one with unused entries: it silently stops -// watching everything. Refusing is the safe failure. -// -// Usage: prune-dependabot-ecosystems.rs - -use std::env; -use std::fs; -use std::path::Path; - -/// True for a line that begins a new `updates:` entry, i.e. matches -/// `^[ \t]*-[ \t]*package-ecosystem:`. -fn is_entry_start(line: &str) -> bool { - let t = line.trim_start_matches(|c| c == ' ' || c == '\t'); - let t = match t.strip_prefix('-') { - Some(t) => t, - None => return false, - }; - t.trim_start_matches(|c| c == ' ' || c == '\t') - .starts_with("package-ecosystem:") -} - -/// Extract the ecosystem name from one entry: `package-ecosystem: "cargo"`. -fn ecosystem_name(entry: &str) -> String { - let needle = "package-ecosystem:"; - match entry.find(needle) { - Some(p) => { - let rest = &entry[p + needle.len()..]; - let rest = rest.trim_start_matches(|c| c == ' ' || c == '\t'); - let rest = rest.trim_start_matches(|c| c == '\'' || c == '"'); - rest.chars() - .take_while(|c| c.is_ascii_alphabetic() || *c == '-') - .collect() - } - None => "?".to_string(), - } -} - -fn main() { - let args: Vec = env::args().skip(1).collect(); - if args.len() < 2 { - eprintln!("Usage: prune-dependabot-ecosystems.rs "); - std::process::exit(1); - } - let path = &args[0]; - - // Nix is not a Dependabot ecosystem; keeping it would match nothing and - // could only ever produce an empty keep-list. - let keep: Vec<&str> = args[1..] - .iter() - .map(|s| s.as_str()) - .filter(|s| *s != "nix") - .collect(); - - if !Path::new(path).is_file() { - println!(" dependabot: {} absent, nothing to prune", path); - return; - } - - let text = match fs::read_to_string(path) { - Ok(t) => t, - Err(_) => return, - }; - - // Split the document at each entry start, keeping the preamble separate. - // `split_inclusive` retains the newline, so re-joining is byte-exact for - // anything we do not drop. - let mut head = String::new(); - let mut entries: Vec = Vec::new(); - for line in text.split_inclusive('\n') { - if is_entry_start(line) { - entries.push(String::new()); - } - match entries.last_mut() { - Some(e) => e.push_str(line), - None => head.push_str(line), - } - } - - if entries.is_empty() { - println!(" dependabot: no ecosystem entries found"); - return; - } - - let mut kept: Vec = Vec::new(); - let mut kept_names: Vec = Vec::new(); - let mut dropped_names: Vec = Vec::new(); - - for entry in &entries { - let name = ecosystem_name(entry); - if keep.contains(&name.as_str()) { - kept_names.push(name); - kept.push(entry.clone()); - } else { - dropped_names.push(name); - } - } - - if kept.is_empty() { - println!(" dependabot: refusing to prune every entry; left unchanged"); - return; - } - if dropped_names.is_empty() { - println!(" dependabot: nothing to prune"); - return; - } - - let mut out = head; - for k in &kept { - out.push_str(k); - } - if fs::write(path, out).is_err() { - return; - } - println!( - " dependabot: kept {} / dropped {}", - kept_names.join(", "), - dropped_names.join(", ") - ); -} diff --git a/czech-file-knife/scripts/rsr-campaign.sh b/czech-file-knife/scripts/rsr-campaign.sh deleted file mode 100644 index 27431f6ed..000000000 --- a/czech-file-knife/scripts/rsr-campaign.sh +++ /dev/null @@ -1,415 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# rsr-campaign.sh — the RSR update mechanism. -# -# WHY THIS EXISTS -# -# ADR-0003 (Forge and Sustain) says a forged repository is *sustained* in four -# modes, of which one is: -# -# adaptive — parent-pin bumps + fan-out campaigns -# -# The check half shipped: scripts/check-variant-drift.sh verifies a child is -# convergent with its parent at the pinned commit, modulo declared divergences. -# The *update* half never existed. That is the gap ADR-0001 lists as a negative -# consequence in one line ("Template updates need propagation mechanism to -# existing repos") while listing "fix once in template, propagate to all repos" -# as a positive one. Both statements were in the same document; only the -# pessimistic one was true. -# -# WHY IT IS A CAMPAIGN AND NOT A MERGE -# -# Measured 2026-09-18 against three representative estate repositories: -# -# repo tracked shared with spine byte-identical -# Axiom.jl 328 55 6 -# wokelang 447 56 6 -# zotero-tools 959 36 6 -# -# and the spine has 546 files. So ~490 spine files are absent from a typical -# repository and only SIX agree exactly. "Converge the estate to the template" -# would therefore rewrite ~50 files and add ~490 in every one of 269 repos — -# roughly 130,000 file additions, against repositories that have legitimately -# spent years diverging. That is not an update mechanism; it is a re-mint, and -# it would destroy customisation at a scale nobody could review. -# -# The honest conclusion: the estate cannot be *converged*, only *updated along -# declared axes*. So a campaign declares exactly what travels. Everything else -# is left alone, by construction rather than by care. -# -# WHAT A CAMPAIGN IS -# -# [meta] -# name / description / why -# [paths] -# spine-relative paths to propagate ('dir/' means the whole directory) -# [exclude] -# paths never to touch, even inside a propagated directory -# -# Per repository, per path: -# -# declared diverged -> SKIPPED (the repo's VARIANT.a2ml says so) -# absent in repo -> ADDED -# present, differs -> UPDATED -# present, identical -> UNCHANGED -# -# SAFETY PROPERTIES, each deliberate: -# -# * Dry-run by default. --push is required to write anything at all. -# * Nothing is ever deleted. A campaign adds and converges; deletion is a -# different operation with a different blast radius and needs its own gate. -# * A repository that declares a path diverged is never touched there, so a -# campaign cannot silently reverse a decision the repo already recorded. -# * Files containing an unfilled {{TOKEN}} are REFUSED by default, because -# propagating one plants a placeholder in a repo whose own placeholder gate -# will then fail its every push. --allow-tokens overrides, deliberately -# loudly. -# * A repository with no changes is not committed to. -# * The spine's own name is rewritten to the target's name, so the campaign -# does not re-plant template identity (the ADR-0003 mint criterion). -# -# Usage: -# rsr-campaign.sh --manifest FILE --repos-file FILE [options] -# -# --manifest FILE campaign manifest (required) -# --repos-file FILE repositories, one per line (required) -# --spine DIR spine checkout; default: the repo this script is in -# --work-dir DIR where to clone; default: a temp dir -# --batch N batch number, 1-based -# --batch-size M repositories per batch (default 25) -# --push commit and push; without it, --apply changes nothing -# --apply write changes into the checkout (still no push) -# --base-ref REF branch to stack on (default: the repo default branch). -# Set this when the campaign depends on work that is not -# merged yet — e.g. the stage-5 sweep branches. -# --branch NAME branch to push to (default: the campaign name) -# --report FILE write a TSV report -# --allow-tokens permit propagating files with {{TOKEN}} placeholders -# --no-substitute do not rewrite the spine name to the repo name -# --quiet less per-repo output -# -# Exit: 0 = every targeted repo reached a terminal state; 1 = at least one failed. - -set -euo pipefail - -PROG="$(basename "$0")" - -MANIFEST=""; REPOS_FILE=""; SPINE=""; WORK_DIR="" -BATCH=""; BATCH_SIZE=25; PUSH=0; APPLY=0; BRANCH=""; REPORT=""; BASE_REF="" -ALLOW_TOKENS=0; SUBSTITUTE=1; QUIET=0 - -die() { printf '%s: %s\n' "$PROG" "$1" >&2; exit 2; } -note() { [ "$QUIET" -eq 1 ] || printf '%s\n' "$*"; } -hr() { [ "$QUIET" -eq 1 ] || printf -- '------------------------------------------------------------\n'; } - -while [ $# -gt 0 ]; do - case "$1" in - --manifest) MANIFEST="${2:?}"; shift 2 ;; - --repos-file) REPOS_FILE="${2:?}"; shift 2 ;; - --spine) SPINE="${2:?}"; shift 2 ;; - --work-dir) WORK_DIR="${2:?}"; shift 2 ;; - --batch) BATCH="${2:?}"; shift 2 ;; - --batch-size) BATCH_SIZE="${2:?}"; shift 2 ;; - --branch) BRANCH="${2:?}"; shift 2 ;; - --base-ref) BASE_REF="${2:?}"; shift 2 ;; - --report) REPORT="${2:?}"; shift 2 ;; - --push) PUSH=1; shift ;; - --apply) APPLY=1; shift ;; - --allow-tokens) ALLOW_TOKENS=1; shift ;; - --no-substitute) SUBSTITUTE=0; shift ;; - --quiet|-q) QUIET=1; shift ;; - -h|--help) sed -n '2,80p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; - *) die "unknown argument: $1" ;; - esac -done - -[ -n "$MANIFEST" ] || die "missing --manifest" -[ -n "$REPOS_FILE" ] || die "missing --repos-file" -[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST" -[ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" - -# The spine is the source of truth. Default to the checkout this script lives in. -if [ -z "$SPINE" ]; then - SPINE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -fi -[ -d "$SPINE/.git" ] || die "spine is not a git checkout: $SPINE" - -if [ -z "$WORK_DIR" ]; then - WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rsr-campaign.XXXXXX")" -fi -mkdir -p "$WORK_DIR" - -# --------------------------------------------------------------------------- -# Manifest parsing -# --------------------------------------------------------------------------- -# Deliberately a tiny INI reader rather than a dependency: the manifest is a -# declaration, not a program, and a campaign that needs a parser installed is a -# campaign that cannot be run during an incident. -MANIFEST_SECTION="" -CAMP_NAME="${MANIFEST##*/}"; CAMP_NAME="${CAMP_NAME%.campaign}" -CAMP_DESC="" -PATHS=(); EXCLUDES=() - -while IFS= read -r line || [ -n "$line" ]; do - # strip comments and surrounding space - line="${line%%#*}" - line="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - [ -z "$line" ] && continue - case "$line" in - \[*\]) MANIFEST_SECTION="$(printf '%s' "$line" | tr -d '[]')"; continue ;; - esac - case "$MANIFEST_SECTION" in - meta) - key="${line%%=*}"; val="${line#*=}" - key="$(printf '%s' "$key" | tr -d '[:space:]')" - val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - case "$key" in - name) [ -n "$val" ] && CAMP_NAME="$val" ;; - description) CAMP_DESC="$val" ;; - esac - ;; - paths) PATHS+=("$line") ;; - exclude) EXCLUDES+=("$line") ;; - *) die "line outside a known section in $MANIFEST: $line" ;; - esac -done < "$MANIFEST" - -[ "${#PATHS[@]}" -gt 0 ] || die "manifest declares no [paths]: $MANIFEST" -[ -n "$BRANCH" ] || BRANCH="$CAMP_NAME" - -note "campaign: $CAMP_NAME" -[ -n "$CAMP_DESC" ] && note " $CAMP_DESC" -note " spine: $SPINE" -note " manifest: $MANIFEST" -note " paths: ${#PATHS[@]} declared, ${#EXCLUDES[@]} excluded" -note " branch: $BRANCH" -[ -n "$BASE_REF" ] && note " stacked on: $BASE_REF" -if [ "$PUSH" -eq 1 ]; then - note " mode: PUSH (commit and push each repository)" -elif [ "$APPLY" -eq 1 ]; then - note " mode: apply (write locally, do not push)" -else - note " mode: dry-run (nothing written)" -fi -hr - -# --------------------------------------------------------------------------- -# Repo selection — same batch arithmetic as the stage-5 sweep, so operators -# only have to know one set of flags. -# --------------------------------------------------------------------------- -mapfile -t ALL_REPOS < <(grep -vE '^\s*(#|$)' "$REPOS_FILE" | sed -e 's/[[:space:]]*$//') -TOTAL=${#ALL_REPOS[@]} -[ "$TOTAL" -gt 0 ] || die "no repositories in $REPOS_FILE" - -if [ -n "$BATCH" ]; then - start=$(( (BATCH - 1) * BATCH_SIZE )) - end=$(( start + BATCH_SIZE )) - [ "$start" -lt "$TOTAL" ] || die "batch $BATCH is past the end ($TOTAL repositories)" - SELECTED=("${ALL_REPOS[@]:start:end-start}") - note "batch $BATCH of size $BATCH_SIZE: ${#SELECTED[@]} of $TOTAL repositories" -else - SELECTED=("${ALL_REPOS[@]}") -fi -hr - -# --------------------------------------------------------------------------- -# Token safety -# --------------------------------------------------------------------------- -# {{TOKEN}} and friends are META tokens: they name a placeholder kind rather -# than being one, and the spine's own gate exempts them. A repository running an -# OLDER check-no-placeholders.sh has no such exemption, so a propagated comment -# mentioning the token would fail that repo's every push. Refusing is the safe -# default; the override exists for a deliberate decision, not a convenience. -META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' - -# --------------------------------------------------------------------------- -# Self-name substitution -# --------------------------------------------------------------------------- -# ADR-0003's mint criterion: no template identity survives in the child. The -# spine's own name is a literal, so propagating it verbatim would re-plant the -# identity the mint pass exists to remove. -SPINE_NAME="$(basename "$SPINE")" -substitute() { # src-file repo-name -> stdout - local f="$1" repo="$2" - if [ "$SUBSTITUTE" -eq 1 ] && [ "$repo" != "$SPINE_NAME" ]; then - sed "s/\b${SPINE_NAME}\b/${repo}/g" "$f" - else - cat "$f" - fi -} - -# --------------------------------------------------------------------------- -# Divergence declarations -# --------------------------------------------------------------------------- -# If the repository carries a VARIANT.a2ml, honour it: a path it declares -# diverged is a decision already made, and a campaign must not silently reverse -# a decision. Matches check-variant-drift.sh's in_list semantics, including the -# 'dir/' prefix form. -declared_diverged() { # repo-path path -> 0 if declared diverged - local dir="$1" want="$2" contract="$1/.machine_readable/descriptiles/VARIANT.a2ml" - [ -f "$contract" ] || return 1 - local e - while IFS= read -r e; do - [ -z "$e" ] && continue - case "$e" in - */) case "$want" in "$e"*) return 0 ;; esac ;; - *) [ "$want" = "$e" ] && return 0 ;; - esac - done < <(awk ' - $0 == "[paths.diverged]" || $0 == "[paths.diverged-pending-upstream]" || - $0 == "[paths.operational-state]" { insec = 1; next } - insec && /^\[/ { insec = 0 } - insec && /^ *"/ { line = $0; sub(/^ *"/,"",line); sub(/".*$/,"",line); print line } - ' "$contract") - return 1 -} - -excluded() { # path -> 0 if excluded by the manifest - local want="$1" e - for e in ${EXCLUDES+"${EXCLUDES[@]}"}; do - case "$e" in - */) case "$want" in "$e"*) return 0 ;; esac ;; - *) [ "$want" = "$e" ] && return 0 ;; - esac - done - return 1 -} - -# --------------------------------------------------------------------------- -# The campaign itself -# --------------------------------------------------------------------------- -REPORT="${REPORT:-$WORK_DIR/campaign-report.tsv}" -printf 'repo\tpath\toutcome\tdetail\n' > "$REPORT" - -ADDED=0; UPDATED=0; UNCHANGED=0; SKIPPED=0; REFUSED=0; NOCHANGE=0; FAILED=0 - -for repo in "${SELECTED[@]}"; do - [ -z "$repo" ] && continue - dir="$WORK_DIR/repos/$repo" - rm -rf "$dir" - mkdir -p "$WORK_DIR/repos" - - # A repos file normally lists bare names under one owner; owner/name is - # accepted too, so a campaign can span owners without a second flag. - case "$repo" in - */*) slug="$repo" ;; - *) slug="${RSR_OWNER:-hyperpolymath}/$repo" ;; - esac - - # --base-ref stacks the campaign on work that is not merged yet. Without it - # the campaign branches from the default branch, which is the honest - # default: a campaign should not silently depend on an unmerged branch. - clone_args=(-q --depth 1) - [ -n "$BASE_REF" ] && clone_args+=(-b "$BASE_REF") - if ! git clone "${clone_args[@]}" "https://github.com/$slug.git" "$dir" 2>/dev/null; then - if [ -n "$BASE_REF" ]; then - printf '%s\t-\tCLONE-FAIL\tbase ref %s not found\n' "$repo" "$BASE_REF" >> "$REPORT" - note " $repo: CLONE-FAIL (no $BASE_REF)" - else - printf '%s\t-\tCLONE-FAIL\t-\n' "$repo" >> "$REPORT" - note " $repo: CLONE-FAIL" - fi - FAILED=$((FAILED+1)); continue - fi - - repo_changed=0 - repo_added=0; repo_updated=0; repo_skipped=0; repo_refused=0 - - # Collect the spine's file list for the declared paths. - while IFS= read -r rel; do - [ -z "$rel" ] && continue - case "$rel" in - *.gitkeep|*/.gitkeep) continue ;; - esac - if excluded "$rel"; then - printf '%s\t%s\tEXCLUDED\tmanifest exclude\n' "$repo" "$rel" >> "$REPORT" - repo_skipped=$((repo_skipped+1)); continue - fi - if declared_diverged "$dir" "$rel"; then - printf '%s\t%s\tDIVERGED\tdeclared in VARIANT.a2ml\n' "$repo" "$rel" >> "$REPORT" - repo_skipped=$((repo_skipped+1)); continue - fi - - src="$SPINE/$rel" - dst="$dir/$rel" - [ -f "$src" ] || continue - - if [ "$ALLOW_TOKENS" -eq 0 ]; then - # Name the actual tokens. A report that says "carries {{TOKEN}}" - # whatever it found is a claim wider than its evidence, which is - # the specific failure mode this estate keeps auditing itself for. - toks="$(grep -ohE '\{\{[A-Z_]+\}\}' "$src" 2>/dev/null \ - | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ')" - if [ -n "$toks" ]; then - # shellcheck disable=SC2086 # deliberate word split for -n - printf '%s\t%s\tREFUSED\tcarries unfilled token(s): %s\n' \ - "$repo" "$rel" "${toks% }" >> "$REPORT" - repo_refused=$((repo_refused+1)); continue - fi - fi - - tmp="$(mktemp)" - substitute "$src" "${repo##*/}" > "$tmp" - - if [ ! -f "$dst" ]; then - printf '%s\t%s\tADDED\t-\n' "$repo" "$rel" >> "$REPORT" - repo_added=$((repo_added+1)); repo_changed=1 - if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then - mkdir -p "$(dirname "$dst")"; cp "$tmp" "$dst" - fi - elif cmp -s "$tmp" "$dst"; then - printf '%s\t%s\tUNCHANGED\t-\n' "$repo" "$rel" >> "$REPORT" - else - printf '%s\t%s\tUPDATED\t-\n' "$repo" "$rel" >> "$REPORT" - repo_updated=$((repo_updated+1)); repo_changed=1 - if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then - cp "$tmp" "$dst" - fi - fi - rm -f "$tmp" - done < <(git -C "$SPINE" ls-files -- "${PATHS[@]}" 2>/dev/null | sort) - - ADDED=$((ADDED+repo_added)); UPDATED=$((UPDATED+repo_updated)) - SKIPPED=$((SKIPPED+repo_skipped)); REFUSED=$((REFUSED+repo_refused)) - - if [ "$repo_changed" -eq 0 ]; then - note " $repo: no change ($repo_skipped skipped, $repo_refused refused)" - NOCHANGE=$((NOCHANGE+1)) - continue - fi - - detail="+$repo_added ~$repo_updated" - if [ "$APPLY" -eq 0 ] && [ "$PUSH" -eq 0 ]; then - note " $repo: would change ($detail$([ "$repo_skipped" -gt 0 ] && printf ', %s skipped' "$repo_skipped"))" - continue - fi - - if [ "$PUSH" -eq 1 ]; then - ( cd "$dir" \ - && git checkout -q -b "$BRANCH" \ - && git add -A -- . \ - && git -c user.name="${RSR_COMMIT_NAME:-rsr-campaign}" \ - -c user.email="${RSR_COMMIT_EMAIL:-rsr-campaign@users.noreply.github.com}" \ - commit -q -m "chore(rsr): campaign '$CAMP_NAME' — $detail" \ - && git push -q origin "$BRANCH" ) >/dev/null 2>&1 || { - printf '%s\t-\tPUSH-FAIL\t-\n' "$repo" >> "$REPORT" - note " $repo: PUSH-FAIL" - FAILED=$((FAILED+1)); continue - } - note " $repo: pushed ($detail)" - else - note " $repo: applied ($detail)" - fi -done - -hr -note "campaign '$CAMP_NAME' complete" -note " paths ADDED: $ADDED" -note " paths UPDATED: $UPDATED" -note " paths SKIPPED: $SKIPPED (declared diverged / excluded)" -note " paths REFUSED: $REFUSED (carried an unfilled placeholder)" -note " repos unchanged: $NOCHANGE" -note " repos failed: $FAILED" -note " report: $REPORT" -[ "$FAILED" -eq 0 ] || exit 1 diff --git a/czech-file-knife/scripts/rust-tool.sh b/czech-file-knife/scripts/rust-tool.sh deleted file mode 100644 index 436fdbaa7..000000000 --- a/czech-file-knife/scripts/rust-tool.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# rust-tool.sh — compile-on-demand runner for the single-file Rust mint tools. -# -# Ruby was the previous implementation of these tools; it is not an -# estate-authorised language, so they are Rust now. Each tool is a single -# std-only file, which `rustc` compiles in well under a second with no -# dependency resolution and no build system. -# -# Binaries are cached, and rebuilt only when the source is newer, so a machine -# pays the compile cost once rather than once per mint. Override the cache -# location with RSR_MINT_TOOL_CACHE. -# -# Usage: scripts/rust-tool.sh [args...] - -set -euo pipefail - -name="${1:-}" -[ -n "$name" ] || { echo "rust-tool: usage: rust-tool.sh [args...]" >&2; exit 2; } -shift - -scripts_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -src="$scripts_dir/$name.rs" - -[ -f "$src" ] || { echo "rust-tool: no such tool: $src" >&2; exit 2; } - -cache="${RSR_MINT_TOOL_CACHE:-${TMPDIR:-/tmp}/rsr-mint-tools}" -mkdir -p "$cache" -bin="$cache/$name" - -# Recompile only when the binary is missing or older than its source, so the -# common case is a straight exec. -if [ ! -x "$bin" ] || [ "$src" -nt "$bin" ]; then - # The toolchain is needed to BUILD, not to RUN. Checking for rustc up - # front made a cached binary unusable on a machine without one, which is - # the common case in CI: the run is a straight exec. Only ask for rustc - # when a build actually has to happen. - command -v rustc >/dev/null 2>&1 || { - echo "rust-tool: rustc not found, and $name is not built yet." >&2 - echo " The mint tools are Rust and need a Rust toolchain." >&2 - echo " Install one from https://rustup.rs, then re-run." >&2 - exit 2 - } - rustc -O -o "$bin" "$src" >&2 || { - echo "rust-tool: failed to compile $src" >&2 - exit 2 - } -fi - -exec "$bin" "$@" diff --git a/czech-file-knife/scripts/scan-dangerous.sh b/czech-file-knife/scripts/scan-dangerous.sh deleted file mode 100755 index 9377ce722..000000000 --- a/czech-file-knife/scripts/scan-dangerous.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# scan-dangerous.sh — flag dangerous/unsafe constructs USED in proof code. -# -# Dangerous constructs (believe_me, assert_total, postulate, sorry, Admitted, -# unsafeCoerce, Obj.magic) escape the proof obligation and must not appear in -# real proofs. BUT the previous `proof-scan-dangerous` recipe grepped raw -# lines, so a comment that merely NAMED a banned construct — -# -- All proofs MUST be constructive (no believe_me, no assert_total). -# — tripped the gate. A check that fires on its own documentation is a -# false-positive gate: it cries wolf, trains people to override it (violating -# "squabble, don't bypass"), and wired into CI it turns the tree red for -# nothing. This version strips comments first, so only real usage is flagged. -# -# Comment syntax handled: `--` line + `{- -}` block (Idris2/Lean4/Agda), -# `(* *)` block (Coq). Comment bodies are blanked in place, so reported line -# numbers still match the source. -# -# Exit: 0 = clean; 1 = a proof uses a dangerous construct in code. - -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")/.." - -PATTERNS='believe_me|assert_total|postulate|sorry|Admitted|unsafeCoerce|Obj\.magic' -dangerous=0 - -# Blank comment content while preserving line count (so grep -n stays accurate). -strip_comments() { - local ext="${1##*.}" lc bo bc - case "$ext" in - idr|lean|agda) lc='--'; bo='{-'; bc='-}' ;; # line + block comments - v) lc=''; bo='(*'; bc='*)' ;; # Coq block comments only - *) lc=''; bo=''; bc='' ;; - esac - awk -v lc="$lc" -v bo="$bo" -v bc="$bc" ' - BEGIN { inblk = 0 } - { - line = $0; out = ""; i = 1; n = length(line) - while (i <= n) { - if (inblk) { - if (bc != "" && substr(line,i,length(bc)) == bc) { inblk = 0; i += length(bc) } - else { i++ } - } else if (bo != "" && substr(line,i,length(bo)) == bo) { - inblk = 1; i += length(bo) - } else if (lc != "" && substr(line,i,length(lc)) == lc) { - break # rest of the line is a line-comment - } else { - out = out substr(line,i,1); i++ - } - } - print out - }' "$1" -} - -while IFS= read -r f; do - [ -z "$f" ] && continue - matches="$(strip_comments "$f" | grep -nE "$PATTERNS" || true)" - if [ -n "$matches" ]; then - echo " DANGEROUS (used in code): $f" - printf '%s\n' "$matches" | sed 's/^/ /' - dangerous=$((dangerous + 1)) - fi -done < <(find verification/proofs \ - \( -name '*.idr' -o -name '*.lean' -o -name '*.agda' -o -name '*.v' \) \ - -not -path '*/build/*' 2>/dev/null | sort) - -echo -if [ "$dangerous" -gt 0 ]; then - echo "FAIL: $dangerous file(s) use dangerous constructs in proof CODE (not comments)" - exit 1 -fi -echo "PASS: no dangerous constructs used in proof code" diff --git a/czech-file-knife/scripts/strip-instruction-blocks.rs b/czech-file-knife/scripts/strip-instruction-blocks.rs deleted file mode 100644 index eb2060d20..000000000 --- a/czech-file-knife/scripts/strip-instruction-blocks.rs +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// -// Remove only HTML comments containing TEMPLATE INSTRUCTIONS after minting. -// -// Ported from strip-instruction-blocks.rb: Ruby is not an estate-authorised -// language, and this runs on every mint. Single file, std only, no -// dependencies — compiled on demand by `just repo-init` (see the rust_tool -// helper in build/just/repo-init.just). -// -// The match is deliberately tempered: an HTML comment is only removed when the -// marker appears BEFORE its own `-->`, so a block cannot swallow the -// terminator of a preceding comment. That is what keeps an SPDX comment that -// sits immediately above an instructions block intact. -// -// Usage: strip-instruction-blocks.rs [ROOT] (default ROOT = .) - -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; - -const MARKER: &str = "TEMPLATE INSTRUCTIONS"; -const OPEN: &str = ""; - -/// Directories never descended into. Mirrors the Ruby original. -const SKIP_DIRS: [&str; 5] = [".git", "node_modules", ".venv", "target", "dist"]; - -/// Remove every HTML comment whose body contains MARKER, then collapse runs of -/// three or more newlines to two. -/// -/// Indexing is by byte, which is safe because every boundary we slice at is an -/// ASCII delimiter (``); a multi-byte character can never be split. -fn strip_blocks(text: &str) -> String { - let bytes = text.as_bytes(); - let mut out = String::with_capacity(text.len()); - let mut i = 0usize; - - while i < bytes.len() { - let start = match find_from(text, OPEN, i) { - Some(p) => p, - None => { - out.push_str(&text[i..]); - break; - } - }; - // Everything before this comment is copied verbatim. - out.push_str(&text[i..start]); - - let end = match find_from(text, CLOSE, start + OPEN.len()) { - Some(p) => p, - // Unterminated comment: not ours to touch. - None => { - out.push_str(&text[start..]); - break; - } - }; - - let body = &text[start + OPEN.len()..end]; - if body.contains(MARKER) { - // Drop the comment, plus trailing horizontal space, plus one - // newline, so a removed block does not leave a blank line behind. - let mut j = end + CLOSE.len(); - while j < bytes.len() && (bytes[j] == b' ' || bytes[j] == b'\t') { - j += 1; - } - if j < bytes.len() && bytes[j] == b'\n' { - j += 1; - } - i = j; - } else { - // A comment without the marker is preserved exactly. - out.push_str(&text[start..end + CLOSE.len()]); - i = end + CLOSE.len(); - } - } - - collapse_blank_runs(&out) -} - -fn find_from(haystack: &str, needle: &str, from: usize) -> Option { - haystack[from..].find(needle).map(|p| from + p) -} - -/// Ruby's `gsub(/\n{3,}/, "\n\n")`: three or more newlines become two. -fn collapse_blank_runs(s: &str) -> String { - let mut out = String::with_capacity(s.len()); - let mut run = 0usize; - for ch in s.chars() { - if ch == '\n' { - run += 1; - if run <= 2 { - out.push(ch); - } - } else { - run = 0; - out.push(ch); - } - } - out -} - -fn process(path: &Path, changed: &mut usize) { - let bytes = match fs::read(path) { - Ok(b) => b, - Err(_) => return, - }; - // Non-UTF-8 and unreadable files are skipped, never rewritten blind. - let text = match String::from_utf8(bytes) { - Ok(t) => t, - Err(_) => return, - }; - if !text.contains(MARKER) { - return; - } - let updated = strip_blocks(&text); - if updated == text { - return; - } - if fs::write(path, updated).is_err() { - return; - } - println!(" instruction block: stripped from {}", path.display()); - *changed += 1; -} - -fn walk(root: &Path, changed: &mut usize) { - // Explicit stack rather than recursion: deep trees must not blow the stack. - let mut stack: Vec = vec![root.to_path_buf()]; - while let Some(dir) = stack.pop() { - let entries = match fs::read_dir(&dir) { - Ok(e) => e, - Err(_) => continue, - }; - for entry in entries.flatten() { - let path = entry.path(); - let ft = match entry.file_type() { - Ok(ft) => ft, - Err(_) => continue, - }; - if ft.is_symlink() { - continue; - } - if ft.is_dir() { - let skip = path - .file_name() - .and_then(|n| n.to_str()) - .map(|n| SKIP_DIRS.contains(&n)) - .unwrap_or(false); - if skip { - continue; - } - stack.push(path); - continue; - } - if ft.is_file() { - process(&path, changed); - } - } - } -} - -fn main() { - let root = env::args().nth(1).unwrap_or_else(|| ".".to_string()); - let mut changed = 0usize; - walk(Path::new(&root), &mut changed); - if changed > 0 { - println!(" instruction blocks: {} file(s) cleaned", changed); - } else { - println!(" instruction blocks: none found"); - } -} diff --git a/czech-file-knife/scripts/sweep-wellknown.sh b/czech-file-knife/scripts/sweep-wellknown.sh deleted file mode 100755 index 733b56513..000000000 --- a/czech-file-knife/scripts/sweep-wellknown.sh +++ /dev/null @@ -1,421 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# sweep-wellknown.sh — stage 5 (#119) batch driver. -# -# #119 enumerated the denominator: 270 of 348 owner repositories carry a root -# .well-known/. This drives the sweep the issue asked for — -# -# per repo: classify -> run migrator -> run suite -> commit -# -# in batches, rather than as 270 hand-made pull requests. It is deliberately -# a driver and not a bot: it never pushes unless you pass --push, and it -# refuses to guess on the one class of repository where migrating is not -# obviously correct. -# -# Classification (one GitHub API call per repo) decides who gets swept: -# -# sweep — identifiably RSR-derived and not served from the repo -# root: migrate it. RSR-ness is judged on a tiered marker -# (root-allow.txt under either spelling, else a -# .machine_readable/ tree), because the allowlist only -# entered the template in August 2026 and most of the -# estate predates it. The marker that fired is recorded. -# review-pages — GitHub Pages / CDN-served from the repo root. #119 names -# these as undecided: for a Pages repo the root -# .well-known/ may be a SERVING REQUIREMENT rather than -# legacy layout, and moving it out of the served root can -# break live discovery. Not swept without --include-review. -# review-other — has a root .well-known/ but no RSR marker at all, so the -# arrangement is not known to be template-derived. -# Not swept without --include-review. -# -# Requirements: bash, git, curl, jq. A token with `repo` scope (public repos -# need only `public_repo`) in GITHUB_TOKEN, or a signed-in `gh` — or pass -# --no-auth with --trees-dir to do everything except push, unauthenticated. -# -# Exit codes: -# 0 — every selected repository ended clean or already-clean -# 1 — at least one repository failed, or quarantined content needs a human -# (suppress the latter with --allow-conflicts) -# 2 — usage / setup error - -set -euo pipefail - -OWNER="hyperpolymath" -REPOS_FILE="" -BATCH="" -BATCH_SIZE=25 -LIMIT="" -DRY_RUN=0 -PUSH=0 -INCLUDE_REVIEW=0 -ALLOW_CONFLICTS=0 -CLASSIFY_ONLY=0 -NO_AUTH=0 -TREES_DIR="" -WORK="" -BRANCH="chore/well-known-to-www" -MIGRATOR="" -SUITE="" - -usage() { - cat <<'EOF' -Usage: scripts/sweep-wellknown.sh [options] [repo ...] - -Classify, migrate and test repositories in batches (czech-file-knife#119). - -Selecting repositories: - --repos-file FILE one repository name per line ('#' comments allowed) - --owner OWNER default: hyperpolymath - --batch N process only batch N (1-based) of --batch-size - --batch-size N default: 25 - --limit N process only the first N selected repositories - -What to do: - --classify-only classify and write classification.csv; migrate nothing - --include-review also sweep review-pages / review-other (see header) - --dry-run classify, migrate --dry-run, run the suite; commit nothing - --push push the branch to origin (default: commit only) - --branch NAME branch to commit on (default: chore/well-known-to-www) - --allow-conflicts exit 0 even where content was quarantined - --work-dir DIR where to put clones and reports (default: mktemp -d) - --no-auth run without a token: clone read-only over https and - classify from --trees-dir. Classify, migrate, test and - commit all work; only --push needs a credential. - --trees-dir DIR classify from cached `git/trees` listings, one file per - repository, one path per line, as written by a previous - run's /trees/. Required by --no-auth: the - unauthenticated API is capped at 60 requests/hour. - -Environment: - GITHUB_TOKEN / GH_TOKEN required; falls back to `gh auth token` -EOF -} - -die() { echo "sweep: ERROR — $*" >&2; exit 2; } -note() { echo "sweep: $*"; } - -POSITIONAL=() - -while [ $# -gt 0 ]; do - case "$1" in - --owner) [ $# -ge 2 ] || die "--owner requires a value"; OWNER="$2"; shift 2 ;; - --repos-file) [ $# -ge 2 ] || die "--repos-file requires a path"; REPOS_FILE="$2"; shift 2 ;; - --batch) [ $# -ge 2 ] || die "--batch requires a number"; BATCH="$2"; shift 2 ;; - --batch-size) [ $# -ge 2 ] || die "--batch-size requires a number"; BATCH_SIZE="$2"; shift 2 ;; - --limit) [ $# -ge 2 ] || die "--limit requires a number"; LIMIT="$2"; shift 2 ;; - --classify-only) CLASSIFY_ONLY=1; shift ;; - --include-review) INCLUDE_REVIEW=1; shift ;; - --no-auth) NO_AUTH=1; shift ;; - --trees-dir) [ $# -ge 2 ] || die "--trees-dir requires a path"; TREES_DIR="$2"; shift 2 ;; - --dry-run) DRY_RUN=1; shift ;; - --push) PUSH=1; shift ;; - --allow-conflicts) ALLOW_CONFLICTS=1; shift ;; - --branch) [ $# -ge 2 ] || die "--branch requires a value"; BRANCH="$2"; shift 2 ;; - --work-dir) [ $# -ge 2 ] || die "--work-dir requires a path"; WORK="$2"; shift 2 ;; - --migrator) [ $# -ge 2 ] || die "--migrator requires a path"; MIGRATOR="$2"; shift 2 ;; - -h|--help) usage; exit 0 ;; - -*) die "unknown option: $1" ;; - *) POSITIONAL+=("$1"); shift ;; - esac -done - -for c in git curl jq; do - command -v "$c" >/dev/null 2>&1 || die "$c is required but not installed" -done - -# Fail before cloning anything: discovering on repo 200 of 270 that commits -# cannot be made wastes the whole run and leaves 199 half-swept checkouts. -# `git var` honours config and GIT_COMMITTER_* alike, so either setup passes. -if [ "$CLASSIFY_ONLY" -eq 0 ] && [ "$DRY_RUN" -eq 0 ]; then - if ! git var GIT_COMMITTER_IDENT >/dev/null 2>&1; then - die "git cannot determine a committer identity, and the sweep commits. Either: - git config --global user.name \"Your Name\" - git config --global user.email \"you@example.com\" -or export GIT_COMMITTER_NAME / GIT_COMMITTER_EMAIL (plus GIT_AUTHOR_*)." - fi -fi - -# ── token ─────────────────────────────────────────────────────────────────── -TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" -if [ -z "$TOKEN" ] && command -v gh >/dev/null 2>&1; then - TOKEN="$(gh auth token 2>/dev/null || true)" -fi -if [ -z "$TOKEN" ]; then - if [ "$NO_AUTH" -eq 0 ]; then - die "no token: set GITHUB_TOKEN (repo scope), sign in with gh, or pass --no-auth to run read-only" - fi - note "no token available; running unauthenticated (--no-auth) — push unavailable" - [ -n "$TREES_DIR" ] || die "--no-auth requires --trees-dir (unauthenticated API is 60 req/hour)" - [ -d "$TREES_DIR" ] || die "trees dir not found: $TREES_DIR" -fi -if [ "$PUSH" -eq 1 ] && [ -z "$TOKEN" ]; then - die "--push requires a token; re-run with GITHUB_TOKEN set" -fi - -# ── paths ─────────────────────────────────────────────────────────────────── -# The migrator is located rather than assumed: this script is run from a -# template checkout, from a copy on $PATH, or from an unrelated directory, and -# each of those puts the scripts/ directory somewhere different. -resolve_migrator() { - if [ -n "$MIGRATOR" ]; then printf '%s' "$MIGRATOR"; return; fi - local c - for c in "$(git rev-parse --show-toplevel 2>/dev/null || true)" \ - "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" \ - "$PWD"; do - if [ -n "$c" ] && [ -f "$c/scripts/migrate-wellknown-to-www.sh" ]; then - printf '%s' "$c/scripts/migrate-wellknown-to-www.sh" - return - fi - done - printf '' -} -MIGRATOR="$(resolve_migrator)" -[ -n "$MIGRATOR" ] || die "migrator not found; pass --migrator /path/to/migrate-wellknown-to-www.sh" - -if [ -z "$WORK" ]; then - WORK="$(mktemp -d "${TMPDIR:-/tmp}/wellknown-sweep.XXXXXX")" -else - mkdir -p "$WORK" -fi -mkdir -p "$WORK/reports" "$WORK/repos" - -CLASS_CSV="$WORK/classification.csv" -RESULT_CSV="$WORK/sweep-results.csv" -printf 'repo,classification,root_wellknown,www_wellknown,notes\n' > "$CLASS_CSV" -printf 'repo,classification,status,detail\n' > "$RESULT_CSV" - -# ── repository list ───────────────────────────────────────────────────────── -REPOS=() -if [ -n "$REPOS_FILE" ]; then - [ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" - while IFS= read -r line; do - line="${line%%#*}" - line="$(printf '%s' "$line" | tr -d '[:space:]')" - [ -n "$line" ] && REPOS+=("$line") - done < "$REPOS_FILE" -fi -REPOS+=(${POSITIONAL[@]+"${POSITIONAL[@]}"}) - -if [ ${#REPOS[@]} -eq 0 ]; then - die "no repositories selected: pass --repos-file FILE or names as arguments" -fi - -if [ -n "$BATCH" ]; then - start=$(( (BATCH - 1) * BATCH_SIZE )) - REPOS=(${REPOS[@]:$start:$BATCH_SIZE}) - [ ${#REPOS[@]} -gt 0 ] || die "batch $BATCH is empty" - note "batch $BATCH of size $BATCH_SIZE: ${#REPOS[@]} repository(ies)" -fi -if [ -n "$LIMIT" ] && [ "$LIMIT" -lt ${#REPOS[@]} ]; then - REPOS=(${REPOS[@]:0:$LIMIT}) -fi -note "selected ${#REPOS[@]} repository(ies) from $OWNER; work dir $WORK" - -# ── classification ───────────────────────────────────────────────────────── -gh_api() { # path... - curl -sS --fail --retry 2 --retry-delay 1 --max-time 60 \ - -H "Authorization: Bearer $TOKEN" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "$@" -} - -classify_repo() { # repo -> "class|root_wk|www_wk|notes"; paths cached in $WORK/trees/ - local repo="$1" - local json paths="" - mkdir -p "$WORK/trees" - - # A cached tree listing is the same data the API would return, so prefer - # it: unauthenticated classification is capped at 60 requests/hour. - if [ -n "$TREES_DIR" ] && [ -f "$TREES_DIR/$repo" ]; then - paths="$(cat "$TREES_DIR/$repo")" - fi - if [ -n "$paths" ]; then - printf '%s\n' "$paths" > "$WORK/trees/$repo" - else - if ! json="$(gh_api "https://api.github.com/repos/$OWNER/$repo/git/trees/HEAD?recursive=1" 2>/dev/null)"; then - printf 'unknown|?|?|API error (missing/empty/private, or rate limited)\n' - return 0 - fi - if [ "$(printf '%s' "$json" | jq -r '.truncated // false')" = "true" ]; then - printf 'review-other|?|?|tree truncated by API — classify by hand\n' - return 0 - fi - paths="$(printf '%s' "$json" | jq -r '.tree[]?.path // empty')" - printf '%s\n' "$paths" > "$WORK/trees/$repo" - fi - - has() { grep -qxF "$1" <<<"$paths"; } - - local root_wk=no www_wk=no rsr=no pages=no notes="" - has ".well-known/security.txt" && root_wk=yes - has "www/.well-known/security.txt" && www_wk=yes - # Any file under either location counts, not just security.txt. - grep -qx "\.well-known/..*" <<<"$paths" && root_wk=yes - grep -qx "www/\.well-known/..*" <<<"$paths" && www_wk=yes - - # RSR-derived, tiered by evidence strength. The root allowlist is the - # marker check-root-shape.sh itself resolves — but it only entered the - # template in August 2026, so a repository minted before then is still - # RSR-derived and simply does not carry it. Measured over the #119 - # denominator: 269/270 have a .machine_readable/ tree while only 59 have - # root-allow.txt. Treating the allowlist as the sole marker therefore - # misclassifies two thirds of the estate as "not an RSR instance", which - # is wrong in the safe direction only by accident. The marker used is - # recorded in the notes column so the call is auditable. - local marker="" - if has ".machine_readable/root-allow.txt" || has "machine-readable/root-allow.txt"; then - rsr=yes - marker="root-allow.txt" - elif grep -q "^\.machine_readable/" <<<"$paths" \ - || grep -q "^machine-readable/" <<<"$paths"; then - rsr=yes - marker=".machine_readable/ (predates root-allow.txt)" - fi - - # Pages / CDN served from the repo root: migrating .well-known/ out of the - # served root is not obviously safe here (#119 special cases). - case "$repo" in - *.github.io) pages=yes; notes="repo name is a Pages site" ;; - esac - if has "CNAME"; then pages=yes; notes="${notes:+$notes; }CNAME at root (custom domain)" ; fi - for f in netlify.toml vercel.json _config.yml wrangler.toml wrangler.jsonc; do - if has "$f"; then pages=yes; notes="${notes:+$notes; }$f present" ; fi - done - - local class - if [ "$pages" = yes ]; then - class=review-pages - elif [ "$rsr" = yes ]; then - class=sweep - else - class=review-other - notes="${notes:+$notes; }no RSR marker found" - fi - [ -n "$marker" ] && notes="${notes:+$notes; }marker: $marker" - printf '%s|%s|%s|%s\n' "$class" "$root_wk" "$www_wk" "$notes" -} - -# ── migration ─────────────────────────────────────────────────────────────── -sweep_repo() { # repo class -> appends to RESULT_CSV - local repo="$1" class="$2" - local dir="$WORK/repos/$repo" - rm -rf "$dir" - - if [ "$CLASSIFY_ONLY" -eq 1 ]; then - printf '%s,%s,skipped,classify-only\n' "$repo" "$class" >> "$RESULT_CSV" - return 0 - fi - - export GIT_TERMINAL_PROMPT=0 - # Public repositories clone fine over plain https; the token only buys - # push access (and a higher rate limit), so do not require it to read. - local clone_url="https://github.com/${OWNER}/${repo}.git" - [ -n "$TOKEN" ] && clone_url="https://x-access-token:${TOKEN}@github.com/${OWNER}/${repo}.git" - if ! git clone --depth 1 -q "$clone_url" "$dir" 2>"$WORK/reports/$repo.clone.log"; then - printf '%s,%s,failed,clone failed (see %s)\n' "$repo" "$class" "$WORK/reports/$repo.clone.log" >> "$RESULT_CSV" - return 0 - fi - - if [ ! -d "$dir/.well-known" ]; then - printf '%s,%s,clean,no root .well-known/ (already migrated or never had one)\n' "$repo" "$class" >> "$RESULT_CSV" - return 0 - fi - - local report="$WORK/reports/$repo.tsv" - local migflags=() - [ "$DRY_RUN" -eq 1 ] && migflags+=(--dry-run) - - local status=migrated detail="" - if (cd "$dir" && bash "$MIGRATOR" "${migflags[@]}" --report "$report") >"$WORK/reports/$repo.migrate.log" 2>&1; then - if [ "$DRY_RUN" -eq 1 ]; then - status=would-migrate - fi - if [ -f "$report" ] && grep -qP '^quarantined\t' "$report"; then - status=quarantined - detail="$(grep -cP '^quarantined\t' "$report") divergent file(s) quarantined — needs a human" - fi - else - status=failed - detail="migrator exited non-zero (see $WORK/reports/$repo.migrate.log)" - fi - - # Post-migration suite: only meaningful where the repo carries the bundle. - if [ -f "$dir/www/tests/run-all.sh" ]; then - if ! (cd "$dir" && bash www/tests/run-all.sh) >"$WORK/reports/$repo.tests.log" 2>&1; then - status=failed - detail="${detail:+$detail; }www/tests/run-all.sh failed (see $WORK/reports/$repo.tests.log)" - fi - else - detail="${detail:+$detail; }no www/tests bundle — run the RSR update mechanism first" - fi - - if [ "$DRY_RUN" -eq 0 ] && [ "$status" != failed ]; then - if [ -n "$(cd "$dir" && git status --porcelain)" ]; then - # A commit failure is recorded, never fatal: one repository with an - # unusual hook or an unwritable ref must not abandon the batch. - if (cd "$dir" && git add -A && git commit -qm "chore(www): migrate root .well-known/ to www/.well-known/ (czech-file-knife#119)"); then - if [ "$PUSH" -eq 1 ]; then - (cd "$dir" && git push -q origin "HEAD:$BRANCH") \ - || detail="${detail:+$detail; }push failed" - fi - else - status=failed - detail="${detail:+$detail; }commit failed" - fi - else - [ "$status" = migrated ] && status=clean - fi - fi - - printf '%s,%s,%s,%s\n' "$repo" "$class" "$status" "$detail" >> "$RESULT_CSV" - return 0 -} - -# ── main ──────────────────────────────────────────────────────────────────── -declare -i total=0 swept=0 review=0 -for repo in "${REPOS[@]}"; do - total+=1 - IFS='|' read -r class root_wk www_wk notes <<<"$(classify_repo "$repo")" - printf '%s,%s,%s,%s,%s\n' "$repo" "$class" "$root_wk" "$www_wk" "$notes" >> "$CLASS_CSV" - printf ' %-40s %s\n' "$repo" "$class${notes:+ — $notes}" - - if [ "$class" = sweep ] || [ "$INCLUDE_REVIEW" -eq 1 ]; then - swept+=1 - # Belt and braces: sweep_repo records its own failures and returns 0, - # but an unexpected abort must still not take the batch down with it. - if ! sweep_repo "$repo" "$class"; then - printf '%s,%s,failed,unexpected abort (see %s)\n' "$repo" "$class" "$WORK" >> "$RESULT_CSV" - fi - else - review+=1 - printf '%s,%s,skipped,%s\n' "$repo" "$class" "needs a decision: $class" >> "$RESULT_CSV" - fi -done - -echo -note "classification: $CLASS_CSV" -note "results: $RESULT_CSV" -echo -echo "--- results by status ---" -tail -n +2 "$RESULT_CSV" | cut -d, -f3 | sort | uniq -c | sort -rn - -failed=$(tail -n +2 "$RESULT_CSV" | grep -c ',failed,' || true) -quarantined=$(tail -n +2 "$RESULT_CSV" | grep -c ',quarantined,' || true) - -if [ "$failed" -gt 0 ]; then - echo - echo "sweep: $failed repository(ies) FAILED:" >&2 - grep ',failed,' "$RESULT_CSV" >&2 - exit 1 -fi -if [ "$quarantined" -gt 0 ] && [ "$ALLOW_CONFLICTS" -eq 0 ]; then - echo - echo "sweep: $quarantined repository(ies) quarantined divergent content — resolve before merging." >&2 - grep ',quarantined,' "$RESULT_CSV" >&2 - exit 1 -fi -note "done: $total classified, $swept swept, $review held for review" -exit 0 diff --git a/czech-file-knife/scripts/validate-session-contracts.sh b/czech-file-knife/scripts/validate-session-contracts.sh deleted file mode 100644 index 2fe653101..000000000 --- a/czech-file-knife/scripts/validate-session-contracts.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Validate the two session policies with their actual Nickel evaluator. -set -euo pipefail -command -v nickel >/dev/null || { - echo "nickel is required to validate .k9.ncl session policies" >&2 - exit 2 -} -if [[ "${1:-}" == --typecheck ]]; then - shift - [[ $# -gt 0 ]] || { echo 'Supply the instantiated Nickel or K9 files to typecheck' >&2; exit 2; } - for file in "$@"; do - envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") - if [[ -n "$envelope_line" ]]; then - sed "${envelope_line}d" "$file" | (cd -- "$(dirname -- "$file")" && nickel typecheck) - else - nickel typecheck "$file" - fi - echo "$file: Nickel typecheck passed (deployment not executed)" - done - exit 0 -fi -[[ $# -eq 0 ]] || { echo 'Usage: validate-session-contracts.sh [--typecheck FILE...]' >&2; exit 2; } -for file in coordination.k9.ncl session/custom-checks.k9.ncl; do - envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") - if [[ -z "$envelope_line" ]]; then - echo "$file: missing K9! envelope" >&2 - exit 1 - fi - # K9! is a transport envelope, not a Nickel expression. These standalone - # records have no imports; evaluation also exercises their field contracts. - sed "${envelope_line}d" "$file" | nickel export --format json >/dev/null - echo "$file: Nickel evaluation passed" -done diff --git a/czech-file-knife/scripts/validate-template.sh b/czech-file-knife/scripts/validate-template.sh deleted file mode 100755 index 30d0c7a02..000000000 --- a/czech-file-knife/scripts/validate-template.sh +++ /dev/null @@ -1,491 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# RSR Template Validation Script -# Verifies that a repository follows the RSR template structure and contains all required files -# -# Exit codes: -# 0 = validation passed -# 1 = validation failed with errors -# 2 = validation failed with warnings (but can proceed) - -set -euo pipefail - -REPO_ROOT="${1:-.}" -VERBOSE="${2:-0}" -ERRORS=0 -WARNINGS=0 - -# ANSI colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Helper functions -log_error() { - echo -e "${RED}ERROR${NC}: $*" >&2 - ERRORS=$((ERRORS + 1)) -} - -log_warning() { - echo -e "${YELLOW}WARN${NC}: $*" >&2 - WARNINGS=$((WARNINGS + 1)) -} - -log_info() { - echo -e "${BLUE}INFO${NC}: $*" >&2 -} - -log_pass() { - echo -e "${GREEN}PASS${NC}: $*" >&2 -} - -check_file_exists() { - local file="$1" - local description="${2:-}" - if [ -f "$REPO_ROOT/$file" ]; then - [ "$VERBOSE" = "1" ] && log_pass "File exists: $file" - return 0 - else - log_error "Required file missing: $file ${description:+(${description})}" - return 1 - fi -} - -# The repo deed's FILENAME carries the repository name (filename dispatch: -# _chora.deed, stem = repo slug — deed.abnf v1.0.0), so the check is a -# glob, not a literal. Pre-deed era this slot was 0-AI-MANIFEST.a2ml; the -# family-7 allocation manifest and the ply tree folded into the deed -# (standards#837 pilot). -check_deed_exists() { - local description="${1:-}" - local f - for f in "$REPO_ROOT"/*_chora.deed; do - if [ -f "$f" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Repo deed exists: ${f#"$REPO_ROOT"/}" - return 0 - fi - done - log_error "Required repo deed missing: no _chora.deed at root ${description:+(${description})}" - return 1 -} - -check_file_either() { - local first="$1" - local second="$2" - local description="${3:-}" - if [ -f "$REPO_ROOT/$first" ] || [ -f "$REPO_ROOT/$second" ]; then - [ "$VERBOSE" = "1" ] && log_pass "File exists: $first or $second" - return 0 - fi - log_error "Required file missing: $first or $second ${description:+($description)}" - return 1 -} - -check_dir_exists() { - local dir="$1" - local description="${2:-}" - if [ -d "$REPO_ROOT/$dir" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $dir" - return 0 - else - log_error "Required directory missing: $dir ${description:+(${description})}" - return 1 - fi -} - -# The machine tree has two estate spellings: `.machine_readable/` is canonical, -# `machine-readable/` is the minority form ~9 repositories still carry. This -# file already had check_file_either for exactly this reason; directories had no -# such helper, and the gap let the matrix check below name the hyphenated form -# while its own message said `.machine_readable/`. Naming only one spelling -# fails whichever half of the estate has not migrated. -check_dir_either() { - local first="$1" - local second="$2" - local description="${3:-}" - if [ -d "$REPO_ROOT/$first" ] || [ -d "$REPO_ROOT/$second" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $first or $second" - return 0 - fi - log_error "Required directory missing: $first or $second ${description:+(${description})}" - return 1 -} - -# Case-tolerant ABI seam checks: accept the canonical case-consistent -# src/interface/Abi/ (matches `module Abi.*`) OR a lowercase src/interface/abi/ -# that some downstream repos ship. Never require BOTH (that would be a case-fold -# collision on case-insensitive filesystems). -check_abi_dir_exists() { - local description="${1:-}" - if [ -d "$REPO_ROOT/src/interface/Abi" ] || [ -d "$REPO_ROOT/src/interface/abi" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Directory exists: src/interface/{Abi,abi}" - return 0 - fi - log_error "Required directory missing: src/interface/Abi ${description:+(${description})}" - return 1 -} -check_abi_file_exists() { - local fname="$1" - local description="${2:-}" - if [ -f "$REPO_ROOT/src/interface/Abi/$fname" ] || [ -f "$REPO_ROOT/src/interface/abi/$fname" ]; then - [ "$VERBOSE" = "1" ] && log_pass "File exists: src/interface/{Abi,abi}/$fname" - return 0 - fi - log_error "Required file missing: src/interface/Abi/$fname ${description:+(${description})}" - return 1 -} - -has_spdx_header() { - local file="$1" - if head -10 "$file" | grep -q "SPDX-License-Identifier"; then - return 0 - fi - return 1 -} - -has_placeholder() { - local file="$1" - if grep -q "{{REPO\|{{OWNER\|{{FORGE\|{{PROJECT\|{{project\|{{AUTHOR" "$file" 2>/dev/null; then - return 0 - fi - return 1 -} - -#============================================================================== -# VALIDATION PHASE 1: CORE STRUCTURE -#============================================================================== - -echo "" -log_info "Phase 1: Core repository structure" -echo "" - -# Root files -check_deed_exists "repo deed (universal AI entry point)" -check_file_exists "README.adoc" "High-level pitch" -check_file_either "EXPLAINME.adoc" "docs/EXPLAINME.adoc" "Developer deep-dive" -check_file_exists "LICENSE" "License file" -check_file_exists "Justfile" "Task runner" -check_file_either "AUDIT.adoc" "docs/AUDIT.adoc" "Release audit gate" - -# Directories -check_dir_either ".machine_readable" "machine-readable" "Machine-readable metadata" -check_dir_exists ".github" "GitHub community metadata" -check_abi_dir_exists "Idris2 ABI definitions" -check_dir_exists "src/interface/ffi" "Zig FFI implementation" -check_dir_exists "src/interface/generated/abi" "Generated C headers" -check_dir_exists "docs" "Documentation" - -#============================================================================== -# VALIDATION PHASE 2: MACHINE-READABLE METADATA -#============================================================================== - -echo "" -log_info "Phase 2: Machine-readable metadata (.machine_readable/)" -echo "" - -check_file_exists ".machine_readable/descriptiles/STATE.a2ml" "Project state" -check_file_exists ".machine_readable/descriptiles/META.a2ml" "Architecture decisions" -check_file_exists ".machine_readable/descriptiles/ECOSYSTEM.a2ml" "Ecosystem position" -check_file_exists ".machine_readable/descriptiles/anchors/ANCHOR.a2ml" "Semantic boundary anchor" -check_file_exists ".machine_readable/policies/MAINTENANCE-AXES.a2ml" "Maintenance axes" - -#============================================================================== -# VALIDATION PHASE 3: REQUIRED WORKFLOWS (17 minimum) -#============================================================================== - -echo "" -log_info "Phase 3: GitHub Actions workflows" -echo "" - -REQUIRED_WORKFLOWS=( - "hypatia-scan.yml" - "codeql.yml" - "scorecard.yml" - "quality.yml" - "mirror.yml" - "guix-policy.yml" - "security-policy.yml" - "wellknown-enforcement.yml" - "workflow-linter.yml" - # npm-bun-blocker.yml and ts-blocker.yml were retired in #14 and replaced by - # runtime-policy.yml: the old gate failed any build carrying bun.lockb with - # "Use Deno instead", which blocked the estate's new first-choice runtime, so - # none of the 55 repos carrying it ever adopted Bun. This list was not - # updated, so it has required two files the template deliberately no longer - # ships — which is why validate-template.sh has been red since that merge. - "runtime-policy.yml" - "secret-scanner.yml" -) - -# A workflow renamed upstream must not read as a missing workflow here. #106 -# moved wellknown-enforcement.yml to dot-wellknown-enforcement.yml to match the -# .well-known/ URL convention and left this list requiring the old name, so this -# gate failed on main — the same way it failed when the two retired files above -# stayed listed. Resolve EITHER spelling and let the alias be dropped once the -# rename has propagated, exactly as check-root-shape.sh resolves both root -# spellings instead of assuming the migration is finished everywhere. -WORKFLOW_ALIASES=( - "wellknown-enforcement.yml:dot-wellknown-enforcement.yml" -) - -resolve_required_workflow() { - local want="$1" pair alt - if [ -f "$REPO_ROOT/.github/workflows/$want" ]; then - printf '%s\n' "$want" - return 0 - fi - for pair in "${WORKFLOW_ALIASES[@]}"; do - [ "${pair%%:*}" = "$want" ] || continue - alt="${pair#*:}" - if [ -f "$REPO_ROOT/.github/workflows/$alt" ]; then - printf '%s\n' "$alt" - return 0 - fi - done - return 1 -} - -# Check required workflows -for workflow in "${REQUIRED_WORKFLOWS[@]}"; do - if found="$(resolve_required_workflow "$workflow")"; then - [ "$VERBOSE" = "1" ] && log_pass "Workflow found: $found" - else - log_error "Required workflow missing: $workflow" - fi -done - -# Verify all workflows have SPDX headers and proper structure -WORKFLOW_FILES=$(find "$REPO_ROOT/.github/workflows" -name "*.yml" -type f 2>/dev/null || true) -WORKFLOW_COUNT=$(echo "$WORKFLOW_FILES" | grep -c "." || true) - -if [ "$WORKFLOW_COUNT" -ge 15 ]; then - log_pass "Found $WORKFLOW_COUNT workflows (>= 15 expected)" -else - log_warning "Found only $WORKFLOW_COUNT workflows (expected >= 15)" -fi - -# Spot-check workflow files for issues -while IFS= read -r workflow_file; do - if [ -z "$workflow_file" ]; then continue; fi - - # Check for SPDX header (optional in YAML workflows, but best practice) - if ! head -5 "$workflow_file" | grep -q "SPDX-License-Identifier"; then - log_warning "Workflow missing SPDX header: $(basename "$workflow_file")" - fi - - # Check for proper YAML structure - if ! grep -q "^name:" "$workflow_file"; then - log_error "Workflow missing 'name' field: $(basename "$workflow_file")" - fi -done <<< "$WORKFLOW_FILES" - -#============================================================================== -# VALIDATION PHASE 4: ABI/FFI SOURCE FILES -#============================================================================== - -echo "" -log_info "Phase 4: Idris2 ABI and Zig FFI source files" -echo "" - -# Idris2 ABI files -check_abi_file_exists "Types.idr" "Core type definitions" -check_abi_file_exists "Layout.idr" "Memory layout specifications" -check_abi_file_exists "Foreign.idr" "FFI foreign declarations" - -# Zig FFI files -check_file_exists "src/interface/ffi/build.zig" "Zig build configuration" -check_file_exists "src/interface/ffi/src/main.zig" "Zig implementation" -check_file_exists "src/interface/ffi/test/integration_test.zig" "Integration tests" - -#============================================================================== -# VALIDATION PHASE 5: PLACEHOLDER TOKENS -#============================================================================== - -echo "" -# The heading is unconditional; the skip below is not. It previously read -# "(skipped in template repo)" on every run, so in an instantiated repo — where -# the check DOES run — the log said it had been skipped. A live check that -# reports itself as skipped is worse than a silent one: it invites people to -# stop reading the phase. The skip announces itself on the line that performs it. -log_info "Phase 5: Placeholder token replacement" -echo "" - -# Note: Template repo is allowed to have placeholders -# For derived repos, we'd check that placeholders are replaced -if [ "$(basename "$REPO_ROOT")" = "czech-file-knife" ]; then - log_pass "Skipping placeholder check for template repo" -else - # Check that key files don't have unresolved placeholders - for file in "$REPO_ROOT/README.adoc" "$REPO_ROOT/Justfile" "$REPO_ROOT/.machine_readable/descriptiles/STATE.a2ml"; do - if [ -f "$file" ]; then - if has_placeholder "$file"; then - log_warning "File contains unresolved placeholders: $(basename "$file")" - fi - fi - done -fi - -#============================================================================== -# VALIDATION PHASE 6: SPDX LICENSE HEADERS -#============================================================================== - -echo "" -log_info "Phase 6: SPDX License Headers" -echo "" - -# Check source files for SPDX headers (excluding build artifacts). -# -# Scans the whole repository, not just src/, and every estate source language — -# not just Idris2 and Zig. -# -# This used to be `find "$REPO_ROOT/src" ... \( -name "*.idr" -o -name "*.zig" \)`. -# That is fine for the template, whose only sources are src/interface/{abi,ffi}, -# but wrong for the repos instantiated from it: a multi-language project keeps -# code in core-zig/, beam/, clients/, normalizer/ and so on, so the scan saw a -# handful of files and printed "SPDX headers: 10/10 (100%)". Measured across all -# languages the same repo was at 172/188 (91%) — the number was not wrong, it was -# answering a much narrower question than it appeared to. -# -# Uses `git ls-files` so it follows .gitignore and never descends into vendored -# or build directories; falls back to `find` outside a work tree. -SPDX_EXTS='idr|zig|rs|ex|exs|res|resi|factor|fs|lean|jl|gleam|ml|mli|hs|sh|nix|scm' -if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - SOURCE_FILES=$(git -C "$REPO_ROOT" ls-files \ - | grep -E "\.($SPDX_EXTS)$" \ - | sed "s|^|$REPO_ROOT/|" || true) -else - SOURCE_FILES=$(find "$REPO_ROOT" -type f \ - ! -path "*/.git/*" ! -path "*/.zig-cache/*" ! -path "*/zig-cache/*" \ - ! -path "*/node_modules/*" ! -path "*/target/*" ! -path "*/_build/*" \ - ! -path "*/.lake/*" ! -path "*/deps/*" \ - 2>/dev/null | grep -E "\.($SPDX_EXTS)$" || true) -fi -SOURCE_COUNT=$(echo "$SOURCE_FILES" | grep -c "." || true) -SPDX_COUNT=0 - -while IFS= read -r src_file; do - if [ -z "$src_file" ]; then continue; fi - if has_spdx_header "$src_file"; then - SPDX_COUNT=$((SPDX_COUNT + 1)) - else - log_warning "Source file missing SPDX header: $(basename "$src_file")" - fi -done <<< "$SOURCE_FILES" - -if [ "$SOURCE_COUNT" -gt 0 ]; then - PERCENT=$((SPDX_COUNT * 100 / SOURCE_COUNT)) - log_pass "SPDX headers: $SPDX_COUNT/$SOURCE_COUNT ($PERCENT%)" - if [ "$PERCENT" -lt 100 ]; then - log_warning "Not all source files have SPDX headers" - fi -fi - -#============================================================================== -# VALIDATION PHASE 7: BUILD VERIFICATION -#============================================================================== - -echo "" -log_info "Phase 7: Build system verification" -echo "" - -# Check Zig build -if [ -f "$REPO_ROOT/src/interface/ffi/build.zig" ]; then - if command -v zig &> /dev/null; then - cd "$REPO_ROOT/src/interface/ffi" - if zig build 2>&1 | grep -q "error"; then - log_error "Zig build failed" - else - log_pass "Zig build successful" - fi - cd - > /dev/null - else - log_warning "Zig compiler not found - skipping Zig build check" - fi -else - log_error "Zig build.zig not found" -fi - -# Check Idris2. Prefer a REAL typecheck via the package (abi.ipkg sets the -# sourcedir so the `module Abi.*` namespace resolves); this catches namespace / -# path / import breakage that a bare per-file `idris2 --check` masks as a -# tolerated "module name does not match file name" warning. -if command -v idris2 &> /dev/null; then - if [ -f "$REPO_ROOT/src/interface/abi.ipkg" ]; then - if (cd "$REPO_ROOT" && idris2 --typecheck src/interface/abi.ipkg) > /dev/null 2>&1; then - log_pass "Idris2 ABI typechecks (abi.ipkg)" - else - log_error "Idris2 ABI does NOT typecheck (abi.ipkg)" - fi - else - # No package: fall back to a best-effort per-file syntax check (warns on - # the expected namespace/path mismatch). Look in either case of the dir. - IDS_FILES=$(find "$REPO_ROOT/src/interface/Abi" "$REPO_ROOT/src/interface/abi" -name "*.idr" -type f 2>/dev/null || true) - while IFS= read -r ids_file; do - if [ -z "$ids_file" ]; then continue; fi - if ! idris2 --check "$ids_file" 2>&1 | grep -q "Error"; then - log_pass "Idris2 syntax OK: $(basename "$ids_file")" - else - log_warning "Idris2 syntax issue: $(basename "$ids_file")" - fi - done <<< "$IDS_FILES" - fi -else - log_warning "Idris2 compiler not found - skipping Idris2 syntax checks" -fi - -#============================================================================== -# VALIDATION PHASE 8: DOCUMENTATION -#============================================================================== - -echo "" -log_info "Phase 8: Documentation requirements" -echo "" - -check_file_exists "docs/developer/ABI-FFI-README.adoc" "ABI/FFI documentation" -# TOPOLOGY may live at root or under docs/architecture/, .md or .adoc -if [ -f "$REPO_ROOT/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/TOPOLOGY.md" ] || \ - [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.md" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Architecture topology found" -else - log_error "Required file missing: TOPOLOGY (root or docs/architecture/, .adoc or .md)" -fi -# CONTRIBUTING.md may live at root or in .github/ (GitHub auto-discovers either) -if [ -f "$REPO_ROOT/CONTRIBUTING.md" ] || [ -f "$REPO_ROOT/.github/CONTRIBUTING.md" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Contribution guide found" -else - log_error "Required file missing: CONTRIBUTING.md (root or .github/)" -fi - -# Governance can be at root or in docs/governance/ -if [ -f "$REPO_ROOT/GOVERNANCE.adoc" ] || [ -f "$REPO_ROOT/GOVERNANCE.md" ] || [ -d "$REPO_ROOT/docs/governance" ]; then - [ "$VERBOSE" = "1" ] && log_pass "Governance files found" -else - log_warning "Governance documentation not found" -fi - -#============================================================================== -# VALIDATION SUMMARY -#============================================================================== - -echo "" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "VALIDATION SUMMARY" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" -echo -e "Errors: ${RED}${ERRORS}${NC}" -echo -e "Warnings: ${YELLOW}${WARNINGS}${NC}" -echo "" - -if [ "$ERRORS" -eq 0 ]; then - echo -e "${GREEN}✓ Validation PASSED${NC}" - [ "$WARNINGS" -gt 0 ] && echo -e " (with $WARNINGS warnings)" - exit 0 -else - echo -e "${RED}✗ Validation FAILED${NC}" - echo " Please fix the errors above." - exit 1 -fi diff --git a/czech-file-knife/session/README.adoc b/czech-file-knife/session/README.adoc deleted file mode 100644 index ffa0c27e4..000000000 --- a/czech-file-knife/session/README.adoc +++ /dev/null @@ -1,50 +0,0 @@ -== Session Bindings (Thin Local Layer) - -This directory provides local integration for central session-management -standards. - -Authoritative protocols live in: - -* `+../standards/3-practice/session-management-standards/+` (or -`+$SESSION_STANDARDS_DIR+`) - -This repo keeps only thin bindings: - -* `+dispatch.sh+` maps canonical commands to central protocol paths. -* `+custom-checks.k9.ncl+` defines repo-local policy checks as a Nickel record. -* `+local-hooks.sh+` provides optional repo-specific hook behavior. - -Run `bash scripts/validate-session-contracts.sh` from the repository root -with Nickel installed to evaluate both session records. The script removes -the `K9!` transport envelope before invoking the actual Nickel evaluator; -a missing evaluator or invalid expression fails validation. - -=== Canonical Commands - -* `+intake repo +` -* `+checkpoint change +` -* `+verify maintenance +` -* `+verify substantial +` -* `+verify release +` -* `+close planned +` -* `+close urgent +` -* `+recover repo +` -* `+handover full +` -* `+handover split +` -* `+handover model +` -* `+handover human +` - -=== Justfile Aliases - -Run `+just session-help+` to list aliases, then use recipes such as: - -* `+just intake-repo path=.+` -* `+just checkpoint-change path=.+` -* `+just verify-maintenance path=.+` -* `+just close-planned path=.+` -* `+just handover-model path=.+` - -=== Runtime Artifacts - -Runtime files are generated per repository in `+.session/+` and are not -canonical standards text. diff --git a/czech-file-knife/session/custom-checks.k9.ncl b/czech-file-knife/session/custom-checks.k9.ncl deleted file mode 100644 index 6f6f36b2b..000000000 --- a/czech-file-knife/session/custom-checks.k9.ncl +++ /dev/null @@ -1,51 +0,0 @@ -K9! -# SPDX-License-Identifier: MPL-2.0 -# Local repository session checks (thin policy layer) - -{ - pedigree = { - schema_version = "1.0.0", - metadata = { - name = "custom-session-checks", - version = "0.1.0", - }, - security = { - leash = 'Kennel, - }, - }, - - checks = [ - { - id = "session-state-has-next-action", - applies_to = [ - "close planned", - "close urgent", - "handover full", - "handover split", - "handover model", - "handover human", - ], - requirement = "LAST-CANONICAL-COMMAND.md contains next intended action", - }, - { - id = "session-state-has-residual-risks", - applies_to = [ - "verify maintenance", - "verify substantial", - "verify release", - "recover repo", - ], - requirement = "Residual risks field is not left blank", - }, - { - id = "session-state-has-recommended-next-protocol", - applies_to = [ - "intake repo", - "checkpoint change", - "recover repo", - "handover full", - ], - requirement = "Recommended next protocol is set", - }, - ], -} diff --git a/czech-file-knife/session/dispatch.sh b/czech-file-knife/session/dispatch.sh deleted file mode 100755 index dd4126c28..000000000 --- a/czech-file-knife/session/dispatch.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -set -euo pipefail - -if [ "$#" -lt 3 ]; then - cat >&2 <<'USAGE' -Usage: ./session/dispatch.sh - -Canonical commands: - intake repo - checkpoint change - verify maintenance - verify substantial - verify release - close planned - close urgent - recover repo - handover full - handover split - handover model - handover human -USAGE - exit 2 -fi - -verb="$1" -object="$2" -repo_path="$3" -cmd_pair="$verb $object" - -if [ ! -d "$repo_path" ]; then - echo "error: repository path '$repo_path' does not exist" >&2 - exit 2 -fi - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -repo_root="$(cd "$script_dir/.." && pwd)" - -standards_dir="${SESSION_STANDARDS_DIR:-}" -if [ -z "$standards_dir" ]; then - if [ -d "$repo_root/../standards/3-practice/session-management-standards" ]; then - standards_dir="$repo_root/../standards/3-practice/session-management-standards" - elif [ -d "$repo_root/standards/3-practice/session-management-standards" ]; then - standards_dir="$repo_root/standards/3-practice/session-management-standards" - fi -fi - -case "$cmd_pair" in - "intake repo") - protocol_rel="continuity/repo-intake" - ;; - "checkpoint change") - protocol_rel="continuity/checkpoint-before-major-change" - ;; - "verify maintenance") - protocol_rel="verify/maintenance-sweep" - ;; - "verify substantial") - protocol_rel="verify/substantial-completion" - ;; - "verify release") - protocol_rel="verify/release-audit" - ;; - "close planned") - protocol_rel="continuity/planned-session-close" - ;; - "close urgent") - protocol_rel="continuity/emergency-termination" - ;; - "recover repo") - protocol_rel="continuity/recovery-operation" - ;; - "handover full") - protocol_rel="handover/full-transfer" - ;; - "handover split") - protocol_rel="handover/collaborative-transfer" - ;; - "handover model") - protocol_rel="handover/model-transfer" - ;; - "handover human") - protocol_rel="handover/human-transfer" - ;; - *) - echo "error: unsupported canonical command '$cmd_pair'" >&2 - exit 2 - ;; -esac - -session_dir="$repo_path/.session" -mkdir -p "$session_dir" - -command_record="$session_dir/LAST-CANONICAL-COMMAND.md" - -cat > "$command_record" <&2 - echo "canonical: $cmd_pair $repo_path" - echo "mapped protocol: $protocol_rel" -fi - -hooks="$script_dir/local-hooks.sh" -if [ -x "$hooks" ]; then - "$hooks" "$verb" "$object" "$repo_path" -fi - -echo "recorded: $command_record" diff --git a/czech-file-knife/session/local-hooks.sh b/czech-file-knife/session/local-hooks.sh deleted file mode 100755 index a726387b9..000000000 --- a/czech-file-knife/session/local-hooks.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -set -euo pipefail - -verb="${1:-}" -object="${2:-}" -repo_path="${3:-.}" - -session_dir="$repo_path/.session" -mkdir -p "$session_dir" -log_file="$session_dir/local-hooks.log" - -echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) hook: $verb $object $repo_path" >> "$log_file" - -case "$verb $object" in - "verify release") - echo "release hook: ensure AUDIT.adoc and session reports are reviewed" >> "$log_file" - ;; - "close urgent") - echo "urgent hook: prioritize EMERGENCY-CHECKPOINT.md generation" >> "$log_file" - ;; -esac diff --git a/czech-file-knife/sonar-project.properties b/czech-file-knife/sonar-project.properties deleted file mode 100644 index add715fe3..000000000 --- a/czech-file-knife/sonar-project.properties +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SonarQube Cloud (SonarCloud) configuration. -# Project: https://sonarcloud.io/project/overview?id=hyperpolymath_czech-file-knife -# Requires the SONAR_TOKEN repository secret + a SonarCloud project (owner setup). -sonar.organization=hyperpolymath -sonar.projectKey=hyperpolymath_czech-file-knife - -# Analysable surface = shell scripts + any JS/TS the template carries. Idris2, -# Zig, Elixir and AffineScript have no SonarCloud analyser; vendored, generated, -# build, proof, doc-template and dependency trees are excluded to keep findings -# signal-rich (mirrors the boj-server arrangement). -# .machine_readable/ (formerly machine-readable/) is DELIBERATELY NOT excluded. -# Un-hiding that tree exposed 108 files to analysis for the first time, 11 of -# them shell scripts. Excluding them here would have bought human legibility -# while preserving the scanner blind spot, which is the defect - not the fix. -# Any findings it surfaces were always there; they were merely unscanned. -# NOTE: the finding-count delta against main must still be measured on the -# branch before merge, since it is unverified whether each scanner skipped -# dot-directories by default. -# -# The rename back to the dotted spelling (2026-09-17) makes that question live -# again rather than moot. Un-hiding the tree is what exposed 108 files to -# analysis; re-hiding it may restore the blind spot this whole decision was -# about, IF a scanner skips dot-directories. Measure before assuming. -sonar.exclusions=build/**,generated/**,**/node_modules/**,**/_build/**,**/deps/**,**/.lake/**,verification/**,build/docs-seed/**,machine-readable-design/**,**/*.idr,**/*.ipkg,**/*.zig -sonar.coverage.exclusions=tests/**,**/*test* diff --git a/czech-file-knife/src/README.adoc b/czech-file-knife/src/README.adoc deleted file mode 100644 index 9e5bd3ce7..000000000 --- a/czech-file-knife/src/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= src Pillar diff --git a/czech-file-knife/src/aspects/README.adoc b/czech-file-knife/src/aspects/README.adoc deleted file mode 100644 index 2b39ea820..000000000 --- a/czech-file-knife/src/aspects/README.adoc +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Aspects Pillar -:icons: font - -[.lead] -The **Aspects Pillar** is where a project declares the cross-cutting -_capabilities_ it wears — the **Aspect-Oriented Language Design (AOLD)** seam of -an RSR repository. - -== What an "aspect" is - -An aspect is a capability woven in from a _specialist language_ without adopting -that language wholesale: distribution (Chapel), GPU kernels (Futhark), -data-race freedom (Pony), fault tolerance (OTP), correct-by-construction proof -(Dafny), energy-awareness (Eclexia), reversibility (Oblíbený), and so on. - -Each aspect is delivered by an **-iser** — a Rust CLI that injects the -capability through a uniform, proof-carrying pipeline: - -[literal] -.... - manifest ──► Idris2 ABI ──► Zig FFI ──► target-language codegen ──► build/run -.... - -Your application stays itself; the aspect is an _addable, removable, reversible_ -exoskeleton bolted on at the boundary (the "mech suit" model). Multiple aspects -compose over the same Idris2-ABI/Zig-FFI seam — the _Integrated Stack of Stacks_ -(iSOS). - -== What lives in `src/aspects/` - -Sub-pillars for the cross-cutting concerns this repo weaves in, e.g.: - -* `integrity/` — attestation, tamper-evidence, provenance. -* `observability/` — logging, metrics, tracing. -* `security/` — authz, sandboxing, supply-chain controls. - -A project records _which_ aspects it wears (and the manifest that configures -each -iser) here, so the augmentation is explicit, auditable, and reversible — -never hidden in the core. - -== Keeping aspects honest - -When an aspect crosses the FFI seam into the host, its proven invariant must not -be silently over-generalised (`theorem → guarantee → "universal claim"`). The -**invariant-path** tool is the conscience of this pillar: it traces each aspect's -proven invariant from its Idris2 ABI into the host call sites and flags any point -where the guarantee is carried further than it was proved. - -== See also - -* https://github.com/hyperpolymath/iseriser/blob/main/docs/ATLAS.adoc[The -iser Atlas] — route a need to the right aspect. -* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/AOLD.adoc[AOLD] — the design philosophy. -* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/iSOS.adoc[iSOS] — composing aspects. -* https://github.com/hyperpolymath/invariant-path[invariant-path] — the claim-path conscience. diff --git a/czech-file-knife/src/aspects/integrity/README.adoc b/czech-file-knife/src/aspects/integrity/README.adoc deleted file mode 100644 index d3e1ee1a7..000000000 --- a/czech-file-knife/src/aspects/integrity/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Integrity Aspect diff --git a/czech-file-knife/src/aspects/observability/README.adoc b/czech-file-knife/src/aspects/observability/README.adoc deleted file mode 100644 index 8a2151cd0..000000000 --- a/czech-file-knife/src/aspects/observability/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Observability Aspect diff --git a/czech-file-knife/src/aspects/security/README.adoc b/czech-file-knife/src/aspects/security/README.adoc deleted file mode 100644 index e28b5dabf..000000000 --- a/czech-file-knife/src/aspects/security/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Security Aspect diff --git a/czech-file-knife/src/bridges/.gitkeep b/czech-file-knife/src/bridges/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/czech-file-knife/src/cfk-cache/Cargo.toml b/czech-file-knife/src/cfk-cache/Cargo.toml deleted file mode 100644 index 4e8453f04..000000000 --- a/czech-file-knife/src/cfk-cache/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "cfk-cache" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Offline caching layer for Czech File Knife" - -[features] -default = ["sled"] -sled = ["dep:sled"] -surrealdb = ["dep:surrealdb"] -redb = ["dep:redb"] -lmdb = ["dep:heed"] -dragonfly = ["dep:redis"] - -[dependencies] -cfk-core = { path = "../cfk-core" } -async-trait.workspace = true -blake3.workspace = true -bytes.workspace = true -chrono.workspace = true -directories.workspace = true -lz4_flex.workspace = true -serde.workspace = true -serde_json.workspace = true -thiserror.workspace = true -tokio.workspace = true -tracing.workspace = true -hex = "0.4" - -# Database backends (choose one) -sled = { version = "0.34", optional = true } -surrealdb = { version = "2.6.1", optional = true, features = ["kv-mem", "kv-rocksdb"] } -redb = { version = "3.1", optional = true } -heed = { version = "0.22", optional = true } # LMDB wrapper -redis = { version = "1.0", optional = true, features = ["tokio-comp"] } # DragonflyDB compatible diff --git a/czech-file-knife/src/cfk-cache/src/blob_store.rs b/czech-file-knife/src/cfk-cache/src/blob_store.rs deleted file mode 100644 index 26a552d5e..000000000 --- a/czech-file-knife/src/cfk-cache/src/blob_store.rs +++ /dev/null @@ -1,450 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Content-addressed blob storage -//! -//! Stores file content using BLAKE3 hashes for deduplication. - -use blake3::Hasher; -use bytes::Bytes; -use lz4_flex::{compress_prepend_size, decompress_size_prepended}; -use std::path::{Path, PathBuf}; -use tokio::fs; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -use crate::{CacheError, CacheResult}; - -/// Content identifier (BLAKE3 hash) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ContentId(pub [u8; 32]); - -impl ContentId { - /// Create from raw bytes - pub fn from_bytes(bytes: [u8; 32]) -> Self { - Self(bytes) - } - - /// Create from hex string - pub fn from_hex(hex: &str) -> CacheResult { - if hex.len() != 64 { - return Err(CacheError::InvalidContentId); - } - - let mut bytes = [0u8; 32]; - hex::decode_to_slice(hex, &mut bytes) - .map_err(|_| CacheError::InvalidContentId)?; - - Ok(Self(bytes)) - } - - /// Convert to hex string - pub fn to_hex(&self) -> String { - hex::encode(self.0) - } - - /// Get the storage path for this content ID - fn storage_path(&self, base: &Path) -> PathBuf { - let hex = self.to_hex(); - // Use first 2 chars as directory for sharding - base.join(&hex[0..2]).join(&hex[2..]) - } -} - -impl std::fmt::Display for ContentId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.to_hex()) - } -} - -/// Blob storage configuration -#[derive(Debug, Clone)] -pub struct BlobStoreConfig { - /// Base directory for blob storage - pub path: PathBuf, - /// Compress blobs with LZ4 - pub compress: bool, - /// Minimum size to compress (bytes) - pub compress_threshold: usize, - /// Verify content on read - pub verify_on_read: bool, -} - -impl Default for BlobStoreConfig { - fn default() -> Self { - let cache_dir = directories::ProjectDirs::from("com", "cfk", "czech-file-knife") - .map(|d| d.cache_dir().to_path_buf()) - .unwrap_or_else(|| PathBuf::from("/tmp/cfk-cache")); - - Self { - path: cache_dir.join("blobs"), - compress: true, - compress_threshold: 1024, // 1KB - verify_on_read: true, - } - } -} - -/// Content-addressed blob store -pub struct BlobStore { - config: BlobStoreConfig, -} - -impl BlobStore { - /// Create a new blob store - pub async fn new(config: BlobStoreConfig) -> CacheResult { - // Ensure base directory exists - fs::create_dir_all(&config.path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - Ok(Self { config }) - } - - /// Create with default configuration - pub async fn default_store() -> CacheResult { - Self::new(BlobStoreConfig::default()).await - } - - /// Compute content ID for data - pub fn hash(data: &[u8]) -> ContentId { - let hash = blake3::hash(data); - ContentId(*hash.as_bytes()) - } - - /// Store blob and return content ID - pub async fn put(&self, data: Bytes) -> CacheResult { - let content_id = Self::hash(&data); - let path = content_id.storage_path(&self.config.path); - - // Check if already exists - if path.exists() { - return Ok(content_id); - } - - // Create parent directory - if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - } - - // Compress if enabled and above threshold - let stored_data = if self.config.compress && data.len() >= self.config.compress_threshold { - let compressed = compress_prepend_size(&data); - // Only use compressed if it's smaller - if compressed.len() < data.len() { - Bytes::from(compressed) - } else { - data - } - } else { - data - }; - - // Write atomically using temp file - let temp_path = path.with_extension("tmp"); - let mut file = fs::File::create(&temp_path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - file.write_all(&stored_data) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - file.sync_all() - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - // Rename to final path - fs::rename(&temp_path, &path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - Ok(content_id) - } - - /// Retrieve blob by content ID - pub async fn get(&self, content_id: &ContentId) -> CacheResult { - let path = content_id.storage_path(&self.config.path); - - if !path.exists() { - return Err(CacheError::NotFound(content_id.to_string())); - } - - let mut file = fs::File::open(&path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - let mut data = Vec::new(); - file.read_to_end(&mut data) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - // Try to decompress - let decompressed = match decompress_size_prepended(&data) { - Ok(d) => Bytes::from(d), - Err(_) => Bytes::from(data), // Not compressed - }; - - // Verify content if enabled - if self.config.verify_on_read { - let computed_id = Self::hash(&decompressed); - if computed_id != *content_id { - return Err(CacheError::CorruptedContent(content_id.to_string())); - } - } - - Ok(decompressed) - } - - /// Check if blob exists - pub async fn exists(&self, content_id: &ContentId) -> bool { - let path = content_id.storage_path(&self.config.path); - path.exists() - } - - /// Delete blob by content ID - pub async fn delete(&self, content_id: &ContentId) -> CacheResult<()> { - let path = content_id.storage_path(&self.config.path); - - if path.exists() { - fs::remove_file(&path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - } - - Ok(()) - } - - /// Get size of stored blob (compressed size) - pub async fn size(&self, content_id: &ContentId) -> CacheResult { - let path = content_id.storage_path(&self.config.path); - - let metadata = fs::metadata(&path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - Ok(metadata.len()) - } - - /// Get total size of blob store - pub async fn total_size(&self) -> CacheResult { - let mut total = 0u64; - - let mut entries = fs::read_dir(&self.config.path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - while let Some(entry) = entries - .next_entry() - .await - .map_err(|e| CacheError::Io(e.to_string()))? - { - if entry.path().is_dir() { - let mut subdir = fs::read_dir(entry.path()) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - while let Some(file) = subdir - .next_entry() - .await - .map_err(|e| CacheError::Io(e.to_string()))? - { - if let Ok(meta) = file.metadata().await { - total += meta.len(); - } - } - } - } - - Ok(total) - } - - /// List all content IDs - pub async fn list(&self) -> CacheResult> { - let mut ids = Vec::new(); - - let mut entries = fs::read_dir(&self.config.path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - while let Some(entry) = entries - .next_entry() - .await - .map_err(|e| CacheError::Io(e.to_string()))? - { - let dir_name = entry.file_name().to_string_lossy().to_string(); - if dir_name.len() != 2 || !entry.path().is_dir() { - continue; - } - - let mut subdir = fs::read_dir(entry.path()) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - while let Some(file) = subdir - .next_entry() - .await - .map_err(|e| CacheError::Io(e.to_string()))? - { - let file_name = file.file_name().to_string_lossy().to_string(); - let hex = format!("{}{}", dir_name, file_name); - - if let Ok(id) = ContentId::from_hex(&hex) { - ids.push(id); - } - } - } - - Ok(ids) - } - - /// Garbage collect blobs not in the provided set - pub async fn gc(&self, keep: &std::collections::HashSet) -> CacheResult { - let mut freed = 0u64; - - let all_ids = self.list().await?; - - for id in all_ids { - if !keep.contains(&id) { - if let Ok(size) = self.size(&id).await { - freed += size; - } - self.delete(&id).await?; - } - } - - Ok(freed) - } -} - -/// Streaming blob writer for large files -pub struct BlobWriter { - hasher: Hasher, - temp_path: PathBuf, - file: Option, - compress: bool, - buffer: Vec, -} - -impl BlobWriter { - /// Create a new blob writer - pub async fn new(store: &BlobStore) -> CacheResult { - let temp_path = store - .config - .path - .join(format!("upload_{}", uuid_simple())); - - let file = fs::File::create(&temp_path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - Ok(Self { - hasher: Hasher::new(), - temp_path, - file: Some(file), - compress: store.config.compress, - buffer: Vec::new(), - }) - } - - /// Write data chunk - pub async fn write(&mut self, data: &[u8]) -> CacheResult<()> { - self.hasher.update(data); - - if let Some(ref mut file) = self.file { - file.write_all(data) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - } - - Ok(()) - } - - /// Finish writing and return content ID - pub async fn finish(mut self, store: &BlobStore) -> CacheResult { - if let Some(mut file) = self.file.take() { - file.sync_all() - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - } - - let hash = self.hasher.finalize(); - let content_id = ContentId(*hash.as_bytes()); - - let final_path = content_id.storage_path(&store.config.path); - - // Create parent directory - if let Some(parent) = final_path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - } - - // Compress if needed - if self.compress { - let data = fs::read(&self.temp_path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - let compressed = compress_prepend_size(&data); - if compressed.len() < data.len() { - fs::write(&final_path, &compressed) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - fs::remove_file(&self.temp_path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - return Ok(content_id); - } - } - - // Move temp file to final location - fs::rename(&self.temp_path, &final_path) - .await - .map_err(|e| CacheError::Io(e.to_string()))?; - - Ok(content_id) - } -} - -/// Generate a simple UUID-like string -fn uuid_simple() -> String { - use std::time::{SystemTime, UNIX_EPOCH}; - - let duration = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default(); - - format!("{:x}{:x}", duration.as_secs(), duration.subsec_nanos()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_blob_store() { - let config = BlobStoreConfig { - path: PathBuf::from("/tmp/cfk-test-blobs"), - compress: true, - compress_threshold: 10, - verify_on_read: true, - }; - - let store = BlobStore::new(config).await.unwrap(); - - // Store data - let data = Bytes::from("Hello, World!"); - let id = store.put(data.clone()).await.unwrap(); - - // Retrieve data - let retrieved = store.get(&id).await.unwrap(); - assert_eq!(data, retrieved); - - // Check exists - assert!(store.exists(&id).await); - - // Delete - store.delete(&id).await.unwrap(); - assert!(!store.exists(&id).await); - } -} diff --git a/czech-file-knife/src/cfk-cache/src/lib.rs b/czech-file-knife/src/cfk-cache/src/lib.rs deleted file mode 100644 index b9a1f42df..000000000 --- a/czech-file-knife/src/cfk-cache/src/lib.rs +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Offline caching layer for Czech File Knife -//! -//! Features: -//! - Content-addressed blob storage with BLAKE3 hashing -//! - LZ4 compression for efficient storage -//! - Metadata caching with TTL support -//! - Multiple eviction policies (LRU, LFU, FIFO, etc.) -//! -//! Supports multiple backends: -//! - sled: Pure Rust embedded KV (default) -//! - SurrealDB: Multi-model with graph queries -//! - redb: Pure Rust alternative to sled -//! - LMDB: Ultra-fast memory-mapped (via heed) -//! - DragonflyDB: Redis-compatible (via redis crate) - -#![allow(dead_code)] // Placeholder structs for future implementation - -use async_trait::async_trait; -use cfk_core::{CfkResult, VirtualPath, Entry}; -use bytes::Bytes; -use thiserror::Error; - -pub mod blob_store; -pub mod metadata_cache; -pub mod policy; - -pub use blob_store::{BlobStore, BlobStoreConfig, ContentId}; -pub use metadata_cache::{MetadataCache, MetadataCacheConfig, CachedEntry}; -pub use policy::{CachePolicy, PolicyConfig, EvictionPolicy}; - -/// Cache-specific errors -#[derive(Debug, Error)] -pub enum CacheError { - #[error("I/O error: {0}")] - Io(String), - - #[error("Database error: {0}")] - Database(String), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Content not found: {0}")] - NotFound(String), - - #[error("Invalid content ID")] - InvalidContentId, - - #[error("Corrupted content: {0}")] - CorruptedContent(String), - - #[error("Cache full")] - CacheFull, -} - -/// Cache result type -pub type CacheResult = Result; - -/// Cache trait for different backends -#[async_trait] -pub trait CacheBackend: Send + Sync { - /// Get cached entry metadata - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult>; - - /// Store entry metadata - async fn put_metadata(&self, path: &VirtualPath, entry: &Entry) -> CfkResult<()>; - - /// Get cached file content - async fn get_content(&self, content_hash: &str) -> CfkResult>; - - /// Store file content (content-addressed) - async fn put_content(&self, data: &[u8]) -> CfkResult; - - /// Delete cached entry - async fn delete(&self, path: &VirtualPath) -> CfkResult<()>; - - /// Clear all cache - async fn clear(&self) -> CfkResult<()>; - - /// Get cache statistics - async fn stats(&self) -> CfkResult; -} - -/// Cache statistics -#[derive(Debug, Clone, Default)] -pub struct CacheStats { - pub entries: u64, - pub total_size: u64, - pub hit_count: u64, - pub miss_count: u64, -} - -impl CacheStats { - pub fn hit_rate(&self) -> f64 { - let total = self.hit_count + self.miss_count; - if total == 0 { 0.0 } else { self.hit_count as f64 / total as f64 } - } -} - -/// Content-addressed blob storage -pub mod blob { - use super::*; - use blake3::Hasher; - use lz4_flex::{compress_prepend_size, decompress_size_prepended}; - - /// Hash content using BLAKE3 - pub fn hash_content(data: &[u8]) -> String { - let mut hasher = Hasher::new(); - hasher.update(data); - hasher.finalize().to_hex().to_string() - } - - /// Compress data using LZ4 - pub fn compress(data: &[u8]) -> Vec { - compress_prepend_size(data) - } - - /// Decompress LZ4 data - pub fn decompress(data: &[u8]) -> CfkResult> { - decompress_size_prepended(data) - .map_err(|e| cfk_core::CfkError::Cache(e.to_string())) - } -} - -/// LRU eviction policy -pub mod eviction { - use std::collections::VecDeque; - - pub struct LruPolicy { - max_size: u64, - max_entries: usize, - entries: VecDeque<(String, u64)>, // (hash, size) - } - - impl LruPolicy { - pub fn new(max_size: u64, max_entries: usize) -> Self { - Self { - max_size, - max_entries, - entries: VecDeque::new(), - } - } - - pub fn access(&mut self, hash: &str, size: u64) { - // Move to front - self.entries.retain(|(h, _)| h != hash); - self.entries.push_front((hash.to_string(), size)); - } - - pub fn evict_candidates(&self, needed_space: u64) -> Vec { - let mut freed = 0u64; - let mut to_evict = Vec::new(); - - for (hash, size) in self.entries.iter().rev() { - if freed >= needed_space { - break; - } - to_evict.push(hash.clone()); - freed += size; - } - - to_evict - } - } -} - -#[cfg(feature = "sled")] -pub mod sled_backend; - -#[cfg(feature = "surrealdb")] -pub mod surreal_backend; - -#[cfg(feature = "lmdb")] -pub mod lmdb_backend; - -#[cfg(feature = "dragonfly")] -pub mod dragonfly_backend; diff --git a/czech-file-knife/src/cfk-cache/src/metadata_cache.rs b/czech-file-knife/src/cfk-cache/src/metadata_cache.rs deleted file mode 100644 index 00432bb77..000000000 --- a/czech-file-knife/src/cfk-cache/src/metadata_cache.rs +++ /dev/null @@ -1,534 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File metadata caching -//! -//! Caches file and directory metadata for offline access and performance. - -use cfk_core::{Entry, EntryKind, Metadata, VirtualPath}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::RwLock; - -use crate::blob_store::ContentId; -use crate::{CacheError, CacheResult}; - -/// Cached entry metadata -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CachedEntry { - /// Virtual path - pub path: String, - /// Backend ID - pub backend_id: String, - /// Entry kind - pub kind: CachedEntryKind, - /// File size (for files) - pub size: Option, - /// Last modified time - pub modified: Option>, - /// Created time - pub created: Option>, - /// Content hash/checksum from provider - pub checksum: Option, - /// MIME type - pub mime_type: Option, - /// Local blob content ID (if cached) - pub content_id: Option, - /// When this entry was cached - pub cached_at: DateTime, - /// When this entry expires - pub expires_at: Option>, - /// Custom metadata - #[serde(default)] - pub custom: HashMap, -} - -/// Cached entry kind -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -pub enum CachedEntryKind { - File, - Directory, - Symlink, - Unknown, -} - -impl From for CachedEntryKind { - fn from(kind: EntryKind) -> Self { - match kind { - EntryKind::File => CachedEntryKind::File, - EntryKind::Directory => CachedEntryKind::Directory, - EntryKind::Symlink => CachedEntryKind::Symlink, - EntryKind::Unknown => CachedEntryKind::Unknown, - } - } -} - -impl From for EntryKind { - fn from(kind: CachedEntryKind) -> Self { - match kind { - CachedEntryKind::File => EntryKind::File, - CachedEntryKind::Directory => EntryKind::Directory, - CachedEntryKind::Symlink => EntryKind::Symlink, - CachedEntryKind::Unknown => EntryKind::Unknown, - } - } -} - -impl CachedEntry { - /// Create from cfk_core Entry - pub fn from_entry(entry: &Entry, ttl_secs: Option) -> Self { - Self { - path: entry.path.to_string(), - backend_id: entry.path.backend.clone(), - kind: entry.kind.into(), - size: entry.metadata.size, - modified: entry.metadata.modified, - created: entry.metadata.created, - checksum: entry.metadata.content_hash.clone(), - mime_type: entry.metadata.mime_type.clone(), - content_id: None, - cached_at: Utc::now(), - expires_at: ttl_secs.map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - custom: entry.metadata.custom.clone(), - } - } - - /// Convert back to cfk_core Entry - pub fn to_entry(&self) -> Entry { - let mut metadata = Metadata::default(); - metadata.size = self.size; - metadata.modified = self.modified; - metadata.created = self.created; - metadata.content_hash = self.checksum.clone(); - metadata.mime_type = self.mime_type.clone(); - metadata.custom = self.custom.clone(); - - Entry { - path: VirtualPath::parse_uri(&self.path).unwrap_or_else(|| { - VirtualPath::new(&self.backend_id, &self.path) - }), - kind: self.kind.into(), - metadata, - } - } - - /// Check if entry is expired - pub fn is_expired(&self) -> bool { - if let Some(expires) = self.expires_at { - Utc::now() > expires - } else { - false - } - } - - /// Set content ID after caching content - pub fn with_content_id(mut self, content_id: &ContentId) -> Self { - self.content_id = Some(content_id.to_hex()); - self - } -} - -/// Cached directory listing -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CachedDirectory { - /// Directory path - pub path: String, - /// Backend ID - pub backend_id: String, - /// Child entry paths - pub children: Vec, - /// When this listing was cached - pub cached_at: DateTime, - /// When this listing expires - pub expires_at: Option>, -} - -impl CachedDirectory { - /// Create new cached directory - pub fn new(path: &VirtualPath, children: Vec, ttl_secs: Option) -> Self { - Self { - path: path.to_string(), - backend_id: path.backend.clone(), - children, - cached_at: Utc::now(), - expires_at: ttl_secs.map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - } - } - - /// Check if listing is expired - pub fn is_expired(&self) -> bool { - if let Some(expires) = self.expires_at { - Utc::now() > expires - } else { - false - } - } -} - -/// Metadata cache configuration -#[derive(Debug, Clone)] -pub struct MetadataCacheConfig { - /// Database path (sled) - pub db_path: PathBuf, - /// Default TTL for entries (seconds) - pub default_ttl: i64, - /// Maximum entries to cache - pub max_entries: usize, -} - -impl Default for MetadataCacheConfig { - fn default() -> Self { - let cache_dir = directories::ProjectDirs::from("com", "cfk", "czech-file-knife") - .map(|d| d.cache_dir().to_path_buf()) - .unwrap_or_else(|| PathBuf::from("/tmp/cfk-cache")); - - Self { - db_path: cache_dir.join("metadata.db"), - default_ttl: 3600, // 1 hour - max_entries: 100000, - } - } -} - -/// Metadata cache -pub struct MetadataCache { - config: MetadataCacheConfig, - db: sled::Db, - /// In-memory LRU cache for hot entries - memory_cache: Arc>>, -} - -impl MetadataCache { - /// Create new metadata cache - pub fn new(config: MetadataCacheConfig) -> CacheResult { - let db = sled::open(&config.db_path) - .map_err(|e| CacheError::Database(e.to_string()))?; - - let memory_cache = Arc::new(RwLock::new(lru::LruCache::new( - std::num::NonZeroUsize::new(10000).unwrap(), - ))); - - Ok(Self { - config, - db, - memory_cache, - }) - } - - /// Create with default configuration - pub fn default_cache() -> CacheResult { - Self::new(MetadataCacheConfig::default()) - } - - /// Cache entry metadata - pub async fn put_entry(&self, entry: &Entry) -> CacheResult<()> { - let cached = CachedEntry::from_entry(entry, Some(self.config.default_ttl)); - let key = entry.path.to_string(); - let value = serde_json::to_vec(&cached) - .map_err(|e| CacheError::Serialization(e.to_string()))?; - - self.db - .insert(format!("entry:{}", key), value) - .map_err(|e| CacheError::Database(e.to_string()))?; - - // Update memory cache - self.memory_cache.write().await.put(key, cached); - - Ok(()) - } - - /// Cache entry with custom TTL - pub async fn put_entry_with_ttl(&self, entry: &Entry, ttl_secs: i64) -> CacheResult<()> { - let cached = CachedEntry::from_entry(entry, Some(ttl_secs)); - let key = entry.path.to_string(); - let value = serde_json::to_vec(&cached) - .map_err(|e| CacheError::Serialization(e.to_string()))?; - - self.db - .insert(format!("entry:{}", key), value) - .map_err(|e| CacheError::Database(e.to_string()))?; - - self.memory_cache.write().await.put(key, cached); - - Ok(()) - } - - /// Get cached entry - pub async fn get_entry(&self, path: &VirtualPath) -> CacheResult> { - let key = path.to_string(); - - // Check memory cache first - { - let mut cache = self.memory_cache.write().await; - if let Some(entry) = cache.get(&key) { - if !entry.is_expired() { - return Ok(Some(entry.clone())); - } - } - } - - // Check database - let db_key = format!("entry:{}", key); - if let Some(data) = self.db.get(&db_key).map_err(|e| CacheError::Database(e.to_string()))? { - let cached: CachedEntry = serde_json::from_slice(&data) - .map_err(|e| CacheError::Serialization(e.to_string()))?; - - if cached.is_expired() { - // Remove expired entry - self.db - .remove(&db_key) - .map_err(|e| CacheError::Database(e.to_string()))?; - return Ok(None); - } - - // Add to memory cache - self.memory_cache.write().await.put(key, cached.clone()); - - return Ok(Some(cached)); - } - - Ok(None) - } - - /// Cache directory listing - pub async fn put_directory(&self, path: &VirtualPath, entries: &[Entry]) -> CacheResult<()> { - let children: Vec = entries.iter().map(|e| e.path.to_string()).collect(); - let cached = CachedDirectory::new(path, children, Some(self.config.default_ttl)); - - let key = format!("dir:{}", path); - let value = serde_json::to_vec(&cached) - .map_err(|e| CacheError::Serialization(e.to_string()))?; - - self.db - .insert(key, value) - .map_err(|e| CacheError::Database(e.to_string()))?; - - // Also cache individual entries - for entry in entries { - self.put_entry(entry).await?; - } - - Ok(()) - } - - /// Get cached directory listing - pub async fn get_directory(&self, path: &VirtualPath) -> CacheResult>> { - let key = format!("dir:{}", path); - - if let Some(data) = self.db.get(&key).map_err(|e| CacheError::Database(e.to_string()))? { - let cached: CachedDirectory = serde_json::from_slice(&data) - .map_err(|e| CacheError::Serialization(e.to_string()))?; - - if cached.is_expired() { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - return Ok(None); - } - - // Fetch individual entries - let mut entries = Vec::new(); - for child_path in &cached.children { - let virtual_path = VirtualPath::parse_uri(child_path).unwrap_or_else(|| { - VirtualPath::new(&cached.backend_id, child_path) - }); - - if let Some(entry) = self.get_entry(&virtual_path).await? { - entries.push(entry.to_entry()); - } - } - - return Ok(Some(entries)); - } - - Ok(None) - } - - /// Invalidate entry - pub async fn invalidate(&self, path: &VirtualPath) -> CacheResult<()> { - let key = path.to_string(); - - self.db - .remove(format!("entry:{}", key)) - .map_err(|e| CacheError::Database(e.to_string()))?; - - self.memory_cache.write().await.pop(&key); - - Ok(()) - } - - /// Invalidate directory and all children - pub async fn invalidate_directory(&self, path: &VirtualPath) -> CacheResult<()> { - let prefix = format!("entry:{}:", path); - - // Remove all entries with this prefix - for result in self.db.scan_prefix(&prefix) { - if let Ok((key, _)) = result { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - } - } - - // Remove directory listing - self.db - .remove(format!("dir:{}", path)) - .map_err(|e| CacheError::Database(e.to_string()))?; - - Ok(()) - } - - /// Clear all cached data for a backend - pub async fn clear_backend(&self, backend_id: &str) -> CacheResult<()> { - let prefix = format!("entry:{}:", backend_id); - - for result in self.db.scan_prefix(&prefix) { - if let Ok((key, _)) = result { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - } - } - - let dir_prefix = format!("dir:{}:", backend_id); - for result in self.db.scan_prefix(&dir_prefix) { - if let Ok((key, _)) = result { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - } - } - - self.memory_cache.write().await.clear(); - - Ok(()) - } - - /// Clear all cached data - pub async fn clear_all(&self) -> CacheResult<()> { - self.db.clear().map_err(|e| CacheError::Database(e.to_string()))?; - self.memory_cache.write().await.clear(); - Ok(()) - } - - /// Get cache statistics - pub async fn stats(&self) -> CacheStats { - let entry_count = self.db.scan_prefix("entry:").count(); - let dir_count = self.db.scan_prefix("dir:").count(); - let memory_size = self.memory_cache.read().await.len(); - let db_size = self.db.size_on_disk().unwrap_or(0); - - CacheStats { - entry_count, - directory_count: dir_count, - memory_entries: memory_size, - disk_size_bytes: db_size, - } - } - - /// Prune expired entries - pub async fn prune_expired(&self) -> CacheResult { - let mut pruned = 0; - - for result in self.db.scan_prefix("entry:") { - if let Ok((key, value)) = result { - if let Ok(cached) = serde_json::from_slice::(&value) { - if cached.is_expired() { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - pruned += 1; - } - } - } - } - - for result in self.db.scan_prefix("dir:") { - if let Ok((key, value)) = result { - if let Ok(cached) = serde_json::from_slice::(&value) { - if cached.is_expired() { - self.db - .remove(&key) - .map_err(|e| CacheError::Database(e.to_string()))?; - pruned += 1; - } - } - } - } - - Ok(pruned) - } -} - -/// Cache statistics -#[derive(Debug, Clone, Default)] -pub struct CacheStats { - pub entry_count: usize, - pub directory_count: usize, - pub memory_entries: usize, - pub disk_size_bytes: u64, -} - -/// Simple LRU cache implementation -mod lru { - use std::collections::HashMap; - use std::hash::Hash; - use std::num::NonZeroUsize; - - pub struct LruCache { - map: HashMap, - order: Vec, - capacity: usize, - } - - impl LruCache { - pub fn new(capacity: NonZeroUsize) -> Self { - Self { - map: HashMap::new(), - order: Vec::new(), - capacity: capacity.get(), - } - } - - pub fn get(&mut self, key: &K) -> Option<&V> { - if self.map.contains_key(key) { - // Move to front - self.order.retain(|k| k != key); - self.order.push(key.clone()); - self.map.get(key) - } else { - None - } - } - - pub fn put(&mut self, key: K, value: V) { - if self.map.contains_key(&key) { - self.order.retain(|k| k != &key); - } else if self.map.len() >= self.capacity { - // Evict oldest - if let Some(oldest) = self.order.first().cloned() { - self.map.remove(&oldest); - self.order.remove(0); - } - } - - self.map.insert(key.clone(), value); - self.order.push(key); - } - - pub fn pop(&mut self, key: &K) -> Option { - self.order.retain(|k| k != key); - self.map.remove(key) - } - - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn clear(&mut self) { - self.map.clear(); - self.order.clear(); - } - } -} diff --git a/czech-file-knife/src/cfk-cache/src/policy.rs b/czech-file-knife/src/cfk-cache/src/policy.rs deleted file mode 100644 index dc9ba9e02..000000000 --- a/czech-file-knife/src/cfk-cache/src/policy.rs +++ /dev/null @@ -1,423 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Cache eviction policies -//! -//! LRU, LFU, FIFO, and size-based eviction strategies. - -use chrono::{DateTime, Utc}; -use std::collections::{BinaryHeap, HashMap}; -use std::cmp::Ordering; - -use crate::blob_store::ContentId; - -/// Cache entry info for eviction decisions -#[derive(Debug, Clone)] -pub struct CacheEntryInfo { - pub content_id: ContentId, - pub size: u64, - pub last_accessed: DateTime, - pub access_count: u64, - pub created: DateTime, - /// Priority (higher = more important) - pub priority: i32, -} - -impl CacheEntryInfo { - pub fn new(content_id: ContentId, size: u64) -> Self { - let now = Utc::now(); - Self { - content_id, - size, - last_accessed: now, - access_count: 1, - created: now, - priority: 0, - } - } - - pub fn touch(&mut self) { - self.last_accessed = Utc::now(); - self.access_count += 1; - } - - pub fn with_priority(mut self, priority: i32) -> Self { - self.priority = priority; - self - } -} - -/// Eviction policy type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EvictionPolicy { - /// Least Recently Used - Lru, - /// Least Frequently Used - Lfu, - /// First In First Out - Fifo, - /// Largest First - LargestFirst, - /// Smallest First - SmallestFirst, - /// Adaptive Replacement Cache (ARC-like) - Adaptive, -} - -impl Default for EvictionPolicy { - fn default() -> Self { - Self::Lru - } -} - -/// Cache policy configuration -#[derive(Debug, Clone)] -pub struct PolicyConfig { - /// Maximum total size in bytes - pub max_size: u64, - /// Maximum number of entries - pub max_entries: usize, - /// Eviction policy - pub policy: EvictionPolicy, - /// Target utilization (0.0-1.0) after eviction - pub target_utilization: f64, - /// Minimum TTL (seconds) - don't evict entries newer than this - pub min_ttl: i64, -} - -impl Default for PolicyConfig { - fn default() -> Self { - Self { - max_size: 10 * 1024 * 1024 * 1024, // 10GB - max_entries: 100000, - policy: EvictionPolicy::Lru, - target_utilization: 0.9, - min_ttl: 60, // 1 minute - } - } -} - -/// Eviction result -#[derive(Debug, Clone)] -pub struct EvictionResult { - /// Content IDs to evict - pub evicted: Vec, - /// Total size freed - pub size_freed: u64, - /// Number of entries evicted - pub count: usize, -} - -/// Cache policy manager -pub struct CachePolicy { - config: PolicyConfig, - entries: HashMap, - total_size: u64, -} - -impl CachePolicy { - pub fn new(config: PolicyConfig) -> Self { - Self { - config, - entries: HashMap::new(), - total_size: 0, - } - } - - /// Record an entry being added to cache - pub fn record_add(&mut self, info: CacheEntryInfo) { - self.total_size += info.size; - self.entries.insert(info.content_id.clone(), info); - } - - /// Record an entry being accessed - pub fn record_access(&mut self, content_id: &ContentId) { - if let Some(entry) = self.entries.get_mut(content_id) { - entry.touch(); - } - } - - /// Record an entry being removed - pub fn record_remove(&mut self, content_id: &ContentId) { - if let Some(entry) = self.entries.remove(content_id) { - self.total_size = self.total_size.saturating_sub(entry.size); - } - } - - /// Check if eviction is needed - pub fn needs_eviction(&self) -> bool { - self.total_size > self.config.max_size || self.entries.len() > self.config.max_entries - } - - /// Get entries to evict - pub fn select_evictions(&self) -> EvictionResult { - if !self.needs_eviction() { - return EvictionResult { - evicted: vec![], - size_freed: 0, - count: 0, - }; - } - - let target_size = (self.config.max_size as f64 * self.config.target_utilization) as u64; - let size_to_free = self.total_size.saturating_sub(target_size); - - let target_entries = - (self.config.max_entries as f64 * self.config.target_utilization) as usize; - let entries_to_free = self.entries.len().saturating_sub(target_entries); - - let mut evicted = Vec::new(); - let mut size_freed = 0u64; - - // Get candidates sorted by eviction policy - let mut candidates: Vec<_> = self - .entries - .values() - .filter(|e| { - // Don't evict entries newer than min_ttl - let age = Utc::now() - .signed_duration_since(e.created) - .num_seconds(); - age >= self.config.min_ttl - }) - .collect(); - - // Sort by policy - match self.config.policy { - EvictionPolicy::Lru => { - candidates.sort_by(|a, b| a.last_accessed.cmp(&b.last_accessed)); - } - EvictionPolicy::Lfu => { - candidates.sort_by(|a, b| a.access_count.cmp(&b.access_count)); - } - EvictionPolicy::Fifo => { - candidates.sort_by(|a, b| a.created.cmp(&b.created)); - } - EvictionPolicy::LargestFirst => { - candidates.sort_by(|a, b| b.size.cmp(&a.size)); - } - EvictionPolicy::SmallestFirst => { - candidates.sort_by(|a, b| a.size.cmp(&b.size)); - } - EvictionPolicy::Adaptive => { - // ARC-like: balance between LRU and LFU - candidates.sort_by(|a, b| { - let a_score = adaptive_score(a); - let b_score = adaptive_score(b); - a_score.partial_cmp(&b_score).unwrap_or(Ordering::Equal) - }); - } - } - - // Select entries to evict - for candidate in candidates { - if size_freed >= size_to_free && evicted.len() >= entries_to_free { - break; - } - - evicted.push(candidate.content_id.clone()); - size_freed += candidate.size; - } - - let count = evicted.len(); - EvictionResult { - evicted, - size_freed, - count, - } - } - - /// Get current cache statistics - pub fn stats(&self) -> PolicyStats { - let avg_size = if self.entries.is_empty() { - 0 - } else { - self.total_size / self.entries.len() as u64 - }; - - let avg_access = if self.entries.is_empty() { - 0.0 - } else { - self.entries.values().map(|e| e.access_count).sum::() as f64 - / self.entries.len() as f64 - }; - - PolicyStats { - total_size: self.total_size, - entry_count: self.entries.len(), - max_size: self.config.max_size, - max_entries: self.config.max_entries, - utilization: self.total_size as f64 / self.config.max_size as f64, - avg_entry_size: avg_size, - avg_access_count: avg_access, - } - } - - /// Update policy configuration - pub fn set_config(&mut self, config: PolicyConfig) { - self.config = config; - } -} - -/// Calculate adaptive eviction score (lower = more likely to evict) -fn adaptive_score(entry: &CacheEntryInfo) -> f64 { - let age_hours = Utc::now() - .signed_duration_since(entry.last_accessed) - .num_hours() as f64; - - let frequency = entry.access_count as f64; - let size_penalty = (entry.size as f64).ln(); - let priority_bonus = entry.priority as f64 * 100.0; - - // Higher score = less likely to evict - frequency / (age_hours + 1.0) - size_penalty / 10.0 + priority_bonus -} - -/// Policy statistics -#[derive(Debug, Clone, Default)] -pub struct PolicyStats { - pub total_size: u64, - pub entry_count: usize, - pub max_size: u64, - pub max_entries: usize, - pub utilization: f64, - pub avg_entry_size: u64, - pub avg_access_count: f64, -} - -/// Priority queue for eviction candidates -struct EvictionCandidate { - content_id: ContentId, - score: f64, - size: u64, -} - -impl PartialEq for EvictionCandidate { - fn eq(&self, other: &Self) -> bool { - self.content_id == other.content_id - } -} - -impl Eq for EvictionCandidate {} - -impl PartialOrd for EvictionCandidate { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EvictionCandidate { - fn cmp(&self, other: &Self) -> Ordering { - // Lower score = higher priority for eviction - other - .score - .partial_cmp(&self.score) - .unwrap_or(Ordering::Equal) - } -} - -/// Size-tiered caching strategy -pub struct TieredPolicy { - /// Hot tier (frequently accessed, small) - hot_tier: CachePolicy, - /// Warm tier (occasionally accessed) - warm_tier: CachePolicy, - /// Cold tier (rarely accessed, large) - cold_tier: CachePolicy, -} - -impl TieredPolicy { - pub fn new(total_size: u64) -> Self { - // 10% hot, 30% warm, 60% cold - let hot_size = total_size / 10; - let warm_size = (total_size * 3) / 10; - let cold_size = (total_size * 6) / 10; - - Self { - hot_tier: CachePolicy::new(PolicyConfig { - max_size: hot_size, - max_entries: 10000, - policy: EvictionPolicy::Lfu, - ..Default::default() - }), - warm_tier: CachePolicy::new(PolicyConfig { - max_size: warm_size, - max_entries: 50000, - policy: EvictionPolicy::Lru, - ..Default::default() - }), - cold_tier: CachePolicy::new(PolicyConfig { - max_size: cold_size, - max_entries: 100000, - policy: EvictionPolicy::LargestFirst, - ..Default::default() - }), - } - } - - /// Determine tier for an entry based on size and access pattern - pub fn determine_tier(&self, info: &CacheEntryInfo) -> Tier { - if info.access_count > 10 && info.size < 1024 * 1024 { - Tier::Hot - } else if info.access_count > 2 || info.size < 10 * 1024 * 1024 { - Tier::Warm - } else { - Tier::Cold - } - } - - /// Record entry in appropriate tier - pub fn record_add(&mut self, info: CacheEntryInfo) { - match self.determine_tier(&info) { - Tier::Hot => self.hot_tier.record_add(info), - Tier::Warm => self.warm_tier.record_add(info), - Tier::Cold => self.cold_tier.record_add(info), - } - } - - /// Get evictions from all tiers - pub fn select_evictions(&self) -> Vec { - vec![ - self.cold_tier.select_evictions(), - self.warm_tier.select_evictions(), - self.hot_tier.select_evictions(), - ] - } -} - -/// Cache tier -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Tier { - Hot, - Warm, - Cold, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_lru_eviction() { - let config = PolicyConfig { - max_size: 1000, - max_entries: 10, - policy: EvictionPolicy::Lru, - target_utilization: 0.8, - min_ttl: 0, - }; - - let mut policy = CachePolicy::new(config); - - // Add entries - for i in 0..15 { - let id = ContentId::from_bytes([i as u8; 32]); - let info = CacheEntryInfo::new(id, 100); - policy.record_add(info); - } - - assert!(policy.needs_eviction()); - - let result = policy.select_evictions(); - assert!(!result.evicted.is_empty()); - assert!(result.size_freed > 0); - } -} diff --git a/czech-file-knife/src/cfk-cache/src/sled_backend.rs b/czech-file-knife/src/cfk-cache/src/sled_backend.rs deleted file mode 100644 index 8f32bb9ef..000000000 --- a/czech-file-knife/src/cfk-cache/src/sled_backend.rs +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Sled database backend for cache storage - -use sled::Db; -use std::path::Path; - -use crate::{CacheError, CacheResult}; - -/// Sled-based storage backend -pub struct SledBackend { - db: Db, -} - -impl SledBackend { - /// Open or create a sled database at the given path - pub fn open(path: impl AsRef) -> CacheResult { - let db = sled::open(path).map_err(|e| CacheError::Database(e.to_string()))?; - Ok(Self { db }) - } - - /// Get a value by key - pub fn get(&self, key: &[u8]) -> CacheResult>> { - self.db - .get(key) - .map_err(|e| CacheError::Database(e.to_string())) - .map(|opt| opt.map(|v| v.to_vec())) - } - - /// Insert a key-value pair - pub fn insert(&self, key: &[u8], value: &[u8]) -> CacheResult<()> { - self.db - .insert(key, value) - .map_err(|e| CacheError::Database(e.to_string()))?; - Ok(()) - } - - /// Remove a key - pub fn remove(&self, key: &[u8]) -> CacheResult>> { - self.db - .remove(key) - .map_err(|e| CacheError::Database(e.to_string())) - .map(|opt| opt.map(|v| v.to_vec())) - } - - /// Flush to disk - pub fn flush(&self) -> CacheResult<()> { - self.db - .flush() - .map_err(|e| CacheError::Database(e.to_string()))?; - Ok(()) - } - - /// Iterate over all keys with a given prefix - pub fn scan_prefix(&self, prefix: &[u8]) -> impl Iterator, Vec)>> + '_ { - self.db.scan_prefix(prefix).map(|result| { - result - .map(|(k, v)| (k.to_vec(), v.to_vec())) - .map_err(|e| CacheError::Database(e.to_string())) - }) - } - - /// Clear all data - pub fn clear(&self) -> CacheResult<()> { - self.db - .clear() - .map_err(|e| CacheError::Database(e.to_string()))?; - Ok(()) - } - - /// Get database size on disk - pub fn size_on_disk(&self) -> CacheResult { - self.db - .size_on_disk() - .map_err(|e| CacheError::Database(e.to_string())) - } -} diff --git a/czech-file-knife/src/cfk-cli/Cargo.toml b/czech-file-knife/src/cfk-cli/Cargo.toml deleted file mode 100644 index 6cf9e0399..000000000 --- a/czech-file-knife/src/cfk-cli/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "cfk-cli" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Command-line interface for Czech File Knife" - -[[bin]] -name = "cfk" -path = "src/main.rs" - -[dependencies] -cfk-core = { path = "../cfk-core" } -cfk-providers = { path = "../cfk-providers" } - -# CLI -clap.workspace = true -console.workspace = true -indicatif.workspace = true -tabled.workspace = true -bytesize.workspace = true - -# Async -tokio.workspace = true -futures.workspace = true - -# Time -chrono.workspace = true - -# Bytes -bytes.workspace = true - -# Packaging metadata for cargo-deb -[package.metadata.deb] -maintainer = "hyperpolymath " -copyright = "2025, hyperpolymath" -license-file = ["../../LICENSE", "0"] -extended-description = """ -Czech File Knife (cfk) is a universal file management toolkit with cloud -provider integration and virtual filesystem support. It provides a unified -interface for managing files across local storage and cloud providers. -""" -depends = "$auto, fuse3" -section = "utils" -priority = "optional" -assets = [ - ["target/release/cfk", "usr/bin/", "755"], - ["../../README.adoc", "usr/share/doc/czech-file-knife/README.adoc", "644"], -] - -# Packaging metadata for cargo-generate-rpm -[package.metadata.generate-rpm] -assets = [ - { source = "target/release/cfk", dest = "/usr/bin/cfk", mode = "755" }, - { source = "../../README.adoc", dest = "/usr/share/doc/czech-file-knife/README.adoc", mode = "644" }, -] - -[package.metadata.generate-rpm.requires] -fuse3 = "*" diff --git a/czech-file-knife/src/cfk-cli/src/commands.rs b/czech-file-knife/src/cfk-cli/src/commands.rs deleted file mode 100644 index 4835e2744..000000000 --- a/czech-file-knife/src/cfk-cli/src/commands.rs +++ /dev/null @@ -1,482 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! CLI command implementations - -use cfk_core::{ - entry::EntryKind, - operations::{CopyOptions, DeleteOptions, ListOptions, MoveOptions, ReadOptions, WriteOptions}, - CfkError, CfkResult, ReversibleBackend, ReversibleConfig, VirtualPath, -}; -use cfk_providers::{BackendRegistry, LocalBackend}; -use chrono::{DateTime, Utc}; -use console::style; -use futures::StreamExt; -use std::path::PathBuf; -use std::sync::Arc; -use tabled::{Table, Tabled}; - -/// Initialize the backend registry with available backends -fn init_registry() -> BackendRegistry { - let mut registry = BackendRegistry::new(); - - // Register local filesystem with root as base. Unless disabled, every - // mutation goes through the reversible journal so `cfk undo` works. - let local = Arc::new(LocalBackend::new("local", "/")); - match journal() { - Some(Ok(rev)) => registry.register(rev), - Some(Err(e)) => { - eprintln!("warning: undo journal unavailable ({e}); operations are NOT reversible"); - registry.register(local); - } - None => registry.register(local), - } - - // Future: register cloud backends based on config - - registry -} - -/// Journal directory: $CFK_JOURNAL_DIR, else $XDG_STATE_HOME/cfk/journal, -/// else ~/.local/state/cfk/journal. -fn journal_dir() -> Option { - if let Some(d) = std::env::var_os("CFK_JOURNAL_DIR") { - return Some(PathBuf::from(d)); - } - let base = std::env::var_os("XDG_STATE_HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".local/state")))?; - Some(base.join("cfk").join("journal")) -} - -/// Reversible wrapper around the local backend. `None` if disabled via -/// CFK_NO_JOURNAL=1 (e.g. to delete a file bigger than the capture limit). -fn journal() -> Option>>> { - if std::env::var_os("CFK_NO_JOURNAL").map(|v| v == "1").unwrap_or(false) { - return None; - } - let dir = journal_dir()?; - let mut config = ReversibleConfig::default(); - if let Some(n) = std::env::var("CFK_JOURNAL_MAX_BYTES").ok().and_then(|v| v.parse().ok()) { - config.max_capture_bytes = n; - } - Some( - ReversibleBackend::new(Arc::new(LocalBackend::new("local", "/")), dir, config).map(Arc::new), - ) -} - -fn journal_or_err() -> CfkResult>> { - journal().unwrap_or_else(|| Err(CfkError::Unsupported("journal disabled (CFK_NO_JOURNAL=1)".into()))) -} - -/// Show the operation journal -pub async fn history(limit: usize, _verbose: bool) -> CfkResult<()> { - let rev = journal_or_err()?; - let records = rev.history()?; - if records.is_empty() { - println!("(no recorded operations)"); - return Ok(()); - } - let undone: std::collections::HashSet = records - .iter() - .filter_map(|r| match r.op { - cfk_core::Operation::Undo { target } => Some(target), - _ => None, - }) - .collect(); - let skip = records.len().saturating_sub(limit); - for r in records.iter().skip(skip) { - let mark = if undone.contains(&r.id) { style(" (undone)").dim().to_string() } else { String::new() }; - println!( - "#{:<5} {} {}{}", - r.id, - r.timestamp.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M:%S"), - r.op.summary(), - mark - ); - } - Ok(()) -} - -/// Undo the latest (or given) operation -pub async fn undo(id: Option, _verbose: bool) -> CfkResult<()> { - let rev = journal_or_err()?; - let rec = match id { - Some(id) => rev.undo(id).await?, - None => rev.undo_last().await?, - }; - println!("{} #{} {}", style("undone").green(), rec.id, rec.op.summary()); - Ok(()) -} - -/// Parse a path string into a VirtualPath -/// Supports: -/// - cfk://backend/path - explicit URI -/// - /absolute/path - local absolute path -/// - relative/path - local relative path -fn parse_path(path: &str) -> CfkResult { - if let Some(vpath) = VirtualPath::parse_uri(path) { - return Ok(vpath); - } - - // Treat as local path - let path_buf = if path.starts_with('/') { - PathBuf::from(path) - } else { - let cwd = std::env::current_dir().map_err(|e| CfkError::Io(e))?; - cwd.join(path) - }; - - // Canonicalize if exists, otherwise use as-is - let canonical = path_buf - .canonicalize() - .unwrap_or_else(|_| path_buf.clone()); - - Ok(VirtualPath::new("local", canonical.to_string_lossy())) -} - -/// Format a timestamp for display -fn format_time(dt: Option>) -> String { - dt.map(|t| t.format("%Y-%m-%d %H:%M").to_string()) - .unwrap_or_else(|| "-".to_string()) -} - -/// Format file size -fn format_size(size: Option, human: bool) -> String { - match size { - Some(s) if human => bytesize::ByteSize(s).to_string(), - Some(s) => s.to_string(), - None => "-".to_string(), - } -} - -/// Format entry kind -fn format_kind(kind: EntryKind) -> String { - match kind { - EntryKind::Directory => style("d").cyan().to_string(), - EntryKind::File => "-".to_string(), - EntryKind::Symlink => style("l").magenta().to_string(), - EntryKind::Unknown => "?".to_string(), - } -} - -/// Format permissions -fn format_permissions(mode: Option) -> String { - match mode { - Some(m) => { - let r = if m & 0o400 != 0 { 'r' } else { '-' }; - let w = if m & 0o200 != 0 { 'w' } else { '-' }; - let x = if m & 0o100 != 0 { 'x' } else { '-' }; - let gr = if m & 0o040 != 0 { 'r' } else { '-' }; - let gw = if m & 0o020 != 0 { 'w' } else { '-' }; - let gx = if m & 0o010 != 0 { 'x' } else { '-' }; - let or = if m & 0o004 != 0 { 'r' } else { '-' }; - let ow = if m & 0o002 != 0 { 'w' } else { '-' }; - let ox = if m & 0o001 != 0 { 'x' } else { '-' }; - format!("{r}{w}{x}{gr}{gw}{gx}{or}{ow}{ox}") - } - None => "---------".to_string(), - } -} - -#[derive(Tabled)] -struct LsEntry { - #[tabled(rename = "Type")] - kind: String, - #[tabled(rename = "Permissions")] - perms: String, - #[tabled(rename = "Size")] - size: String, - #[tabled(rename = "Modified")] - modified: String, - #[tabled(rename = "Name")] - name: String, -} - -/// List directory contents -pub async fn ls(path: &str, long: bool, all: bool, human: bool, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - let vpath = parse_path(path)?; - - if verbose { - eprintln!("Listing: {}", vpath); - } - - let backend = registry.get_or_err(&vpath.backend)?; - let options = ListOptions { - include_hidden: all, - ..Default::default() - }; - - let listing = backend.list_directory(&vpath, &options).await?; - - if long { - let entries: Vec = listing - .entries - .iter() - .filter(|e| all || !e.name().map(|n| n.starts_with('.')).unwrap_or(false)) - .map(|e| LsEntry { - kind: format_kind(e.kind), - perms: format_permissions(e.metadata.permissions.map(|p| p.mode)), - size: format_size(e.metadata.size, human), - modified: format_time(e.metadata.modified), - name: e.name().unwrap_or("?").to_string(), - }) - .collect(); - - if entries.is_empty() { - println!("(empty directory)"); - } else { - let table = Table::new(entries).to_string(); - println!("{table}"); - } - } else { - let names: Vec<&str> = listing - .entries - .iter() - .filter(|e| all || !e.name().map(|n| n.starts_with('.')).unwrap_or(false)) - .filter_map(|e| e.name()) - .collect(); - - if names.is_empty() { - println!("(empty directory)"); - } else { - for name in names { - println!("{name}"); - } - } - } - - Ok(()) -} - -/// Display file contents -pub async fn cat(path: &str, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - let vpath = parse_path(path)?; - - if verbose { - eprintln!("Reading: {}", vpath); - } - - let backend = registry.get_or_err(&vpath.backend)?; - let options = ReadOptions::default(); - - let mut stream = backend.read_file(&vpath, &options).await?; - while let Some(chunk) = stream.next().await { - let bytes = chunk?; - // Write raw bytes to stdout - use std::io::Write; - std::io::stdout().write_all(&bytes).map_err(CfkError::Io)?; - } - - Ok(()) -} - -/// Copy files -pub async fn cp(source: &str, dest: &str, _recursive: bool, force: bool, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - let src_path = parse_path(source)?; - let dst_path = parse_path(dest)?; - - if verbose { - eprintln!("Copying: {} -> {}", src_path, dst_path); - } - - // Check if source and dest are on the same backend - if src_path.backend == dst_path.backend { - let backend = registry.get_or_err(&src_path.backend)?; - let options = CopyOptions { - overwrite: force, - preserve_metadata: true, - }; - backend.copy(&src_path, &dst_path, &options).await?; - } else { - // Cross-backend copy: read from source, write to dest - let src_backend = registry.get_or_err(&src_path.backend)?; - let dst_backend = registry.get_or_err(&dst_path.backend)?; - - let read_options = ReadOptions::default(); - let stream = src_backend.read_file(&src_path, &read_options).await?; - - let write_options = WriteOptions { - overwrite: force, - create_parents: true, - ..Default::default() - }; - - // Get source metadata for size hint - let src_meta = src_backend.get_metadata(&src_path).await?; - dst_backend - .write_file_stream(&dst_path, stream, src_meta.metadata.size, &write_options) - .await?; - } - - println!("Copied {} -> {}", source, dest); - Ok(()) -} - -/// Move/rename files -pub async fn mv(source: &str, dest: &str, force: bool, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - let src_path = parse_path(source)?; - let dst_path = parse_path(dest)?; - - if verbose { - eprintln!("Moving: {} -> {}", src_path, dst_path); - } - - if src_path.backend == dst_path.backend { - // Same backend: use rename - let backend = registry.get_or_err(&src_path.backend)?; - let options = MoveOptions { overwrite: force }; - backend.rename(&src_path, &dst_path, &options).await?; - } else { - // Cross-backend: copy then delete - cp(source, dest, true, force, verbose).await?; - rm(&[source.to_string()], true, true, verbose).await?; - } - - println!("Moved {} -> {}", source, dest); - Ok(()) -} - -/// Remove files or directories -pub async fn rm(paths: &[String], recursive: bool, force: bool, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - - for path in paths { - let vpath = parse_path(path)?; - - if verbose { - eprintln!("Removing: {}", vpath); - } - - let backend = registry.get_or_err(&vpath.backend)?; - let options = DeleteOptions { recursive, force }; - - backend.delete(&vpath, &options).await?; - println!("Removed {}", path); - } - - Ok(()) -} - -/// Create directories -pub async fn mkdir(paths: &[String], parents: bool, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - - for path in paths { - let vpath = parse_path(path)?; - - if verbose { - eprintln!("Creating directory: {}", vpath); - } - - let backend = registry.get_or_err(&vpath.backend)?; - - if parents { - // Create with parents - create_directory already does this - backend.create_directory(&vpath).await?; - } else { - // Check parent exists first - if let Some(parent) = vpath.parent() { - let parent_meta = backend.get_metadata(&parent).await; - if parent_meta.is_err() { - return Err(CfkError::NotFound(format!( - "Parent directory does not exist: {}", - parent - ))); - } - } - backend.create_directory(&vpath).await?; - } - - println!("Created {}", path); - } - - Ok(()) -} - -/// Show file/directory information -pub async fn stat(path: &str, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - let vpath = parse_path(path)?; - - if verbose { - eprintln!("Getting info: {}", vpath); - } - - let backend = registry.get_or_err(&vpath.backend)?; - let entry = backend.get_metadata(&vpath).await?; - - println!(" Path: {}", entry.path); - println!(" Type: {:?}", entry.kind); - - if let Some(size) = entry.metadata.size { - println!(" Size: {} ({})", size, bytesize::ByteSize(size)); - } - - if let Some(perms) = entry.metadata.permissions { - println!(" Mode: {:o} ({})", perms.mode, format_permissions(Some(perms.mode))); - } - - if let Some(modified) = entry.metadata.modified { - println!(" Modified: {}", modified); - } - - if let Some(created) = entry.metadata.created { - println!(" Created: {}", created); - } - - if let Some(hash) = &entry.metadata.content_hash { - println!(" Hash: {}", hash); - } - - Ok(()) -} - -/// List registered backends -pub async fn backends(_verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - - println!("Registered backends:"); - for id in registry.list() { - if let Some(backend) = registry.get(id) { - let available = if backend.is_available().await { - style("available").green() - } else { - style("unavailable").red() - }; - println!(" {} ({}) - {}", id, backend.display_name(), available); - } - } - - Ok(()) -} - -/// Show storage space information -pub async fn df(backend_id: &str, verbose: bool) -> CfkResult<()> { - let registry = init_registry(); - - if verbose { - eprintln!("Getting space info for: {}", backend_id); - } - - let backend = registry.get_or_err(backend_id)?; - let info = backend.get_space_info().await?; - - println!("Storage: {} ({})", backend_id, backend.display_name()); - - match (info.total, info.used, info.available) { - (Some(total), Some(used), Some(avail)) => { - let pct = (used as f64 / total as f64) * 100.0; - println!(" Total: {}", bytesize::ByteSize(total)); - println!(" Used: {} ({:.1}%)", bytesize::ByteSize(used), pct); - println!(" Available: {}", bytesize::ByteSize(avail)); - } - _ => { - println!(" Space information not available for this backend"); - } - } - - Ok(()) -} diff --git a/czech-file-knife/src/cfk-cli/src/main.rs b/czech-file-knife/src/cfk-cli/src/main.rs deleted file mode 100644 index 7817bd8b2..000000000 --- a/czech-file-knife/src/cfk-cli/src/main.rs +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Czech File Knife CLI -//! -//! A cloud-native, universal file management tool. - -mod commands; - -use clap::{Parser, Subcommand}; -use std::process::ExitCode; - -#[derive(Parser)] -#[command(name = "cfk")] -#[command(author, version, about = "Czech File Knife - Universal file management", long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, - - /// Verbose output - #[arg(short, long, global = true)] - verbose: bool, -} - -#[derive(Subcommand)] -enum Commands { - /// List directory contents - #[command(alias = "dir")] - Ls { - /// Path to list (defaults to current directory) - #[arg(default_value = ".")] - path: String, - - /// Long format with details - #[arg(short, long)] - long: bool, - - /// Show all files including hidden - #[arg(short, long)] - all: bool, - - /// Human-readable sizes - #[arg(short = 'H', long)] - human: bool, - }, - - /// Display file contents - Cat { - /// File to display - path: String, - }, - - /// Copy files or directories - Cp { - /// Source path - source: String, - - /// Destination path - dest: String, - - /// Recursive copy for directories - #[arg(short, long)] - recursive: bool, - - /// Force overwrite existing files - #[arg(short, long)] - force: bool, - }, - - /// Move or rename files - Mv { - /// Source path - source: String, - - /// Destination path - dest: String, - - /// Force overwrite existing files - #[arg(short, long)] - force: bool, - }, - - /// Remove files or directories - Rm { - /// Path(s) to remove - #[arg(required = true)] - paths: Vec, - - /// Recursive removal for directories - #[arg(short, long)] - recursive: bool, - - /// Force removal without confirmation - #[arg(short, long)] - force: bool, - }, - - /// Create directories - Mkdir { - /// Directory path(s) to create - #[arg(required = true)] - paths: Vec, - - /// Create parent directories as needed - #[arg(short, long)] - parents: bool, - }, - - /// Show file or directory information - Stat { - /// Path to inspect - path: String, - }, - - /// Show the reversible-operation journal (JanusKey model) - History { - /// Show at most N most-recent records - #[arg(short = 'n', long, default_value_t = 20)] - limit: usize, - }, - - /// Undo the most recent operation (or a specific id, which must be the latest) - Undo { - /// Operation id from `cfk history` - id: Option, - }, - - /// List registered backends - Backends, - - /// Show storage space information - Df { - /// Backend to query (defaults to local) - #[arg(default_value = "local")] - backend: String, - }, -} - -#[tokio::main] -async fn main() -> ExitCode { - let cli = Cli::parse(); - - let result = match cli.command { - Commands::Ls { path, long, all, human } => { - commands::ls(&path, long, all, human, cli.verbose).await - } - Commands::Cat { path } => { - commands::cat(&path, cli.verbose).await - } - Commands::Cp { source, dest, recursive, force } => { - commands::cp(&source, &dest, recursive, force, cli.verbose).await - } - Commands::Mv { source, dest, force } => { - commands::mv(&source, &dest, force, cli.verbose).await - } - Commands::Rm { paths, recursive, force } => { - commands::rm(&paths, recursive, force, cli.verbose).await - } - Commands::Mkdir { paths, parents } => { - commands::mkdir(&paths, parents, cli.verbose).await - } - Commands::Stat { path } => { - commands::stat(&path, cli.verbose).await - } - Commands::History { limit } => { - commands::history(limit, cli.verbose).await - } - Commands::Undo { id } => { - commands::undo(id, cli.verbose).await - } - Commands::Backends => { - commands::backends(cli.verbose).await - } - Commands::Df { backend } => { - commands::df(&backend, cli.verbose).await - } - }; - - match result { - Ok(()) => ExitCode::SUCCESS, - Err(e) => { - eprintln!("Error: {e}"); - ExitCode::FAILURE - } - } -} diff --git a/czech-file-knife/src/cfk-core/Cargo.toml b/czech-file-knife/src/cfk-core/Cargo.toml deleted file mode 100644 index d5151b76a..000000000 --- a/czech-file-knife/src/cfk-core/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "cfk-core" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Core traits and types for Czech File Knife" - -[dependencies] -async-trait.workspace = true -bytes.workspace = true -chrono.workspace = true -futures.workspace = true -serde.workspace = true -thiserror.workspace = true -tokio = { workspace = true, features = ["sync"] } -serde_json.workspace = true -hex.workspace = true -sha2 = "0.10" - -[dev-dependencies] -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/czech-file-knife/src/cfk-core/src/backend.rs b/czech-file-knife/src/cfk-core/src/backend.rs deleted file mode 100644 index dc538f382..000000000 --- a/czech-file-knife/src/cfk-core/src/backend.rs +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Storage backend trait - -use async_trait::async_trait; -use bytes::Bytes; -use std::pin::Pin; -use futures::Stream; - -use crate::{ - entry::{DirectoryListing, Entry}, - error::CfkResult, - operations::*, - VirtualPath, -}; - -/// Byte stream type -pub type ByteStream = Pin> + Send>>; - -/// Storage backend capabilities -#[derive(Debug, Clone, Default)] -pub struct StorageCapabilities { - pub read: bool, - pub write: bool, - pub delete: bool, - pub rename: bool, - pub copy: bool, - pub list: bool, - pub search: bool, - pub versioning: bool, - pub sharing: bool, - pub offline: bool, - pub streaming: bool, - pub resumable_uploads: bool, - pub content_hashing: bool, -} - -impl StorageCapabilities { - pub fn full() -> Self { - Self { - read: true, write: true, delete: true, rename: true, - copy: true, list: true, search: true, versioning: true, - sharing: true, offline: true, streaming: true, - resumable_uploads: true, content_hashing: true, - } - } - - pub fn read_only() -> Self { - Self { read: true, list: true, ..Default::default() } - } - - pub fn local_filesystem() -> Self { - Self { - read: true, write: true, delete: true, rename: true, - copy: true, list: true, search: true, offline: true, - streaming: true, content_hashing: true, - ..Default::default() - } - } -} - -/// Space information -#[derive(Debug, Clone, Default)] -pub struct SpaceInfo { - pub total: Option, - pub used: Option, - pub available: Option, -} - -impl SpaceInfo { - pub fn unknown() -> Self { - Self::default() - } -} - -/// File version information -#[derive(Debug, Clone)] -pub struct FileVersion { - pub id: String, - pub modified: chrono::DateTime, - pub size: Option, - pub author: Option, -} - -/// Search options -#[derive(Debug, Clone, Default)] -pub struct SearchOptions { - pub query: String, - pub path: Option, - pub recursive: bool, - pub limit: Option, -} - -/// Storage backend trait -#[async_trait] -pub trait StorageBackend: Send + Sync { - fn id(&self) -> &str; - fn display_name(&self) -> &str; - fn capabilities(&self) -> &StorageCapabilities; - - async fn is_available(&self) -> bool; - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult; - async fn list_directory(&self, path: &VirtualPath, options: &ListOptions) -> CfkResult; - async fn read_file(&self, path: &VirtualPath, options: &ReadOptions) -> CfkResult; - async fn write_file(&self, path: &VirtualPath, data: Bytes, options: &WriteOptions) -> CfkResult; - async fn write_file_stream(&self, path: &VirtualPath, stream: ByteStream, size_hint: Option, options: &WriteOptions) -> CfkResult; - async fn create_directory(&self, path: &VirtualPath) -> CfkResult; - async fn delete(&self, path: &VirtualPath, options: &DeleteOptions) -> CfkResult<()>; - async fn copy(&self, source: &VirtualPath, dest: &VirtualPath, options: &CopyOptions) -> CfkResult; - async fn rename(&self, source: &VirtualPath, dest: &VirtualPath, options: &MoveOptions) -> CfkResult; - async fn get_space_info(&self) -> CfkResult; - - // Optional methods with defaults - async fn search(&self, _options: &SearchOptions) -> CfkResult> { - Err(crate::CfkError::Unsupported("Search not supported".into())) - } - - async fn get_versions(&self, _path: &VirtualPath) -> CfkResult> { - Err(crate::CfkError::Unsupported("Versioning not supported".into())) - } - - async fn get_version(&self, _path: &VirtualPath, _version_id: &str) -> CfkResult { - Err(crate::CfkError::Unsupported("Versioning not supported".into())) - } -} diff --git a/czech-file-knife/src/cfk-core/src/entry.rs b/czech-file-knife/src/cfk-core/src/entry.rs deleted file mode 100644 index a8a1ff470..000000000 --- a/czech-file-knife/src/cfk-core/src/entry.rs +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File system entries - -use crate::{Metadata, VirtualPath}; -use serde::{Deserialize, Serialize}; - -/// Entry kind -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum EntryKind { - File, - Directory, - Symlink, - Unknown, -} - -/// A file system entry -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Entry { - pub path: VirtualPath, - pub kind: EntryKind, - pub metadata: Metadata, -} - -impl Entry { - pub fn file(path: VirtualPath, metadata: Metadata) -> Self { - Self { path, kind: EntryKind::File, metadata } - } - - pub fn directory(path: VirtualPath, metadata: Metadata) -> Self { - Self { path, kind: EntryKind::Directory, metadata } - } - - pub fn is_file(&self) -> bool { - self.kind == EntryKind::File - } - - pub fn is_directory(&self) -> bool { - self.kind == EntryKind::Directory - } - - pub fn name(&self) -> Option<&str> { - self.path.name() - } - - pub fn size(&self) -> Option { - self.metadata.size - } -} - -/// Directory listing result -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DirectoryListing { - pub path: VirtualPath, - pub entries: Vec, - pub cursor: Option, - pub has_more: bool, -} - -impl DirectoryListing { - pub fn new(path: VirtualPath, entries: Vec) -> Self { - Self { path, entries, cursor: None, has_more: false } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn make_path(p: &str) -> VirtualPath { - VirtualPath::new("local", p) - } - - #[test] - fn test_entry_file() { - let entry = Entry::file(make_path("/home/user/file.txt"), Metadata::new()); - assert!(entry.is_file()); - assert!(!entry.is_directory()); - assert_eq!(entry.kind, EntryKind::File); - } - - #[test] - fn test_entry_directory() { - let entry = Entry::directory(make_path("/home/user"), Metadata::new()); - assert!(entry.is_directory()); - assert!(!entry.is_file()); - assert_eq!(entry.kind, EntryKind::Directory); - } - - #[test] - fn test_entry_name() { - let entry = Entry::file(make_path("/home/user/document.pdf"), Metadata::new()); - assert_eq!(entry.name(), Some("document.pdf")); - } - - #[test] - fn test_entry_size() { - let mut meta = Metadata::new(); - meta.size = Some(1024); - let entry = Entry::file(make_path("/file.txt"), meta); - assert_eq!(entry.size(), Some(1024)); - } - - #[test] - fn test_directory_listing() { - let root = make_path("/home"); - let entries = vec![ - Entry::directory(make_path("/home/user1"), Metadata::new()), - Entry::directory(make_path("/home/user2"), Metadata::new()), - ]; - let listing = DirectoryListing::new(root.clone(), entries); - - assert_eq!(listing.path, root); - assert_eq!(listing.entries.len(), 2); - assert!(!listing.has_more); - assert!(listing.cursor.is_none()); - } -} diff --git a/czech-file-knife/src/cfk-core/src/error.rs b/czech-file-knife/src/cfk-core/src/error.rs deleted file mode 100644 index 3e60f1da0..000000000 --- a/czech-file-knife/src/cfk-core/src/error.rs +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Error types for Czech File Knife - -use thiserror::Error; - -/// Result type alias -pub type CfkResult = Result; - -/// Main error type -#[derive(Error, Debug)] -pub enum CfkError { - #[error("Path not found: {0}")] - NotFound(String), - - #[error("Already exists: {0}")] - AlreadyExists(String), - - #[error("Permission denied: {0}")] - PermissionDenied(String), - - #[error("Not a directory: {0}")] - NotADirectory(String), - - #[error("Not a file: {0}")] - NotAFile(String), - - #[error("Directory not empty: {0}")] - DirectoryNotEmpty(String), - - #[error("Invalid path: {0}")] - InvalidPath(String), - - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - #[error("Network error: {0}")] - Network(String), - - #[error("Authentication required: {0}")] - AuthRequired(String), - - #[error("Authentication failed: {0}")] - AuthFailed(String), - - #[error("Token expired")] - TokenExpired, - - #[error("Rate limited: retry after {retry_after_secs:?}s")] - RateLimited { retry_after_secs: Option }, - - #[error("Provider API error ({provider}): {message}")] - ProviderApi { provider: String, message: String }, - - #[error("Quota exceeded: {0}")] - QuotaExceeded(String), - - #[error("Conflict: {0}")] - Conflict(String), - - #[error("Unsupported operation: {0}")] - Unsupported(String), - - #[error("Serialization error: {0}")] - Serialization(String), - - #[error("Cache error: {0}")] - Cache(String), - - #[error("Backend not found: {0}")] - BackendNotFound(String), - - #[error("Offline and no cached version")] - OfflineNoCache, - - #[error("Checksum mismatch")] - ChecksumMismatch, - - #[error("Timeout")] - Timeout, - - #[error("Cancelled")] - Cancelled, - - #[error("{0}")] - Other(String), -} - -impl CfkError { - pub fn is_retryable(&self) -> bool { - matches!( - self, - CfkError::Network(_) - | CfkError::RateLimited { .. } - | CfkError::Timeout - | CfkError::TokenExpired - ) - } - - pub fn is_auth_error(&self) -> bool { - matches!( - self, - CfkError::AuthRequired(_) | CfkError::AuthFailed(_) | CfkError::TokenExpired - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_retryable() { - assert!(CfkError::Network("connection reset".into()).is_retryable()); - assert!(CfkError::RateLimited { retry_after_secs: Some(30) }.is_retryable()); - assert!(CfkError::Timeout.is_retryable()); - assert!(CfkError::TokenExpired.is_retryable()); - - assert!(!CfkError::NotFound("file.txt".into()).is_retryable()); - assert!(!CfkError::PermissionDenied("/root".into()).is_retryable()); - assert!(!CfkError::Cancelled.is_retryable()); - } - - #[test] - fn test_is_auth_error() { - assert!(CfkError::AuthRequired("login needed".into()).is_auth_error()); - assert!(CfkError::AuthFailed("bad password".into()).is_auth_error()); - assert!(CfkError::TokenExpired.is_auth_error()); - - assert!(!CfkError::Network("timeout".into()).is_auth_error()); - assert!(!CfkError::NotFound("file.txt".into()).is_auth_error()); - } - - #[test] - fn test_error_display() { - let err = CfkError::NotFound("/path/to/file".into()); - assert_eq!(format!("{}", err), "Path not found: /path/to/file"); - - let err = CfkError::RateLimited { retry_after_secs: Some(60) }; - assert!(format!("{}", err).contains("60")); - } - - #[test] - fn test_from_io_error() { - let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); - let cfk_err: CfkError = io_err.into(); - assert!(matches!(cfk_err, CfkError::Io(_))); - } -} diff --git a/czech-file-knife/src/cfk-core/src/lib.rs b/czech-file-knife/src/cfk-core/src/lib.rs deleted file mode 100644 index cf17d5870..000000000 --- a/czech-file-knife/src/cfk-core/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Czech File Knife Core -//! -//! Core traits, types, and abstractions for the unified filesystem interface. - -pub mod backend; -pub mod entry; -pub mod error; -pub mod metadata; -pub mod operations; -pub mod path; -pub mod platform; -pub mod reversible; - -pub use backend::{StorageBackend, StorageCapabilities}; -pub use entry::{Entry, EntryKind}; -pub use error::{CfkError, CfkResult}; -pub use metadata::Metadata; -pub use path::VirtualPath; -pub use reversible::{ContentStore, OpLog, Operation, ReversibleBackend, ReversibleConfig}; diff --git a/czech-file-knife/src/cfk-core/src/metadata.rs b/czech-file-knife/src/cfk-core/src/metadata.rs deleted file mode 100644 index 8a7c89e0b..000000000 --- a/czech-file-knife/src/cfk-core/src/metadata.rs +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File and directory metadata - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// File/directory metadata -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Metadata { - pub size: Option, - pub created: Option>, - pub modified: Option>, - pub accessed: Option>, - pub permissions: Option, - pub content_hash: Option, - pub mime_type: Option, - pub provider_id: Option, - pub revision: Option, - pub custom: HashMap, -} - -/// Unix-style permissions -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Permissions { - pub mode: u32, -} - -impl Permissions { - pub fn new(mode: u32) -> Self { - Self { mode } - } - - pub fn is_readable(&self) -> bool { - self.mode & 0o444 != 0 - } - - pub fn is_writable(&self) -> bool { - self.mode & 0o222 != 0 - } - - pub fn is_executable(&self) -> bool { - self.mode & 0o111 != 0 - } -} - -impl Metadata { - pub fn new() -> Self { - Self::default() - } - - pub fn with_size(mut self, size: u64) -> Self { - self.size = Some(size); - self - } - - pub fn with_modified(mut self, modified: DateTime) -> Self { - self.modified = Some(modified); - self - } -} diff --git a/czech-file-knife/src/cfk-core/src/operations.rs b/czech-file-knife/src/cfk-core/src/operations.rs deleted file mode 100644 index 58151e5c9..000000000 --- a/czech-file-knife/src/cfk-core/src/operations.rs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Operation options - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct ListOptions { - pub recursive: bool, - pub include_hidden: bool, - pub limit: Option, - pub cursor: Option, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct ReadOptions { - pub range: Option<(u64, u64)>, - pub use_cache: bool, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct WriteOptions { - pub overwrite: bool, - pub create_parents: bool, - pub content_hash: Option, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct CopyOptions { - pub overwrite: bool, - pub preserve_metadata: bool, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct MoveOptions { - pub overwrite: bool, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DeleteOptions { - pub recursive: bool, - pub force: bool, -} diff --git a/czech-file-knife/src/cfk-core/src/path.rs b/czech-file-knife/src/cfk-core/src/path.rs deleted file mode 100644 index 64164821d..000000000 --- a/czech-file-knife/src/cfk-core/src/path.rs +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Virtual path abstraction - -use serde::{Deserialize, Serialize}; -use std::fmt; - -/// Virtual path representing a location across any backend -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct VirtualPath { - /// Backend identifier (e.g., "local", "dropbox", "gdrive") - pub backend: String, - /// Path segments - pub segments: Vec, -} - -impl VirtualPath { - pub fn new(backend: impl Into, path: impl AsRef) -> Self { - let path = path.as_ref(); - let segments = path - .split('/') - .filter(|s| !s.is_empty()) - .map(String::from) - .collect(); - Self { - backend: backend.into(), - segments, - } - } - - pub fn root(backend: impl Into) -> Self { - Self { - backend: backend.into(), - segments: Vec::new(), - } - } - - pub fn join(&self, name: impl AsRef) -> Self { - let mut segments = self.segments.clone(); - for part in name.as_ref().split('/').filter(|s| !s.is_empty()) { - if part == ".." { - segments.pop(); - } else if part != "." { - segments.push(part.to_string()); - } - } - Self { - backend: self.backend.clone(), - segments, - } - } - - pub fn parent(&self) -> Option { - if self.segments.is_empty() { - None - } else { - let mut segments = self.segments.clone(); - segments.pop(); - Some(Self { - backend: self.backend.clone(), - segments, - }) - } - } - - pub fn name(&self) -> Option<&str> { - self.segments.last().map(|s| s.as_str()) - } - - pub fn extension(&self) -> Option<&str> { - self.name().and_then(|n| n.rsplit_once('.')).map(|(_, ext)| ext) - } - - pub fn is_root(&self) -> bool { - self.segments.is_empty() - } - - pub fn to_path_string(&self) -> String { - if self.segments.is_empty() { - "/".to_string() - } else { - format!("/{}", self.segments.join("/")) - } - } - - pub fn to_uri(&self) -> String { - format!("cfk://{}{}", self.backend, self.to_path_string()) - } - - pub fn parse_uri(uri: &str) -> Option { - let uri = uri.strip_prefix("cfk://")?; - let (backend, path) = uri.split_once('/').unwrap_or((uri, "")); - Some(Self::new(backend, path)) - } -} - -impl fmt::Display for VirtualPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_uri()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new() { - let path = VirtualPath::new("local", "/home/user/docs"); - assert_eq!(path.backend, "local"); - assert_eq!(path.segments, vec!["home", "user", "docs"]); - } - - #[test] - fn test_new_handles_empty_segments() { - let path = VirtualPath::new("local", "//home//user//"); - assert_eq!(path.segments, vec!["home", "user"]); - } - - #[test] - fn test_root() { - let path = VirtualPath::root("dropbox"); - assert_eq!(path.backend, "dropbox"); - assert!(path.segments.is_empty()); - assert!(path.is_root()); - } - - #[test] - fn test_join() { - let root = VirtualPath::root("local"); - let path = root.join("home").join("user"); - assert_eq!(path.segments, vec!["home", "user"]); - } - - #[test] - fn test_join_with_dotdot() { - let path = VirtualPath::new("local", "/home/user/docs"); - let new_path = path.join("../pictures"); - assert_eq!(new_path.segments, vec!["home", "user", "pictures"]); - } - - #[test] - fn test_join_with_dot() { - let path = VirtualPath::new("local", "/home/user"); - let new_path = path.join("./docs"); - assert_eq!(new_path.segments, vec!["home", "user", "docs"]); - } - - #[test] - fn test_parent() { - let path = VirtualPath::new("local", "/home/user/docs"); - let parent = path.parent().unwrap(); - assert_eq!(parent.segments, vec!["home", "user"]); - } - - #[test] - fn test_parent_of_root() { - let root = VirtualPath::root("local"); - assert!(root.parent().is_none()); - } - - #[test] - fn test_name() { - let path = VirtualPath::new("local", "/home/user/file.txt"); - assert_eq!(path.name(), Some("file.txt")); - } - - #[test] - fn test_name_of_root() { - let root = VirtualPath::root("local"); - assert!(root.name().is_none()); - } - - #[test] - fn test_extension() { - let path = VirtualPath::new("local", "/home/user/file.txt"); - assert_eq!(path.extension(), Some("txt")); - - let path_no_ext = VirtualPath::new("local", "/home/user/file"); - assert!(path_no_ext.extension().is_none()); - - let path_multi = VirtualPath::new("local", "/archive.tar.gz"); - assert_eq!(path_multi.extension(), Some("gz")); - } - - #[test] - fn test_to_path_string() { - let root = VirtualPath::root("local"); - assert_eq!(root.to_path_string(), "/"); - - let path = VirtualPath::new("local", "/home/user"); - assert_eq!(path.to_path_string(), "/home/user"); - } - - #[test] - fn test_to_uri() { - let path = VirtualPath::new("dropbox", "/Documents/file.txt"); - assert_eq!(path.to_uri(), "cfk://dropbox/Documents/file.txt"); - - let root = VirtualPath::root("gdrive"); - assert_eq!(root.to_uri(), "cfk://gdrive/"); - } - - #[test] - fn test_parse_uri() { - let path = VirtualPath::parse_uri("cfk://local/home/user").unwrap(); - assert_eq!(path.backend, "local"); - assert_eq!(path.segments, vec!["home", "user"]); - } - - #[test] - fn test_parse_uri_root() { - let path = VirtualPath::parse_uri("cfk://dropbox").unwrap(); - assert_eq!(path.backend, "dropbox"); - assert!(path.segments.is_empty()); - } - - #[test] - fn test_parse_uri_invalid() { - assert!(VirtualPath::parse_uri("http://example.com").is_none()); - assert!(VirtualPath::parse_uri("/local/path").is_none()); - } - - #[test] - fn test_display() { - let path = VirtualPath::new("s3", "/bucket/key"); - assert_eq!(format!("{}", path), "cfk://s3/bucket/key"); - } - - #[test] - fn test_equality() { - let path1 = VirtualPath::new("local", "/home/user"); - let path2 = VirtualPath::new("local", "home/user"); - assert_eq!(path1, path2); - } -} diff --git a/czech-file-knife/src/cfk-core/src/platform.rs b/czech-file-knife/src/cfk-core/src/platform.rs deleted file mode 100644 index b1bebac53..000000000 --- a/czech-file-knife/src/cfk-core/src/platform.rs +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Platform-specific abstractions -//! -//! Supports: Linux, macOS, Windows, iOS, Android, Minix, z/OS, RISC-V - - -/// Platform capabilities -#[derive(Debug, Clone)] -pub struct PlatformCapabilities { - pub fuse_available: bool, - pub async_io: bool, - pub file_watching: bool, - pub symlinks: bool, - pub hard_links: bool, - pub extended_attributes: bool, - pub sparse_files: bool, - pub memory_mapping: bool, -} - -impl PlatformCapabilities { - pub fn detect() -> Self { - #[cfg(target_os = "linux")] - return Self::linux(); - - #[cfg(target_os = "macos")] - return Self::macos(); - - #[cfg(target_os = "windows")] - return Self::windows(); - - #[cfg(target_os = "ios")] - return Self::ios(); - - #[cfg(target_os = "android")] - return Self::android(); - - #[cfg(not(any( - target_os = "linux", - target_os = "macos", - target_os = "windows", - target_os = "ios", - target_os = "android" - )))] - return Self::minimal(); - } - - pub fn linux() -> Self { - Self { - fuse_available: true, - async_io: true, - file_watching: true, - symlinks: true, - hard_links: true, - extended_attributes: true, - sparse_files: true, - memory_mapping: true, - } - } - - pub fn macos() -> Self { - Self { - fuse_available: true, // via macFUSE - async_io: true, - file_watching: true, - symlinks: true, - hard_links: true, - extended_attributes: true, - sparse_files: true, - memory_mapping: true, - } - } - - pub fn windows() -> Self { - Self { - fuse_available: true, // via WinFsp - async_io: true, - file_watching: true, - symlinks: true, // requires admin or dev mode - hard_links: true, - extended_attributes: false, // different model (ADS) - sparse_files: true, - memory_mapping: true, - } - } - - pub fn ios() -> Self { - Self { - fuse_available: false, - async_io: true, - file_watching: false, - symlinks: false, - hard_links: false, - extended_attributes: false, - sparse_files: false, - memory_mapping: true, - } - } - - pub fn android() -> Self { - Self { - fuse_available: false, // root only - async_io: true, - file_watching: true, - symlinks: true, - hard_links: false, - extended_attributes: false, - sparse_files: false, - memory_mapping: true, - } - } - - pub fn minimal() -> Self { - Self { - fuse_available: false, - async_io: false, - file_watching: false, - symlinks: false, - hard_links: false, - extended_attributes: false, - sparse_files: false, - memory_mapping: false, - } - } -} - -/// z/OS dataset path conversion -pub mod zos { - /// Convert VirtualPath to z/OS dataset name - /// cfk://zos/SYS1/PARMLIB/IEASYS00 → SYS1.PARMLIB(IEASYS00) - pub fn to_dataset_name(segments: &[String]) -> String { - if segments.is_empty() { - return String::new(); - } - - let mut parts = segments.to_vec(); - if let Some(member) = parts.pop() { - if parts.is_empty() { - member - } else { - format!("{}({})", parts.join("."), member) - } - } else { - String::new() - } - } - - /// Convert z/OS dataset name to path segments - pub fn from_dataset_name(dsn: &str) -> Vec { - if let Some((prefix, member)) = dsn.rsplit_once('(') { - let member = member.trim_end_matches(')'); - let mut segments: Vec = prefix.split('.').map(String::from).collect(); - segments.push(member.to_string()); - segments - } else { - dsn.split('.').map(String::from).collect() - } - } -} - -/// EBCDIC/ASCII transcoding for z/OS -pub mod encoding { - /// Simple EBCDIC to ASCII (US EBCDIC code page 037) - pub fn ebcdic_to_ascii(input: &[u8]) -> Vec { - input.iter().map(|&b| EBCDIC_TO_ASCII[b as usize]).collect() - } - - /// Simple ASCII to EBCDIC - pub fn ascii_to_ebcdic(input: &[u8]) -> Vec { - input.iter().map(|&b| ASCII_TO_EBCDIC[b as usize]).collect() - } - - // EBCDIC code page 037 to ASCII mapping (simplified) - static EBCDIC_TO_ASCII: [u8; 256] = [ - 0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F, // 0x00-0x07 - 0x97, 0x8D, 0x8E, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 0x08-0x0F - 0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87, // 0x10-0x17 - 0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F, // 0x18-0x1F - 0x80, 0x81, 0x82, 0x83, 0x84, 0x0A, 0x17, 0x1B, // 0x20-0x27 - 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07, // 0x28-0x2F - 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, // 0x30-0x37 - 0x98, 0x99, 0x9A, 0x9B, 0x14, 0x15, 0x9E, 0x1A, // 0x38-0x3F - 0x20, 0xA0, 0xE2, 0xE4, 0xE0, 0xE1, 0xE3, 0xE5, // 0x40-0x47 - 0xE7, 0xF1, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, // 0x48-0x4F - 0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF, // 0x50-0x57 - 0xEC, 0xDF, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAC, // 0x58-0x5F - 0x2D, 0x2F, 0xC2, 0xC4, 0xC0, 0xC1, 0xC3, 0xC5, // 0x60-0x67 - 0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, // 0x68-0x6F - 0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF, // 0x70-0x77 - 0xCC, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, // 0x78-0x7F - // ... continuing would fill all 256 bytes - 0xD8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, - 0x68, 0x69, 0xAB, 0xBB, 0xF0, 0xFD, 0xFE, 0xB1, - 0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, - 0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4, - 0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x79, 0x7A, 0xA1, 0xBF, 0xD0, 0xDD, 0xDE, 0xAE, - 0x5E, 0xA3, 0xA5, 0xB7, 0xA9, 0xA7, 0xB6, 0xBC, - 0xBD, 0xBE, 0x5B, 0x5D, 0xAF, 0xA8, 0xB4, 0xD7, - 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0xAD, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5, - 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, - 0x51, 0x52, 0xB9, 0xFB, 0xFC, 0xF9, 0xFA, 0xFF, - 0x5C, 0xF7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x9F, - ]; - - // ASCII to EBCDIC (inverse mapping) - static ASCII_TO_EBCDIC: [u8; 256] = { - let mut table = [0x3Fu8; 256]; // Default to '?' - let mut i = 0; - while i < 256 { - let ascii_val = EBCDIC_TO_ASCII[i]; - table[ascii_val as usize] = i as u8; - i += 1; - } - table - }; -} diff --git a/czech-file-knife/src/cfk-core/src/reversible.rs b/czech-file-knife/src/cfk-core/src/reversible.rs deleted file mode 100644 index 5dcd6a0ef..000000000 --- a/czech-file-knife/src/cfk-core/src/reversible.rs +++ /dev/null @@ -1,740 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Reversible operations (JanusKey model). -//! -//! [`ReversibleBackend`] wraps any [`StorageBackend`] and, before every -//! destructive call, captures enough state to invert it: -//! -//! * prior content goes into a content-addressed [`ContentStore`] (SHA-256, -//! `objects/ab/cdef…`), the same scheme JanusKey uses; -//! * the inverse is recorded in an append-only JSON-lines [`OpLog`]. -//! -//! Undo never rewrites the log: it appends an `Undo { target }` record, so -//! the full history (including undos) stays auditable. -//! -//! Because the wrapper works at the `StorageBackend` level it gives rollback -//! to *every* backend — including ones with no native versioning — and the -//! captured objects also back `get_versions` / `get_version`. -//! -//! Limitations (honest residue, mirroring JanusKey's own caveat): -//! * content is buffered in memory when captured; `max_capture_bytes` -//! guards against capturing huge files (the op is refused, not silently -//! made irreversible, unless `allow_irreversible` is set); -//! * metadata (permissions, mtimes, xattrs) is not yet restored; -//! * the reversibility guarantee is not yet mechanically proven. - -use async_trait::async_trait; -use bytes::{Bytes, BytesMut}; -use futures::StreamExt; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::fs::{self, OpenOptions}; -use std::io::{BufRead, BufReader, Write}; -use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; - -use crate::{ - backend::{ByteStream, FileVersion, SearchOptions, SpaceInfo, StorageBackend, StorageCapabilities}, - entry::{DirectoryListing, Entry, EntryKind}, - error::{CfkError, CfkResult}, - operations::*, - VirtualPath, -}; - -// ───────────────────────────── content store ────────────────────────────── - -/// Content-addressed object store keyed by SHA-256 hex digest. -#[derive(Debug, Clone)] -pub struct ContentStore { - root: PathBuf, -} - -impl ContentStore { - pub fn open(root: impl Into) -> CfkResult { - let root = root.into(); - fs::create_dir_all(&root)?; - Ok(Self { root }) - } - - pub fn hash(data: &[u8]) -> String { - hex::encode(Sha256::digest(data)) - } - - fn object_path(&self, hash: &str) -> CfkResult { - if hash.len() != 64 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) { - return Err(CfkError::Other(format!("invalid object hash: {hash}"))); - } - Ok(self.root.join(&hash[..2]).join(&hash[2..])) - } - - /// Store `data`, returning its hash. Idempotent (deduplicating). - pub fn put(&self, data: &[u8]) -> CfkResult { - let hash = Self::hash(data); - let path = self.object_path(&hash)?; - if !path.exists() { - fs::create_dir_all(path.parent().expect("object has parent"))?; - // write-then-rename so a crash never leaves a truncated object - let tmp = path.with_extension("tmp"); - { - let mut f = fs::File::create(&tmp)?; - f.write_all(data)?; - f.sync_all()?; - } - fs::rename(&tmp, &path)?; - } - Ok(hash) - } - - /// Fetch an object, verifying its hash. - pub fn get(&self, hash: &str) -> CfkResult { - let data = fs::read(self.object_path(hash)?)?; - if Self::hash(&data) != hash { - return Err(CfkError::ChecksumMismatch); - } - Ok(Bytes::from(data)) - } - - pub fn contains(&self, hash: &str) -> bool { - self.object_path(hash).map(|p| p.exists()).unwrap_or(false) - } -} - -// ─────────────────────────────── op log ─────────────────────────────────── - -/// One node of a captured directory tree (for recursive deletes). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TreeNode { - pub path: VirtualPath, - /// `None` = directory, `Some(hash)` = file content. - pub content: Option, -} - -/// A recorded operation together with the data needed to invert it. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "op", rename_all = "snake_case")] -pub enum Operation { - /// File written; `prior` is the old content (None = did not exist). - Write { path: VirtualPath, prior: Option }, - /// Directory created (undo removes it if still empty). - CreateDir { path: VirtualPath }, - /// Path deleted; tree is parent-first. - Delete { path: VirtualPath, tree: Vec }, - /// Copy to `dest`; `prior` is what `dest` held before. - Copy { source: VirtualPath, dest: VirtualPath, prior: Option }, - /// Rename; `overwritten` is what `dest` held before. - Rename { source: VirtualPath, dest: VirtualPath, overwritten: Option }, - /// Undo of an earlier record. - Undo { target: u64 }, -} - -impl Operation { - pub fn summary(&self) -> String { - match self { - Operation::Write { path, prior } => format!( - "{} {}", if prior.is_some() { "modify" } else { "create" }, path), - Operation::CreateDir { path } => format!("mkdir {path}"), - Operation::Delete { path, tree } => format!("delete {path} ({} item(s))", tree.len()), - Operation::Copy { source, dest, .. } => format!("copy {source} -> {dest}"), - Operation::Rename { source, dest, .. } => format!("move {source} -> {dest}"), - Operation::Undo { target } => format!("undo #{target}"), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct LogRecord { - pub id: u64, - pub timestamp: chrono::DateTime, - #[serde(flatten)] - pub op: Operation, -} - -/// Append-only JSON-lines operation log. -#[derive(Debug)] -pub struct OpLog { - path: PathBuf, - next_id: u64, -} - -impl OpLog { - pub fn open(path: impl Into) -> CfkResult { - let path = path.into(); - if let Some(p) = path.parent() { - fs::create_dir_all(p)?; - } - let next_id = Self::read_all(&path)?.last().map(|r| r.id + 1).unwrap_or(1); - Ok(Self { path, next_id }) - } - - fn read_all(path: &Path) -> CfkResult> { - if !path.exists() { - return Ok(Vec::new()); - } - let mut out = Vec::new(); - for (n, line) in BufReader::new(fs::File::open(path)?).lines().enumerate() { - let line = line?; - if line.trim().is_empty() { - continue; - } - match serde_json::from_str::(&line) { - Ok(r) => out.push(r), - // A torn final line (crash mid-append) is tolerated; anything - // else is corruption and must be surfaced. - Err(e) => { - return Err(CfkError::Serialization(format!("oplog line {}: {e}", n + 1))) - } - } - } - Ok(out) - } - - pub fn records(&self) -> CfkResult> { - Self::read_all(&self.path) - } - - pub fn append(&mut self, op: Operation) -> CfkResult { - let rec = LogRecord { id: self.next_id, timestamp: chrono::Utc::now(), op }; - let line = serde_json::to_string(&rec).map_err(|e| CfkError::Serialization(e.to_string()))?; - let mut f = OpenOptions::new().create(true).append(true).open(&self.path)?; - writeln!(f, "{line}")?; - f.sync_data()?; - self.next_id += 1; - Ok(rec) - } - - /// Records that are not undos and have not themselves been undone. - pub fn undoable(&self) -> CfkResult> { - let all = self.records()?; - let undone: std::collections::HashSet = all - .iter() - .filter_map(|r| match r.op { Operation::Undo { target } => Some(target), _ => None }) - .collect(); - Ok(all - .into_iter() - .filter(|r| !matches!(r.op, Operation::Undo { .. }) && !undone.contains(&r.id)) - .collect()) - } -} - -// ───────────────────────────── the wrapper ──────────────────────────────── - -#[derive(Debug, Clone)] -pub struct ReversibleConfig { - /// Refuse to capture files bigger than this (bytes). - pub max_capture_bytes: u64, - /// If true, operations too big to capture proceed unrecorded instead of - /// failing. Off by default: silent irreversibility is the thing we avoid. - pub allow_irreversible: bool, -} - -impl Default for ReversibleConfig { - fn default() -> Self { - Self { max_capture_bytes: 1 << 30, allow_irreversible: false } - } -} - -/// A [`StorageBackend`] decorator that makes every mutation undoable. -pub struct ReversibleBackend { - inner: Arc, - store: ContentStore, - log: Mutex, - config: ReversibleConfig, - caps: StorageCapabilities, -} - -impl ReversibleBackend { - /// `state_dir` holds `objects/` and `oplog.jsonl`. - pub fn new(inner: Arc, state_dir: impl AsRef, config: ReversibleConfig) -> CfkResult { - let dir = state_dir.as_ref(); - let mut caps = inner.capabilities().clone(); - caps.versioning = true; - Ok(Self { - store: ContentStore::open(dir.join("objects"))?, - log: Mutex::new(OpLog::open(dir.join("oplog.jsonl"))?), - inner, - config, - caps, - }) - } - - pub fn inner(&self) -> &Arc { - &self.inner - } - - pub fn store(&self) -> &ContentStore { - &self.store - } - - /// Full history, oldest first (including undo records). - pub fn history(&self) -> CfkResult> { - self.log.lock().expect("oplog poisoned").records() - } - - fn record(&self, op: Operation) -> CfkResult { - self.log.lock().expect("oplog poisoned").append(op) - } - - async fn read_all(&self, path: &VirtualPath) -> CfkResult { - let mut s = self.inner.read_file(path, &ReadOptions::default()).await?; - let mut buf = BytesMut::new(); - while let Some(chunk) = s.next().await { - let chunk = chunk?; - buf.extend_from_slice(&chunk); - if buf.len() as u64 > self.config.max_capture_bytes { - return Err(CfkError::Unsupported(format!( - "{path} exceeds reversible capture limit ({} bytes)", - self.config.max_capture_bytes - ))); - } - } - Ok(buf.freeze()) - } - - /// Existing file content at `path` stored in the CAS, or None if absent. - async fn capture_file(&self, path: &VirtualPath) -> CfkResult> { - match self.inner.get_metadata(path).await { - Ok(e) if e.kind == EntryKind::File => { - if let Some(sz) = e.metadata.size { - if sz > self.config.max_capture_bytes { - return Err(CfkError::Unsupported(format!( - "{path} ({sz} bytes) exceeds reversible capture limit" - ))); - } - } - let data = self.read_all(path).await?; - Ok(Some(self.store.put(&data)?)) - } - Ok(e) if e.kind == EntryKind::Directory => { - Err(CfkError::Unsupported(format!("{path} is a directory; cannot capture as file"))) - } - Ok(_) => Err(CfkError::Unsupported(format!("{path}: unsupported entry kind"))), - Err(CfkError::NotFound(_)) => Ok(None), - Err(e) => Err(e), - } - } - - /// Capture a whole tree, parent-first. - async fn capture_tree(&self, root: &VirtualPath) -> CfkResult> { - let mut out = Vec::new(); - let mut stack = vec![root.clone()]; - while let Some(p) = stack.pop() { - let entry = self.inner.get_metadata(&p).await?; - match entry.kind { - EntryKind::Directory => { - out.push(TreeNode { path: p.clone(), content: None }); - let opts = ListOptions { include_hidden: true, ..Default::default() }; - let listing = self.inner.list_directory(&p, &opts).await?; - // push reversed so traversal is stable / listing-ordered - for child in listing.entries.into_iter().rev() { - stack.push(child.path); - } - } - EntryKind::File => { - let hash = self.capture_file(&p).await?; - out.push(TreeNode { path: p, content: hash }); - } - _ => { - return Err(CfkError::Unsupported(format!( - "{p}: symlinks/special files not yet reversible" - ))) - } - } - } - Ok(out) - } - - /// Wrap capture errors according to `allow_irreversible`. - fn capture_or(&self, r: CfkResult) -> CfkResult> { - match r { - Ok(v) => Ok(Some(v)), - Err(CfkError::Unsupported(_)) if self.config.allow_irreversible => Ok(None), - Err(e) => Err(e), - } - } - - async fn restore(&self, path: &VirtualPath, content: &Option) -> CfkResult<()> { - match content { - Some(hash) => { - let data = self.store.get(hash)?; - let opts = WriteOptions { overwrite: true, create_parents: true, content_hash: None }; - self.inner.write_file(path, data, &opts).await?; - } - None => { - self.inner - .delete(path, &DeleteOptions { recursive: false, force: true }) - .await?; - } - } - Ok(()) - } - - /// Undo the most recent undoable operation. Returns the undone record. - pub async fn undo_last(&self) -> CfkResult { - let last = self - .log - .lock() - .expect("oplog poisoned") - .undoable()? - .pop() - .ok_or_else(|| CfkError::NotFound("nothing to undo".into()))?; - self.undo(last.id).await - } - - /// Undo a specific operation by id. - /// - /// Only the most recent undoable operation may be undone: undoing out of - /// order could clobber later changes to the same path. (Selective undo - /// with conflict detection is future work.) - pub async fn undo(&self, id: u64) -> CfkResult { - let undoable = self.log.lock().expect("oplog poisoned").undoable()?; - let rec = undoable - .iter() - .find(|r| r.id == id) - .cloned() - .ok_or_else(|| CfkError::NotFound(format!("operation #{id} is not undoable")))?; - if undoable.last().map(|r| r.id) != Some(id) { - return Err(CfkError::Conflict(format!( - "operation #{id} is not the latest; undo later operations first" - ))); - } - - match &rec.op { - Operation::Write { path, prior } => self.restore(path, prior).await?, - Operation::Copy { dest, prior, .. } => self.restore(dest, prior).await?, - Operation::CreateDir { path } => { - self.inner - .delete(path, &DeleteOptions { recursive: false, force: true }) - .await?; - } - Operation::Rename { source, dest, overwritten } => { - self.inner - .rename(dest, source, &MoveOptions { overwrite: false }) - .await?; - if overwritten.is_some() { - self.restore(dest, overwritten).await?; - } - } - Operation::Delete { tree, .. } => { - for node in tree { - match &node.content { - None => match self.inner.create_directory(&node.path).await { - Ok(_) | Err(CfkError::AlreadyExists(_)) => {} - Err(e) => return Err(e), - }, - Some(_) => self.restore(&node.path, &node.content).await?, - } - } - } - Operation::Undo { .. } => unreachable!("undo records are filtered out"), - } - - self.record(Operation::Undo { target: id })?; - Ok(rec) - } -} - -#[async_trait] -impl StorageBackend for ReversibleBackend { - fn id(&self) -> &str { - self.inner.id() - } - fn display_name(&self) -> &str { - self.inner.display_name() - } - fn capabilities(&self) -> &StorageCapabilities { - &self.caps - } - async fn is_available(&self) -> bool { - self.inner.is_available().await - } - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - self.inner.get_metadata(path).await - } - async fn list_directory(&self, path: &VirtualPath, options: &ListOptions) -> CfkResult { - self.inner.list_directory(path, options).await - } - async fn read_file(&self, path: &VirtualPath, options: &ReadOptions) -> CfkResult { - self.inner.read_file(path, options).await - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes, options: &WriteOptions) -> CfkResult { - let prior = self.capture_or(self.capture_file(path).await)?; - let entry = self.inner.write_file(path, data, options).await?; - if let Some(prior) = prior { - self.record(Operation::Write { path: path.clone(), prior })?; - } - Ok(entry) - } - - async fn write_file_stream( - &self, - path: &VirtualPath, - stream: ByteStream, - size_hint: Option, - options: &WriteOptions, - ) -> CfkResult { - let prior = self.capture_or(self.capture_file(path).await)?; - let entry = self.inner.write_file_stream(path, stream, size_hint, options).await?; - if let Some(prior) = prior { - self.record(Operation::Write { path: path.clone(), prior })?; - } - Ok(entry) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let entry = self.inner.create_directory(path).await?; - self.record(Operation::CreateDir { path: path.clone() })?; - Ok(entry) - } - - async fn delete(&self, path: &VirtualPath, options: &DeleteOptions) -> CfkResult<()> { - let tree = match self.inner.get_metadata(path).await { - Ok(_) => self.capture_or(self.capture_tree(path).await)?, - Err(CfkError::NotFound(_)) if options.force => return Ok(()), - Err(e) => return Err(e), - }; - self.inner.delete(path, options).await?; - if let Some(tree) = tree { - self.record(Operation::Delete { path: path.clone(), tree })?; - } - Ok(()) - } - - async fn copy(&self, source: &VirtualPath, dest: &VirtualPath, options: &CopyOptions) -> CfkResult { - let prior = self.capture_or(self.capture_file(dest).await)?; - let entry = self.inner.copy(source, dest, options).await?; - if let Some(prior) = prior { - self.record(Operation::Copy { source: source.clone(), dest: dest.clone(), prior })?; - } - Ok(entry) - } - - async fn rename(&self, source: &VirtualPath, dest: &VirtualPath, options: &MoveOptions) -> CfkResult { - let overwritten = if options.overwrite { - self.capture_or(self.capture_file(dest).await)? - } else { - Some(None) - }; - let entry = self.inner.rename(source, dest, options).await?; - if let Some(overwritten) = overwritten { - self.record(Operation::Rename { source: source.clone(), dest: dest.clone(), overwritten })?; - } - Ok(entry) - } - - async fn get_space_info(&self) -> CfkResult { - self.inner.get_space_info().await - } - - async fn search(&self, options: &SearchOptions) -> CfkResult> { - self.inner.search(options).await - } - - /// Versions = prior contents captured in the op log for this path, - /// newest first. Version id is the content hash. - async fn get_versions(&self, path: &VirtualPath) -> CfkResult> { - let mut out = Vec::new(); - for r in self.history()?.into_iter().rev() { - let hash = match &r.op { - Operation::Write { path: p, prior: Some(h) } if p == path => Some(h.clone()), - Operation::Copy { dest, prior: Some(h), .. } if dest == path => Some(h.clone()), - Operation::Rename { dest, overwritten: Some(h), .. } if dest == path => Some(h.clone()), - Operation::Delete { tree, .. } => tree - .iter() - .find(|n| &n.path == path) - .and_then(|n| n.content.clone()), - _ => None, - }; - if let Some(h) = hash { - let size = fs::metadata(self.store.object_path(&h)?).ok().map(|m| m.len()); - out.push(FileVersion { id: h, modified: r.timestamp, size, author: None }); - } - } - Ok(out) - } - - async fn get_version(&self, _path: &VirtualPath, version_id: &str) -> CfkResult { - let data = self.store.get(version_id)?; - Ok(Box::pin(futures::stream::once(async move { Ok(data) }))) - } -} - -// ─────────────────────────────── tests ──────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use crate::Metadata; - use std::collections::BTreeMap; - - /// Minimal in-memory backend: key = path string, None = directory. - #[derive(Default)] - struct MemBackend { - files: Mutex>>, - caps: StorageCapabilities, - } - - fn key(p: &VirtualPath) -> String { - p.to_path_string() - } - - impl MemBackend { - fn get(&self, p: &str) -> Option> { - self.files.lock().unwrap().get(p).cloned() - } - } - - #[async_trait] - impl StorageBackend for MemBackend { - fn id(&self) -> &str { "mem" } - fn display_name(&self) -> &str { "mem" } - fn capabilities(&self) -> &StorageCapabilities { &self.caps } - async fn is_available(&self) -> bool { true } - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - match self.files.lock().unwrap().get(&key(path)) { - Some(Some(b)) => Ok(Entry::file(path.clone(), Metadata::new().with_size(b.len() as u64))), - Some(None) => Ok(Entry::directory(path.clone(), Metadata::new())), - None if path.is_root() => Ok(Entry::directory(path.clone(), Metadata::new())), - None => Err(CfkError::NotFound(key(path))), - } - } - async fn list_directory(&self, path: &VirtualPath, _o: &ListOptions) -> CfkResult { - let files = self.files.lock().unwrap(); - let entries = files - .iter() - .filter_map(|(k, v)| { - let vp = VirtualPath::new("mem", k); - (vp.parent().as_ref() == Some(path)).then(|| match v { - Some(b) => Entry::file(vp, Metadata::new().with_size(b.len() as u64)), - None => Entry::directory(vp, Metadata::new()), - }) - }) - .collect(); - Ok(DirectoryListing::new(path.clone(), entries)) - } - async fn read_file(&self, path: &VirtualPath, _o: &ReadOptions) -> CfkResult { - match self.get(&key(path)) { - Some(Some(b)) => Ok(Box::pin(futures::stream::once(async move { Ok(b) }))), - _ => Err(CfkError::NotFound(key(path))), - } - } - async fn write_file(&self, path: &VirtualPath, data: Bytes, _o: &WriteOptions) -> CfkResult { - self.files.lock().unwrap().insert(key(path), Some(data)); - self.get_metadata(path).await - } - async fn write_file_stream(&self, path: &VirtualPath, mut s: ByteStream, _h: Option, o: &WriteOptions) -> CfkResult { - let mut buf = BytesMut::new(); - while let Some(c) = s.next().await { buf.extend_from_slice(&c?); } - self.write_file(path, buf.freeze(), o).await - } - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - self.files.lock().unwrap().insert(key(path), None); - self.get_metadata(path).await - } - async fn delete(&self, path: &VirtualPath, _o: &DeleteOptions) -> CfkResult<()> { - let k = key(path); - let prefix = format!("{k}/"); - self.files.lock().unwrap().retain(|p, _| p != &k && !p.starts_with(&prefix)); - Ok(()) - } - async fn copy(&self, s: &VirtualPath, d: &VirtualPath, _o: &CopyOptions) -> CfkResult { - let v = self.get(&key(s)).ok_or_else(|| CfkError::NotFound(key(s)))?; - self.files.lock().unwrap().insert(key(d), v); - self.get_metadata(d).await - } - async fn rename(&self, s: &VirtualPath, d: &VirtualPath, _o: &MoveOptions) -> CfkResult { - let v = self.files.lock().unwrap().remove(&key(s)).ok_or_else(|| CfkError::NotFound(key(s)))?; - self.files.lock().unwrap().insert(key(d), v); - self.get_metadata(d).await - } - async fn get_space_info(&self) -> CfkResult { Ok(SpaceInfo::unknown()) } - } - - fn vp(p: &str) -> VirtualPath { VirtualPath::new("mem", p) } - - fn setup() -> (Arc, ReversibleBackend, PathBuf) { - let dir = std::env::temp_dir().join(format!( - "cfk-rev-{}-{}", std::process::id(), - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() - )); - let mem = Arc::new(MemBackend::default()); - let rev = ReversibleBackend::new(mem.clone(), &dir, ReversibleConfig::default()).unwrap(); - (mem, rev, dir) - } - - #[tokio::test] - async fn modify_then_undo_restores_content() { - let (mem, rev, _d) = setup(); - let w = WriteOptions { overwrite: true, ..Default::default() }; - rev.write_file(&vp("/a"), Bytes::from_static(b"one"), &w).await.unwrap(); - rev.write_file(&vp("/a"), Bytes::from_static(b"two"), &w).await.unwrap(); - rev.undo_last().await.unwrap(); - assert_eq!(mem.get(&key(&vp("/a"))), Some(Some(Bytes::from_static(b"one")))); - rev.undo_last().await.unwrap(); - assert_eq!(mem.get(&key(&vp("/a"))), None); - } - - #[tokio::test] - async fn recursive_delete_then_undo_restores_tree() { - let (mem, rev, _d) = setup(); - let w = WriteOptions::default(); - rev.create_directory(&vp("/d")).await.unwrap(); - rev.write_file(&vp("/d/x"), Bytes::from_static(b"x"), &w).await.unwrap(); - rev.create_directory(&vp("/d/sub")).await.unwrap(); - rev.write_file(&vp("/d/sub/y"), Bytes::from_static(b"y"), &w).await.unwrap(); - let before = mem.files.lock().unwrap().clone(); - rev.delete(&vp("/d"), &DeleteOptions { recursive: true, force: false }).await.unwrap(); - assert!(mem.get("/d").is_none()); - rev.undo_last().await.unwrap(); - assert_eq!(*mem.files.lock().unwrap(), before); - } - - #[tokio::test] - async fn rename_overwrite_then_undo() { - let (mem, rev, _d) = setup(); - let w = WriteOptions::default(); - rev.write_file(&vp("/src"), Bytes::from_static(b"S"), &w).await.unwrap(); - rev.write_file(&vp("/dst"), Bytes::from_static(b"D"), &w).await.unwrap(); - rev.rename(&vp("/src"), &vp("/dst"), &MoveOptions { overwrite: true }).await.unwrap(); - rev.undo_last().await.unwrap(); - assert_eq!(mem.get("/src"), Some(Some(Bytes::from_static(b"S")))); - assert_eq!(mem.get("/dst"), Some(Some(Bytes::from_static(b"D")))); - } - - #[tokio::test] - async fn out_of_order_undo_is_refused_and_log_is_append_only() { - let (_mem, rev, _d) = setup(); - let w = WriteOptions::default(); - rev.write_file(&vp("/a"), Bytes::from_static(b"1"), &w).await.unwrap(); - rev.write_file(&vp("/b"), Bytes::from_static(b"2"), &w).await.unwrap(); - assert!(matches!(rev.undo(1).await, Err(CfkError::Conflict(_)))); - rev.undo_last().await.unwrap(); - let h = rev.history().unwrap(); - assert_eq!(h.len(), 3); - assert_eq!(h[2].op, Operation::Undo { target: 2 }); - } - - #[tokio::test] - async fn versions_come_from_captured_priors() { - let (_mem, rev, _d) = setup(); - let w = WriteOptions { overwrite: true, ..Default::default() }; - for v in [&b"v1"[..], b"v2", b"v3"] { - rev.write_file(&vp("/f"), Bytes::copy_from_slice(v), &w).await.unwrap(); - } - let versions = rev.get_versions(&vp("/f")).await.unwrap(); - assert_eq!(versions.len(), 2); // v2 and v1 (v3 is current) - let mut s = rev.get_version(&vp("/f"), &versions[0].id).await.unwrap(); - assert_eq!(s.next().await.unwrap().unwrap(), Bytes::from_static(b"v2")); - } - - #[test] - fn content_store_dedups_and_verifies() { - let dir = std::env::temp_dir().join(format!("cfk-cas-{}", std::process::id())); - let cas = ContentStore::open(&dir).unwrap(); - let h1 = cas.put(b"hello").unwrap(); - let h2 = cas.put(b"hello").unwrap(); - assert_eq!(h1, h2); - assert_eq!(h1, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"); - assert_eq!(&cas.get(&h1).unwrap()[..], b"hello"); - assert!(cas.get("zz").is_err()); - } -} diff --git a/czech-file-knife/src/cfk-integrations/Cargo.toml b/czech-file-knife/src/cfk-integrations/Cargo.toml deleted file mode 100644 index 893f2fa51..000000000 --- a/czech-file-knife/src/cfk-integrations/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "cfk-integrations" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "External tool integrations: aria2, agrep, pandoc, OCR, eza" - -[features] -default = [] -aria2 = [] -agrep = [] -pandoc = [] -ocr = ["tesseract"] -tesseract = [] -eza = [] -all = ["aria2", "agrep", "pandoc", "ocr", "eza"] - -[dependencies] -cfk-core = { path = "../cfk-core" } -async-trait.workspace = true -serde.workspace = true -serde_json.workspace = true -thiserror.workspace = true -tokio = { workspace = true, features = ["process"] } -tracing.workspace = true -regex.workspace = true diff --git a/czech-file-knife/src/cfk-integrations/src/agrep.rs b/czech-file-knife/src/cfk-integrations/src/agrep.rs deleted file mode 100644 index 025fe920f..000000000 --- a/czech-file-knife/src/cfk-integrations/src/agrep.rs +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! agrep integration for approximate/fuzzy grep -//! -//! agrep allows errors in pattern matching (Levenshtein distance) - -use crate::{run_command, CfkResult}; -use serde::{Deserialize, Serialize}; -use std::path::Path; - -/// agrep match result -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AgrepMatch { - pub file: String, - pub line_number: usize, - pub line: String, - pub errors: u8, -} - -/// agrep search options -#[derive(Debug, Clone, Default)] -pub struct AgrepOptions { - pub max_errors: u8, // -k N: allow N errors - pub case_insensitive: bool, // -i - pub word_match: bool, // -w - pub line_match: bool, // -x (whole line) - pub count_only: bool, // -c - pub files_only: bool, // -l - pub recursive: bool, // -r -} - -/// Search for approximate pattern matches -pub async fn search( - pattern: &str, - path: &Path, - options: &AgrepOptions, -) -> CfkResult> { - let mut args = vec!["-n".to_string()]; // line numbers - - if options.max_errors > 0 { - args.push(format!("-{}", options.max_errors)); - } - if options.case_insensitive { - args.push("-i".to_string()); - } - if options.word_match { - args.push("-w".to_string()); - } - if options.line_match { - args.push("-x".to_string()); - } - if options.recursive { - args.push("-r".to_string()); - } - - args.push(pattern.to_string()); - args.push(path.to_string_lossy().to_string()); - - let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); - let output = run_command("agrep", &args_ref).await?; - - let mut matches = Vec::new(); - for line in String::from_utf8_lossy(&output.stdout).lines() { - if let Some((file_line, content)) = line.split_once(':') { - if let Some((file, line_num)) = file_line.rsplit_once(':') { - matches.push(AgrepMatch { - file: file.to_string(), - line_number: line_num.parse().unwrap_or(0), - line: content.to_string(), - errors: options.max_errors, - }); - } - } - } - - Ok(matches) -} - -/// Fuzzy file name search -pub async fn find_files( - pattern: &str, - dir: &Path, - max_errors: u8, -) -> CfkResult> { - // Use find + agrep for fuzzy filename matching - let find_output = run_command("find", &[ - dir.to_str().unwrap(), - "-type", "f", - "-print" - ]).await?; - - let mut args = vec![format!("-{}", max_errors), pattern.to_string()]; - let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); - - // Pipe find output to agrep (simplified - actual impl would use pipes) - let files = String::from_utf8_lossy(&find_output.stdout) - .lines() - .filter(|f| f.contains(pattern) || pattern.len() < 3) // Simplified - .map(String::from) - .collect(); - - Ok(files) -} diff --git a/czech-file-knife/src/cfk-integrations/src/aria2.rs b/czech-file-knife/src/cfk-integrations/src/aria2.rs deleted file mode 100644 index 9f1aa7718..000000000 --- a/czech-file-knife/src/cfk-integrations/src/aria2.rs +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! aria2 integration for high-speed downloads -//! -//! aria2 supports: HTTP/HTTPS, FTP, SFTP, BitTorrent, Metalink - -use crate::{run_command, CfkResult}; -use serde::{Deserialize, Serialize}; -use std::path::Path; - -/// aria2 download options -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Aria2Options { - pub connections: u8, // max connections per server (default: 5) - pub split: u8, // split file into N parts (default: 5) - pub min_split_size: String, // minimum split size (default: "20M") - pub continue_download: bool, - pub max_speed: Option, // e.g., "1M" - pub user_agent: Option, - pub headers: Vec<(String, String)>, -} - -/// Download a file using aria2 -pub async fn download( - url: &str, - output: &Path, - options: &Aria2Options, -) -> CfkResult<()> { - let mut args = vec![ - url, - "-d", output.parent().unwrap_or(Path::new(".")).to_str().unwrap(), - "-o", output.file_name().unwrap().to_str().unwrap(), - "-x", &options.connections.to_string(), - "-s", &options.split.to_string(), - "-k", &options.min_split_size, - ]; - - if options.continue_download { - args.push("-c"); - } - - let output = run_command("aria2c", &args).await?; - if !output.status.success() { - return Err(cfk_core::CfkError::Other( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - Ok(()) -} - -/// Download multiple files in parallel -pub async fn download_batch(urls: &[&str], output_dir: &Path) -> CfkResult<()> { - let mut args = vec![ - "-d", output_dir.to_str().unwrap(), - "-x", "5", - "-s", "5", - "-j", "5", // concurrent downloads - ]; - args.extend(urls.iter().map(|s| *s)); - - let output = run_command("aria2c", &args).await?; - if !output.status.success() { - return Err(cfk_core::CfkError::Other( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - Ok(()) -} - -/// Check aria2 version -pub async fn version() -> CfkResult { - let output = run_command("aria2c", &["--version"]).await?; - Ok(String::from_utf8_lossy(&output.stdout) - .lines() - .next() - .unwrap_or("unknown") - .to_string()) -} diff --git a/czech-file-knife/src/cfk-integrations/src/lib.rs b/czech-file-knife/src/cfk-integrations/src/lib.rs deleted file mode 100644 index 2d21760ab..000000000 --- a/czech-file-knife/src/cfk-integrations/src/lib.rs +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! External tool integrations for Czech File Knife -//! -//! Integrates: aria2, agrep, pandoc, tesseract OCR, eza - -#[cfg(feature = "aria2")] -pub mod aria2; - -#[cfg(feature = "agrep")] -pub mod agrep; - -#[cfg(feature = "pandoc")] -pub mod pandoc; - -#[cfg(feature = "ocr")] -pub mod ocr; - -#[cfg(feature = "eza")] -pub mod eza; - -use cfk_core::error::{CfkError, CfkResult}; -use std::process::Output; -use tokio::process::Command; - -/// Check if an external tool is available -pub async fn check_tool(name: &str) -> bool { - Command::new("which") - .arg(name) - .output() - .await - .map(|o| o.status.success()) - .unwrap_or(false) -} - -/// Run an external command and get output -pub async fn run_command(program: &str, args: &[&str]) -> CfkResult { - Command::new(program) - .args(args) - .output() - .await - .map_err(|e| CfkError::Other(format!("Failed to run {}: {}", program, e))) -} - -/// Tool availability status -#[derive(Debug, Clone)] -pub struct ToolStatus { - pub aria2: bool, - pub agrep: bool, - pub pandoc: bool, - pub tesseract: bool, - pub eza: bool, -} - -impl ToolStatus { - pub async fn detect() -> Self { - Self { - aria2: check_tool("aria2c").await, - agrep: check_tool("agrep").await, - pandoc: check_tool("pandoc").await, - tesseract: check_tool("tesseract").await, - eza: check_tool("eza").await || check_tool("exa").await, - } - } -} diff --git a/czech-file-knife/src/cfk-integrations/src/pandoc.rs b/czech-file-knife/src/cfk-integrations/src/pandoc.rs deleted file mode 100644 index ddef8bbad..000000000 --- a/czech-file-knife/src/cfk-integrations/src/pandoc.rs +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! pandoc integration for document format conversion -//! -//! Supports: markdown, docx, pdf, html, epub, rst, latex, and 40+ formats - -use crate::{run_command, CfkResult}; -use std::path::Path; - -/// Supported input/output formats -#[derive(Debug, Clone, Copy)] -pub enum Format { - Markdown, - Html, - Docx, - Pdf, - Epub, - Rst, - Latex, - Org, - Asciidoc, - Json, - Plain, -} - -impl Format { - pub fn as_str(&self) -> &'static str { - match self { - Format::Markdown => "markdown", - Format::Html => "html", - Format::Docx => "docx", - Format::Pdf => "pdf", - Format::Epub => "epub", - Format::Rst => "rst", - Format::Latex => "latex", - Format::Org => "org", - Format::Asciidoc => "asciidoc", - Format::Json => "json", - Format::Plain => "plain", - } - } -} - -/// Convert a file between formats -pub async fn convert( - input: &Path, - output: &Path, - from: Option, - to: Option, -) -> CfkResult<()> { - let mut args = vec![ - input.to_str().unwrap(), - "-o", output.to_str().unwrap(), - ]; - - let from_str; - let to_str; - - if let Some(f) = from { - from_str = f.as_str().to_string(); - args.extend(["-f", &from_str]); - } - if let Some(t) = to { - to_str = t.as_str().to_string(); - args.extend(["-t", &to_str]); - } - - let output = run_command("pandoc", &args).await?; - if !output.status.success() { - return Err(cfk_core::CfkError::Other( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - Ok(()) -} - -/// Convert string content between formats -pub async fn convert_string( - content: &str, - from: Format, - to: Format, -) -> CfkResult { - use tokio::process::Command; - use tokio::io::AsyncWriteExt; - - let mut child = Command::new("pandoc") - .args(["-f", from.as_str(), "-t", to.as_str()]) - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .spawn() - .map_err(|e| cfk_core::CfkError::Other(e.to_string()))?; - - if let Some(mut stdin) = child.stdin.take() { - stdin.write_all(content.as_bytes()).await - .map_err(|e| cfk_core::CfkError::Other(e.to_string()))?; - } - - let output = child.wait_with_output().await - .map_err(|e| cfk_core::CfkError::Other(e.to_string()))?; - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) -} - -/// Get pandoc version -pub async fn version() -> CfkResult { - let output = run_command("pandoc", &["--version"]).await?; - Ok(String::from_utf8_lossy(&output.stdout) - .lines() - .next() - .unwrap_or("unknown") - .to_string()) -} diff --git a/czech-file-knife/src/cfk-ios/Cargo.toml b/czech-file-knife/src/cfk-ios/Cargo.toml deleted file mode 100644 index 05c5e483f..000000000 --- a/czech-file-knife/src/cfk-ios/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "cfk-ios" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -authors.workspace = true -description = "iOS File Provider extension for Czech File Knife" - -[lib] -crate-type = ["staticlib", "cdylib"] - -[dependencies] -cfk-core = { path = "../cfk-core" } -cfk-providers = { path = "../cfk-providers" } -cfk-cache = { path = "../cfk-cache" } - -# FFI -libc = "0.2" -once_cell = "1.19" - -# Async -tokio = { workspace = true } -async-trait.workspace = true -futures.workspace = true - -# Serialization -serde.workspace = true -serde_json.workspace = true - -# Error handling -thiserror.workspace = true - -# Logging -tracing.workspace = true -tracing-subscriber = { workspace = true } - -# Time -chrono.workspace = true - -# Bytes -bytes.workspace = true - -[target.'cfg(target_os = "ios")'.dependencies] -objc = "0.2" -objc-foundation = "0.1" -block = "0.1" - -[features] -default = [] -simulator = [] # iOS Simulator builds diff --git a/czech-file-knife/src/cfk-ios/src/domain.rs b/czech-file-knife/src/cfk-ios/src/domain.rs deleted file mode 100644 index ade75ab74..000000000 --- a/czech-file-knife/src/cfk-ios/src/domain.rs +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File Provider Domain management -//! -//! Maps to NSFileProviderDomain in iOS. - -use crate::error::{IosError, IosResult}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// Unique domain identifier -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct DomainIdentifier(pub String); - -impl DomainIdentifier { - pub fn new(id: impl Into) -> Self { - Self(id.into()) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -/// File Provider Domain -/// -/// Represents a storage location (e.g., a Dropbox account, Google Drive). -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FileDomain { - /// Unique identifier - pub identifier: DomainIdentifier, - /// Display name shown in Files app - pub display_name: String, - /// Backend type (dropbox, gdrive, onedrive, etc.) - pub backend_type: String, - /// Backend configuration as JSON - pub config_json: String, - /// Whether the domain is currently enabled - pub enabled: bool, - /// Path prefix for this domain - pub path_prefix: Option, - /// Custom metadata - #[serde(default)] - pub metadata: HashMap, -} - -impl FileDomain { - /// Create a new domain - pub fn new( - identifier: impl Into, - display_name: impl Into, - backend_type: impl Into, - ) -> Self { - Self { - identifier: DomainIdentifier::new(identifier), - display_name: display_name.into(), - backend_type: backend_type.into(), - config_json: "{}".to_string(), - enabled: true, - path_prefix: None, - metadata: HashMap::new(), - } - } - - /// Set configuration JSON - pub fn with_config(mut self, config: impl Into) -> Self { - self.config_json = config.into(); - self - } - - /// Set path prefix - pub fn with_prefix(mut self, prefix: impl Into) -> Self { - self.path_prefix = Some(prefix.into()); - self - } - - /// Check if this is a cloud backend - pub fn is_cloud(&self) -> bool { - matches!( - self.backend_type.as_str(), - "dropbox" | "gdrive" | "onedrive" | "box" | "icloud" | "s3" | "webdav" - ) - } -} - -/// Domain manager -pub struct DomainManager { - domains: Arc>>, - storage_path: std::path::PathBuf, -} - -impl DomainManager { - /// Create a new domain manager - pub fn new(storage_path: impl Into) -> Self { - Self { - domains: Arc::new(RwLock::new(HashMap::new())), - storage_path: storage_path.into(), - } - } - - /// Load domains from persistent storage - pub async fn load(&self) -> IosResult<()> { - let path = self.storage_path.join("domains.json"); - if path.exists() { - let data = tokio::fs::read_to_string(&path) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - let domains: Vec = serde_json::from_str(&data) - .map_err(|e| IosError::Ffi(format!("Failed to parse domains: {}", e)))?; - - let mut map = self.domains.write().await; - for domain in domains { - map.insert(domain.identifier.clone(), domain); - } - } - Ok(()) - } - - /// Save domains to persistent storage - pub async fn save(&self) -> IosResult<()> { - let domains: Vec = self.domains.read().await.values().cloned().collect(); - let data = serde_json::to_string_pretty(&domains) - .map_err(|e| IosError::Ffi(format!("Failed to serialize domains: {}", e)))?; - - tokio::fs::create_dir_all(&self.storage_path) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - let path = self.storage_path.join("domains.json"); - tokio::fs::write(&path, data) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - Ok(()) - } - - /// Add a domain - pub async fn add(&self, domain: FileDomain) -> IosResult<()> { - let mut domains = self.domains.write().await; - domains.insert(domain.identifier.clone(), domain); - drop(domains); - self.save().await - } - - /// Remove a domain - pub async fn remove(&self, id: &DomainIdentifier) -> IosResult> { - let mut domains = self.domains.write().await; - let removed = domains.remove(id); - drop(domains); - self.save().await?; - Ok(removed) - } - - /// Get a domain by ID - pub async fn get(&self, id: &DomainIdentifier) -> Option { - self.domains.read().await.get(id).cloned() - } - - /// List all domains - pub async fn list(&self) -> Vec { - self.domains.read().await.values().cloned().collect() - } - - /// Get enabled domains only - pub async fn list_enabled(&self) -> Vec { - self.domains - .read() - .await - .values() - .filter(|d| d.enabled) - .cloned() - .collect() - } - - /// Enable/disable a domain - pub async fn set_enabled(&self, id: &DomainIdentifier, enabled: bool) -> IosResult<()> { - let mut domains = self.domains.write().await; - if let Some(domain) = domains.get_mut(id) { - domain.enabled = enabled; - } - drop(domains); - self.save().await - } -} - -/// Domain change notification type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(i32)] -pub enum DomainChangeType { - Added = 0, - Removed = 1, - Updated = 2, -} diff --git a/czech-file-knife/src/cfk-ios/src/error.rs b/czech-file-knife/src/cfk-ios/src/error.rs deleted file mode 100644 index ee973b7af..000000000 --- a/czech-file-knife/src/cfk-ios/src/error.rs +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! iOS-specific error types - -use cfk_core::CfkError as CoreError; -use std::ffi::CString; -use thiserror::Error; - -/// iOS-specific errors -#[derive(Debug, Error)] -pub enum IosError { - #[error("Core error: {0}")] - Core(#[from] CoreError), - - #[error("Invalid identifier: {0}")] - InvalidIdentifier(String), - - #[error("Item not found: {0}")] - NotFound(String), - - #[error("Operation not supported: {0}")] - NotSupported(String), - - #[error("Authentication required")] - AuthRequired, - - #[error("Network unavailable")] - NetworkUnavailable, - - #[error("Quota exceeded")] - QuotaExceeded, - - #[error("Conflict: {0}")] - Conflict(String), - - #[error("Server error: {0}")] - ServerError(String), - - #[error("FFI error: {0}")] - Ffi(String), - - #[error("Sync error: {0}")] - Sync(String), -} - -/// iOS result type -pub type IosResult = Result; - -/// NSFileProviderError codes (matching Apple's NSFileProviderErrorCode) -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FileProviderErrorCode { - /// No error - Success = 0, - /// Item not found - NoSuchItem = -1000, - /// Item already exists - ItemAlreadyExists = -1001, - /// Not authenticated - NotAuthenticated = -1002, - /// Server unreachable - ServerUnreachable = -1003, - /// Quota exceeded - QuotaExceeded = -1004, - /// Invalid filename - FilenameInvalid = -1005, - /// Version out of date - VersionOutOfDate = -1006, - /// Page expired - PageExpired = -1007, - /// Sync anchor expired - SyncAnchorExpired = -1008, - /// Insufficient quota - InsufficientQuota = -1009, - /// Cannot sync - CannotSync = -1010, - /// Unknown error - Unknown = -9999, -} - -impl From<&IosError> for FileProviderErrorCode { - fn from(err: &IosError) -> Self { - match err { - IosError::NotFound(_) => FileProviderErrorCode::NoSuchItem, - IosError::AuthRequired => FileProviderErrorCode::NotAuthenticated, - IosError::NetworkUnavailable => FileProviderErrorCode::ServerUnreachable, - IosError::QuotaExceeded => FileProviderErrorCode::QuotaExceeded, - IosError::Conflict(_) => FileProviderErrorCode::VersionOutOfDate, - IosError::Sync(_) => FileProviderErrorCode::CannotSync, - _ => FileProviderErrorCode::Unknown, - } - } -} - -/// FFI-safe error structure -#[repr(C)] -pub struct FfiError { - /// Error code - pub code: i32, - /// Error message (null-terminated, caller must free) - pub message: *mut libc::c_char, -} - -impl FfiError { - /// Create a success result - pub fn success() -> Self { - Self { - code: 0, - message: std::ptr::null_mut(), - } - } - - /// Create from IosError - pub fn from_error(err: &IosError) -> Self { - let code: FileProviderErrorCode = err.into(); - let message = CString::new(err.to_string()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()); - - Self { - code: code as i32, - message, - } - } -} - -/// Free an error message -/// -/// # Safety -/// The pointer must have been returned by a CFK function. -#[no_mangle] -pub unsafe extern "C" fn cfk_error_free(error: *mut FfiError) { - if !error.is_null() { - let err = &mut *error; - if !err.message.is_null() { - drop(CString::from_raw(err.message)); - err.message = std::ptr::null_mut(); - } - } -} diff --git a/czech-file-knife/src/cfk-ios/src/ffi.rs b/czech-file-knife/src/cfk-ios/src/ffi.rs deleted file mode 100644 index 4cf93d83c..000000000 --- a/czech-file-knife/src/cfk-ios/src/ffi.rs +++ /dev/null @@ -1,475 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! C FFI layer for iOS integration -//! -//! This module exposes a C API that can be called from Swift/Objective-C. - -use crate::domain::{DomainIdentifier, FileDomain}; -use crate::error::{FfiError, FileProviderErrorCode}; -use crate::item::{FileProviderItem, ItemIdentifier}; -use crate::provider::FileProviderManager; -use once_cell::sync::OnceCell; -use std::ffi::{CStr, CString}; -use std::os::raw::c_char; -use std::path::PathBuf; -use std::sync::Arc; - -/// Global provider manager -static MANAGER: OnceCell> = OnceCell::new(); - -/// Initialize the FFI layer -fn get_manager() -> Result<&'static Arc, FfiError> { - MANAGER - .get() - .ok_or_else(|| FfiError::from_error(&crate::error::IosError::Ffi( - "Manager not initialized".into(), - ))) -} - -// --- Initialization --- - -/// Initialize the provider manager -/// -/// # Safety -/// All string parameters must be valid null-terminated UTF-8 strings. -#[no_mangle] -pub unsafe extern "C" fn cfk_provider_init( - storage_path: *const c_char, - cache_path: *const c_char, - temp_path: *const c_char, -) -> i32 { - let storage = match CStr::from_ptr(storage_path).to_str() { - Ok(s) => PathBuf::from(s), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let cache = match CStr::from_ptr(cache_path).to_str() { - Ok(s) => PathBuf::from(s), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let temp = match CStr::from_ptr(temp_path).to_str() { - Ok(s) => PathBuf::from(s), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let manager = FileProviderManager::new(storage, cache, temp); - - // Initialize async - let rt = crate::runtime(); - if let Err(e) = rt.block_on(Arc::new(manager).initialize()) { - tracing::error!("Failed to initialize: {}", e); - return FileProviderErrorCode::Unknown as i32; - } - - let manager = FileProviderManager::new( - CStr::from_ptr(storage_path).to_str().unwrap_or(""), - CStr::from_ptr(cache_path).to_str().unwrap_or(""), - CStr::from_ptr(temp_path).to_str().unwrap_or(""), - ); - - let _ = MANAGER.set(Arc::new(manager)); - - FileProviderErrorCode::Success as i32 -} - -// --- Domain Management --- - -/// FFI-safe domain structure -#[repr(C)] -pub struct CfkDomain { - pub identifier: *mut c_char, - pub display_name: *mut c_char, - pub backend_type: *mut c_char, - pub enabled: bool, -} - -impl CfkDomain { - fn from_domain(domain: &FileDomain) -> Self { - Self { - identifier: CString::new(domain.identifier.0.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - display_name: CString::new(domain.display_name.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - backend_type: CString::new(domain.backend_type.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - enabled: domain.enabled, - } - } -} - -/// Free a domain structure -/// -/// # Safety -/// The pointer must have been returned by a CFK function. -#[no_mangle] -pub unsafe extern "C" fn cfk_domain_free(domain: *mut CfkDomain) { - if !domain.is_null() { - let d = &mut *domain; - if !d.identifier.is_null() { - drop(CString::from_raw(d.identifier)); - } - if !d.display_name.is_null() { - drop(CString::from_raw(d.display_name)); - } - if !d.backend_type.is_null() { - drop(CString::from_raw(d.backend_type)); - } - } -} - -/// Add a domain -/// -/// # Safety -/// All string parameters must be valid null-terminated UTF-8 strings. -#[no_mangle] -pub unsafe extern "C" fn cfk_domain_add( - identifier: *const c_char, - display_name: *const c_char, - backend_type: *const c_char, - config_json: *const c_char, -) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let id = match CStr::from_ptr(identifier).to_str() { - Ok(s) => s, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let name = match CStr::from_ptr(display_name).to_str() { - Ok(s) => s, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let backend = match CStr::from_ptr(backend_type).to_str() { - Ok(s) => s, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let config = if config_json.is_null() { - "{}".to_string() - } else { - CStr::from_ptr(config_json) - .to_str() - .unwrap_or("{}") - .to_string() - }; - - let domain = FileDomain::new(id, name, backend).with_config(config); - - let rt = crate::runtime(); - match rt.block_on(manager.add_domain(domain)) { - Ok(_) => FileProviderErrorCode::Success as i32, - Err(_) => FileProviderErrorCode::Unknown as i32, - } -} - -/// Remove a domain -/// -/// # Safety -/// The identifier must be a valid null-terminated UTF-8 string. -#[no_mangle] -pub unsafe extern "C" fn cfk_domain_remove(identifier: *const c_char) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let id = match CStr::from_ptr(identifier).to_str() { - Ok(s) => DomainIdentifier::new(s), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let rt = crate::runtime(); - match rt.block_on(manager.remove_domain(&id)) { - Ok(_) => FileProviderErrorCode::Success as i32, - Err(_) => FileProviderErrorCode::NoSuchItem as i32, - } -} - -// --- Item Operations --- - -/// FFI-safe item structure -#[repr(C)] -pub struct CfkItem { - pub identifier: *mut c_char, - pub parent_identifier: *mut c_char, - pub filename: *mut c_char, - pub item_type: u32, - pub size: u64, - pub has_size: bool, - pub capabilities: u64, - pub is_downloaded: bool, - pub is_uploaded: bool, -} - -impl CfkItem { - fn from_item(item: &FileProviderItem) -> Self { - Self { - identifier: CString::new(item.identifier.0.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - parent_identifier: CString::new(item.parent_identifier.0.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - filename: CString::new(item.filename.clone()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()), - item_type: item.item_type, - size: item.size.unwrap_or(0), - has_size: item.size.is_some(), - capabilities: item.capabilities, - is_downloaded: item.is_downloaded, - is_uploaded: item.is_uploaded, - } - } -} - -/// Free an item structure -/// -/// # Safety -/// The pointer must have been returned by a CFK function. -#[no_mangle] -pub unsafe extern "C" fn cfk_item_free(item: *mut CfkItem) { - if !item.is_null() { - let i = &mut *item; - if !i.identifier.is_null() { - drop(CString::from_raw(i.identifier)); - } - if !i.parent_identifier.is_null() { - drop(CString::from_raw(i.parent_identifier)); - } - if !i.filename.is_null() { - drop(CString::from_raw(i.filename)); - } - } -} - -/// Get item by identifier -/// -/// # Safety -/// The identifier must be a valid null-terminated UTF-8 string. -/// The caller must free the returned item with cfk_item_free. -#[no_mangle] -pub unsafe extern "C" fn cfk_item_get( - identifier: *const c_char, - out_item: *mut CfkItem, -) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let id = match CStr::from_ptr(identifier).to_str() { - Ok(s) => ItemIdentifier(s.to_string()), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let rt = crate::runtime(); - match rt.block_on(manager.item(&id)) { - Ok(item) => { - if !out_item.is_null() { - *out_item = CfkItem::from_item(&item); - } - FileProviderErrorCode::Success as i32 - } - Err(_) => FileProviderErrorCode::NoSuchItem as i32, - } -} - -/// Item list for enumeration -#[repr(C)] -pub struct CfkItemList { - pub items: *mut CfkItem, - pub count: usize, - pub next_page_token: *mut c_char, -} - -/// Free an item list -/// -/// # Safety -/// The pointer must have been returned by a CFK function. -#[no_mangle] -pub unsafe extern "C" fn cfk_item_list_free(list: *mut CfkItemList) { - if !list.is_null() { - let l = &mut *list; - if !l.items.is_null() { - let items = std::slice::from_raw_parts_mut(l.items, l.count); - for item in items { - cfk_item_free(item); - } - drop(Vec::from_raw_parts(l.items, l.count, l.count)); - } - if !l.next_page_token.is_null() { - drop(CString::from_raw(l.next_page_token)); - } - } -} - -/// Enumerate items in a container -/// -/// # Safety -/// All string parameters must be valid null-terminated UTF-8 strings. -#[no_mangle] -pub unsafe extern "C" fn cfk_enumerate_items( - container: *const c_char, - page_token: *const c_char, - out_list: *mut CfkItemList, -) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let container_id = match CStr::from_ptr(container).to_str() { - Ok(s) => ItemIdentifier(s.to_string()), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let token = if page_token.is_null() { - None - } else { - CStr::from_ptr(page_token).to_str().ok() - }; - - let rt = crate::runtime(); - match rt.block_on(manager.enumerate_items(&container_id, token)) { - Ok(page) => { - if !out_list.is_null() { - let items: Vec = page.items.iter().map(CfkItem::from_item).collect(); - let count = items.len(); - let ptr = items.as_ptr() as *mut CfkItem; - std::mem::forget(items); - - (*out_list).items = ptr; - (*out_list).count = count; - (*out_list).next_page_token = page - .next_page_token - .and_then(|t| CString::new(t).ok()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()); - } - FileProviderErrorCode::Success as i32 - } - Err(_) => FileProviderErrorCode::NoSuchItem as i32, - } -} - -/// Fetch file contents to local path -/// -/// # Safety -/// All string parameters must be valid null-terminated UTF-8 strings. -/// The caller must free the returned path string. -#[no_mangle] -pub unsafe extern "C" fn cfk_fetch_contents( - identifier: *const c_char, - out_path: *mut *mut c_char, -) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let id = match CStr::from_ptr(identifier).to_str() { - Ok(s) => ItemIdentifier(s.to_string()), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let rt = crate::runtime(); - match rt.block_on(manager.fetch_contents(&id)) { - Ok(path) => { - if !out_path.is_null() { - *out_path = CString::new(path.to_string_lossy().as_ref()) - .map(|s| s.into_raw()) - .unwrap_or(std::ptr::null_mut()); - } - FileProviderErrorCode::Success as i32 - } - Err(_) => FileProviderErrorCode::NoSuchItem as i32, - } -} - -/// Create a new item -/// -/// # Safety -/// All string parameters must be valid null-terminated UTF-8 strings. -#[no_mangle] -pub unsafe extern "C" fn cfk_create_item( - parent: *const c_char, - filename: *const c_char, - item_type: u32, - contents: *const u8, - contents_len: usize, - out_item: *mut CfkItem, -) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let parent_id = match CStr::from_ptr(parent).to_str() { - Ok(s) => ItemIdentifier(s.to_string()), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let name = match CStr::from_ptr(filename).to_str() { - Ok(s) => s, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let data = if contents.is_null() || contents_len == 0 { - None - } else { - Some(std::slice::from_raw_parts(contents, contents_len)) - }; - - let rt = crate::runtime(); - match rt.block_on(manager.create_item(&parent_id, name, item_type, data)) { - Ok(item) => { - if !out_item.is_null() { - *out_item = CfkItem::from_item(&item); - } - FileProviderErrorCode::Success as i32 - } - Err(_) => FileProviderErrorCode::Unknown as i32, - } -} - -/// Delete an item -/// -/// # Safety -/// The identifier must be a valid null-terminated UTF-8 string. -#[no_mangle] -pub unsafe extern "C" fn cfk_delete_item(identifier: *const c_char) -> i32 { - let manager = match get_manager() { - Ok(m) => m, - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let id = match CStr::from_ptr(identifier).to_str() { - Ok(s) => ItemIdentifier(s.to_string()), - Err(_) => return FileProviderErrorCode::Unknown as i32, - }; - - let rt = crate::runtime(); - match rt.block_on(manager.delete_item(&id)) { - Ok(_) => FileProviderErrorCode::Success as i32, - Err(_) => FileProviderErrorCode::NoSuchItem as i32, - } -} - -/// Free a string returned by CFK functions -/// -/// # Safety -/// The pointer must have been returned by a CFK function. -#[no_mangle] -pub unsafe extern "C" fn cfk_string_free(s: *mut c_char) { - if !s.is_null() { - drop(CString::from_raw(s)); - } -} diff --git a/czech-file-knife/src/cfk-ios/src/item.rs b/czech-file-knife/src/cfk-ios/src/item.rs deleted file mode 100644 index d7343fef6..000000000 --- a/czech-file-knife/src/cfk-ios/src/item.rs +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File Provider Item representation -//! -//! Maps to NSFileProviderItem in iOS. - -use crate::domain::DomainIdentifier; -use crate::error::{IosError, IosResult}; -use cfk_core::{Entry, EntryKind, VirtualPath}; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Item identifier (opaque string) -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct ItemIdentifier(pub String); - -impl ItemIdentifier { - /// Root container identifier - pub fn root() -> Self { - Self("root".to_string()) - } - - /// Working set identifier - pub fn working_set() -> Self { - Self(".workingset".to_string()) - } - - /// Trash identifier - pub fn trash() -> Self { - Self(".trash".to_string()) - } - - /// Create from domain and path - pub fn from_path(domain: &DomainIdentifier, path: &VirtualPath) -> Self { - Self(format!("{}:{}", domain.0, path)) - } - - /// Parse into domain and path - pub fn parse(&self) -> Option<(DomainIdentifier, String)> { - let parts: Vec<&str> = self.0.splitn(2, ':').collect(); - if parts.len() == 2 { - Some((DomainIdentifier::new(parts[0]), parts[1].to_string())) - } else { - None - } - } - - /// Check if this is a special identifier - pub fn is_special(&self) -> bool { - self.0.starts_with('.') - } - - /// Check if this is the root - pub fn is_root(&self) -> bool { - self.0 == "root" - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -/// Item type flags -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ItemType { - File = 0, - Directory = 1, - Symlink = 2, - Package = 3, // macOS/iOS package (folder displayed as file) - Unknown = 255, -} - -impl From for ItemType { - fn from(kind: EntryKind) -> Self { - match kind { - EntryKind::File => ItemType::File, - EntryKind::Directory => ItemType::Directory, - EntryKind::Symlink => ItemType::Symlink, - EntryKind::Unknown => ItemType::Unknown, - } - } -} - -/// Item capabilities (what operations are allowed) -#[derive(Debug, Clone, Copy)] -pub struct ItemCapabilities(pub u64); - -impl ItemCapabilities { - pub const READING: u64 = 1 << 0; - pub const WRITING: u64 = 1 << 1; - pub const REPARENTING: u64 = 1 << 2; // Can be moved - pub const RENAMING: u64 = 1 << 3; - pub const TRASHING: u64 = 1 << 4; - pub const DELETING: u64 = 1 << 5; - pub const EVICTING: u64 = 1 << 6; // Can be removed from local storage - pub const ADDING_SUBITEM: u64 = 1 << 7; - pub const CONTENT_ENUMERATION: u64 = 1 << 8; - pub const PLAYING: u64 = 1 << 9; - - /// Default capabilities for a file - pub fn file_default() -> Self { - Self( - Self::READING - | Self::WRITING - | Self::REPARENTING - | Self::RENAMING - | Self::TRASHING - | Self::DELETING - | Self::EVICTING, - ) - } - - /// Default capabilities for a directory - pub fn directory_default() -> Self { - Self( - Self::READING - | Self::REPARENTING - | Self::RENAMING - | Self::TRASHING - | Self::DELETING - | Self::ADDING_SUBITEM - | Self::CONTENT_ENUMERATION, - ) - } - - /// Read-only capabilities - pub fn read_only() -> Self { - Self(Self::READING | Self::EVICTING | Self::CONTENT_ENUMERATION) - } -} - -/// File Provider Item -/// -/// Represents a file or folder in the File Provider. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FileProviderItem { - /// Unique identifier - pub identifier: ItemIdentifier, - /// Parent identifier - pub parent_identifier: ItemIdentifier, - /// Filename (not full path) - pub filename: String, - /// Item type - pub item_type: u32, - /// File size (for files) - pub size: Option, - /// Creation date - pub creation_date: Option>, - /// Modification date - pub content_modification_date: Option>, - /// Content type (UTI) - pub content_type: Option, - /// Capabilities - pub capabilities: u64, - /// Whether downloaded - pub is_downloaded: bool, - /// Whether downloading - pub is_downloading: bool, - /// Whether uploaded - pub is_uploaded: bool, - /// Whether uploading - pub is_uploading: bool, - /// Download progress (0.0 - 1.0) - pub download_progress: f64, - /// Upload progress (0.0 - 1.0) - pub upload_progress: f64, - /// Version identifier (for conflict resolution) - pub version_identifier: Option, - /// Content checksum - pub checksum: Option, - /// Favorite status - pub is_favorite: bool, - /// Tag data - pub tag_data: Option>, - /// Custom metadata - #[serde(default)] - pub user_info: HashMap, -} - -impl FileProviderItem { - /// Create from cfk_core Entry - pub fn from_entry( - domain: &DomainIdentifier, - entry: &Entry, - parent: &ItemIdentifier, - ) -> Self { - let filename = entry - .path - .segments - .last() - .cloned() - .unwrap_or_else(|| entry.path.backend.clone()); - - let item_type: ItemType = entry.kind.into(); - let capabilities = match entry.kind { - EntryKind::Directory => ItemCapabilities::directory_default(), - _ => ItemCapabilities::file_default(), - }; - - let content_type = entry.metadata.mime_type.clone().or_else(|| { - // Guess from filename - if filename.ends_with(".txt") { - Some("public.plain-text".to_string()) - } else if filename.ends_with(".pdf") { - Some("com.adobe.pdf".to_string()) - } else if filename.ends_with(".jpg") || filename.ends_with(".jpeg") { - Some("public.jpeg".to_string()) - } else if filename.ends_with(".png") { - Some("public.png".to_string()) - } else { - Some("public.data".to_string()) - } - }); - - Self { - identifier: ItemIdentifier::from_path(domain, &entry.path), - parent_identifier: parent.clone(), - filename, - item_type: item_type as u32, - size: entry.metadata.size, - creation_date: entry.metadata.created, - content_modification_date: entry.metadata.modified, - content_type, - capabilities: capabilities.0, - is_downloaded: false, - is_downloading: false, - is_uploaded: true, - is_uploading: false, - download_progress: 0.0, - upload_progress: 1.0, - version_identifier: entry.metadata.content_hash.clone(), - checksum: entry.metadata.content_hash.clone(), - is_favorite: false, - tag_data: None, - user_info: entry.metadata.custom.clone(), - } - } - - /// Create a root item - pub fn root(domain: &DomainIdentifier, display_name: &str) -> Self { - Self { - identifier: ItemIdentifier::root(), - parent_identifier: ItemIdentifier::root(), - filename: display_name.to_string(), - item_type: ItemType::Directory as u32, - size: None, - creation_date: None, - content_modification_date: None, - content_type: Some("public.folder".to_string()), - capabilities: ItemCapabilities::directory_default().0, - is_downloaded: true, - is_downloading: false, - is_uploaded: true, - is_uploading: false, - download_progress: 1.0, - upload_progress: 1.0, - version_identifier: None, - checksum: None, - is_favorite: false, - tag_data: None, - user_info: HashMap::new(), - } - } - - /// Check if this is a directory - pub fn is_directory(&self) -> bool { - self.item_type == ItemType::Directory as u32 - } - - /// Check if this is a file - pub fn is_file(&self) -> bool { - self.item_type == ItemType::File as u32 - } - - /// Set download state - pub fn set_downloading(&mut self, progress: f64) { - self.is_downloading = progress < 1.0; - self.is_downloaded = progress >= 1.0; - self.download_progress = progress; - } - - /// Set upload state - pub fn set_uploading(&mut self, progress: f64) { - self.is_uploading = progress < 1.0; - self.is_uploaded = progress >= 1.0; - self.upload_progress = progress; - } -} - -/// Item version for conflict resolution -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ItemVersion { - /// Content version (changes when file content changes) - pub content_version: Vec, - /// Metadata version (changes when metadata changes) - pub metadata_version: Vec, -} - -impl ItemVersion { - pub fn new(content: impl AsRef<[u8]>, metadata: impl AsRef<[u8]>) -> Self { - Self { - content_version: content.as_ref().to_vec(), - metadata_version: metadata.as_ref().to_vec(), - } - } - - pub fn from_checksum(checksum: &str) -> Self { - Self { - content_version: checksum.as_bytes().to_vec(), - metadata_version: checksum.as_bytes().to_vec(), - } - } -} - -/// Enumeration page for paginated listing -#[derive(Debug, Clone)] -pub struct EnumerationPage { - pub items: Vec, - pub next_page_token: Option, - pub sync_anchor: Option>, -} - -impl EnumerationPage { - pub fn new(items: Vec) -> Self { - Self { - items, - next_page_token: None, - sync_anchor: None, - } - } - - pub fn with_next_page(mut self, token: String) -> Self { - self.next_page_token = Some(token); - self - } - - pub fn with_sync_anchor(mut self, anchor: Vec) -> Self { - self.sync_anchor = Some(anchor); - self - } -} diff --git a/czech-file-knife/src/cfk-ios/src/lib.rs b/czech-file-knife/src/cfk-ios/src/lib.rs deleted file mode 100644 index 0b1a3ea0e..000000000 --- a/czech-file-knife/src/cfk-ios/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! iOS File Provider extension for Czech File Knife -//! -//! This crate provides iOS integration via Apple's File Provider framework. -//! It exposes a C FFI layer that can be called from Swift/Objective-C. -//! -//! # Architecture -//! -//! ```text -//! ┌─────────────────────────────────────────────────────────────┐ -//! │ iOS App / Extension │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Swift File Provider │ -//! │ (CfkFileProviderExtension.swift) │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ C FFI Bridge Layer │ -//! │ (ffi.rs + CfkBridge.h) │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Rust Core Library │ -//! │ (cfk-core, cfk-providers, cfk-cache) │ -//! └─────────────────────────────────────────────────────────────┘ -//! ``` -//! -//! # Usage -//! -//! 1. Build as a static library for iOS targets -//! 2. Link with your File Provider extension -//! 3. Use the Swift wrapper classes - -#![allow(dead_code)] // FFI functions may not be called from Rust - -pub mod domain; -pub mod error; -pub mod ffi; -pub mod item; -pub mod provider; - -pub use domain::FileDomain; -pub use error::{IosError, IosResult}; -pub use item::{FileProviderItem, ItemIdentifier}; -pub use provider::FileProviderManager; - -use once_cell::sync::OnceCell; -use std::sync::Arc; -use tokio::runtime::Runtime; - -/// Global Tokio runtime for async operations -static RUNTIME: OnceCell = OnceCell::new(); - -/// Initialize the global runtime -pub fn init_runtime() -> &'static Runtime { - RUNTIME.get_or_init(|| { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(2) - .enable_all() - .build() - .expect("Failed to create Tokio runtime") - }) -} - -/// Get the global runtime -pub fn runtime() -> &'static Runtime { - RUNTIME.get().expect("Runtime not initialized") -} - -/// Initialize the iOS integration -/// -/// Must be called before any other FFI functions. -#[no_mangle] -pub extern "C" fn cfk_ios_init() -> i32 { - // Initialize tracing for iOS - #[cfg(debug_assertions)] - { - use tracing_subscriber::prelude::*; - let _ = tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer()) - .try_init(); - } - - // Initialize runtime - let _ = init_runtime(); - - tracing::info!("CFK iOS initialized"); - 0 -} - -/// Shutdown the iOS integration -#[no_mangle] -pub extern "C" fn cfk_ios_shutdown() { - tracing::info!("CFK iOS shutting down"); - // Runtime will be dropped when the process exits -} diff --git a/czech-file-knife/src/cfk-ios/src/provider.rs b/czech-file-knife/src/cfk-ios/src/provider.rs deleted file mode 100644 index d25bd78fc..000000000 --- a/czech-file-knife/src/cfk-ios/src/provider.rs +++ /dev/null @@ -1,499 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! File Provider Manager -//! -//! Coordinates between iOS File Provider and CFK backends. - -use crate::domain::{DomainIdentifier, DomainManager, FileDomain}; -use crate::error::{IosError, IosResult}; -use crate::item::{EnumerationPage, FileProviderItem, ItemIdentifier}; -use bytes::Bytes; -use cfk_core::backend::{ByteStream, SpaceInfo}; -use cfk_core::entry::DirectoryListing; -use cfk_core::operations::{ - CopyOptions, DeleteOptions, ListOptions, MoveOptions, ReadOptions, WriteOptions, -}; -use cfk_core::{Entry, StorageBackend, StorageCapabilities, VirtualPath}; -use futures::StreamExt; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// Placeholder backend for when real backends aren't available -struct PlaceholderBackend { - id: String, -} - -#[async_trait::async_trait] -impl StorageBackend for PlaceholderBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Placeholder" - } - - fn capabilities(&self) -> &StorageCapabilities { - static CAPS: StorageCapabilities = StorageCapabilities { - read: false, - write: false, - delete: false, - rename: false, - copy: false, - list: false, - search: false, - versioning: false, - sharing: false, - offline: false, - streaming: false, - resumable_uploads: false, - content_hashing: false, - }; - &CAPS - } - - async fn is_available(&self) -> bool { - false - } - - async fn get_metadata(&self, _path: &VirtualPath) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn list_directory( - &self, - _path: &VirtualPath, - _options: &ListOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn read_file( - &self, - _path: &VirtualPath, - _options: &ReadOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn write_file( - &self, - _path: &VirtualPath, - _data: Bytes, - _options: &WriteOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn write_file_stream( - &self, - _path: &VirtualPath, - _stream: ByteStream, - _size_hint: Option, - _options: &WriteOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn delete( - &self, - _path: &VirtualPath, - _options: &DeleteOptions, - ) -> cfk_core::CfkResult<()> { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn create_directory(&self, _path: &VirtualPath) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn copy( - &self, - _from: &VirtualPath, - _to: &VirtualPath, - _options: &CopyOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn rename( - &self, - _from: &VirtualPath, - _to: &VirtualPath, - _options: &MoveOptions, - ) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } - - async fn get_space_info(&self) -> cfk_core::CfkResult { - Err(cfk_core::CfkError::Unsupported("Placeholder backend".into())) - } -} - -/// File Provider Manager -/// -/// Main entry point for iOS File Provider operations. -pub struct FileProviderManager { - /// Domain manager - domains: Arc, - /// Active backends - backends: Arc>>>, - /// Local cache directory - cache_dir: PathBuf, - /// Temporary file directory - temp_dir: PathBuf, -} - -impl FileProviderManager { - /// Create a new manager - pub fn new( - storage_path: impl Into, - cache_dir: impl Into, - temp_dir: impl Into, - ) -> Self { - Self { - domains: Arc::new(DomainManager::new(storage_path)), - backends: Arc::new(RwLock::new(HashMap::new())), - cache_dir: cache_dir.into(), - temp_dir: temp_dir.into(), - } - } - - /// Initialize the manager - pub async fn initialize(&self) -> IosResult<()> { - // Load saved domains - self.domains.load().await?; - - // Create cache/temp directories - tokio::fs::create_dir_all(&self.cache_dir) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - tokio::fs::create_dir_all(&self.temp_dir) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - // Initialize backends for enabled domains - for domain in self.domains.list_enabled().await { - if let Err(e) = self.init_backend(&domain).await { - tracing::warn!("Failed to init backend for {}: {}", domain.identifier.0, e); - } - } - - Ok(()) - } - - /// Initialize a backend for a domain - async fn init_backend(&self, domain: &FileDomain) -> IosResult<()> { - // In a full implementation, this would create the appropriate backend - // based on domain.backend_type and domain.config_json - let backend: Arc = Arc::new(PlaceholderBackend { - id: domain.identifier.0.clone(), - }); - - self.backends - .write() - .await - .insert(domain.identifier.clone(), backend); - - Ok(()) - } - - /// Get backend for a domain - async fn get_backend( - &self, - domain_id: &DomainIdentifier, - ) -> IosResult> { - self.backends - .read() - .await - .get(domain_id) - .cloned() - .ok_or_else(|| IosError::NotFound(format!("Backend not found: {}", domain_id.0))) - } - - // --- Domain Management --- - - /// Add a new domain - pub async fn add_domain(&self, domain: FileDomain) -> IosResult<()> { - self.domains.add(domain.clone()).await?; - if domain.enabled { - self.init_backend(&domain).await?; - } - Ok(()) - } - - /// Remove a domain - pub async fn remove_domain(&self, id: &DomainIdentifier) -> IosResult<()> { - self.domains.remove(id).await?; - self.backends.write().await.remove(id); - Ok(()) - } - - /// List all domains - pub async fn list_domains(&self) -> Vec { - self.domains.list().await - } - - // --- Item Operations --- - - /// Get item for identifier - pub async fn item(&self, identifier: &ItemIdentifier) -> IosResult { - if identifier.is_root() { - // Return root item - return Ok(FileProviderItem::root( - &DomainIdentifier::new("default"), - "Root", - )); - } - - let (domain_id, path_str) = identifier - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(identifier.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - let entry = backend - .get_metadata(&path) - .await - .map_err(IosError::Core)?; - - // Determine parent - let parent = if path.segments.is_empty() { - ItemIdentifier::root() - } else { - let mut parent_path = path.clone(); - parent_path.segments.pop(); - ItemIdentifier::from_path(&domain_id, &parent_path) - }; - - Ok(FileProviderItem::from_entry(&domain_id, &entry, &parent)) - } - - /// Enumerate items in a container - pub async fn enumerate_items( - &self, - container: &ItemIdentifier, - _page_token: Option<&str>, - ) -> IosResult { - if container.is_root() { - // List domains as root items - let domains = self.domains.list_enabled().await; - let items: Vec = domains - .iter() - .map(|d| { - let mut item = FileProviderItem::root(&d.identifier, &d.display_name); - item.identifier = ItemIdentifier(d.identifier.0.clone() + ":/"); - item.parent_identifier = ItemIdentifier::root(); - item - }) - .collect(); - - return Ok(EnumerationPage::new(items)); - } - - let (domain_id, path_str) = container - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(container.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - let listing = backend - .list_directory(&path, &ListOptions::default()) - .await - .map_err(IosError::Core)?; - - let items: Vec = listing - .entries - .iter() - .map(|e| FileProviderItem::from_entry(&domain_id, e, container)) - .collect(); - - Ok(EnumerationPage::new(items)) - } - - /// Fetch contents of a file - pub async fn fetch_contents(&self, identifier: &ItemIdentifier) -> IosResult { - let (domain_id, path_str) = identifier - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(identifier.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - let mut stream = backend - .read_file(&path, &ReadOptions::default()) - .await - .map_err(IosError::Core)?; - - // Collect stream into bytes - let mut data = Vec::new(); - while let Some(chunk_result) = stream.next().await { - let chunk = chunk_result.map_err(IosError::Core)?; - data.extend_from_slice(&chunk); - } - - // Write to cache - let cache_path = self.cache_dir.join(&identifier.0.replace([':', '/'], "_")); - tokio::fs::write(&cache_path, &data) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - - Ok(cache_path) - } - - /// Create a new item - pub async fn create_item( - &self, - parent: &ItemIdentifier, - filename: &str, - item_type: u32, - contents: Option<&[u8]>, - ) -> IosResult { - let (domain_id, parent_path_str) = parent - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(parent.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let parent_path = - VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, parent_path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &parent_path_str)); - - let item_path = parent_path.join(filename); - - let entry = if item_type == 1 { - // Directory - backend - .create_directory(&item_path) - .await - .map_err(IosError::Core)? - } else { - // File - let data = contents.map(Bytes::copy_from_slice).unwrap_or_default(); - backend - .write_file(&item_path, data, &WriteOptions::default()) - .await - .map_err(IosError::Core)? - }; - - Ok(FileProviderItem::from_entry(&domain_id, &entry, parent)) - } - - /// Modify item contents - pub async fn modify_item( - &self, - identifier: &ItemIdentifier, - contents: &[u8], - ) -> IosResult { - let (domain_id, path_str) = identifier - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(identifier.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - let entry = backend - .write_file(&path, Bytes::copy_from_slice(contents), &WriteOptions::default()) - .await - .map_err(IosError::Core)?; - - // Determine parent - let parent = if path.segments.len() <= 1 { - ItemIdentifier::root() - } else { - let mut parent_path = path.clone(); - parent_path.segments.pop(); - ItemIdentifier::from_path(&domain_id, &parent_path) - }; - - Ok(FileProviderItem::from_entry(&domain_id, &entry, &parent)) - } - - /// Delete an item - pub async fn delete_item(&self, identifier: &ItemIdentifier) -> IosResult<()> { - let (domain_id, path_str) = identifier - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(identifier.0.clone()))?; - - let backend = self.get_backend(&domain_id).await?; - let path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - backend - .delete(&path, &DeleteOptions::default()) - .await - .map_err(IosError::Core)?; - - // Remove from cache - let cache_path = self.cache_dir.join(&identifier.0.replace([':', '/'], "_")); - let _ = tokio::fs::remove_file(&cache_path).await; - - Ok(()) - } - - /// Rename/move an item - pub async fn reparent_item( - &self, - identifier: &ItemIdentifier, - new_parent: &ItemIdentifier, - new_name: Option<&str>, - ) -> IosResult { - let (domain_id, path_str) = identifier - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(identifier.0.clone()))?; - - let (new_domain_id, new_parent_path_str) = new_parent - .parse() - .ok_or_else(|| IosError::InvalidIdentifier(new_parent.0.clone()))?; - - if domain_id != new_domain_id { - return Err(IosError::NotSupported( - "Cross-domain move not supported".into(), - )); - } - - let backend = self.get_backend(&domain_id).await?; - let from_path = VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &path_str)); - - let new_parent_path = - VirtualPath::parse_uri(&format!("cfk://{}/{}", domain_id.0, new_parent_path_str)) - .unwrap_or_else(|| VirtualPath::new(&domain_id.0, &new_parent_path_str)); - - let new_name = new_name.unwrap_or_else(|| { - from_path.segments.last().map(|s| s.as_str()).unwrap_or("") - }); - - let to_path = new_parent_path.join(new_name); - - let entry = backend - .rename(&from_path, &to_path, &MoveOptions::default()) - .await - .map_err(IosError::Core)?; - - Ok(FileProviderItem::from_entry(&domain_id, &entry, new_parent)) - } - - /// Get storage space info for a domain - pub async fn space_info(&self, domain_id: &DomainIdentifier) -> IosResult<(u64, u64)> { - let backend = self.get_backend(domain_id).await?; - let info = backend.get_space_info().await.map_err(IosError::Core)?; - Ok((info.total.unwrap_or(0), info.used.unwrap_or(0))) - } - - /// Evict item from local cache - pub async fn evict_item(&self, identifier: &ItemIdentifier) -> IosResult<()> { - let cache_path = self.cache_dir.join(&identifier.0.replace([':', '/'], "_")); - tokio::fs::remove_file(&cache_path) - .await - .map_err(|e| IosError::Core(cfk_core::CfkError::Io(e)))?; - Ok(()) - } -} diff --git a/czech-file-knife/src/cfk-ios/swift/CfkBridge.h b/czech-file-knife/src/cfk-ios/swift/CfkBridge.h deleted file mode 100644 index b1211ccc2..000000000 --- a/czech-file-knife/src/cfk-ios/swift/CfkBridge.h +++ /dev/null @@ -1,239 +0,0 @@ -/* - * CfkBridge.h - C bridging header for Czech File Knife iOS integration - * - * This header exposes the Rust FFI functions to Swift/Objective-C. - * Include this in your File Provider extension's bridging header. - */ - -#ifndef CFK_BRIDGE_H -#define CFK_BRIDGE_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* --- Initialization --- */ - -/** - * Initialize the CFK iOS runtime. - * Must be called before any other CFK functions. - * Returns 0 on success, negative on error. - */ -int32_t cfk_ios_init(void); - -/** - * Shutdown the CFK iOS runtime. - * Call when the extension is terminating. - */ -void cfk_ios_shutdown(void); - -/** - * Initialize the provider manager. - * @param storage_path Path to store domain configuration - * @param cache_path Path for cached file contents - * @param temp_path Path for temporary files - * Returns 0 on success. - */ -int32_t cfk_provider_init( - const char *storage_path, - const char *cache_path, - const char *temp_path -); - -/* --- Error Handling --- */ - -/** - * Error structure returned by CFK functions. - */ -typedef struct { - int32_t code; /* Error code (0 = success) */ - char *message; /* Error message (caller must free) */ -} CfkError; - -/** - * Free an error structure. - */ -void cfk_error_free(CfkError *error); - -/** - * Free a string returned by CFK functions. - */ -void cfk_string_free(char *s); - -/* --- Domain Management --- */ - -/** - * Domain information structure. - */ -typedef struct { - char *identifier; /* Unique domain ID */ - char *display_name; /* Name shown in Files app */ - char *backend_type; /* Backend type (dropbox, gdrive, etc.) */ - bool enabled; /* Whether domain is enabled */ -} CfkDomain; - -/** - * Free a domain structure. - */ -void cfk_domain_free(CfkDomain *domain); - -/** - * Add a new domain. - * @param identifier Unique identifier for the domain - * @param display_name Name shown in Files app - * @param backend_type Type of backend (dropbox, gdrive, onedrive, etc.) - * @param config_json JSON configuration for the backend (can be NULL) - * Returns 0 on success. - */ -int32_t cfk_domain_add( - const char *identifier, - const char *display_name, - const char *backend_type, - const char *config_json -); - -/** - * Remove a domain. - * @param identifier Domain identifier to remove - * Returns 0 on success. - */ -int32_t cfk_domain_remove(const char *identifier); - -/* --- Item Operations --- */ - -/** - * Item information structure. - */ -typedef struct { - char *identifier; /* Unique item identifier */ - char *parent_identifier; /* Parent item identifier */ - char *filename; /* File/folder name */ - uint32_t item_type; /* 0=file, 1=directory, 2=symlink */ - uint64_t size; /* File size in bytes */ - bool has_size; /* Whether size is valid */ - uint64_t capabilities; /* Capability flags */ - bool is_downloaded; /* Whether content is cached locally */ - bool is_uploaded; /* Whether content is synced to server */ -} CfkItem; - -/** - * Free an item structure. - */ -void cfk_item_free(CfkItem *item); - -/** - * Get item by identifier. - * @param identifier Item identifier - * @param out_item Output item structure - * Returns 0 on success. - */ -int32_t cfk_item_get( - const char *identifier, - CfkItem *out_item -); - -/** - * Item list for enumeration results. - */ -typedef struct { - CfkItem *items; /* Array of items */ - size_t count; /* Number of items */ - char *next_page_token; /* Token for next page (NULL if no more) */ -} CfkItemList; - -/** - * Free an item list. - */ -void cfk_item_list_free(CfkItemList *list); - -/** - * Enumerate items in a container. - * @param container Container identifier (use "root" for root) - * @param page_token Page token for pagination (can be NULL) - * @param out_list Output item list - * Returns 0 on success. - */ -int32_t cfk_enumerate_items( - const char *container, - const char *page_token, - CfkItemList *out_list -); - -/** - * Fetch file contents to local storage. - * @param identifier File identifier - * @param out_path Output path where file was downloaded - * Returns 0 on success. - */ -int32_t cfk_fetch_contents( - const char *identifier, - char **out_path -); - -/** - * Create a new item. - * @param parent Parent container identifier - * @param filename Name of the new item - * @param item_type Type (0=file, 1=directory) - * @param contents File contents (can be NULL for directories) - * @param contents_len Length of contents - * @param out_item Output item structure - * Returns 0 on success. - */ -int32_t cfk_create_item( - const char *parent, - const char *filename, - uint32_t item_type, - const uint8_t *contents, - size_t contents_len, - CfkItem *out_item -); - -/** - * Delete an item. - * @param identifier Item identifier - * Returns 0 on success. - */ -int32_t cfk_delete_item(const char *identifier); - -/* --- Error Codes (matching NSFileProviderErrorCode) --- */ - -#define CFK_ERROR_SUCCESS 0 -#define CFK_ERROR_NO_SUCH_ITEM -1000 -#define CFK_ERROR_ITEM_ALREADY_EXISTS -1001 -#define CFK_ERROR_NOT_AUTHENTICATED -1002 -#define CFK_ERROR_SERVER_UNREACHABLE -1003 -#define CFK_ERROR_QUOTA_EXCEEDED -1004 -#define CFK_ERROR_FILENAME_INVALID -1005 -#define CFK_ERROR_VERSION_OUT_OF_DATE -1006 -#define CFK_ERROR_CANNOT_SYNC -1010 -#define CFK_ERROR_UNKNOWN -9999 - -/* --- Item Capabilities --- */ - -#define CFK_CAP_READING (1ULL << 0) -#define CFK_CAP_WRITING (1ULL << 1) -#define CFK_CAP_REPARENTING (1ULL << 2) -#define CFK_CAP_RENAMING (1ULL << 3) -#define CFK_CAP_TRASHING (1ULL << 4) -#define CFK_CAP_DELETING (1ULL << 5) -#define CFK_CAP_EVICTING (1ULL << 6) -#define CFK_CAP_ADDING_SUBITEM (1ULL << 7) -#define CFK_CAP_CONTENT_ENUMERATION (1ULL << 8) -#define CFK_CAP_PLAYING (1ULL << 9) - -/* --- Item Types --- */ - -#define CFK_ITEM_TYPE_FILE 0 -#define CFK_ITEM_TYPE_DIRECTORY 1 -#define CFK_ITEM_TYPE_SYMLINK 2 -#define CFK_ITEM_TYPE_PACKAGE 3 - -#ifdef __cplusplus -} -#endif - -#endif /* CFK_BRIDGE_H */ diff --git a/czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift b/czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift deleted file mode 100644 index 4d3a0aeb5..000000000 --- a/czech-file-knife/src/cfk-ios/swift/CfkFileProviderExtension.swift +++ /dev/null @@ -1,337 +0,0 @@ -/* - * CfkFileProviderExtension.swift - File Provider Extension implementation - * - * This class implements NSFileProviderReplicatedExtension for full - * file provider support in iOS/macOS. - */ - -import FileProvider -import UniformTypeIdentifiers - -/// Czech File Knife File Provider Extension -@available(iOS 16.0, macOS 13.0, *) -open class CfkFileProviderExtension: NSObject, NSFileProviderReplicatedExtension { - - // MARK: - Properties - - /// The domain this extension is serving - public let domain: NSFileProviderDomain - - /// Manager instance (initialized lazily) - private var isInitialized = false - - // MARK: - Initialization - - public required init(domain: NSFileProviderDomain) { - self.domain = domain - super.init() - } - - /// Initialize the CFK backend - private func ensureInitialized() throws { - guard !isInitialized else { return } - - let containerURL = NSFileProviderManager(for: domain)?.documentStorageURL - ?? FileManager.default.temporaryDirectory - - let storageURL = containerURL.appendingPathComponent("cfk-storage") - let cacheURL = containerURL.appendingPathComponent("cfk-cache") - let tempURL = containerURL.appendingPathComponent("cfk-temp") - - // Create directories - try FileManager.default.createDirectory(at: storageURL, withIntermediateDirectories: true) - try FileManager.default.createDirectory(at: cacheURL, withIntermediateDirectories: true) - try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true) - - try initializeCfk(storagePath: storageURL, cachePath: cacheURL, tempPath: tempURL) - isInitialized = true - } - - // MARK: - NSFileProviderReplicatedExtension - - public func invalidate() { - shutdownCfk() - isInitialized = false - } - - public func item( - for identifier: NSFileProviderItemIdentifier, - request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void - ) -> Progress { - let progress = Progress(totalUnitCount: 1) - - Task { - do { - try ensureInitialized() - - let id = identifier == .rootContainer ? "root" : identifier.rawValue - let item = try CfkFileProviderItem.fetch(identifier: id) - - progress.completedUnitCount = 1 - completionHandler(item, nil) - } catch { - completionHandler(nil, error) - } - } - - return progress - } - - public func fetchContents( - for itemIdentifier: NSFileProviderItemIdentifier, - version requestedVersion: NSFileProviderItemVersion?, - request: NSFileProviderRequest, - completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void - ) -> Progress { - let progress = Progress(totalUnitCount: 100) - - Task { - do { - try ensureInitialized() - - var pathPtr: UnsafeMutablePointer? - let result = itemIdentifier.rawValue.withCString { id in - cfk_fetch_contents(id, &pathPtr) - } - - guard result == CFK_ERROR_SUCCESS, let path = pathPtr else { - throw CfkError.from(code: result) - } - - let localPath = URL(fileURLWithPath: String(cString: path)) - cfk_string_free(pathPtr) - - let item = try CfkFileProviderItem.fetch(identifier: itemIdentifier.rawValue) - - progress.completedUnitCount = 100 - completionHandler(localPath, item, nil) - } catch { - completionHandler(nil, nil, error) - } - } - - return progress - } - - public func createItem( - basedOn itemTemplate: NSFileProviderItem, - fields: NSFileProviderItemFields, - contents url: URL?, - options: NSFileProviderCreateItemOptions = [], - request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void - ) -> Progress { - let progress = Progress(totalUnitCount: 100) - - Task { - do { - try ensureInitialized() - - let parentId = itemTemplate.parentItemIdentifier == .rootContainer - ? "root" - : itemTemplate.parentItemIdentifier.rawValue - let filename = itemTemplate.filename - let isDirectory = itemTemplate.contentType == .folder - - // Read contents if file - var contentsData: Data? - if let url = url, !isDirectory { - contentsData = try Data(contentsOf: url) - } - - var cItem = CfkItem() - let result: Int32 = parentId.withCString { parent in - filename.withCString { name in - if let data = contentsData { - return data.withUnsafeBytes { bytes in - cfk_create_item( - parent, - name, - isDirectory ? UInt32(CFK_ITEM_TYPE_DIRECTORY) : UInt32(CFK_ITEM_TYPE_FILE), - bytes.baseAddress?.assumingMemoryBound(to: UInt8.self), - data.count, - &cItem - ) - } - } else { - return cfk_create_item( - parent, - name, - isDirectory ? UInt32(CFK_ITEM_TYPE_DIRECTORY) : UInt32(CFK_ITEM_TYPE_FILE), - nil, - 0, - &cItem - ) - } - } - } - - guard result == CFK_ERROR_SUCCESS else { - throw CfkError.from(code: result) - } - - defer { cfk_item_free(&cItem) } - let item = CfkFileProviderItem(from: cItem) - - progress.completedUnitCount = 100 - completionHandler(item, [], false, nil) - } catch { - completionHandler(nil, [], false, error) - } - } - - return progress - } - - public func modifyItem( - _ item: NSFileProviderItem, - baseVersion version: NSFileProviderItemVersion, - changedFields: NSFileProviderItemFields, - contents newContents: URL?, - options: NSFileProviderModifyItemOptions = [], - request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void - ) -> Progress { - let progress = Progress(totalUnitCount: 100) - - Task { - do { - try ensureInitialized() - - // For now, just refetch the item - let updated = try CfkFileProviderItem.fetch(identifier: item.itemIdentifier.rawValue) - - progress.completedUnitCount = 100 - completionHandler(updated, [], false, nil) - } catch { - completionHandler(nil, [], false, error) - } - } - - return progress - } - - public func deleteItem( - identifier: NSFileProviderItemIdentifier, - baseVersion version: NSFileProviderItemVersion, - options: NSFileProviderDeleteItemOptions = [], - request: NSFileProviderRequest, - completionHandler: @escaping (Error?) -> Void - ) -> Progress { - let progress = Progress(totalUnitCount: 1) - - Task { - do { - try ensureInitialized() - - let result = identifier.rawValue.withCString { id in - cfk_delete_item(id) - } - - guard result == CFK_ERROR_SUCCESS else { - throw CfkError.from(code: result) - } - - progress.completedUnitCount = 1 - completionHandler(nil) - } catch { - completionHandler(error) - } - } - - return progress - } - - // MARK: - Enumeration - - public func enumerator( - for containerItemIdentifier: NSFileProviderItemIdentifier, - request: NSFileProviderRequest - ) throws -> NSFileProviderEnumerator { - try ensureInitialized() - return CfkEnumerator(containerIdentifier: containerItemIdentifier) - } -} - -// MARK: - Enumerator - -@available(iOS 16.0, macOS 13.0, *) -class CfkEnumerator: NSObject, NSFileProviderEnumerator { - - let containerIdentifier: NSFileProviderItemIdentifier - - init(containerIdentifier: NSFileProviderItemIdentifier) { - self.containerIdentifier = containerIdentifier - super.init() - } - - func invalidate() { - // Clean up if needed - } - - func enumerateItems( - for observer: NSFileProviderEnumerationObserver, - startingAt page: NSFileProviderPage - ) { - let containerId = containerIdentifier == .rootContainer - ? "root" - : containerIdentifier.rawValue - - var itemList = CfkItemList() - let pageToken: String? = page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage - || page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage - ? nil - : String(data: page.rawValue, encoding: .utf8) - - let result: Int32 = containerId.withCString { container in - if let token = pageToken { - return token.withCString { tokenPtr in - cfk_enumerate_items(container, tokenPtr, &itemList) - } - } else { - return cfk_enumerate_items(container, nil, &itemList) - } - } - - guard result == CFK_ERROR_SUCCESS else { - observer.finishEnumeratingWithError(CfkError.from(code: result).fileProviderError) - return - } - - defer { cfk_item_list_free(&itemList) } - - // Convert items - var items: [CfkFileProviderItem] = [] - if let itemsPtr = itemList.items { - for i in 0.. Void) { - // Return a simple anchor based on current time - let anchor = NSFileProviderSyncAnchor(Date().timeIntervalSince1970.description.data(using: .utf8)!) - completionHandler(anchor) - } -} diff --git a/czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift b/czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift deleted file mode 100644 index 43c4721bb..000000000 --- a/czech-file-knife/src/cfk-ios/swift/CfkFileProviderItem.swift +++ /dev/null @@ -1,243 +0,0 @@ -/* - * CfkFileProviderItem.swift - NSFileProviderItem implementation - * - * This class wraps CfkItem from the Rust FFI layer and implements - * the NSFileProviderItem protocol for use in File Provider extensions. - */ - -import FileProvider -import UniformTypeIdentifiers - -/// File Provider Item backed by CFK Rust library -@available(iOS 16.0, macOS 13.0, *) -public class CfkFileProviderItem: NSObject, NSFileProviderItem { - - // MARK: - Properties - - private let cfkItem: CfkItemWrapper - - /// Unique identifier for this item - public var itemIdentifier: NSFileProviderItemIdentifier { - return NSFileProviderItemIdentifier(cfkItem.identifier) - } - - /// Parent folder identifier - public var parentItemIdentifier: NSFileProviderItemIdentifier { - if cfkItem.parentIdentifier == "root" { - return .rootContainer - } - return NSFileProviderItemIdentifier(cfkItem.parentIdentifier) - } - - /// File or folder name - public var filename: String { - return cfkItem.filename - } - - /// Content type (UTI) - public var contentType: UTType { - if cfkItem.itemType == CFK_ITEM_TYPE_DIRECTORY { - return .folder - } - // Infer from filename - if let type = UTType(filenameExtension: (filename as NSString).pathExtension) { - return type - } - return .data - } - - /// Document size - public var documentSize: NSNumber? { - guard cfkItem.hasSize else { return nil } - return NSNumber(value: cfkItem.size) - } - - /// Item capabilities - public var capabilities: NSFileProviderItemCapabilities { - var caps: NSFileProviderItemCapabilities = [] - - if cfkItem.capabilities & UInt64(CFK_CAP_READING) != 0 { - caps.insert(.allowsReading) - } - if cfkItem.capabilities & UInt64(CFK_CAP_WRITING) != 0 { - caps.insert(.allowsWriting) - } - if cfkItem.capabilities & UInt64(CFK_CAP_REPARENTING) != 0 { - caps.insert(.allowsReparenting) - } - if cfkItem.capabilities & UInt64(CFK_CAP_RENAMING) != 0 { - caps.insert(.allowsRenaming) - } - if cfkItem.capabilities & UInt64(CFK_CAP_TRASHING) != 0 { - caps.insert(.allowsTrashing) - } - if cfkItem.capabilities & UInt64(CFK_CAP_DELETING) != 0 { - caps.insert(.allowsDeleting) - } - if cfkItem.capabilities & UInt64(CFK_CAP_EVICTING) != 0 { - caps.insert(.allowsEvicting) - } - if cfkItem.capabilities & UInt64(CFK_CAP_ADDING_SUBITEM) != 0 { - caps.insert(.allowsAddingSubItems) - } - if cfkItem.capabilities & UInt64(CFK_CAP_CONTENT_ENUMERATION) != 0 { - caps.insert(.allowsContentEnumerating) - } - - return caps - } - - /// Whether content is downloaded - public var isDownloaded: Bool { - return cfkItem.isDownloaded - } - - /// Whether content is uploaded - public var isUploaded: Bool { - return cfkItem.isUploaded - } - - // MARK: - Initialization - - /// Initialize from CfkItem C structure - public init(from cItem: CfkItem) { - self.cfkItem = CfkItemWrapper(from: cItem) - super.init() - } - - /// Fetch item from Rust backend - public static func fetch(identifier: String) throws -> CfkFileProviderItem { - var cItem = CfkItem() - let result = identifier.withCString { ptr in - cfk_item_get(ptr, &cItem) - } - - guard result == CFK_ERROR_SUCCESS else { - throw CfkError.from(code: result) - } - - defer { cfk_item_free(&cItem) } - return CfkFileProviderItem(from: cItem) - } -} - -// MARK: - Swift Wrapper for C Structure - -/// Swift-friendly wrapper around CfkItem -private struct CfkItemWrapper { - let identifier: String - let parentIdentifier: String - let filename: String - let itemType: UInt32 - let size: UInt64 - let hasSize: Bool - let capabilities: UInt64 - let isDownloaded: Bool - let isUploaded: Bool - - init(from cItem: CfkItem) { - self.identifier = cItem.identifier.map { String(cString: $0) } ?? "" - self.parentIdentifier = cItem.parent_identifier.map { String(cString: $0) } ?? "root" - self.filename = cItem.filename.map { String(cString: $0) } ?? "" - self.itemType = cItem.item_type - self.size = cItem.size - self.hasSize = cItem.has_size - self.capabilities = cItem.capabilities - self.isDownloaded = cItem.is_downloaded - self.isUploaded = cItem.is_uploaded - } -} - -// MARK: - Error Handling - -/// CFK Error type -public enum CfkError: Error { - case noSuchItem - case itemAlreadyExists - case notAuthenticated - case serverUnreachable - case quotaExceeded - case filenameInvalid - case versionOutOfDate - case cannotSync - case unknown(Int32) - - static func from(code: Int32) -> CfkError { - switch code { - case CFK_ERROR_NO_SUCH_ITEM: - return .noSuchItem - case CFK_ERROR_ITEM_ALREADY_EXISTS: - return .itemAlreadyExists - case CFK_ERROR_NOT_AUTHENTICATED: - return .notAuthenticated - case CFK_ERROR_SERVER_UNREACHABLE: - return .serverUnreachable - case CFK_ERROR_QUOTA_EXCEEDED: - return .quotaExceeded - case CFK_ERROR_FILENAME_INVALID: - return .filenameInvalid - case CFK_ERROR_VERSION_OUT_OF_DATE: - return .versionOutOfDate - case CFK_ERROR_CANNOT_SYNC: - return .cannotSync - default: - return .unknown(code) - } - } - - /// Convert to NSFileProviderError - @available(iOS 16.0, macOS 13.0, *) - public var fileProviderError: NSError { - let code: NSFileProviderError.Code - switch self { - case .noSuchItem: - code = .noSuchItem - case .itemAlreadyExists: - code = .filenameCollision - case .notAuthenticated: - code = .notAuthenticated - case .serverUnreachable: - code = .serverUnreachable - case .quotaExceeded: - code = .insufficientQuota - case .filenameInvalid: - code = .filenameCollision - case .versionOutOfDate: - code = .newerExtensionVersionFound - case .cannotSync: - code = .cannotSynchronize - case .unknown: - code = .cannotSynchronize - } - return NSError(domain: NSFileProviderErrorDomain, code: code.rawValue) - } -} - -// MARK: - Initialization Helper - -/// Initialize CFK library -public func initializeCfk(storagePath: URL, cachePath: URL, tempPath: URL) throws { - // Initialize runtime - let initResult = cfk_ios_init() - guard initResult == 0 else { - throw CfkError.unknown(initResult) - } - - // Initialize provider - let result = storagePath.path.withCString { storage in - cachePath.path.withCString { cache in - tempPath.path.withCString { temp in - cfk_provider_init(storage, cache, temp) - } - } - } - - guard result == CFK_ERROR_SUCCESS else { - throw CfkError.from(code: result) - } -} - -/// Shutdown CFK library -public func shutdownCfk() { - cfk_ios_shutdown() -} diff --git a/czech-file-knife/src/cfk-providers/Cargo.toml b/czech-file-knife/src/cfk-providers/Cargo.toml deleted file mode 100644 index 967930ba6..000000000 --- a/czech-file-knife/src/cfk-providers/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "cfk-providers" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Storage backend providers for Czech File Knife" - -[features] -default = ["local"] -local = [] -dropbox = ["oauth2"] -gdrive = ["oauth2"] -onedrive = ["oauth2"] -box = ["oauth2"] -s3 = [] -ipfs = [] -webdav = [] -afs = [] -ninep = [] -sftp = [] -nfs = [] -smb = [] -syncthing = [] -all = ["local", "dropbox", "gdrive", "onedrive", "box", "s3", "ipfs", "webdav", "afs", "ninep", "sftp", "nfs", "smb", "syncthing"] - -[dependencies] -cfk-core = { path = "../cfk-core" } -async-trait.workspace = true -bytes.workspace = true -chrono.workspace = true -futures.workspace = true -reqwest = { workspace = true, optional = true } -oauth2 = { workspace = true, optional = true } -serde.workspace = true -serde_json.workspace = true -thiserror.workspace = true -tokio.workspace = true -tracing.workspace = true -blake3.workspace = true -libc.workspace = true - -[dev-dependencies] -tempfile = "3.24" diff --git a/czech-file-knife/src/cfk-providers/src/afs.rs b/czech-file-knife/src/cfk-providers/src/afs.rs deleted file mode 100644 index b86088dc0..000000000 --- a/czech-file-knife/src/cfk-providers/src/afs.rs +++ /dev/null @@ -1,531 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Andrew File System (AFS) storage backend -//! -//! OpenAFS client implementation with Kerberos authentication. -//! Designed for academic and enterprise distributed file systems. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use std::path::{Path, PathBuf}; -use std::process::Command; -use tokio::fs; - -/// AFS backend configuration -#[derive(Debug, Clone)] -pub struct AfsConfig { - /// AFS cell name (e.g., "athena.mit.edu") - pub cell: String, - /// Local AFS mount point (typically /afs) - pub mount_point: PathBuf, - /// Kerberos principal (optional, uses default if not set) - pub principal: Option, - /// Keytab file path (optional, for service accounts) - pub keytab: Option, -} - -impl Default for AfsConfig { - fn default() -> Self { - Self { - cell: String::new(), - mount_point: PathBuf::from("/afs"), - principal: None, - keytab: None, - } - } -} - -/// AFS storage backend -pub struct AfsBackend { - id: String, - config: AfsConfig, - capabilities: StorageCapabilities, -} - -impl AfsBackend { - pub fn new(id: impl Into, config: AfsConfig) -> Self { - Self { - id: id.into(), - config, - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: false, - versioning: false, - sharing: true, // ACLs - streaming: true, - resume: true, - watch: false, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - } - } - - /// Authenticate with Kerberos and obtain AFS tokens - pub fn authenticate(&self) -> CfkResult<()> { - // Use kinit for Kerberos authentication - if let Some(ref keytab) = self.config.keytab { - let principal = self - .config - .principal - .as_deref() - .ok_or_else(|| CfkError::Auth("Principal required with keytab".into()))?; - - let status = Command::new("kinit") - .args(["-k", "-t", keytab.to_str().unwrap(), principal]) - .status() - .map_err(|e| CfkError::Auth(format!("kinit failed: {}", e)))?; - - if !status.success() { - return Err(CfkError::Auth("kinit failed".into())); - } - } else if let Some(ref principal) = self.config.principal { - // Interactive kinit - let status = Command::new("kinit") - .arg(principal) - .status() - .map_err(|e| CfkError::Auth(format!("kinit failed: {}", e)))?; - - if !status.success() { - return Err(CfkError::Auth("kinit failed".into())); - } - } - - // Get AFS tokens using aklog - let status = Command::new("aklog") - .args(["-c", &self.config.cell]) - .status() - .map_err(|e| CfkError::Auth(format!("aklog failed: {}", e)))?; - - if !status.success() { - return Err(CfkError::Auth("aklog failed".into())); - } - - Ok(()) - } - - /// Check if we have valid AFS tokens - pub fn has_tokens(&self) -> bool { - Command::new("tokens") - .status() - .map(|s| s.success()) - .unwrap_or(false) - } - - /// Convert VirtualPath to local filesystem path - fn to_local_path(&self, path: &VirtualPath) -> PathBuf { - let mut local_path = self.config.mount_point.clone(); - local_path.push(&self.config.cell); - for segment in &path.segments { - local_path.push(segment); - } - local_path - } - - /// Convert local path to VirtualPath - fn to_virtual_path(&self, local_path: &Path) -> CfkResult { - let cell_path = self.config.mount_point.join(&self.config.cell); - let relative = local_path - .strip_prefix(&cell_path) - .map_err(|_| CfkError::InvalidPath("Path not in AFS cell".into()))?; - - let segments: Vec = relative - .components() - .filter_map(|c| c.as_os_str().to_str().map(String::from)) - .collect(); - - Ok(VirtualPath { - backend_id: self.id.clone(), - segments, - }) - } - - /// Get AFS ACL for a directory - pub fn get_acl(&self, path: &VirtualPath) -> CfkResult { - let local_path = self.to_local_path(path); - - let output = Command::new("fs") - .args(["listacl", local_path.to_str().unwrap()]) - .output() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !output.status.success() { - return Err(CfkError::ProviderApi { - provider: "afs".into(), - message: String::from_utf8_lossy(&output.stderr).to_string(), - }); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - parse_acl(&stdout) - } - - /// Set AFS ACL - pub fn set_acl(&self, path: &VirtualPath, principal: &str, rights: &str) -> CfkResult<()> { - let local_path = self.to_local_path(path); - - let status = Command::new("fs") - .args([ - "setacl", - local_path.to_str().unwrap(), - principal, - rights, - ]) - .status() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !status.success() { - return Err(CfkError::ProviderApi { - provider: "afs".into(), - message: "Failed to set ACL".into(), - }); - } - - Ok(()) - } - - /// Get quota information for a volume - pub fn get_quota(&self, path: &VirtualPath) -> CfkResult { - let local_path = self.to_local_path(path); - - let output = Command::new("fs") - .args(["listquota", local_path.to_str().unwrap()]) - .output() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !output.status.success() { - return Err(CfkError::ProviderApi { - provider: "afs".into(), - message: String::from_utf8_lossy(&output.stderr).to_string(), - }); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - parse_quota(&stdout) - } -} - -/// AFS Access Control List -#[derive(Debug, Clone, Default)] -pub struct AfsAcl { - pub positive: Vec, - pub negative: Vec, -} - -/// AFS ACL entry -#[derive(Debug, Clone)] -pub struct AfsAclEntry { - pub principal: String, - pub rights: String, -} - -/// AFS Quota information -#[derive(Debug, Clone, Default)] -pub struct AfsQuota { - pub volume: String, - pub quota_kb: u64, - pub used_kb: u64, - pub percent_used: f32, -} - -/// Parse AFS ACL output -fn parse_acl(output: &str) -> CfkResult { - let mut acl = AfsAcl::default(); - let mut in_positive = true; - - for line in output.lines() { - let line = line.trim(); - if line.starts_with("Access list for") || line.is_empty() { - continue; - } - if line.starts_with("Normal rights:") { - in_positive = true; - continue; - } - if line.starts_with("Negative rights:") { - in_positive = false; - continue; - } - - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 2 { - let entry = AfsAclEntry { - principal: parts[0].to_string(), - rights: parts[1].to_string(), - }; - if in_positive { - acl.positive.push(entry); - } else { - acl.negative.push(entry); - } - } - } - - Ok(acl) -} - -/// Parse AFS quota output -fn parse_quota(output: &str) -> CfkResult { - let mut quota = AfsQuota::default(); - - for line in output.lines().skip(1) { - // Skip header - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 4 { - quota.volume = parts[0].to_string(); - quota.quota_kb = parts[1].parse().unwrap_or(0); - quota.used_kb = parts[2].parse().unwrap_or(0); - quota.percent_used = parts[3] - .trim_end_matches('%') - .parse() - .unwrap_or(0.0); - break; - } - } - - Ok(quota) -} - -#[async_trait] -impl StorageBackend for AfsBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "AFS" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - let cell_path = self.config.mount_point.join(&self.config.cell); - cell_path.exists() && self.has_tokens() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let local_path = self.to_local_path(path); - - let metadata = fs::metadata(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - let kind = if metadata.is_dir() { - EntryKind::Directory - } else if metadata.is_symlink() { - EntryKind::Symlink - } else { - EntryKind::File - }; - - let mut meta = Metadata::default(); - meta.size = Some(metadata.len()); - - #[cfg(unix)] - { - use std::os::unix::fs::MetadataExt; - meta.permissions = Some(metadata.mode()); - meta.uid = Some(metadata.uid()); - meta.gid = Some(metadata.gid()); - } - - if let Ok(modified) = metadata.modified() { - meta.modified = Some(chrono::DateTime::from(modified)); - } - if let Ok(created) = metadata.created() { - meta.created = Some(chrono::DateTime::from(created)); - } - - Ok(Entry { - path: path.clone(), - kind, - metadata: meta, - }) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let local_path = self.to_local_path(path); - - let mut entries = Vec::new(); - let mut dir = fs::read_dir(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - while let Some(entry) = dir - .next_entry() - .await - .map_err(|e| CfkError::Io(e.to_string()))? - { - let entry_path = entry.path(); - let virtual_path = self.to_virtual_path(&entry_path)?; - - let metadata = entry - .metadata() - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - let kind = if metadata.is_dir() { - EntryKind::Directory - } else if metadata.is_symlink() { - EntryKind::Symlink - } else { - EntryKind::File - }; - - let mut meta = Metadata::default(); - meta.size = Some(metadata.len()); - - if let Ok(modified) = metadata.modified() { - meta.modified = Some(chrono::DateTime::from(modified)); - } - - entries.push(Entry { - path: virtual_path, - kind, - metadata: meta, - }); - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let local_path = self.to_local_path(path); - - let data = fs::read(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - Ok(Bytes::from(data)) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let local_path = self.to_local_path(path); - - // Ensure parent directory exists - if let Some(parent) = local_path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - } - - fs::write(&local_path, &data) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - self.get_metadata(path).await - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let local_path = self.to_local_path(path); - - let metadata = fs::metadata(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - if metadata.is_dir() { - fs::remove_dir_all(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - } else { - fs::remove_file(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let local_path = self.to_local_path(path); - - fs::create_dir_all(&local_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - self.get_metadata(path).await - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_local_path(from); - let to_path = self.to_local_path(to); - - // Ensure parent directory exists - if let Some(parent) = to_path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - } - - fs::copy(&from_path, &to_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - self.get_metadata(to).await - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_local_path(from); - let to_path = self.to_local_path(to); - - // Ensure parent directory exists - if let Some(parent) = to_path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - } - - fs::rename(&from_path, &to_path) - .await - .map_err(|e| CfkError::Io(e.to_string()))?; - - self.get_metadata(to).await - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - let root = VirtualPath::new(&self.id, ""); - let quota = self.get_quota(&root)?; - - let total = quota.quota_kb * 1024; - let used = quota.used_kb * 1024; - let available = total.saturating_sub(used); - - Ok((available, total)) - } -} - -/// AFS rights constants -pub mod rights { - /// Read files - pub const READ: &str = "r"; - /// List directory - pub const LIST: &str = "l"; - /// Insert (create) files - pub const INSERT: &str = "i"; - /// Delete files - pub const DELETE: &str = "d"; - /// Write/modify files - pub const WRITE: &str = "w"; - /// Lock files - pub const LOCK: &str = "k"; - /// Administer ACLs - pub const ADMIN: &str = "a"; - - /// All rights - pub const ALL: &str = "rlidwka"; - /// Read-only - pub const READ_ONLY: &str = "rl"; - /// Write (no admin) - pub const WRITE_NO_ADMIN: &str = "rlidwk"; -} diff --git a/czech-file-knife/src/cfk-providers/src/box_com.rs b/czech-file-knife/src/cfk-providers/src/box_com.rs deleted file mode 100644 index 22cde768b..000000000 --- a/czech-file-knife/src/cfk-providers/src/box_com.rs +++ /dev/null @@ -1,722 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Box.com storage backend -//! -//! Box API implementation with OAuth 2.0 authentication. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, - PkceCodeVerifier, RedirectUrl, Scope, TokenUrl, -}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -const BOX_AUTH_URL: &str = "https://account.box.com/api/oauth2/authorize"; -const BOX_TOKEN_URL: &str = "https://api.box.com/oauth2/token"; -const BOX_API_URL: &str = "https://api.box.com/2.0"; -const BOX_UPLOAD_URL: &str = "https://upload.box.com/api/2.0"; - -/// Box OAuth tokens -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BoxTokens { - pub access_token: String, - pub refresh_token: Option, - pub expires_at: Option>, -} - -/// Box backend configuration -#[derive(Debug, Clone)] -pub struct BoxConfig { - pub client_id: String, - pub client_secret: String, - pub redirect_uri: String, -} - -/// Box storage backend -pub struct BoxBackend { - id: String, - config: BoxConfig, - tokens: Arc>>, - http: Client, - capabilities: StorageCapabilities, - /// Cache of path to folder ID - folder_cache: Arc>>, -} - -impl BoxBackend { - pub fn new(id: impl Into, config: BoxConfig) -> Self { - Self { - id: id.into(), - config, - tokens: Arc::new(RwLock::new(None)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: true, - versioning: true, - sharing: true, - streaming: true, - resume: true, - watch: true, - metadata: true, - thumbnails: true, - max_file_size: Some(150 * 1024 * 1024 * 1024), // 150GB for enterprise - }, - folder_cache: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Start OAuth 2.0 flow - pub fn start_auth(&self) -> (String, PkceCodeVerifier) { - let client = BasicClient::new(ClientId::new(self.config.client_id.clone())) - .set_client_secret(ClientSecret::new(self.config.client_secret.clone())) - .set_auth_uri(AuthUrl::new(BOX_AUTH_URL.to_string()).unwrap()) - .set_token_uri(TokenUrl::new(BOX_TOKEN_URL.to_string()).unwrap()) - .set_redirect_uri(RedirectUrl::new(self.config.redirect_uri.clone()).unwrap()); - - let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - - let (auth_url, _csrf_token) = client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new("root_readwrite".to_string())) - .set_pkce_challenge(pkce_challenge) - .url(); - - (auth_url.to_string(), pkce_verifier) - } - - /// Complete OAuth flow - pub async fn complete_auth( - &self, - code: &str, - _verifier: PkceCodeVerifier, - ) -> CfkResult { - let params = [ - ("grant_type", "authorization_code"), - ("code", code), - ("client_id", &self.config.client_id), - ("client_secret", &self.config.client_secret), - ]; - - let response = self - .http - .post(BOX_TOKEN_URL) - .form(¶ms) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::Auth(format!("Token exchange failed: {}", error_text))); - } - - #[derive(Deserialize)] - struct TokenResponse { - access_token: String, - refresh_token: Option, - expires_in: Option, - } - - let token_resp: TokenResponse = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let tokens = BoxTokens { - access_token: token_resp.access_token, - refresh_token: token_resp.refresh_token, - expires_at: token_resp - .expires_in - .map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - }; - - *self.tokens.write().await = Some(tokens.clone()); - Ok(tokens) - } - - /// Set tokens directly - pub async fn set_tokens(&self, tokens: BoxTokens) { - *self.tokens.write().await = Some(tokens); - } - - /// Get access token - async fn get_access_token(&self) -> CfkResult { - let tokens = self.tokens.read().await; - tokens - .as_ref() - .map(|t| t.access_token.clone()) - .ok_or_else(|| CfkError::Auth("Not authenticated".into())) - } - - /// Resolve path to folder ID - async fn resolve_folder_id(&self, path: &VirtualPath) -> CfkResult { - if path.segments.is_empty() { - return Ok("0".to_string()); // Root folder - } - - let path_str = path.to_string(); - { - let cache = self.folder_cache.read().await; - if let Some(id) = cache.get(&path_str) { - return Ok(id.clone()); - } - } - - // Navigate path - let mut current_id = "0".to_string(); - - for segment in &path.segments { - let response = self - .http - .get(format!("{}/folders/{}/items", BOX_API_URL, current_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[("fields", "id,name,type")]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct ItemList { - entries: Vec, - } - - let list: ItemList = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - current_id = list - .entries - .iter() - .find(|e| e.name == *segment) - .map(|e| e.id.clone()) - .ok_or_else(|| CfkError::NotFound(path.to_string()))?; - } - - // Cache - { - let mut cache = self.folder_cache.write().await; - cache.insert(path_str, current_id.clone()); - } - - Ok(current_id) - } -} - -/// Box item metadata -#[derive(Debug, Clone, Deserialize)] -struct BoxItem { - id: String, - #[serde(rename = "type")] - item_type: String, - name: String, - size: Option, - created_at: Option, - modified_at: Option, - sha1: Option, -} - -impl BoxItem { - fn to_entry(&self, backend_id: &str, base_path: &str) -> Entry { - let path_str = if base_path.is_empty() { - self.name.clone() - } else { - format!("{}/{}", base_path, self.name) - }; - let virtual_path = VirtualPath::new(backend_id, &path_str); - - let kind = if self.item_type == "folder" { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = self.size; - - if let Some(ref modified) = self.modified_at { - if let Ok(dt) = DateTime::parse_from_rfc3339(modified) { - metadata.modified = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref created) = self.created_at { - if let Ok(dt) = DateTime::parse_from_rfc3339(created) { - metadata.created = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref sha1) = self.sha1 { - metadata.checksum = Some(sha1.clone()); - } - - Entry { - path: virtual_path, - kind, - metadata, - } - } -} - -#[async_trait] -impl StorageBackend for BoxBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Box" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.tokens.read().await.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let item_id = self.resolve_folder_id(path).await?; - - // Try as folder first - let response = self - .http - .get(format!("{}/folders/{}", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if response.status().is_success() { - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if path.segments.len() > 1 { - path.segments[..path.segments.len() - 1].join("/") - } else { - String::new() - }; - - return Ok(item.to_entry(&self.id, &base_path)); - } - - // Try as file - let response = self - .http - .get(format!("{}/files/{}", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if path.segments.len() > 1 { - path.segments[..path.segments.len() - 1].join("/") - } else { - String::new() - }; - - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let folder_id = self.resolve_folder_id(path).await?; - - let mut entries = Vec::new(); - let mut offset = 0; - let limit = 1000; - - loop { - let response = self - .http - .get(format!("{}/folders/{}/items", BOX_API_URL, folder_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[ - ("fields", "id,type,name,size,created_at,modified_at,sha1"), - ("limit", &limit.to_string()), - ("offset", &offset.to_string()), - ]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct ItemList { - entries: Vec, - total_count: u64, - } - - let list: ItemList = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = path.segments.join("/"); - - for item in &list.entries { - entries.push(item.to_entry(&self.id, &base_path)); - } - - offset += list.entries.len(); - if offset as u64 >= list.total_count { - break; - } - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let file_id = self.resolve_folder_id(path).await?; - - let response = self - .http - .get(format!("{}/files/{}/content", BOX_API_URL, file_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "box".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let parent_path = if path.segments.len() > 1 { - VirtualPath::new(&self.id, &path.segments[..path.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - - let parent_id = self.resolve_folder_id(&parent_path).await?; - let name = path.segments.last().cloned().unwrap_or_default(); - - // Use multipart upload - let boundary = "cfk_box_boundary"; - - #[derive(Serialize)] - struct FileAttributes { - name: String, - parent: Parent, - } - - #[derive(Serialize)] - struct Parent { - id: String, - } - - let attributes = FileAttributes { - name: name.clone(), - parent: Parent { id: parent_id }, - }; - - let attributes_json = - serde_json::to_string(&attributes).map_err(|e| CfkError::Serialization(e.to_string()))?; - - let body = format!( - "--{}\r\nContent-Disposition: form-data; name=\"attributes\"\r\n\r\n{}\r\n--{}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{}\"\r\nContent-Type: application/octet-stream\r\n\r\n", - boundary, attributes_json, boundary, name - ); - - let mut full_body = body.into_bytes(); - full_body.extend_from_slice(&data); - full_body.extend_from_slice(format!("\r\n--{}--", boundary).as_bytes()); - - let response = self - .http - .post(format!("{}/files/content", BOX_UPLOAD_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .header( - "Content-Type", - format!("multipart/form-data; boundary={}", boundary), - ) - .body(full_body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct UploadResponse { - entries: Vec, - } - - let upload_resp: UploadResponse = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let item = upload_resp - .entries - .first() - .ok_or_else(|| CfkError::ProviderApi { - provider: "box".into(), - message: "No file returned".into(), - })?; - - let base_path = parent_path.segments.join("/"); - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let item_id = self.resolve_folder_id(path).await?; - - // Try as file first - let response = self - .http - .delete(format!("{}/files/{}", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if response.status().is_success() || response.status() == reqwest::StatusCode::NO_CONTENT { - return Ok(()); - } - - // Try as folder - let response = self - .http - .delete(format!("{}/folders/{}?recursive=true", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != reqwest::StatusCode::NO_CONTENT { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "box".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let parent_path = if path.segments.len() > 1 { - VirtualPath::new(&self.id, &path.segments[..path.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - - let parent_id = self.resolve_folder_id(&parent_path).await?; - let name = path.segments.last().cloned().unwrap_or_default(); - - #[derive(Serialize)] - struct CreateFolder { - name: String, - parent: Parent, - } - - #[derive(Serialize)] - struct Parent { - id: String, - } - - let body = CreateFolder { - name, - parent: Parent { id: parent_id }, - }; - - let response = self - .http - .post(format!("{}/folders", BOX_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = parent_path.segments.join("/"); - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let item_id = self.resolve_folder_id(from).await?; - let parent_path = if to.segments.len() > 1 { - VirtualPath::new(&self.id, &to.segments[..to.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - let parent_id = self.resolve_folder_id(&parent_path).await?; - let name = to.segments.last().cloned().unwrap_or_default(); - - #[derive(Serialize)] - struct CopyRequest { - parent: Parent, - name: String, - } - - #[derive(Serialize)] - struct Parent { - id: String, - } - - let body = CopyRequest { - parent: Parent { id: parent_id }, - name, - }; - - // Try as file - let response = self - .http - .post(format!("{}/files/{}/copy", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if response.status().is_success() { - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = parent_path.segments.join("/"); - return Ok(item.to_entry(&self.id, &base_path)); - } - - // Try as folder - let response = self - .http - .post(format!("{}/folders/{}/copy", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = parent_path.segments.join("/"); - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let item_id = self.resolve_folder_id(from).await?; - let name = to.segments.last().cloned().unwrap_or_default(); - - #[derive(Serialize)] - struct RenameRequest { - name: String, - } - - let body = RenameRequest { name }; - - // Try as file - let response = self - .http - .put(format!("{}/files/{}", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if response.status().is_success() { - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if to.segments.len() > 1 { - to.segments[..to.segments.len() - 1].join("/") - } else { - String::new() - }; - return Ok(item.to_entry(&self.id, &base_path)); - } - - // Try as folder - let response = self - .http - .put(format!("{}/folders/{}", BOX_API_URL, item_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: BoxItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if to.segments.len() > 1 { - to.segments[..to.segments.len() - 1].join("/") - } else { - String::new() - }; - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - let response = self - .http - .get(format!("{}/users/me", BOX_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[("fields", "space_amount,space_used")]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct User { - space_amount: Option, - space_used: Option, - } - - let user: User = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let total = user.space_amount.unwrap_or(0); - let used = user.space_used.unwrap_or(0); - let available = total.saturating_sub(used); - - Ok((available, total)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/ceph.rs b/czech-file-knife/src/cfk-providers/src/ceph.rs deleted file mode 100644 index d95598180..000000000 --- a/czech-file-knife/src/cfk-providers/src/ceph.rs +++ /dev/null @@ -1,455 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Ceph storage backend -//! -//! Distributed object storage via RADOS, CephFS, or S3/Swift gateway. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; - -/// Ceph access mode -#[derive(Debug, Clone)] -pub enum CephMode { - /// Direct RADOS object access - Rados { - monitors: Vec, - user: String, - key: String, - pool: String, - }, - /// CephFS filesystem access - CephFs { - monitors: Vec, - user: String, - key: String, - mount_path: String, - }, - /// Ceph Object Gateway (S3-compatible) - Rgw { - endpoint: String, - access_key: String, - secret_key: String, - bucket: String, - }, -} - -/// Ceph backend configuration -#[derive(Debug, Clone)] -pub struct CephConfig { - pub mode: CephMode, -} - -/// Ceph storage backend -/// -/// Note: This is a stub implementation. Full implementation would use -/// `ceph` or `rados` crate for RADOS, or the S3 backend for RGW. -pub struct CephBackend { - id: String, - config: CephConfig, - capabilities: StorageCapabilities, -} - -impl CephBackend { - pub fn new(id: impl Into, config: CephConfig) -> Self { - let caps = match &config.mode { - CephMode::Rados { .. } => StorageCapabilities { - read: true, - write: true, - delete: true, - rename: false, // RADOS doesn't have rename - copy: false, - list: true, - search: false, - versioning: false, - sharing: false, - streaming: true, - resume: true, // Offset reads/writes - watch: true, // RADOS watch/notify - metadata: true, - thumbnails: false, - max_file_size: None, - }, - CephMode::CephFs { .. } => StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: false, - versioning: false, // CephFS has snapshots - sharing: true, // POSIX ACLs - streaming: true, - resume: true, - watch: true, // inotify - metadata: true, - thumbnails: false, - max_file_size: None, - }, - CephMode::Rgw { .. } => StorageCapabilities { - read: true, - write: true, - delete: true, - rename: false, // S3-style - copy: true, - list: true, - search: false, - versioning: true, - sharing: true, // Presigned URLs - streaming: true, - resume: true, - watch: false, - metadata: true, - thumbnails: false, - max_file_size: Some(5 * 1024 * 1024 * 1024 * 1024), // 5TB - }, - }; - - Self { - id: id.into(), - config, - capabilities: caps, - } - } - - /// Create RADOS backend - pub fn rados( - id: impl Into, - monitors: Vec, - user: &str, - key: &str, - pool: &str, - ) -> Self { - Self::new( - id, - CephConfig { - mode: CephMode::Rados { - monitors, - user: user.to_string(), - key: key.to_string(), - pool: pool.to_string(), - }, - }, - ) - } - - /// Create CephFS backend - pub fn cephfs( - id: impl Into, - monitors: Vec, - user: &str, - key: &str, - mount_path: &str, - ) -> Self { - Self::new( - id, - CephConfig { - mode: CephMode::CephFs { - monitors, - user: user.to_string(), - key: key.to_string(), - mount_path: mount_path.to_string(), - }, - }, - ) - } - - /// Create RGW (S3) backend - pub fn rgw( - id: impl Into, - endpoint: &str, - access_key: &str, - secret_key: &str, - bucket: &str, - ) -> Self { - Self::new( - id, - CephConfig { - mode: CephMode::Rgw { - endpoint: endpoint.to_string(), - access_key: access_key.to_string(), - secret_key: secret_key.to_string(), - bucket: bucket.to_string(), - }, - }, - ) - } - - /// Connect to Ceph cluster - pub async fn connect(&self) -> CfkResult<()> { - match &self.config.mode { - CephMode::Rados { monitors, user, key, pool } => { - // Would use rados_create(), rados_conf_set(), rados_connect() - // rados_ioctx_create() for pool access - } - CephMode::CephFs { monitors, user, key, mount_path } => { - // Would use ceph_mount(), ceph_conf_set(), etc. - } - CephMode::Rgw { .. } => { - // Use S3 backend (already implemented) - return Ok(()); - } - } - - Err(CfkError::Unsupported( - "Ceph backend is a stub. Use rados/ceph crate or S3 backend for RGW.".into(), - )) - } - - /// Convert VirtualPath to object/path name - fn to_object_name(&self, path: &VirtualPath) -> String { - path.segments.join("/") - } -} - -#[async_trait] -impl StorageBackend for CephBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - match &self.config.mode { - CephMode::Rados { .. } => "Ceph RADOS", - CephMode::CephFs { .. } => "CephFS", - CephMode::Rgw { .. } => "Ceph RGW", - } - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - false // Would check cluster connection - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let _name = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_stat() for object - } - CephMode::CephFs { .. } => { - // Would use ceph_stat() - } - CephMode::Rgw { .. } => { - // Use S3 HEAD - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let _prefix = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_nobjects_list_open/next - } - CephMode::CephFs { .. } => { - // Would use ceph_readdir() - } - CephMode::Rgw { .. } => { - // Use S3 LIST - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let _name = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_read() - } - CephMode::CephFs { .. } => { - // Would use ceph_read() - } - CephMode::Rgw { .. } => { - // Use S3 GET - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn write_file(&self, path: &VirtualPath, _data: Bytes) -> CfkResult { - let _name = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_write_full() or rados_write() - } - CephMode::CephFs { .. } => { - // Would use ceph_write() - } - CephMode::Rgw { .. } => { - // Use S3 PUT - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let _name = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_remove() - } - CephMode::CephFs { .. } => { - // Would use ceph_unlink() - } - CephMode::Rgw { .. } => { - // Use S3 DELETE - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let _name = self.to_object_name(path); - - match &self.config.mode { - CephMode::Rados { .. } => { - // RADOS doesn't have directories - return Err(CfkError::Unsupported( - "RADOS doesn't support directories".into(), - )); - } - CephMode::CephFs { .. } => { - // Would use ceph_mkdir() - } - CephMode::Rgw { .. } => { - // Create zero-byte object with trailing / - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn copy(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - match &self.config.mode { - CephMode::Rados { .. } => { - return Err(CfkError::Unsupported("RADOS doesn't support copy".into())); - } - CephMode::CephFs { .. } | CephMode::Rgw { .. } => { - // CephFS: read + write - // RGW: S3 COPY - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn rename(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - match &self.config.mode { - CephMode::Rados { .. } | CephMode::Rgw { .. } => { - return Err(CfkError::Unsupported( - "RADOS/RGW doesn't support rename".into(), - )); - } - CephMode::CephFs { .. } => { - // Would use ceph_rename() - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_cluster_stat() - } - CephMode::CephFs { .. } => { - // Would use ceph_statfs() - } - CephMode::Rgw { .. } => { - // RGW doesn't expose quota - return Ok((0, 0)); - } - } - - Err(CfkError::Unsupported("Ceph stub".into())) - } -} - -/// RADOS object extended attributes -impl CephBackend { - /// Get extended attribute - pub async fn getxattr(&self, _path: &VirtualPath, _name: &str) -> CfkResult> { - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_getxattr() - } - CephMode::CephFs { .. } => { - // Would use ceph_getxattr() - } - _ => {} - } - Err(CfkError::Unsupported("Ceph stub".into())) - } - - /// Set extended attribute - pub async fn setxattr(&self, _path: &VirtualPath, _name: &str, _value: &[u8]) -> CfkResult<()> { - match &self.config.mode { - CephMode::Rados { .. } => { - // Would use rados_setxattr() - } - CephMode::CephFs { .. } => { - // Would use ceph_setxattr() - } - _ => {} - } - Err(CfkError::Unsupported("Ceph stub".into())) - } - - /// Create snapshot (CephFS only) - pub async fn create_snapshot(&self, _path: &VirtualPath, _name: &str) -> CfkResult<()> { - match &self.config.mode { - CephMode::CephFs { .. } => { - // Would create .snap/name directory - } - _ => { - return Err(CfkError::Unsupported( - "Snapshots only supported on CephFS".into(), - )); - } - } - Err(CfkError::Unsupported("Ceph stub".into())) - } -} - -/// Ceph cluster statistics -#[derive(Debug, Clone, Default)] -pub struct ClusterStat { - pub kb: u64, - pub kb_used: u64, - pub kb_avail: u64, - pub num_objects: u64, -} - -/// Pool statistics -#[derive(Debug, Clone, Default)] -pub struct PoolStat { - pub num_bytes: u64, - pub num_kb: u64, - pub num_objects: u64, - pub num_object_clones: u64, - pub num_object_copies: u64, - pub num_rd: u64, - pub num_rd_kb: u64, - pub num_wr: u64, - pub num_wr_kb: u64, -} diff --git a/czech-file-knife/src/cfk-providers/src/dropbox.rs b/czech-file-knife/src/cfk-providers/src/dropbox.rs deleted file mode 100644 index bc1b65549..000000000 --- a/czech-file-knife/src/cfk-providers/src/dropbox.rs +++ /dev/null @@ -1,583 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Dropbox storage backend -//! -//! Full implementation of Dropbox API v2 with OAuth 2.0 + PKCE authentication. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, - RedirectUrl, Scope, TokenUrl, -}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tokio::sync::RwLock; - -const DROPBOX_AUTH_URL: &str = "https://www.dropbox.com/oauth2/authorize"; -const DROPBOX_TOKEN_URL: &str = "https://api.dropboxapi.com/oauth2/token"; -const DROPBOX_API_URL: &str = "https://api.dropboxapi.com/2"; -const DROPBOX_CONTENT_URL: &str = "https://content.dropboxapi.com/2"; - -/// Dropbox OAuth tokens -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DropboxTokens { - pub access_token: String, - pub refresh_token: Option, - pub expires_at: Option>, -} - -/// Dropbox backend configuration -#[derive(Debug, Clone)] -pub struct DropboxConfig { - pub client_id: String, - pub redirect_uri: String, -} - -/// Dropbox storage backend -pub struct DropboxBackend { - id: String, - config: DropboxConfig, - tokens: Arc>>, - http: Client, - capabilities: StorageCapabilities, -} - -impl DropboxBackend { - pub fn new(id: impl Into, config: DropboxConfig) -> Self { - Self { - id: id.into(), - config, - tokens: Arc::new(RwLock::new(None)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: true, - versioning: true, - sharing: true, - streaming: true, - resume: true, - watch: false, - metadata: true, - thumbnails: true, - max_file_size: Some(350 * 1024 * 1024 * 1024), // 350GB - }, - } - } - - /// Start OAuth 2.0 + PKCE flow - pub fn start_auth(&self) -> (String, PkceCodeVerifier) { - let client = BasicClient::new(ClientId::new(self.config.client_id.clone())) - .set_auth_uri(AuthUrl::new(DROPBOX_AUTH_URL.to_string()).unwrap()) - .set_token_uri(TokenUrl::new(DROPBOX_TOKEN_URL.to_string()).unwrap()) - .set_redirect_uri(RedirectUrl::new(self.config.redirect_uri.clone()).unwrap()); - - let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - - let (auth_url, _csrf_token) = client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new("files.metadata.read".to_string())) - .add_scope(Scope::new("files.metadata.write".to_string())) - .add_scope(Scope::new("files.content.read".to_string())) - .add_scope(Scope::new("files.content.write".to_string())) - .set_pkce_challenge(pkce_challenge) - .url(); - - (auth_url.to_string(), pkce_verifier) - } - - /// Complete OAuth flow with authorization code - pub async fn complete_auth( - &self, - code: &str, - verifier: PkceCodeVerifier, - ) -> CfkResult { - let params = [ - ("code", code), - ("grant_type", "authorization_code"), - ("client_id", &self.config.client_id), - ("redirect_uri", &self.config.redirect_uri), - ("code_verifier", verifier.secret()), - ]; - - let response = self - .http - .post(DROPBOX_TOKEN_URL) - .form(¶ms) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::Auth(format!("Token exchange failed: {}", error_text))); - } - - #[derive(Deserialize)] - struct TokenResponse { - access_token: String, - refresh_token: Option, - expires_in: Option, - } - - let token_resp: TokenResponse = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let tokens = DropboxTokens { - access_token: token_resp.access_token, - refresh_token: token_resp.refresh_token, - expires_at: token_resp - .expires_in - .map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - }; - - *self.tokens.write().await = Some(tokens.clone()); - Ok(tokens) - } - - /// Set tokens directly (for restoring from storage) - pub async fn set_tokens(&self, tokens: DropboxTokens) { - *self.tokens.write().await = Some(tokens); - } - - /// Get current access token - async fn get_access_token(&self) -> CfkResult { - let tokens = self.tokens.read().await; - tokens - .as_ref() - .map(|t| t.access_token.clone()) - .ok_or_else(|| CfkError::Auth("Not authenticated".into())) - } - - /// Make authenticated API request - async fn api_request Deserialize<'de>>( - &self, - endpoint: &str, - body: impl Serialize, - ) -> CfkResult { - let token = self.get_access_token().await?; - let url = format!("{}/{}", DROPBOX_API_URL, endpoint); - - let response = self - .http - .post(&url) - .header("Authorization", format!("Bearer {}", token)) - .header("Content-Type", "application/json") - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "dropbox".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string())) - } - - /// Convert Dropbox path to VirtualPath - fn to_virtual_path(&self, dropbox_path: &str) -> VirtualPath { - let path = dropbox_path.trim_start_matches('/'); - VirtualPath::new(&self.id, path) - } - - /// Convert VirtualPath to Dropbox path - fn to_dropbox_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - String::new() - } else { - format!("/{}", path.segments.join("/")) - } - } -} - -/// Dropbox file metadata response -#[derive(Debug, Deserialize)] -struct DropboxMetadata { - #[serde(rename = ".tag")] - tag: String, - name: String, - path_display: Option, - id: Option, - size: Option, - client_modified: Option, - server_modified: Option, - rev: Option, - content_hash: Option, -} - -impl DropboxMetadata { - fn to_entry(&self, backend_id: &str) -> Entry { - let path = self - .path_display - .as_deref() - .unwrap_or(&self.name) - .trim_start_matches('/'); - let virtual_path = VirtualPath::new(backend_id, path); - - let kind = if self.tag == "folder" { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = self.size; - if let Some(ref modified) = self.server_modified { - if let Ok(dt) = DateTime::parse_from_rfc3339(modified) { - metadata.modified = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref hash) = self.content_hash { - metadata.checksum = Some(hash.clone()); - } - - Entry { - path: virtual_path, - kind, - metadata, - } - } -} - -/// List folder response -#[derive(Debug, Deserialize)] -struct ListFolderResponse { - entries: Vec, - cursor: String, - has_more: bool, -} - -#[async_trait] -impl StorageBackend for DropboxBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Dropbox" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.tokens.read().await.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let dropbox_path = self.to_dropbox_path(path); - - if dropbox_path.is_empty() { - // Root folder - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - - #[derive(Serialize)] - struct GetMetadataArg { - path: String, - } - - let result: DropboxMetadata = self - .api_request("files/get_metadata", GetMetadataArg { path: dropbox_path }) - .await?; - - Ok(result.to_entry(&self.id)) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let dropbox_path = self.to_dropbox_path(path); - - #[derive(Serialize)] - struct ListFolderArg { - path: String, - recursive: bool, - include_deleted: bool, - limit: u32, - } - - let result: ListFolderResponse = self - .api_request( - "files/list_folder", - ListFolderArg { - path: if dropbox_path.is_empty() { - String::new() - } else { - dropbox_path - }, - recursive: false, - include_deleted: false, - limit: 2000, - }, - ) - .await?; - - let mut entries: Vec = result - .entries - .iter() - .map(|m| m.to_entry(&self.id)) - .collect(); - - // Handle pagination - let mut cursor = result.cursor; - let mut has_more = result.has_more; - - while has_more { - #[derive(Serialize)] - struct ListFolderContinueArg { - cursor: String, - } - - let continue_result: ListFolderResponse = self - .api_request( - "files/list_folder/continue", - ListFolderContinueArg { - cursor: cursor.clone(), - }, - ) - .await?; - - entries.extend(continue_result.entries.iter().map(|m| m.to_entry(&self.id))); - cursor = continue_result.cursor; - has_more = continue_result.has_more; - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let token = self.get_access_token().await?; - let dropbox_path = self.to_dropbox_path(path); - - #[derive(Serialize)] - struct DownloadArg { - path: String, - } - - let arg = serde_json::to_string(&DownloadArg { path: dropbox_path }) - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let response = self - .http - .post(format!("{}/files/download", DROPBOX_CONTENT_URL)) - .header("Authorization", format!("Bearer {}", token)) - .header("Dropbox-API-Arg", arg) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "dropbox".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let token = self.get_access_token().await?; - let dropbox_path = self.to_dropbox_path(path); - - #[derive(Serialize)] - struct UploadArg { - path: String, - mode: String, - autorename: bool, - mute: bool, - } - - let arg = serde_json::to_string(&UploadArg { - path: dropbox_path, - mode: "overwrite".to_string(), - autorename: false, - mute: false, - }) - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let response = self - .http - .post(format!("{}/files/upload", DROPBOX_CONTENT_URL)) - .header("Authorization", format!("Bearer {}", token)) - .header("Dropbox-API-Arg", arg) - .header("Content-Type", "application/octet-stream") - .body(data.to_vec()) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "dropbox".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let metadata: DropboxMetadata = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - Ok(metadata.to_entry(&self.id)) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let dropbox_path = self.to_dropbox_path(path); - - #[derive(Serialize)] - struct DeleteArg { - path: String, - } - - let _: serde_json::Value = self - .api_request("files/delete_v2", DeleteArg { path: dropbox_path }) - .await?; - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let dropbox_path = self.to_dropbox_path(path); - - #[derive(Serialize)] - struct CreateFolderArg { - path: String, - autorename: bool, - } - - #[derive(Deserialize)] - struct CreateFolderResult { - metadata: DropboxMetadata, - } - - let result: CreateFolderResult = self - .api_request( - "files/create_folder_v2", - CreateFolderArg { - path: dropbox_path, - autorename: false, - }, - ) - .await?; - - Ok(result.metadata.to_entry(&self.id)) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_dropbox_path(from); - let to_path = self.to_dropbox_path(to); - - #[derive(Serialize)] - struct CopyArg { - from_path: String, - to_path: String, - autorename: bool, - } - - #[derive(Deserialize)] - struct CopyResult { - metadata: DropboxMetadata, - } - - let result: CopyResult = self - .api_request( - "files/copy_v2", - CopyArg { - from_path, - to_path, - autorename: false, - }, - ) - .await?; - - Ok(result.metadata.to_entry(&self.id)) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_dropbox_path(from); - let to_path = self.to_dropbox_path(to); - - #[derive(Serialize)] - struct MoveArg { - from_path: String, - to_path: String, - autorename: bool, - } - - #[derive(Deserialize)] - struct MoveResult { - metadata: DropboxMetadata, - } - - let result: MoveResult = self - .api_request( - "files/move_v2", - MoveArg { - from_path, - to_path, - autorename: false, - }, - ) - .await?; - - Ok(result.metadata.to_entry(&self.id)) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - #[derive(Deserialize)] - struct SpaceUsage { - used: u64, - allocation: SpaceAllocation, - } - - #[derive(Deserialize)] - struct SpaceAllocation { - #[serde(rename = ".tag")] - tag: String, - allocated: Option, - } - - let result: SpaceUsage = self - .api_request("users/get_space_usage", serde_json::json!(null)) - .await?; - - let total = result.allocation.allocated.unwrap_or(0); - let used = result.used; - let available = total.saturating_sub(used); - - Ok((available, total)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/gdrive.rs b/czech-file-knife/src/cfk-providers/src/gdrive.rs deleted file mode 100644 index afb300275..000000000 --- a/czech-file-knife/src/cfk-providers/src/gdrive.rs +++ /dev/null @@ -1,732 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Google Drive storage backend -//! -//! Full implementation of Google Drive API v3 with OAuth 2.0 + PKCE authentication. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, - RedirectUrl, Scope, TokenUrl, -}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -const GOOGLE_AUTH_URL: &str = "https://accounts.google.com/o/oauth2/v2/auth"; -const GOOGLE_TOKEN_URL: &str = "https://oauth2.googleapis.com/token"; -const DRIVE_API_URL: &str = "https://www.googleapis.com/drive/v3"; -const DRIVE_UPLOAD_URL: &str = "https://www.googleapis.com/upload/drive/v3"; - -/// Google OAuth tokens -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GoogleTokens { - pub access_token: String, - pub refresh_token: Option, - pub expires_at: Option>, -} - -/// Google Drive backend configuration -#[derive(Debug, Clone)] -pub struct GoogleDriveConfig { - pub client_id: String, - pub client_secret: Option, - pub redirect_uri: String, -} - -/// Google Drive storage backend -pub struct GoogleDriveBackend { - id: String, - config: GoogleDriveConfig, - tokens: Arc>>, - http: Client, - capabilities: StorageCapabilities, - /// Cache of file ID to path mapping - path_cache: Arc>>, -} - -impl GoogleDriveBackend { - pub fn new(id: impl Into, config: GoogleDriveConfig) -> Self { - Self { - id: id.into(), - config, - tokens: Arc::new(RwLock::new(None)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: true, - versioning: true, - sharing: true, - streaming: true, - resume: true, - watch: true, - metadata: true, - thumbnails: true, - max_file_size: Some(5 * 1024 * 1024 * 1024 * 1024), // 5TB - }, - path_cache: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Start OAuth 2.0 + PKCE flow - pub fn start_auth(&self) -> (String, PkceCodeVerifier) { - let client = BasicClient::new(ClientId::new(self.config.client_id.clone())) - .set_auth_uri(AuthUrl::new(GOOGLE_AUTH_URL.to_string()).unwrap()) - .set_token_uri(TokenUrl::new(GOOGLE_TOKEN_URL.to_string()).unwrap()) - .set_redirect_uri(RedirectUrl::new(self.config.redirect_uri.clone()).unwrap()); - - let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - - let (auth_url, _csrf_token) = client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new( - "https://www.googleapis.com/auth/drive".to_string(), - )) - .add_scope(Scope::new( - "https://www.googleapis.com/auth/drive.metadata.readonly".to_string(), - )) - .set_pkce_challenge(pkce_challenge) - .add_extra_param("access_type", "offline") - .add_extra_param("prompt", "consent") - .url(); - - (auth_url.to_string(), pkce_verifier) - } - - /// Complete OAuth flow with authorization code - pub async fn complete_auth( - &self, - code: &str, - verifier: PkceCodeVerifier, - ) -> CfkResult { - let mut params = vec![ - ("code", code.to_string()), - ("grant_type", "authorization_code".to_string()), - ("client_id", self.config.client_id.clone()), - ("redirect_uri", self.config.redirect_uri.clone()), - ("code_verifier", verifier.secret().to_string()), - ]; - - if let Some(ref secret) = self.config.client_secret { - params.push(("client_secret", secret.clone())); - } - - let response = self - .http - .post(GOOGLE_TOKEN_URL) - .form(¶ms) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::Auth(format!("Token exchange failed: {}", error_text))); - } - - #[derive(Deserialize)] - struct TokenResponse { - access_token: String, - refresh_token: Option, - expires_in: Option, - } - - let token_resp: TokenResponse = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let tokens = GoogleTokens { - access_token: token_resp.access_token, - refresh_token: token_resp.refresh_token, - expires_at: token_resp - .expires_in - .map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - }; - - *self.tokens.write().await = Some(tokens.clone()); - Ok(tokens) - } - - /// Set tokens directly - pub async fn set_tokens(&self, tokens: GoogleTokens) { - *self.tokens.write().await = Some(tokens); - } - - /// Get current access token - async fn get_access_token(&self) -> CfkResult { - let tokens = self.tokens.read().await; - tokens - .as_ref() - .map(|t| t.access_token.clone()) - .ok_or_else(|| CfkError::Auth("Not authenticated".into())) - } - - /// Resolve path to file ID - async fn resolve_file_id(&self, path: &VirtualPath) -> CfkResult { - if path.segments.is_empty() { - return Ok("root".to_string()); - } - - // Check cache first - let path_str = path.to_string(); - { - let cache = self.path_cache.read().await; - if let Some(id) = cache.get(&path_str) { - return Ok(id.clone()); - } - } - - // Resolve path segment by segment - let mut current_id = "root".to_string(); - - for segment in &path.segments { - let query = format!( - "'{}' in parents and name = '{}' and trashed = false", - current_id, segment - ); - - let response = self - .http - .get(format!("{}/files", DRIVE_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[("q", &query), ("fields", &"files(id,name)".to_string())]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct FileList { - files: Vec, - } - - let list: FileList = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - current_id = list - .files - .first() - .map(|f| f.id.clone()) - .ok_or_else(|| CfkError::NotFound(path.to_string()))?; - } - - // Cache the result - { - let mut cache = self.path_cache.write().await; - cache.insert(path_str, current_id.clone()); - } - - Ok(current_id) - } - - /// Convert VirtualPath to parent folder ID and filename - fn path_to_parent_and_name(&self, path: &VirtualPath) -> (String, String) { - if path.segments.is_empty() { - ("root".to_string(), String::new()) - } else if path.segments.len() == 1 { - ("root".to_string(), path.segments[0].clone()) - } else { - let parent_segments = &path.segments[..path.segments.len() - 1]; - let parent_path = parent_segments.join("/"); - let name = path.segments.last().unwrap().clone(); - (parent_path, name) - } - } -} - -/// Google Drive file metadata -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct DriveFile { - id: String, - name: String, - mime_type: String, - #[serde(default)] - size: Option, - created_time: Option, - modified_time: Option, - #[serde(default)] - parents: Vec, - #[serde(default)] - trashed: bool, - md5_checksum: Option, -} - -impl DriveFile { - fn to_entry(&self, backend_id: &str, path: &str) -> Entry { - let virtual_path = VirtualPath::new(backend_id, path); - - let kind = if self.mime_type == "application/vnd.google-apps.folder" { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = self.size.as_ref().and_then(|s| s.parse().ok()); - metadata.mime_type = Some(self.mime_type.clone()); - - if let Some(ref modified) = self.modified_time { - if let Ok(dt) = DateTime::parse_from_rfc3339(modified) { - metadata.modified = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref created) = self.created_time { - if let Ok(dt) = DateTime::parse_from_rfc3339(created) { - metadata.created = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref checksum) = self.md5_checksum { - metadata.checksum = Some(checksum.clone()); - } - - Entry { - path: virtual_path, - kind, - metadata, - } - } -} - -#[async_trait] -impl StorageBackend for GoogleDriveBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Google Drive" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.tokens.read().await.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let file_id = self.resolve_file_id(path).await?; - - let response = self - .http - .get(format!("{}/files/{}", DRIVE_API_URL, file_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[( - "fields", - "id,name,mimeType,size,createdTime,modifiedTime,parents,trashed,md5Checksum", - )]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - if status == reqwest::StatusCode::NOT_FOUND { - return Err(CfkError::NotFound(path.to_string())); - } - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "gdrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let file: DriveFile = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let path_str = path.segments.join("/"); - Ok(file.to_entry(&self.id, &path_str)) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let folder_id = self.resolve_file_id(path).await?; - - let mut entries = Vec::new(); - let mut page_token: Option = None; - - loop { - let query = format!("'{}' in parents and trashed = false", folder_id); - - let mut request = self - .http - .get(format!("{}/files", DRIVE_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[ - ("q", query.as_str()), - ( - "fields", - "nextPageToken,files(id,name,mimeType,size,createdTime,modifiedTime,md5Checksum)", - ), - ("pageSize", "1000"), - ]); - - if let Some(ref token) = page_token { - request = request.query(&[("pageToken", token.as_str())]); - } - - let response = request - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct FileList { - files: Vec, - next_page_token: Option, - } - - let list: FileList = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if path.segments.is_empty() { - String::new() - } else { - format!("{}/", path.segments.join("/")) - }; - - for file in list.files { - let file_path = format!("{}{}", base_path, file.name); - entries.push(file.to_entry(&self.id, &file_path)); - } - - page_token = list.next_page_token; - if page_token.is_none() { - break; - } - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let file_id = self.resolve_file_id(path).await?; - - let response = self - .http - .get(format!("{}/files/{}?alt=media", DRIVE_API_URL, file_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "gdrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let token = self.get_access_token().await?; - - // Check if file exists - let existing_id = self.resolve_file_id(path).await.ok(); - - let (parent_path, name) = self.path_to_parent_and_name(path); - let parent_id = if parent_path == "root" { - "root".to_string() - } else { - let parent_virtual = VirtualPath::new(&self.id, &parent_path); - self.resolve_file_id(&parent_virtual).await? - }; - - let file: DriveFile = if let Some(file_id) = existing_id { - // Update existing file - let response = self - .http - .patch(format!( - "{}/files/{}?uploadType=media", - DRIVE_UPLOAD_URL, file_id - )) - .header("Authorization", format!("Bearer {}", token)) - .header("Content-Type", "application/octet-stream") - .body(data.to_vec()) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))? - } else { - // Create new file - #[derive(Serialize)] - struct FileMetadata { - name: String, - parents: Vec, - } - - let metadata = FileMetadata { - name: name.clone(), - parents: vec![parent_id], - }; - - let metadata_json = - serde_json::to_string(&metadata).map_err(|e| CfkError::Serialization(e.to_string()))?; - - // Use multipart upload - let boundary = "cfk_boundary_12345"; - let body = format!( - "--{}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n{}\r\n--{}\r\nContent-Type: application/octet-stream\r\n\r\n", - boundary, metadata_json, boundary - ); - - let mut full_body = body.into_bytes(); - full_body.extend_from_slice(&data); - full_body.extend_from_slice(format!("\r\n--{}--", boundary).as_bytes()); - - let response = self - .http - .post(format!("{}?uploadType=multipart", DRIVE_UPLOAD_URL)) - .header("Authorization", format!("Bearer {}", token)) - .header( - "Content-Type", - format!("multipart/related; boundary={}", boundary), - ) - .body(full_body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))? - }; - - let path_str = path.segments.join("/"); - Ok(file.to_entry(&self.id, &path_str)) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let file_id = self.resolve_file_id(path).await?; - - let response = self - .http - .delete(format!("{}/files/{}", DRIVE_API_URL, file_id)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != reqwest::StatusCode::NO_CONTENT { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "gdrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - // Invalidate cache - { - let mut cache = self.path_cache.write().await; - cache.remove(&path.to_string()); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let token = self.get_access_token().await?; - let (parent_path, name) = self.path_to_parent_and_name(path); - - let parent_id = if parent_path == "root" { - "root".to_string() - } else { - let parent_virtual = VirtualPath::new(&self.id, &parent_path); - self.resolve_file_id(&parent_virtual).await? - }; - - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - struct FolderMetadata { - name: String, - mime_type: String, - parents: Vec, - } - - let metadata = FolderMetadata { - name, - mime_type: "application/vnd.google-apps.folder".to_string(), - parents: vec![parent_id], - }; - - let response = self - .http - .post(format!("{}/files", DRIVE_API_URL)) - .header("Authorization", format!("Bearer {}", token)) - .json(&metadata) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let file: DriveFile = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let path_str = path.segments.join("/"); - Ok(file.to_entry(&self.id, &path_str)) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let token = self.get_access_token().await?; - let file_id = self.resolve_file_id(from).await?; - - let (parent_path, name) = self.path_to_parent_and_name(to); - let parent_id = if parent_path == "root" { - "root".to_string() - } else { - let parent_virtual = VirtualPath::new(&self.id, &parent_path); - self.resolve_file_id(&parent_virtual).await? - }; - - #[derive(Serialize)] - struct CopyMetadata { - name: String, - parents: Vec, - } - - let metadata = CopyMetadata { - name, - parents: vec![parent_id], - }; - - let response = self - .http - .post(format!("{}/files/{}/copy", DRIVE_API_URL, file_id)) - .header("Authorization", format!("Bearer {}", token)) - .json(&metadata) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let file: DriveFile = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let path_str = to.segments.join("/"); - Ok(file.to_entry(&self.id, &path_str)) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let token = self.get_access_token().await?; - let file_id = self.resolve_file_id(from).await?; - - let (parent_path, name) = self.path_to_parent_and_name(to); - let parent_id = if parent_path == "root" { - "root".to_string() - } else { - let parent_virtual = VirtualPath::new(&self.id, &parent_path); - self.resolve_file_id(&parent_virtual).await? - }; - - #[derive(Serialize)] - struct UpdateMetadata { - name: String, - } - - let metadata = UpdateMetadata { name }; - - let response = self - .http - .patch(format!( - "{}/files/{}?addParents={}&removeParents={}", - DRIVE_API_URL, file_id, parent_id, "root" - )) - .header("Authorization", format!("Bearer {}", token)) - .json(&metadata) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let file: DriveFile = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - // Invalidate cache - { - let mut cache = self.path_cache.write().await; - cache.remove(&from.to_string()); - } - - let path_str = to.segments.join("/"); - Ok(file.to_entry(&self.id, &path_str)) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - let response = self - .http - .get(format!("{}/about", DRIVE_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .query(&[("fields", "storageQuota")]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct About { - storage_quota: StorageQuota, - } - - #[derive(Deserialize)] - #[serde(rename_all = "camelCase")] - struct StorageQuota { - limit: Option, - usage: Option, - } - - let about: About = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let total: u64 = about - .storage_quota - .limit - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - let used: u64 = about - .storage_quota - .usage - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - let available = total.saturating_sub(used); - - Ok((available, total)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/ipfs.rs b/czech-file-knife/src/cfk-providers/src/ipfs.rs deleted file mode 100644 index a79de001a..000000000 --- a/czech-file-knife/src/cfk-providers/src/ipfs.rs +++ /dev/null @@ -1,803 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! IPFS storage backend -//! -//! Content-addressed distributed file system. -//! Supports local IPFS daemon, pinning services, and MFS (Mutable File System). - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use reqwest::{multipart, Client}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tokio::sync::RwLock; - -const DEFAULT_API_URL: &str = "http://127.0.0.1:5001/api/v0"; -const DEFAULT_GATEWAY_URL: &str = "http://127.0.0.1:8080"; - -/// IPFS backend configuration -#[derive(Debug, Clone)] -pub struct IpfsConfig { - /// IPFS API URL (default: http://127.0.0.1:5001/api/v0) - pub api_url: String, - /// IPFS Gateway URL (default: http://127.0.0.1:8080) - pub gateway_url: String, - /// Use MFS (Mutable File System) for path-based operations - pub use_mfs: bool, - /// Pin files after adding - pub auto_pin: bool, -} - -impl Default for IpfsConfig { - fn default() -> Self { - Self { - api_url: DEFAULT_API_URL.to_string(), - gateway_url: DEFAULT_GATEWAY_URL.to_string(), - use_mfs: true, - auto_pin: true, - } - } -} - -/// IPFS storage backend -pub struct IpfsBackend { - id: String, - config: Arc>, - http: Client, - capabilities: StorageCapabilities, -} - -impl IpfsBackend { - pub fn new(id: impl Into, config: IpfsConfig) -> Self { - Self { - id: id.into(), - config: Arc::new(RwLock::new(config)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: false, - versioning: true, // Content-addressed = immutable versions - sharing: true, - streaming: true, - resume: false, - watch: false, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - } - } - - /// Make API POST request - async fn api_post(&self, endpoint: &str, params: &[(&str, &str)]) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/{}", config.api_url, endpoint); - - let mut request = self.http.post(&url); - - for (key, value) in params { - request = request.query(&[(key, value)]); - } - - let response = request - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "ipfs".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .text() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - /// Make API POST request with JSON response - async fn api_post_json Deserialize<'de>>( - &self, - endpoint: &str, - params: &[(&str, &str)], - ) -> CfkResult { - let text = self.api_post(endpoint, params).await?; - serde_json::from_str(&text).map_err(|e| CfkError::Serialization(e.to_string())) - } - - /// Add content to IPFS - pub async fn add(&self, data: Bytes, name: Option<&str>) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/add", config.api_url); - - let mut params = vec![("pin", if config.auto_pin { "true" } else { "false" })]; - if let Some(n) = name { - params.push(("path", n)); - } - - let part = multipart::Part::bytes(data.to_vec()) - .file_name(name.unwrap_or("file").to_string()); - let form = multipart::Form::new().part("file", part); - - let mut request = self.http.post(&url); - for (key, value) in ¶ms { - request = request.query(&[(key, value)]); - } - - let response = request - .multipart(form) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "ipfs".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let text = response - .text() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - // IPFS returns newline-delimited JSON for directories - let last_line = text.lines().last().unwrap_or(&text); - serde_json::from_str(last_line).map_err(|e| CfkError::Serialization(e.to_string())) - } - - /// Get content by CID - pub async fn cat(&self, cid: &str) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/cat", config.api_url); - - let response = self - .http - .post(&url) - .query(&[("arg", cid)]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "ipfs".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - /// Pin a CID - pub async fn pin(&self, cid: &str) -> CfkResult<()> { - self.api_post("pin/add", &[("arg", cid)]).await?; - Ok(()) - } - - /// Unpin a CID - pub async fn unpin(&self, cid: &str) -> CfkResult<()> { - self.api_post("pin/rm", &[("arg", cid)]).await?; - Ok(()) - } - - /// List pinned CIDs - pub async fn list_pins(&self) -> CfkResult> { - #[derive(Deserialize)] - struct PinLsResponse { - #[serde(rename = "Keys")] - keys: std::collections::HashMap, - } - - #[derive(Deserialize)] - struct PinType { - #[serde(rename = "Type")] - pin_type: String, - } - - let resp: PinLsResponse = self.api_post_json("pin/ls", &[("type", "all")]).await?; - - Ok(resp - .keys - .into_iter() - .map(|(cid, pt)| PinInfo { - cid, - pin_type: pt.pin_type, - }) - .collect()) - } - - /// MFS: Write file to path - async fn mfs_write(&self, path: &str, data: Bytes) -> CfkResult<()> { - let config = self.config.read().await; - let url = format!("{}/files/write", config.api_url); - - let part = multipart::Part::bytes(data.to_vec()).file_name("file"); - let form = multipart::Form::new().part("file", part); - - let response = self - .http - .post(&url) - .query(&[ - ("arg", path), - ("create", "true"), - ("parents", "true"), - ("truncate", "true"), - ]) - .multipart(form) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "ipfs".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(()) - } - - /// MFS: Read file from path - async fn mfs_read(&self, path: &str) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/files/read", config.api_url); - - let response = self - .http - .post(&url) - .query(&[("arg", path)]) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "ipfs".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - /// MFS: List directory - async fn mfs_ls(&self, path: &str) -> CfkResult> { - #[derive(Deserialize)] - struct LsResponse { - #[serde(rename = "Entries")] - entries: Option>, - } - - let resp: LsResponse = self - .api_post_json("files/ls", &[("arg", path), ("long", "true")]) - .await?; - - Ok(resp.entries.unwrap_or_default()) - } - - /// MFS: Get file/directory stat - async fn mfs_stat(&self, path: &str) -> CfkResult { - self.api_post_json("files/stat", &[("arg", path)]).await - } - - /// MFS: Create directory - async fn mfs_mkdir(&self, path: &str) -> CfkResult<()> { - self.api_post("files/mkdir", &[("arg", path), ("parents", "true")]) - .await?; - Ok(()) - } - - /// MFS: Remove file/directory - async fn mfs_rm(&self, path: &str, recursive: bool) -> CfkResult<()> { - self.api_post( - "files/rm", - &[("arg", path), ("recursive", if recursive { "true" } else { "false" })], - ) - .await?; - Ok(()) - } - - /// MFS: Copy - async fn mfs_cp(&self, from: &str, to: &str) -> CfkResult<()> { - self.api_post("files/cp", &[("arg", from), ("arg", to)]) - .await?; - Ok(()) - } - - /// MFS: Move - async fn mfs_mv(&self, from: &str, to: &str) -> CfkResult<()> { - self.api_post("files/mv", &[("arg", from), ("arg", to)]) - .await?; - Ok(()) - } - - /// Convert VirtualPath to MFS path - fn to_mfs_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - "/".to_string() - } else { - format!("/{}", path.segments.join("/")) - } - } -} - -/// IPFS add response -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct AddResponse { - #[serde(rename = "Name")] - pub name: String, - #[serde(rename = "Hash")] - pub hash: String, - #[serde(rename = "Size")] - pub size: String, -} - -/// Pin information -#[derive(Debug, Clone)] -pub struct PinInfo { - pub cid: String, - pub pin_type: String, -} - -/// MFS directory entry -#[derive(Debug, Clone, Deserialize)] -pub struct MfsEntry { - #[serde(rename = "Name")] - pub name: String, - #[serde(rename = "Type")] - pub entry_type: u8, - #[serde(rename = "Size")] - pub size: u64, - #[serde(rename = "Hash")] - pub hash: String, -} - -/// MFS stat response -#[derive(Debug, Clone, Deserialize)] -pub struct MfsStat { - #[serde(rename = "Hash")] - pub hash: String, - #[serde(rename = "Size")] - pub size: u64, - #[serde(rename = "CumulativeSize")] - pub cumulative_size: u64, - #[serde(rename = "Type")] - pub entry_type: String, -} - -#[async_trait] -impl StorageBackend for IpfsBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "IPFS" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - #[derive(Deserialize)] - struct IdResponse { - #[serde(rename = "ID")] - _id: String, - } - - self.api_post_json::("id", &[]).await.is_ok() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - let stat = self.mfs_stat(&mfs_path).await?; - - let kind = if stat.entry_type == "directory" { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(stat.size); - metadata.checksum = Some(stat.hash); - - return Ok(Entry { - path: path.clone(), - kind, - metadata, - }); - } - - // For CID-based paths - if path.segments.is_empty() { - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - - // Assume first segment is CID - let cid = &path.segments[0]; - - #[derive(Deserialize)] - struct ObjectStat { - #[serde(rename = "Hash")] - hash: String, - #[serde(rename = "NumLinks")] - num_links: u64, - #[serde(rename = "DataSize")] - data_size: u64, - #[serde(rename = "CumulativeSize")] - cumulative_size: u64, - } - - let stat: ObjectStat = self.api_post_json("object/stat", &[("arg", cid)]).await?; - - let kind = if stat.num_links > 0 { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(stat.cumulative_size); - metadata.checksum = Some(stat.hash); - - Ok(Entry { - path: path.clone(), - kind, - metadata, - }) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - let entries = self.mfs_ls(&mfs_path).await?; - - let base_path = if path.segments.is_empty() { - String::new() - } else { - format!("{}/", path.segments.join("/")) - }; - - return Ok(entries - .iter() - .map(|e| { - let kind = if e.entry_type == 1 { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(e.size); - metadata.checksum = Some(e.hash.clone()); - - Entry { - path: VirtualPath::new(&self.id, &format!("{}{}", base_path, e.name)), - kind, - metadata, - } - }) - .collect()); - } - - // For non-MFS, list pins at root - if path.segments.is_empty() { - let pins = self.list_pins().await?; - - return Ok(pins - .iter() - .map(|p| { - let mut metadata = Metadata::default(); - metadata.custom.insert("pin_type".to_string(), p.pin_type.clone()); - - Entry { - path: VirtualPath::new(&self.id, &p.cid), - kind: EntryKind::File, // Assume file, could be directory - metadata, - } - }) - .collect()); - } - - // List IPFS directory by CID - let cid = &path.segments[0]; - - #[derive(Deserialize)] - struct LsResponse { - #[serde(rename = "Objects")] - objects: Vec, - } - - #[derive(Deserialize)] - struct LsObject { - #[serde(rename = "Links")] - links: Vec, - } - - #[derive(Deserialize)] - struct LsLink { - #[serde(rename = "Name")] - name: String, - #[serde(rename = "Hash")] - hash: String, - #[serde(rename = "Size")] - size: u64, - #[serde(rename = "Type")] - link_type: u64, - } - - let resp: LsResponse = self.api_post_json("ls", &[("arg", cid)]).await?; - - let links = resp - .objects - .first() - .map(|o| &o.links) - .cloned() - .unwrap_or_default(); - - let base_path = path.segments.join("/"); - - Ok(links - .iter() - .map(|l| { - let kind = if l.link_type == 1 { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(l.size); - metadata.checksum = Some(l.hash.clone()); - - Entry { - path: VirtualPath::new(&self.id, &format!("{}/{}", base_path, l.name)), - kind, - metadata, - } - }) - .collect()) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - return self.mfs_read(&mfs_path).await; - } - - // Read by CID - if path.segments.is_empty() { - return Err(CfkError::InvalidPath("No CID specified".into())); - } - - let cid = path.segments.join("/"); - self.cat(&cid).await - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - self.mfs_write(&mfs_path, data).await?; - - // Get updated metadata - let stat = self.mfs_stat(&mfs_path).await?; - - let mut metadata = Metadata::default(); - metadata.size = Some(stat.size); - metadata.checksum = Some(stat.hash); - - return Ok(Entry { - path: path.clone(), - kind: EntryKind::File, - metadata, - }); - } - - // Add to IPFS and return the CID path - let name = path.segments.last().map(String::as_str); - let add_resp = self.add(data, name).await?; - - let mut metadata = Metadata::default(); - metadata.size = Some(add_resp.size.parse().unwrap_or(0)); - metadata.checksum = Some(add_resp.hash.clone()); - - Ok(Entry { - path: VirtualPath::new(&self.id, &add_resp.hash), - kind: EntryKind::File, - metadata, - }) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - return self.mfs_rm(&mfs_path, true).await; - } - - // For CID paths, unpin - if !path.segments.is_empty() { - let cid = &path.segments[0]; - return self.unpin(cid).await; - } - - Err(CfkError::InvalidPath("Cannot delete root".into())) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let mfs_path = self.to_mfs_path(path); - self.mfs_mkdir(&mfs_path).await?; - - let stat = self.mfs_stat(&mfs_path).await?; - - let mut metadata = Metadata::default(); - metadata.checksum = Some(stat.hash); - - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata, - }); - } - - Err(CfkError::Unsupported( - "Cannot create directory without MFS enabled".into(), - )) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let from_path = self.to_mfs_path(from); - let to_path = self.to_mfs_path(to); - self.mfs_cp(&from_path, &to_path).await?; - - return self.get_metadata(to).await; - } - - Err(CfkError::Unsupported("Copy requires MFS enabled".into())) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let config = self.config.read().await; - - if config.use_mfs { - let from_path = self.to_mfs_path(from); - let to_path = self.to_mfs_path(to); - self.mfs_mv(&from_path, &to_path).await?; - - return self.get_metadata(to).await; - } - - Err(CfkError::Unsupported("Rename requires MFS enabled".into())) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - #[derive(Deserialize)] - struct RepoStat { - #[serde(rename = "RepoSize")] - repo_size: u64, - #[serde(rename = "StorageMax")] - storage_max: u64, - } - - let stat: RepoStat = self.api_post_json("repo/stat", &[]).await?; - - let available = stat.storage_max.saturating_sub(stat.repo_size); - Ok((available, stat.storage_max)) - } -} - -/// Additional IPFS-specific operations -impl IpfsBackend { - /// Resolve IPNS name to CID - pub async fn resolve_ipns(&self, name: &str) -> CfkResult { - #[derive(Deserialize)] - struct ResolveResponse { - #[serde(rename = "Path")] - path: String, - } - - let resp: ResolveResponse = self - .api_post_json("name/resolve", &[("arg", name)]) - .await?; - - Ok(resp.path) - } - - /// Publish to IPNS - pub async fn publish_ipns(&self, cid: &str) -> CfkResult { - #[derive(Deserialize)] - struct PublishResponse { - #[serde(rename = "Name")] - name: String, - #[serde(rename = "Value")] - value: String, - } - - let resp: PublishResponse = self - .api_post_json("name/publish", &[("arg", cid)]) - .await?; - - Ok(resp.name) - } - - /// Get peer information - pub async fn swarm_peers(&self) -> CfkResult> { - #[derive(Deserialize)] - struct SwarmPeersResponse { - #[serde(rename = "Peers")] - peers: Option>, - } - - #[derive(Deserialize)] - struct PeerInfo { - #[serde(rename = "Peer")] - peer: String, - } - - let resp: SwarmPeersResponse = self.api_post_json("swarm/peers", &[]).await?; - - Ok(resp - .peers - .unwrap_or_default() - .into_iter() - .map(|p| p.peer) - .collect()) - } - - /// Garbage collect unpinned blocks - pub async fn repo_gc(&self) -> CfkResult<()> { - self.api_post("repo/gc", &[]).await?; - Ok(()) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/lib.rs b/czech-file-knife/src/cfk-providers/src/lib.rs deleted file mode 100644 index 423bf5e98..000000000 --- a/czech-file-knife/src/cfk-providers/src/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Storage providers for Czech File Knife -//! -//! Supports 15+ backends: local, cloud, distributed, and exotic filesystems. -//! Plus exotic protocols: Gopher, Gemini, NNTP, RTSP, BitTorrent, etc. -//! Transport layers: TCP, QUIC, UDP, Unix sockets. - -mod local; -pub mod protocols; -pub mod transport; - -#[cfg(feature = "dropbox")] -pub mod dropbox; - -#[cfg(feature = "gdrive")] -pub mod gdrive; - -#[cfg(feature = "onedrive")] -pub mod onedrive; - -#[cfg(feature = "box")] -pub mod box_com; - -#[cfg(feature = "s3")] -pub mod s3; - -#[cfg(feature = "ipfs")] -pub mod ipfs; - -#[cfg(feature = "webdav")] -pub mod webdav; - -#[cfg(feature = "afs")] -pub mod afs; - -#[cfg(feature = "ninep")] -pub mod ninep; - -#[cfg(feature = "sftp")] -pub mod sftp; - -#[cfg(feature = "nfs")] -pub mod nfs; - -#[cfg(feature = "smb")] -pub mod smb; - -#[cfg(feature = "syncthing")] -pub mod syncthing; - -#[cfg(feature = "ceph")] -pub mod ceph; - -pub use local::LocalBackend; - -// Re-export provider types when features are enabled -#[cfg(feature = "dropbox")] -pub use dropbox::{DropboxBackend, DropboxConfig, DropboxTokens}; - -#[cfg(feature = "gdrive")] -pub use gdrive::{GoogleDriveBackend, GoogleDriveConfig, GoogleTokens}; - -#[cfg(feature = "onedrive")] -pub use onedrive::{OneDriveBackend, OneDriveConfig, OneDriveTokens}; - -#[cfg(feature = "box")] -pub use box_com::{BoxBackend, BoxConfig, BoxTokens}; - -#[cfg(feature = "s3")] -pub use s3::{S3Backend, S3Config}; - -#[cfg(feature = "ipfs")] -pub use ipfs::{IpfsBackend, IpfsConfig}; - -#[cfg(feature = "webdav")] -pub use webdav::{WebDavBackend, WebDavConfig, WebDavAuth}; - -#[cfg(feature = "afs")] -pub use afs::{AfsBackend, AfsConfig}; - -#[cfg(feature = "ninep")] -pub use ninep::{NinePBackend, NinePConfig}; - -#[cfg(feature = "sftp")] -pub use sftp::{SftpBackend, SftpConfig, SftpAuth}; - -#[cfg(feature = "nfs")] -pub use nfs::{NfsBackend, NfsConfig, NfsVersion}; - -#[cfg(feature = "smb")] -pub use smb::{SmbBackend, SmbConfig, SmbVersion, SmbAuth}; - -#[cfg(feature = "syncthing")] -pub use syncthing::{SyncthingBackend, SyncthingConfig}; - -#[cfg(feature = "ceph")] -pub use ceph::{CephBackend, CephConfig, CephMode}; - -use cfk_core::{StorageBackend, CfkResult, CfkError}; -use std::collections::HashMap; -use std::sync::Arc; - -/// Registry of storage backends -pub struct BackendRegistry { - backends: HashMap>, -} - -impl BackendRegistry { - pub fn new() -> Self { - Self { backends: HashMap::new() } - } - - pub fn register(&mut self, backend: Arc) { - self.backends.insert(backend.id().to_string(), backend); - } - - pub fn get(&self, id: &str) -> Option> { - self.backends.get(id).cloned() - } - - pub fn get_or_err(&self, id: &str) -> CfkResult> { - self.get(id).ok_or_else(|| CfkError::BackendNotFound(id.to_string())) - } - - pub fn list(&self) -> Vec<&str> { - self.backends.keys().map(|s| s.as_str()).collect() - } - - pub fn remove(&mut self, id: &str) -> Option> { - self.backends.remove(id) - } -} - -impl Default for BackendRegistry { - fn default() -> Self { - Self::new() - } -} diff --git a/czech-file-knife/src/cfk-providers/src/local.rs b/czech-file-knife/src/cfk-providers/src/local.rs deleted file mode 100644 index 01b39c221..000000000 --- a/czech-file-knife/src/cfk-providers/src/local.rs +++ /dev/null @@ -1,485 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Local filesystem backend - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - backend::{ByteStream, SpaceInfo, StorageBackend, StorageCapabilities}, - entry::{DirectoryListing, Entry, EntryKind}, - error::{CfkError, CfkResult}, - metadata::{Metadata, Permissions}, - operations::*, - VirtualPath, -}; -use std::path::{Path, PathBuf}; -use tokio::fs; -use tokio::io::AsyncReadExt; - -/// Local filesystem backend -pub struct LocalBackend { - id: String, - root: PathBuf, - capabilities: StorageCapabilities, -} - -impl LocalBackend { - pub fn new(id: impl Into, root: impl AsRef) -> Self { - Self { - id: id.into(), - root: root.as_ref().to_path_buf(), - capabilities: StorageCapabilities::local_filesystem(), - } - } - - fn to_real_path(&self, path: &VirtualPath) -> PathBuf { - let mut real = self.root.clone(); - for seg in &path.segments { - real.push(seg); - } - real - } - - fn to_virtual_path(&self, real: &Path) -> CfkResult { - let relative = real - .strip_prefix(&self.root) - .map_err(|_| CfkError::InvalidPath(real.display().to_string()))?; - Ok(VirtualPath::new(&self.id, relative.to_string_lossy())) - } - - async fn metadata_from_path(&self, path: &Path) -> CfkResult<(EntryKind, Metadata)> { - let meta = fs::metadata(path).await?; - let kind = if meta.is_dir() { - EntryKind::Directory - } else if meta.is_file() { - EntryKind::File - } else if meta.is_symlink() { - EntryKind::Symlink - } else { - EntryKind::Unknown - }; - - let mut metadata = Metadata::new(); - metadata.size = Some(meta.len()); - - #[cfg(unix)] - { - use std::os::unix::fs::MetadataExt; - metadata.permissions = Some(Permissions::new(meta.mode())); - } - - if let Ok(modified) = meta.modified() { - metadata.modified = Some(modified.into()); - } - if let Ok(created) = meta.created() { - metadata.created = Some(created.into()); - } - - Ok((kind, metadata)) - } -} - -#[async_trait] -impl StorageBackend for LocalBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Local Filesystem" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.root.exists() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let real = self.to_real_path(path); - if !real.exists() { - return Err(CfkError::NotFound(path.to_string())); - } - let (kind, metadata) = self.metadata_from_path(&real).await?; - Ok(Entry { path: path.clone(), kind, metadata }) - } - - async fn list_directory(&self, path: &VirtualPath, _options: &ListOptions) -> CfkResult { - let real = self.to_real_path(path); - if !real.is_dir() { - return Err(CfkError::NotADirectory(path.to_string())); - } - - let mut entries = Vec::new(); - let mut read_dir = fs::read_dir(&real).await?; - - while let Some(entry) = read_dir.next_entry().await? { - let entry_path = entry.path(); - let vpath = self.to_virtual_path(&entry_path)?; - let (kind, metadata) = self.metadata_from_path(&entry_path).await?; - entries.push(Entry { path: vpath, kind, metadata }); - } - - Ok(DirectoryListing::new(path.clone(), entries)) - } - - async fn read_file(&self, path: &VirtualPath, options: &ReadOptions) -> CfkResult { - let real = self.to_real_path(path); - if !real.is_file() { - return Err(CfkError::NotAFile(path.to_string())); - } - - let mut file = fs::File::open(&real).await?; - let mut buffer = Vec::new(); - - if let Some((start, end)) = options.range { - use tokio::io::AsyncSeekExt; - file.seek(std::io::SeekFrom::Start(start)).await?; - let len = (end - start) as usize; - buffer.resize(len, 0); - file.read_exact(&mut buffer).await?; - } else { - file.read_to_end(&mut buffer).await?; - } - - let bytes = Bytes::from(buffer); - Ok(Box::pin(futures::stream::once(async { Ok(bytes) }))) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes, options: &WriteOptions) -> CfkResult { - let real = self.to_real_path(path); - - if real.exists() && !options.overwrite { - return Err(CfkError::AlreadyExists(path.to_string())); - } - - if options.create_parents { - if let Some(parent) = real.parent() { - fs::create_dir_all(parent).await?; - } - } - - fs::write(&real, &data).await?; - self.get_metadata(path).await - } - - async fn write_file_stream(&self, path: &VirtualPath, mut stream: ByteStream, _size_hint: Option, options: &WriteOptions) -> CfkResult { - use futures::StreamExt; - - let mut data = Vec::new(); - while let Some(chunk) = stream.next().await { - data.extend_from_slice(&chunk?); - } - self.write_file(path, Bytes::from(data), options).await - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let real = self.to_real_path(path); - fs::create_dir_all(&real).await?; - self.get_metadata(path).await - } - - async fn delete(&self, path: &VirtualPath, options: &DeleteOptions) -> CfkResult<()> { - let real = self.to_real_path(path); - - if !real.exists() { - if options.force { - return Ok(()); - } - return Err(CfkError::NotFound(path.to_string())); - } - - if real.is_dir() { - if options.recursive { - fs::remove_dir_all(&real).await?; - } else { - fs::remove_dir(&real).await?; - } - } else { - fs::remove_file(&real).await?; - } - Ok(()) - } - - async fn copy(&self, source: &VirtualPath, dest: &VirtualPath, options: &CopyOptions) -> CfkResult { - let src_real = self.to_real_path(source); - let dst_real = self.to_real_path(dest); - - if !src_real.exists() { - return Err(CfkError::NotFound(source.to_string())); - } - if dst_real.exists() && !options.overwrite { - return Err(CfkError::AlreadyExists(dest.to_string())); - } - - fs::copy(&src_real, &dst_real).await?; - self.get_metadata(dest).await - } - - async fn rename(&self, source: &VirtualPath, dest: &VirtualPath, options: &MoveOptions) -> CfkResult { - let src_real = self.to_real_path(source); - let dst_real = self.to_real_path(dest); - - if !src_real.exists() { - return Err(CfkError::NotFound(source.to_string())); - } - if dst_real.exists() && !options.overwrite { - return Err(CfkError::AlreadyExists(dest.to_string())); - } - - fs::rename(&src_real, &dst_real).await?; - self.get_metadata(dest).await - } - - async fn get_space_info(&self) -> CfkResult { - #[cfg(unix)] - { - use std::ffi::CString; - use std::mem::MaybeUninit; - - let path_cstr = CString::new(self.root.to_string_lossy().as_bytes()) - .map_err(|_| CfkError::InvalidPath(self.root.display().to_string()))?; - - let mut stat: MaybeUninit = MaybeUninit::uninit(); - let result = unsafe { libc::statvfs(path_cstr.as_ptr(), stat.as_mut_ptr()) }; - - if result == 0 { - let stat = unsafe { stat.assume_init() }; - let block_size = stat.f_frsize as u64; - let total = stat.f_blocks as u64 * block_size; - let available = stat.f_bavail as u64 * block_size; - let free = stat.f_bfree as u64 * block_size; - let used = total - free; - - Ok(SpaceInfo { - total: Some(total), - used: Some(used), - available: Some(available), - }) - } else { - Ok(SpaceInfo::unknown()) - } - } - - #[cfg(not(unix))] - { - Ok(SpaceInfo::unknown()) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::StreamExt; - use tempfile::TempDir; - - fn make_backend(dir: &TempDir) -> LocalBackend { - LocalBackend::new("test", dir.path()) - } - - fn make_path(backend: &LocalBackend, p: &str) -> VirtualPath { - VirtualPath::new(backend.id(), p) - } - - #[tokio::test] - async fn test_backend_properties() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - - assert_eq!(backend.id(), "test"); - assert_eq!(backend.display_name(), "Local Filesystem"); - assert!(backend.is_available().await); - assert!(backend.capabilities().read); - assert!(backend.capabilities().write); - } - - #[tokio::test] - async fn test_create_and_read_file() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/test.txt"); - - // Write file - let data = Bytes::from("Hello, World!"); - let options = WriteOptions { overwrite: true, ..Default::default() }; - let entry = backend.write_file(&path, data.clone(), &options).await.unwrap(); - - assert!(entry.is_file()); - assert_eq!(entry.name(), Some("test.txt")); - - // Read file - let read_opts = ReadOptions::default(); - let mut stream = backend.read_file(&path, &read_opts).await.unwrap(); - let mut content = Vec::new(); - while let Some(chunk) = stream.next().await { - content.extend_from_slice(&chunk.unwrap()); - } - assert_eq!(content, b"Hello, World!"); - } - - #[tokio::test] - async fn test_write_without_overwrite_fails() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/test.txt"); - - // First write succeeds - let options = WriteOptions { overwrite: false, ..Default::default() }; - backend.write_file(&path, Bytes::from("first"), &options).await.unwrap(); - - // Second write should fail - let result = backend.write_file(&path, Bytes::from("second"), &options).await; - assert!(matches!(result, Err(CfkError::AlreadyExists(_)))); - } - - #[tokio::test] - async fn test_create_directory() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/subdir/nested"); - - let entry = backend.create_directory(&path).await.unwrap(); - assert!(entry.is_directory()); - } - - #[tokio::test] - async fn test_list_directory() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - - // Create some files and dirs - backend.write_file( - &make_path(&backend, "/file1.txt"), - Bytes::from("content1"), - &WriteOptions { overwrite: true, ..Default::default() }, - ).await.unwrap(); - - backend.write_file( - &make_path(&backend, "/file2.txt"), - Bytes::from("content2"), - &WriteOptions { overwrite: true, ..Default::default() }, - ).await.unwrap(); - - backend.create_directory(&make_path(&backend, "/subdir")).await.unwrap(); - - // List root - let listing = backend - .list_directory(&VirtualPath::root("test"), &ListOptions::default()) - .await - .unwrap(); - - assert_eq!(listing.entries.len(), 3); - let names: Vec<_> = listing.entries.iter().filter_map(|e| e.name()).collect(); - assert!(names.contains(&"file1.txt")); - assert!(names.contains(&"file2.txt")); - assert!(names.contains(&"subdir")); - } - - #[tokio::test] - async fn test_delete_file() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/to_delete.txt"); - - backend.write_file(&path, Bytes::from("delete me"), &WriteOptions::default()).await.unwrap(); - - // Delete - backend.delete(&path, &DeleteOptions::default()).await.unwrap(); - - // Should not exist now - let result = backend.get_metadata(&path).await; - assert!(matches!(result, Err(CfkError::NotFound(_)))); - } - - #[tokio::test] - async fn test_delete_nonexistent_with_force() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/nonexistent.txt"); - - // Without force - should fail - let result = backend.delete(&path, &DeleteOptions::default()).await; - assert!(matches!(result, Err(CfkError::NotFound(_)))); - - // With force - should succeed - let options = DeleteOptions { force: true, ..Default::default() }; - backend.delete(&path, &options).await.unwrap(); - } - - #[tokio::test] - async fn test_copy_file() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let src = make_path(&backend, "/original.txt"); - let dst = make_path(&backend, "/copied.txt"); - - backend.write_file(&src, Bytes::from("original content"), &WriteOptions::default()).await.unwrap(); - - // Copy - let entry = backend.copy(&src, &dst, &CopyOptions::default()).await.unwrap(); - assert!(entry.is_file()); - assert_eq!(entry.name(), Some("copied.txt")); - - // Verify content - let mut stream = backend.read_file(&dst, &ReadOptions::default()).await.unwrap(); - let mut content = Vec::new(); - while let Some(chunk) = stream.next().await { - content.extend_from_slice(&chunk.unwrap()); - } - assert_eq!(content, b"original content"); - } - - #[tokio::test] - async fn test_rename_file() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let src = make_path(&backend, "/old_name.txt"); - let dst = make_path(&backend, "/new_name.txt"); - - backend.write_file(&src, Bytes::from("content"), &WriteOptions::default()).await.unwrap(); - - // Rename - let entry = backend.rename(&src, &dst, &MoveOptions::default()).await.unwrap(); - assert!(entry.is_file()); - assert_eq!(entry.name(), Some("new_name.txt")); - - // Old path should not exist - let result = backend.get_metadata(&src).await; - assert!(matches!(result, Err(CfkError::NotFound(_)))); - } - - #[tokio::test] - async fn test_read_file_range() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/ranged.txt"); - - backend.write_file(&path, Bytes::from("0123456789"), &WriteOptions::default()).await.unwrap(); - - // Read range 3-7 (bytes 3, 4, 5, 6) - let options = ReadOptions { range: Some((3, 7)), ..Default::default() }; - let mut stream = backend.read_file(&path, &options).await.unwrap(); - let mut content = Vec::new(); - while let Some(chunk) = stream.next().await { - content.extend_from_slice(&chunk.unwrap()); - } - assert_eq!(content, b"3456"); - } - - #[tokio::test] - async fn test_get_metadata() { - let tmp = TempDir::new().unwrap(); - let backend = make_backend(&tmp); - let path = make_path(&backend, "/meta_test.txt"); - - let content = "Test content for metadata"; - backend.write_file(&path, Bytes::from(content), &WriteOptions::default()).await.unwrap(); - - let entry = backend.get_metadata(&path).await.unwrap(); - assert!(entry.is_file()); - assert_eq!(entry.size(), Some(content.len() as u64)); - assert!(entry.metadata.modified.is_some()); - } -} diff --git a/czech-file-knife/src/cfk-providers/src/nfs.rs b/czech-file-knife/src/cfk-providers/src/nfs.rs deleted file mode 100644 index 0fad098eb..000000000 --- a/czech-file-knife/src/cfk-providers/src/nfs.rs +++ /dev/null @@ -1,390 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! NFS storage backend -//! -//! Network File System client implementation. -//! Supports NFSv3 and NFSv4 protocols. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use std::path::PathBuf; - -/// NFS version -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum NfsVersion { - V3, - V4, - V41, -} - -impl Default for NfsVersion { - fn default() -> Self { - Self::V4 - } -} - -/// NFS authentication flavor -#[derive(Debug, Clone)] -pub enum NfsAuth { - /// AUTH_SYS (Unix authentication) - Sys { uid: u32, gid: u32, gids: Vec }, - /// AUTH_NONE - None, - /// RPCSEC_GSS (Kerberos) - Gss { principal: String }, -} - -impl Default for NfsAuth { - fn default() -> Self { - Self::Sys { - uid: 65534, // nobody - gid: 65534, - gids: vec![], - } - } -} - -/// NFS backend configuration -#[derive(Debug, Clone)] -pub struct NfsConfig { - /// Server hostname or IP - pub server: String, - /// Export path - pub export: String, - /// NFS version - pub version: NfsVersion, - /// Authentication - pub auth: NfsAuth, - /// Read size (NFSv3: 65536, NFSv4: 1MB) - pub rsize: u32, - /// Write size - pub wsize: u32, - /// Use TCP (vs UDP for NFSv3) - pub tcp: bool, - /// Port (0 = use portmapper/rpcbind) - pub port: u16, -} - -impl Default for NfsConfig { - fn default() -> Self { - Self { - server: "localhost".to_string(), - export: "/".to_string(), - version: NfsVersion::V4, - auth: NfsAuth::default(), - rsize: 1048576, // 1MB - wsize: 1048576, - tcp: true, - port: 2049, - } - } -} - -/// NFS file handle -#[derive(Debug, Clone, Default)] -struct NfsFileHandle { - data: Vec, -} - -/// NFS storage backend -/// -/// Note: This is a stub implementation. Full implementation would require -/// ONC RPC and XDR encoding, which is complex. Consider using `nfs` crate -/// or system mount. -pub struct NfsBackend { - id: String, - config: NfsConfig, - capabilities: StorageCapabilities, - /// Root file handle (obtained from MOUNT/PUTROOTFH) - root_fh: Option, -} - -impl NfsBackend { - pub fn new(id: impl Into, config: NfsConfig) -> Self { - Self { - id: id.into(), - config, - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: false, - list: true, - search: false, - versioning: false, - sharing: true, // ACLs - streaming: true, - resume: true, - watch: false, // NFSv4.1 has callbacks - metadata: true, - thumbnails: false, - max_file_size: None, - }, - root_fh: None, - } - } - - /// Create from NFS URL: nfs://server/export - pub fn from_url(id: impl Into, url: &str) -> CfkResult { - let parsed = url::Url::parse(url) - .map_err(|e| CfkError::InvalidPath(format!("Invalid URL: {}", e)))?; - - if parsed.scheme() != "nfs" { - return Err(CfkError::InvalidPath("URL scheme must be nfs".into())); - } - - let server = parsed - .host_str() - .ok_or_else(|| CfkError::InvalidPath("Missing server".into()))? - .to_string(); - - let export = parsed.path().to_string(); - let port = parsed.port().unwrap_or(2049); - - Ok(Self::new( - id, - NfsConfig { - server, - export, - port, - ..Default::default() - }, - )) - } - - /// Mount the NFS export - pub async fn mount(&mut self) -> CfkResult<()> { - // In a full implementation: - // 1. For NFSv3: Contact portmapper, get MOUNT port, call MOUNT - // 2. For NFSv4: Use PUTROOTFH compound operation - - match self.config.version { - NfsVersion::V3 => { - // NFSv3 mount protocol - // 1. RPC call to rpcbind to get mount daemon port - // 2. RPC MOUNT call to get root file handle - } - NfsVersion::V4 | NfsVersion::V41 => { - // NFSv4 uses COMPOUND operations - // PUTROOTFH + GETFH to get root handle - } - } - - Err(CfkError::Unsupported( - "NFS backend is a stub. Use system mount or nfs crate.".into(), - )) - } - - /// Convert VirtualPath to NFS path components - fn to_path_components(&self, path: &VirtualPath) -> Vec { - path.segments.clone() - } - - /// Lookup a path and return file handle - async fn lookup(&self, _path: &VirtualPath) -> CfkResult { - // Would use LOOKUP (v3) or LOOKUP in COMPOUND (v4) - Err(CfkError::Unsupported("NFS stub".into())) - } -} - -#[async_trait] -impl StorageBackend for NfsBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - match self.config.version { - NfsVersion::V3 => "NFSv3", - NfsVersion::V4 => "NFSv4", - NfsVersion::V41 => "NFSv4.1", - } - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.root_fh.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let _fh = self.lookup(path).await?; - // Would use GETATTR operation - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let _fh = self.lookup(path).await?; - // Would use READDIR/READDIRPLUS (v3) or READDIR in COMPOUND (v4) - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let _fh = self.lookup(path).await?; - // Would use READ operation with offset/count - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn write_file(&self, path: &VirtualPath, _data: Bytes) -> CfkResult { - // Would use CREATE + WRITE operations - let _components = self.to_path_components(path); - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let _fh = self.lookup(path).await?; - // Would use REMOVE (file) or RMDIR (directory) - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - // Would use MKDIR operation - let _components = self.to_path_components(path); - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn copy(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - // NFS doesn't have native copy (until NFSv4.2 COPY operation) - Err(CfkError::Unsupported("NFS doesn't support native copy".into())) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - // Would use RENAME operation - let _from_components = self.to_path_components(from); - let _to_components = self.to_path_components(to); - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // Would use FSSTAT (v3) or GETATTR with fsinfo (v4) - - Err(CfkError::Unsupported("NFS stub - use system mount".into())) - } -} - -/// NFS file types -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum NfsFileType { - Regular = 1, - Directory = 2, - BlockDevice = 3, - CharDevice = 4, - Symlink = 5, - Socket = 6, - Fifo = 7, -} - -/// NFS file attributes (fattr3/fattr4) -#[derive(Debug, Clone, Default)] -pub struct NfsAttributes { - pub file_type: u32, - pub mode: u32, - pub nlink: u32, - pub uid: u32, - pub gid: u32, - pub size: u64, - pub used: u64, - pub fsid: u64, - pub fileid: u64, - pub atime_sec: u32, - pub atime_nsec: u32, - pub mtime_sec: u32, - pub mtime_nsec: u32, - pub ctime_sec: u32, - pub ctime_nsec: u32, -} - -impl NfsAttributes { - pub fn to_entry(&self, backend_id: &str, path: &str) -> Entry { - let kind = match self.file_type { - 2 => EntryKind::Directory, - 5 => EntryKind::Symlink, - _ => EntryKind::File, - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(self.size); - metadata.permissions = Some(self.mode); - metadata.uid = Some(self.uid); - metadata.gid = Some(self.gid); - - if self.mtime_sec > 0 { - metadata.modified = chrono::DateTime::from_timestamp( - self.mtime_sec as i64, - self.mtime_nsec, - ); - } - - Entry { - path: VirtualPath::new(backend_id, path), - kind, - metadata, - } - } -} - -/// Helper function to use system NFS mount -impl NfsBackend { - /// Mount using system mount command (requires root or fuse-nfs) - pub fn mount_system(&self, mount_point: &PathBuf) -> CfkResult<()> { - use std::process::Command; - - let source = format!("{}:{}", self.config.server, self.config.export); - let version = match self.config.version { - NfsVersion::V3 => "3", - NfsVersion::V4 => "4", - NfsVersion::V41 => "4.1", - }; - - let status = Command::new("mount") - .args([ - "-t", "nfs", - "-o", &format!("vers={}", version), - &source, - mount_point.to_str().unwrap_or("/mnt"), - ]) - .status() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !status.success() { - return Err(CfkError::ProviderApi { - provider: "nfs".into(), - message: "mount command failed".into(), - }); - } - - Ok(()) - } - - /// Unmount system mount - pub fn unmount_system(&self, mount_point: &PathBuf) -> CfkResult<()> { - use std::process::Command; - - let status = Command::new("umount") - .arg(mount_point.to_str().unwrap_or("/mnt")) - .status() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !status.success() { - return Err(CfkError::ProviderApi { - provider: "nfs".into(), - message: "umount command failed".into(), - }); - } - - Ok(()) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/ninep.rs b/czech-file-knife/src/cfk-providers/src/ninep.rs deleted file mode 100644 index 2838f23d2..000000000 --- a/czech-file-knife/src/cfk-providers/src/ninep.rs +++ /dev/null @@ -1,961 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! 9P/Plan 9 filesystem protocol backend -//! -//! Used in WSL2 (drvfs), QEMU/KVM (virtio-9p), and Plan 9/Inferno systems. -//! Implements 9P2000.L (Linux extensions) protocol. - -use async_trait::async_trait; -use bytes::{Buf, BufMut, Bytes, BytesMut}; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use std::collections::HashMap; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::Arc; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpStream; -use tokio::sync::RwLock; - -/// 9P message types -mod msg { - pub const TVERSION: u8 = 100; - pub const RVERSION: u8 = 101; - pub const TAUTH: u8 = 102; - pub const RAUTH: u8 = 103; - pub const TATTACH: u8 = 104; - pub const RATTACH: u8 = 105; - pub const RERROR: u8 = 107; - pub const TFLUSH: u8 = 108; - pub const RFLUSH: u8 = 109; - pub const TWALK: u8 = 110; - pub const RWALK: u8 = 111; - pub const TOPEN: u8 = 112; - pub const ROPEN: u8 = 113; - pub const TCREATE: u8 = 114; - pub const RCREATE: u8 = 115; - pub const TREAD: u8 = 116; - pub const RREAD: u8 = 117; - pub const TWRITE: u8 = 118; - pub const RWRITE: u8 = 119; - pub const TCLUNK: u8 = 120; - pub const RCLUNK: u8 = 121; - pub const TREMOVE: u8 = 122; - pub const RREMOVE: u8 = 123; - pub const TSTAT: u8 = 124; - pub const RSTAT: u8 = 125; - pub const TWSTAT: u8 = 126; - pub const RWSTAT: u8 = 127; - - // 9P2000.L extensions - pub const TLOPEN: u8 = 12; - pub const RLOPEN: u8 = 13; - pub const TLCREATE: u8 = 14; - pub const RLCREATE: u8 = 15; - pub const TREADDIR: u8 = 40; - pub const RREADDIR: u8 = 41; - pub const TGETATTR: u8 = 24; - pub const RGETATTR: u8 = 25; -} - -/// Open modes -mod omode { - pub const READ: u8 = 0; - pub const WRITE: u8 = 1; - pub const RDWR: u8 = 2; - pub const TRUNC: u16 = 0x10; -} - -/// 9P QID type -#[derive(Debug, Clone, Default)] -struct Qid { - qid_type: u8, - version: u32, - path: u64, -} - -impl Qid { - fn is_dir(&self) -> bool { - self.qid_type & 0x80 != 0 - } -} - -/// 9P backend configuration -#[derive(Debug, Clone)] -pub struct NinePConfig { - /// Server address (host:port) - pub address: String, - /// Attach name (usually empty or a mount tag) - pub aname: String, - /// Username for authentication - pub uname: String, - /// Maximum message size - pub msize: u32, -} - -impl Default for NinePConfig { - fn default() -> Self { - Self { - address: "127.0.0.1:564".to_string(), - aname: String::new(), - uname: "nobody".to_string(), - msize: 8192, - } - } -} - -/// 9P connection state -struct Connection { - stream: TcpStream, - msize: u32, - root_fid: u32, -} - -/// 9P storage backend -pub struct NinePBackend { - id: String, - config: NinePConfig, - connection: Arc>>, - fid_counter: AtomicU32, - capabilities: StorageCapabilities, - /// Cache of path to fid mapping - fid_cache: Arc>>, -} - -impl NinePBackend { - pub fn new(id: impl Into, config: NinePConfig) -> Self { - Self { - id: id.into(), - config, - connection: Arc::new(RwLock::new(None)), - fid_counter: AtomicU32::new(1), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: false, // 9P doesn't have native copy - list: true, - search: false, - versioning: false, - sharing: false, - streaming: true, - resume: false, - watch: false, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - fid_cache: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Connect to 9P server - pub async fn connect(&self) -> CfkResult<()> { - let stream = TcpStream::connect(&self.config.address) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let mut conn = Connection { - stream, - msize: self.config.msize, - root_fid: 0, - }; - - // Send Tversion - let tag = 0xFFFF; // NOTAG for version - let mut buf = BytesMut::new(); - buf.put_u32_le(0); // size placeholder - buf.put_u8(msg::TVERSION); - buf.put_u16_le(tag); - buf.put_u32_le(self.config.msize); - put_string(&mut buf, "9P2000.L"); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - // Read Rversion - let reply = read_message(&mut conn.stream).await?; - if reply[4] != msg::RVERSION { - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: "Version negotiation failed".into(), - }); - } - - let mut cursor = &reply[7..]; - conn.msize = cursor.get_u32_le(); - - // Send Tattach - let root_fid = self.alloc_fid(); - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TATTACH); - buf.put_u16_le(1); // tag - buf.put_u32_le(root_fid); - buf.put_u32_le(0xFFFFFFFF); // afid (no auth) - put_string(&mut buf, &self.config.uname); - put_string(&mut buf, &self.config.aname); - buf.put_u32_le(0); // n_uname (9P2000.L) - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - conn.root_fid = root_fid; - *self.connection.write().await = Some(conn); - - Ok(()) - } - - /// Allocate a new fid - fn alloc_fid(&self) -> u32 { - self.fid_counter.fetch_add(1, Ordering::SeqCst) - } - - /// Walk to a path and return the fid - async fn walk(&self, path: &VirtualPath) -> CfkResult { - let path_str = path.to_string(); - - // Check cache - { - let cache = self.fid_cache.read().await; - if let Some(&fid) = cache.get(&path_str) { - return Ok(fid); - } - } - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - let new_fid = self.alloc_fid(); - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TWALK); - buf.put_u16_le(2); // tag - buf.put_u32_le(conn.root_fid); - buf.put_u32_le(new_fid); - - // Path segments - let segments = &path.segments; - buf.put_u16_le(segments.len() as u16); - for seg in segments { - put_string(&mut buf, seg); - } - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::NotFound(format!("{}: {}", path, error))); - } - - // Cache the fid - { - let mut cache = self.fid_cache.write().await; - cache.insert(path_str, new_fid); - } - - Ok(new_fid) - } - - /// Clunk (release) a fid - async fn clunk(&self, fid: u32) -> CfkResult<()> { - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TCLUNK); - buf.put_u16_le(3); - buf.put_u32_le(fid); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let _reply = read_message(&mut conn.stream).await?; - Ok(()) - } - - /// Get file attributes (9P2000.L Tgetattr) - async fn getattr(&self, fid: u32) -> CfkResult { - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TGETATTR); - buf.put_u16_le(4); - buf.put_u32_le(fid); - buf.put_u64_le(0x7FF); // request_mask: all basic attrs - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - parse_getattr(&reply) - } -} - -/// File attributes -#[derive(Debug, Clone, Default)] -struct FileAttr { - mode: u32, - uid: u32, - gid: u32, - nlink: u64, - size: u64, - atime_sec: u64, - mtime_sec: u64, - ctime_sec: u64, -} - -/// Parse Rgetattr response -fn parse_getattr(data: &[u8]) -> CfkResult { - if data.len() < 100 { - return Err(CfkError::Serialization("Rgetattr too short".into())); - } - - let mut cursor = &data[7..]; // Skip size, type, tag - let _valid = cursor.get_u64_le(); - let _qid_type = cursor.get_u8(); - let _qid_version = cursor.get_u32_le(); - let _qid_path = cursor.get_u64_le(); - - Ok(FileAttr { - mode: cursor.get_u32_le(), - uid: cursor.get_u32_le(), - gid: cursor.get_u32_le(), - nlink: cursor.get_u64_le(), - _rdev: cursor.get_u64_le(), - size: cursor.get_u64_le(), - _blksize: cursor.get_u64_le(), - _blocks: cursor.get_u64_le(), - atime_sec: cursor.get_u64_le(), - _atime_nsec: cursor.get_u64_le(), - mtime_sec: cursor.get_u64_le(), - _mtime_nsec: cursor.get_u64_le(), - ctime_sec: cursor.get_u64_le(), - ..Default::default() - }) -} - -/// Parse error from Rerror message -fn parse_error(data: &[u8]) -> CfkResult { - if data.len() < 9 { - return Ok("Unknown error".into()); - } - - let mut cursor = &data[7..]; - let len = cursor.get_u16_le() as usize; - if cursor.len() >= len { - Ok(String::from_utf8_lossy(&cursor[..len]).to_string()) - } else { - Ok("Unknown error".into()) - } -} - -/// Read a 9P message -async fn read_message(stream: &mut TcpStream) -> CfkResult> { - let mut size_buf = [0u8; 4]; - stream - .read_exact(&mut size_buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let size = u32::from_le_bytes(size_buf) as usize; - if size < 4 || size > 1024 * 1024 { - return Err(CfkError::Serialization("Invalid message size".into())); - } - - let mut buf = vec![0u8; size]; - buf[0..4].copy_from_slice(&size_buf); - - stream - .read_exact(&mut buf[4..]) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - Ok(buf) -} - -/// Write string in 9P format (2-byte length prefix) -fn put_string(buf: &mut BytesMut, s: &str) { - let bytes = s.as_bytes(); - buf.put_u16_le(bytes.len() as u16); - buf.put_slice(bytes); -} - -#[async_trait] -impl StorageBackend for NinePBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "9P" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - let conn = self.connection.read().await; - conn.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let fid = self.walk(path).await?; - let attr = self.getattr(fid).await?; - - let kind = if (attr.mode & 0o40000) != 0 { - EntryKind::Directory - } else if (attr.mode & 0o120000) == 0o120000 { - EntryKind::Symlink - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(attr.size); - metadata.permissions = Some(attr.mode); - metadata.uid = Some(attr.uid); - metadata.gid = Some(attr.gid); - - if attr.mtime_sec > 0 { - metadata.modified = chrono::DateTime::from_timestamp(attr.mtime_sec as i64, 0); - } - - Ok(Entry { - path: path.clone(), - kind, - metadata, - }) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let fid = self.walk(path).await?; - - // Open directory for reading - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - // Tlopen - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TLOPEN); - buf.put_u16_le(5); - buf.put_u32_le(fid); - buf.put_u32_le(omode::READ as u32); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - // Treaddir - let mut entries = Vec::new(); - let mut offset = 0u64; - - loop { - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TREADDIR); - buf.put_u16_le(6); - buf.put_u32_le(fid); - buf.put_u64_le(offset); - buf.put_u32_le(conn.msize - 24); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - break; - } - - let mut cursor = &reply[7..]; - let count = cursor.get_u32_le() as usize; - if count == 0 { - break; - } - - // Parse directory entries - let data = &cursor[..count]; - let mut pos = 0; - - while pos < data.len() { - if pos + 24 > data.len() { - break; - } - - let mut entry_cursor = &data[pos..]; - let qid_type = entry_cursor.get_u8(); - let _qid_version = entry_cursor.get_u32_le(); - let _qid_path = entry_cursor.get_u64_le(); - offset = entry_cursor.get_u64_le(); - let dtype = entry_cursor.get_u8(); - let name_len = entry_cursor.get_u16_le() as usize; - - if pos + 24 + name_len > data.len() { - break; - } - - let name = String::from_utf8_lossy(&entry_cursor[..name_len]).to_string(); - pos += 24 + name_len; - - if name == "." || name == ".." { - continue; - } - - let kind = if qid_type & 0x80 != 0 { - EntryKind::Directory - } else { - EntryKind::File - }; - - let entry_path = if path.segments.is_empty() { - VirtualPath::new(&self.id, &name) - } else { - VirtualPath::new(&self.id, &format!("{}/{}", path.segments.join("/"), name)) - }; - - entries.push(Entry { - path: entry_path, - kind, - metadata: Metadata::default(), - }); - } - } - - drop(conn_guard); - self.clunk(fid).await?; - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let fid = self.walk(path).await?; - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - // Tlopen - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TLOPEN); - buf.put_u16_le(7); - buf.put_u32_le(fid); - buf.put_u32_le(omode::READ as u32); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - // Read file content - let mut content = Vec::new(); - let mut offset = 0u64; - let chunk_size = conn.msize - 24; - - loop { - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TREAD); - buf.put_u16_le(8); - buf.put_u32_le(fid); - buf.put_u64_le(offset); - buf.put_u32_le(chunk_size); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - let mut cursor = &reply[7..]; - let count = cursor.get_u32_le() as usize; - if count == 0 { - break; - } - - content.extend_from_slice(&cursor[..count]); - offset += count as u64; - } - - drop(conn_guard); - self.clunk(fid).await?; - - Ok(Bytes::from(content)) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - // Walk to parent and create file - let parent = if path.segments.len() > 1 { - VirtualPath::new(&self.id, &path.segments[..path.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - - let parent_fid = self.walk(&parent).await?; - let name = path.segments.last().cloned().unwrap_or_default(); - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - // Tlcreate - let new_fid = self.fid_counter.fetch_add(1, Ordering::SeqCst); - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TLCREATE); - buf.put_u16_le(9); - buf.put_u32_le(parent_fid); - put_string(&mut buf, &name); - buf.put_u32_le(omode::RDWR as u32 | omode::TRUNC as u32); - buf.put_u32_le(0o644); // mode - buf.put_u32_le(0); // gid - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - // Write data - let mut offset = 0u64; - let chunk_size = (conn.msize - 24) as usize; - - while offset < data.len() as u64 { - let end = std::cmp::min(offset as usize + chunk_size, data.len()); - let chunk = &data[offset as usize..end]; - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TWRITE); - buf.put_u16_le(10); - buf.put_u32_le(new_fid); - buf.put_u64_le(offset); - buf.put_u32_le(chunk.len() as u32); - buf.put_slice(chunk); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - let mut cursor = &reply[7..]; - let written = cursor.get_u32_le() as u64; - offset += written; - } - - drop(conn_guard); - self.clunk(new_fid).await?; - - self.get_metadata(path).await - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let fid = self.walk(path).await?; - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(msg::TREMOVE); - buf.put_u16_le(11); - buf.put_u32_le(fid); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - // Remove from cache - { - let mut cache = self.fid_cache.write().await; - cache.remove(&path.to_string()); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let parent = if path.segments.len() > 1 { - VirtualPath::new(&self.id, &path.segments[..path.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - - let parent_fid = self.walk(&parent).await?; - let name = path.segments.last().cloned().unwrap_or_default(); - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - // Tmkdir (9P2000.L) - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(72); // Tmkdir - buf.put_u16_le(12); - buf.put_u32_le(parent_fid); - put_string(&mut buf, &name); - buf.put_u32_le(0o755); // mode - buf.put_u32_le(0); // gid - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - drop(conn_guard); - self.get_metadata(path).await - } - - async fn copy(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - Err(CfkError::Unsupported("9P does not support copy".into())) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - // 9P2000.L has Trename - let old_fid = self.walk(from).await?; - let new_parent = if to.segments.len() > 1 { - VirtualPath::new(&self.id, &to.segments[..to.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - let new_parent_fid = self.walk(&new_parent).await?; - let new_name = to.segments.last().cloned().unwrap_or_default(); - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - // Trenameat - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(74); // Trenameat - buf.put_u16_le(13); - buf.put_u32_le(old_fid); - put_string(&mut buf, from.segments.last().unwrap_or(&String::new())); - buf.put_u32_le(new_parent_fid); - put_string(&mut buf, &new_name); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - let error = parse_error(&reply)?; - return Err(CfkError::ProviderApi { - provider: "9p".into(), - message: error, - }); - } - - // Update cache - { - let mut cache = self.fid_cache.write().await; - cache.remove(&from.to_string()); - } - - drop(conn_guard); - self.get_metadata(to).await - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // 9P2000.L has Tstatfs - let root = VirtualPath::new(&self.id, ""); - let fid = self.walk(&root).await?; - - let mut conn_guard = self.connection.write().await; - let conn = conn_guard - .as_mut() - .ok_or_else(|| CfkError::Network("Not connected".into()))?; - - let mut buf = BytesMut::new(); - buf.put_u32_le(0); - buf.put_u8(8); // Tstatfs - buf.put_u16_le(14); - buf.put_u32_le(fid); - - let size = buf.len() as u32; - buf[0..4].copy_from_slice(&size.to_le_bytes()); - - conn.stream - .write_all(&buf) - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let reply = read_message(&mut conn.stream).await?; - if reply[4] == msg::RERROR { - return Ok((0, 0)); - } - - let mut cursor = &reply[7..]; - let _type = cursor.get_u32_le(); - let bsize = cursor.get_u32_le() as u64; - let blocks = cursor.get_u64_le(); - let bfree = cursor.get_u64_le(); - let bavail = cursor.get_u64_le(); - - let total = blocks * bsize; - let available = bavail * bsize; - - Ok((available, total)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/onedrive.rs b/czech-file-knife/src/cfk-providers/src/onedrive.rs deleted file mode 100644 index 330e0bfb3..000000000 --- a/czech-file-knife/src/cfk-providers/src/onedrive.rs +++ /dev/null @@ -1,621 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! OneDrive storage backend -//! -//! Microsoft Graph API implementation for OneDrive Personal and Business. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, - RedirectUrl, Scope, TokenUrl, -}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tokio::sync::RwLock; - -const MS_AUTH_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; -const MS_TOKEN_URL: &str = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; -const GRAPH_API_URL: &str = "https://graph.microsoft.com/v1.0"; - -/// Microsoft OAuth tokens -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct OneDriveTokens { - pub access_token: String, - pub refresh_token: Option, - pub expires_at: Option>, -} - -/// OneDrive backend configuration -#[derive(Debug, Clone)] -pub struct OneDriveConfig { - pub client_id: String, - pub redirect_uri: String, - /// Use OneDrive for Business (SharePoint) instead of personal - pub business: bool, -} - -/// OneDrive storage backend -pub struct OneDriveBackend { - id: String, - config: OneDriveConfig, - tokens: Arc>>, - http: Client, - capabilities: StorageCapabilities, -} - -impl OneDriveBackend { - pub fn new(id: impl Into, config: OneDriveConfig) -> Self { - Self { - id: id.into(), - config, - tokens: Arc::new(RwLock::new(None)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: true, - versioning: true, - sharing: true, - streaming: true, - resume: true, - watch: true, - metadata: true, - thumbnails: true, - max_file_size: Some(250 * 1024 * 1024 * 1024), // 250GB - }, - } - } - - /// Start OAuth 2.0 + PKCE flow - pub fn start_auth(&self) -> (String, PkceCodeVerifier) { - let client = BasicClient::new(ClientId::new(self.config.client_id.clone())) - .set_auth_uri(AuthUrl::new(MS_AUTH_URL.to_string()).unwrap()) - .set_token_uri(TokenUrl::new(MS_TOKEN_URL.to_string()).unwrap()) - .set_redirect_uri(RedirectUrl::new(self.config.redirect_uri.clone()).unwrap()); - - let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - - let (auth_url, _csrf_token) = client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new("Files.ReadWrite.All".to_string())) - .add_scope(Scope::new("offline_access".to_string())) - .set_pkce_challenge(pkce_challenge) - .url(); - - (auth_url.to_string(), pkce_verifier) - } - - /// Complete OAuth flow with authorization code - pub async fn complete_auth( - &self, - code: &str, - verifier: PkceCodeVerifier, - ) -> CfkResult { - let params = [ - ("code", code.to_string()), - ("grant_type", "authorization_code".to_string()), - ("client_id", self.config.client_id.clone()), - ("redirect_uri", self.config.redirect_uri.clone()), - ("code_verifier", verifier.secret().to_string()), - ]; - - let response = self - .http - .post(MS_TOKEN_URL) - .form(¶ms) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::Auth(format!("Token exchange failed: {}", error_text))); - } - - #[derive(Deserialize)] - struct TokenResponse { - access_token: String, - refresh_token: Option, - expires_in: Option, - } - - let token_resp: TokenResponse = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let tokens = OneDriveTokens { - access_token: token_resp.access_token, - refresh_token: token_resp.refresh_token, - expires_at: token_resp - .expires_in - .map(|secs| Utc::now() + chrono::Duration::seconds(secs)), - }; - - *self.tokens.write().await = Some(tokens.clone()); - Ok(tokens) - } - - /// Set tokens directly - pub async fn set_tokens(&self, tokens: OneDriveTokens) { - *self.tokens.write().await = Some(tokens); - } - - /// Get current access token - async fn get_access_token(&self) -> CfkResult { - let tokens = self.tokens.read().await; - tokens - .as_ref() - .map(|t| t.access_token.clone()) - .ok_or_else(|| CfkError::Auth("Not authenticated".into())) - } - - /// Build API path for OneDrive - fn api_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - format!("{}/me/drive/root", GRAPH_API_URL) - } else { - let path_str = path.segments.join("/"); - format!("{}/me/drive/root:/{}", GRAPH_API_URL, path_str) - } - } - - /// Build children API path - fn children_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - format!("{}/me/drive/root/children", GRAPH_API_URL) - } else { - let path_str = path.segments.join("/"); - format!("{}/me/drive/root:/{}:/children", GRAPH_API_URL, path_str) - } - } -} - -/// OneDrive item metadata -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct DriveItem { - id: String, - name: String, - size: Option, - created_date_time: Option, - last_modified_date_time: Option, - folder: Option, - file: Option, - parent_reference: Option, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct FolderFacet { - child_count: Option, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct FileFacet { - mime_type: Option, - hashes: Option, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct FileHashes { - quick_xor_hash: Option, - sha1_hash: Option, - sha256_hash: Option, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ParentReference { - path: Option, -} - -impl DriveItem { - fn to_entry(&self, backend_id: &str, base_path: &str) -> Entry { - let path_str = if base_path.is_empty() { - self.name.clone() - } else { - format!("{}/{}", base_path, self.name) - }; - let virtual_path = VirtualPath::new(backend_id, &path_str); - - let kind = if self.folder.is_some() { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = self.size; - - if let Some(ref file) = self.file { - metadata.mime_type = file.mime_type.clone(); - if let Some(ref hashes) = file.hashes { - metadata.checksum = hashes - .sha256_hash - .clone() - .or_else(|| hashes.sha1_hash.clone()); - } - } - - if let Some(ref modified) = self.last_modified_date_time { - if let Ok(dt) = DateTime::parse_from_rfc3339(modified) { - metadata.modified = Some(dt.with_timezone(&Utc)); - } - } - if let Some(ref created) = self.created_date_time { - if let Ok(dt) = DateTime::parse_from_rfc3339(created) { - metadata.created = Some(dt.with_timezone(&Utc)); - } - } - - Entry { - path: virtual_path, - kind, - metadata, - } - } -} - -#[async_trait] -impl StorageBackend for OneDriveBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - if self.config.business { - "OneDrive for Business" - } else { - "OneDrive" - } - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.tokens.read().await.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let url = self.api_path(path); - - let response = self - .http - .get(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - if status == reqwest::StatusCode::NOT_FOUND { - return Err(CfkError::NotFound(path.to_string())); - } - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "onedrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let item: DriveItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if path.segments.len() > 1 { - path.segments[..path.segments.len() - 1].join("/") - } else { - String::new() - }; - - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let url = self.children_path(path); - - let mut entries = Vec::new(); - let mut next_link: Option = Some(url); - - while let Some(url) = next_link.take() { - let response = self - .http - .get(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct ItemList { - value: Vec, - #[serde(rename = "@odata.nextLink")] - next_link: Option, - } - - let list: ItemList = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = path.segments.join("/"); - - for item in list.value { - entries.push(item.to_entry(&self.id, &base_path)); - } - - next_link = list.next_link; - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let url = format!("{}:/content", self.api_path(path)); - - let response = self - .http - .get(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "onedrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let url = format!("{}:/content", self.api_path(path)); - - let response = self - .http - .put(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .header("Content-Type", "application/octet-stream") - .body(data.to_vec()) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "onedrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let item: DriveItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if path.segments.len() > 1 { - path.segments[..path.segments.len() - 1].join("/") - } else { - String::new() - }; - - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let url = self.api_path(path); - - let response = self - .http - .delete(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != reqwest::StatusCode::NO_CONTENT { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "onedrive".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let parent_path = if path.segments.len() > 1 { - VirtualPath::new(&self.id, &path.segments[..path.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - - let name = path.segments.last().cloned().unwrap_or_default(); - let url = self.children_path(&parent_path); - - #[derive(Serialize)] - struct CreateFolder { - name: String, - folder: serde_json::Value, - #[serde(rename = "@microsoft.graph.conflictBehavior")] - conflict_behavior: String, - } - - let body = CreateFolder { - name, - folder: serde_json::json!({}), - conflict_behavior: "fail".to_string(), - }; - - let response = self - .http - .post(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: DriveItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = parent_path.segments.join("/"); - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_url = self.api_path(from); - let to_parent = if to.segments.len() > 1 { - VirtualPath::new(&self.id, &to.segments[..to.segments.len() - 1].join("/")) - } else { - VirtualPath::new(&self.id, "") - }; - let to_name = to.segments.last().cloned().unwrap_or_default(); - - // Get the parent folder's drive item id - let parent_response = self - .http - .get(&self.api_path(&to_parent)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let parent_item: DriveItem = parent_response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - struct CopyRequest { - parent_reference: ParentRef, - name: String, - } - - #[derive(Serialize)] - struct ParentRef { - id: String, - } - - let body = CopyRequest { - parent_reference: ParentRef { id: parent_item.id }, - name: to_name, - }; - - let _response = self - .http - .post(format!("{}:/copy", from_url)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - // Copy is async in OneDrive, return metadata of destination - self.get_metadata(to).await - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let url = self.api_path(from); - let to_name = to.segments.last().cloned().unwrap_or_default(); - - #[derive(Serialize)] - struct RenameRequest { - name: String, - } - - let body = RenameRequest { name: to_name }; - - let response = self - .http - .patch(&url) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let item: DriveItem = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let base_path = if to.segments.len() > 1 { - to.segments[..to.segments.len() - 1].join("/") - } else { - String::new() - }; - - Ok(item.to_entry(&self.id, &base_path)) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - let response = self - .http - .get(format!("{}/me/drive", GRAPH_API_URL)) - .header("Authorization", format!("Bearer {}", self.get_access_token().await?)) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - #[derive(Deserialize)] - struct Drive { - quota: Option, - } - - #[derive(Deserialize)] - struct DriveQuota { - total: Option, - used: Option, - remaining: Option, - } - - let drive: Drive = response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string()))?; - - let quota = drive.quota.unwrap_or(DriveQuota { - total: None, - used: None, - remaining: None, - }); - - let total = quota.total.unwrap_or(0); - let available = quota.remaining.unwrap_or(0); - - Ok((available, total)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/protocols.rs b/czech-file-knife/src/cfk-providers/src/protocols.rs deleted file mode 100644 index ece607d4f..000000000 --- a/czech-file-knife/src/cfk-providers/src/protocols.rs +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Exotic protocol support -//! -//! Additional protocols beyond standard cloud/file systems: -//! - NNTP/NNTPS: Usenet -//! - Gopher/Gopher+: Pre-web protocol -//! - Gemini: Modern minimalist protocol -//! - RTSP: Streaming -//! - BitTorrent: P2P file sharing -//! - DAT/Hypercore: P2P versioned data -//! - Freenet: Anonymous storage -//! - I2P: Anonymous network -//! - Tor: Onion services -//! - MTP: Mobile devices -//! - AFP: Apple Filing -//! - DLNA/UPnP: Media discovery - -use cfk_core::{CfkError, CfkResult}; - -/// Protocol capabilities -#[derive(Debug, Clone, Default)] -pub struct ProtocolInfo { - pub name: &'static str, - pub scheme: &'static str, - pub default_port: u16, - pub encrypted: bool, - pub bidirectional: bool, // can upload - pub streaming: bool, - pub anonymous: bool, -} - -/// Supported exotic protocols -pub const PROTOCOLS: &[ProtocolInfo] = &[ - ProtocolInfo { - name: "Usenet (NNTP)", - scheme: "nntp", - default_port: 119, - encrypted: false, - bidirectional: true, - streaming: false, - anonymous: false, - }, - ProtocolInfo { - name: "Usenet Secure (NNTPS)", - scheme: "nntps", - default_port: 563, - encrypted: true, - bidirectional: true, - streaming: false, - anonymous: false, - }, - ProtocolInfo { - name: "Gopher", - scheme: "gopher", - default_port: 70, - encrypted: false, - bidirectional: false, - streaming: false, - anonymous: true, - }, - ProtocolInfo { - name: "Gemini", - scheme: "gemini", - default_port: 1965, - encrypted: true, - bidirectional: false, - streaming: false, - anonymous: true, - }, - ProtocolInfo { - name: "RTSP (Streaming)", - scheme: "rtsp", - default_port: 554, - encrypted: false, - bidirectional: false, - streaming: true, - anonymous: false, - }, - ProtocolInfo { - name: "BitTorrent", - scheme: "magnet", - default_port: 6881, - encrypted: false, - bidirectional: true, - streaming: false, - anonymous: false, - }, - ProtocolInfo { - name: "DAT/Hypercore", - scheme: "dat", - default_port: 3282, - encrypted: true, - bidirectional: true, - streaming: true, - anonymous: false, - }, - ProtocolInfo { - name: "Freenet", - scheme: "freenet", - default_port: 8888, - encrypted: true, - bidirectional: true, - streaming: false, - anonymous: true, - }, - ProtocolInfo { - name: "I2P", - scheme: "i2p", - default_port: 7657, - encrypted: true, - bidirectional: true, - streaming: false, - anonymous: true, - }, - ProtocolInfo { - name: "Tor Onion", - scheme: "onion", - default_port: 9050, - encrypted: true, - bidirectional: true, - streaming: false, - anonymous: true, - }, - ProtocolInfo { - name: "MTP (Mobile)", - scheme: "mtp", - default_port: 0, - encrypted: false, - bidirectional: true, - streaming: false, - anonymous: false, - }, - ProtocolInfo { - name: "AFP (Apple)", - scheme: "afp", - default_port: 548, - encrypted: false, - bidirectional: true, - streaming: false, - anonymous: false, - }, - ProtocolInfo { - name: "DLNA/UPnP", - scheme: "dlna", - default_port: 1900, - encrypted: false, - bidirectional: false, - streaming: true, - anonymous: false, - }, - ProtocolInfo { - name: "Matrix", - scheme: "matrix", - default_port: 8448, - encrypted: true, - bidirectional: true, - streaming: false, - anonymous: false, - }, -]; - -/// Find protocol info by scheme -pub fn get_protocol(scheme: &str) -> Option<&'static ProtocolInfo> { - PROTOCOLS.iter().find(|p| p.scheme == scheme) -} - -/// List all supported protocol schemes -pub fn list_schemes() -> Vec<&'static str> { - PROTOCOLS.iter().map(|p| p.scheme).collect() -} - -/// Gopher client stub -pub mod gopher { - use super::*; - - /// Gopher item types - #[derive(Debug, Clone, Copy)] - pub enum ItemType { - TextFile, // 0 - Directory, // 1 - CsoServer, // 2 - Error, // 3 - BinHex, // 4 - DosBinary, // 5 - UuEncoded, // 6 - IndexSearch, // 7 - Telnet, // 8 - Binary, // 9 - Mirror, // + - Gif, // g - Image, // I - Html, // h - Info, // i - Sound, // s - } - - /// Gopher directory entry - #[derive(Debug, Clone)] - pub struct GopherEntry { - pub item_type: ItemType, - pub display: String, - pub selector: String, - pub host: String, - pub port: u16, - } - - /// Fetch a gopher URL - pub async fn fetch(_url: &str) -> CfkResult> { - // TODO: Implement gopher client - Err(CfkError::Unsupported("Gopher client not yet implemented".into())) - } - - /// Parse gopher directory listing - pub fn parse_directory(_data: &[u8]) -> Vec { - // TODO: Parse gopher menu format - Vec::new() - } -} - -/// Gemini client stub -pub mod gemini { - use super::*; - - /// Gemini response status - #[derive(Debug, Clone, Copy)] - pub enum Status { - Input = 10, - Success = 20, - Redirect = 30, - TemporaryFailure = 40, - PermanentFailure = 50, - ClientCertRequired = 60, - } - - /// Fetch a gemini URL - pub async fn fetch(_url: &str) -> CfkResult<(Status, String, Vec)> { - // TODO: Implement gemini client with TLS - Err(CfkError::Unsupported("Gemini client not yet implemented".into())) - } -} - -/// NNTP client stub -pub mod nntp { - use super::*; - - /// NNTP article - #[derive(Debug, Clone)] - pub struct Article { - pub message_id: String, - pub subject: String, - pub from: String, - pub date: String, - pub newsgroups: Vec, - pub body: String, - } - - /// Connect to NNTP server - pub async fn connect(_host: &str, _port: u16, _tls: bool) -> CfkResult<()> { - Err(CfkError::Unsupported("NNTP client not yet implemented".into())) - } - - /// List newsgroups - pub async fn list_groups() -> CfkResult> { - Err(CfkError::Unsupported("NNTP client not yet implemented".into())) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/s3.rs b/czech-file-knife/src/cfk-providers/src/s3.rs deleted file mode 100644 index 3f7c077a7..000000000 --- a/czech-file-knife/src/cfk-providers/src/s3.rs +++ /dev/null @@ -1,712 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! S3-compatible storage backend -//! -//! Works with AWS S3, MinIO, Wasabi, DigitalOcean Spaces, Backblaze B2, -//! Cloudflare R2, and any S3-compatible object storage. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use reqwest::{header, Client, Method, StatusCode}; -use serde::Deserialize; -use std::collections::BTreeMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// S3 backend configuration -#[derive(Debug, Clone)] -pub struct S3Config { - /// S3 endpoint URL (e.g., "https://s3.amazonaws.com" or "https://minio.example.com") - pub endpoint: String, - /// Bucket name - pub bucket: String, - /// AWS region - pub region: String, - /// Access key ID - pub access_key_id: String, - /// Secret access key - pub secret_access_key: String, - /// Use path-style URLs (required for MinIO and some providers) - pub path_style: bool, -} - -impl S3Config { - /// Create AWS S3 configuration - pub fn aws(bucket: &str, region: &str, access_key: &str, secret_key: &str) -> Self { - Self { - endpoint: format!("https://s3.{}.amazonaws.com", region), - bucket: bucket.to_string(), - region: region.to_string(), - access_key_id: access_key.to_string(), - secret_access_key: secret_key.to_string(), - path_style: false, - } - } - - /// Create MinIO configuration - pub fn minio(endpoint: &str, bucket: &str, access_key: &str, secret_key: &str) -> Self { - Self { - endpoint: endpoint.to_string(), - bucket: bucket.to_string(), - region: "us-east-1".to_string(), - access_key_id: access_key.to_string(), - secret_access_key: secret_key.to_string(), - path_style: true, - } - } - - /// Create Cloudflare R2 configuration - pub fn r2(account_id: &str, bucket: &str, access_key: &str, secret_key: &str) -> Self { - Self { - endpoint: format!("https://{}.r2.cloudflarestorage.com", account_id), - bucket: bucket.to_string(), - region: "auto".to_string(), - access_key_id: access_key.to_string(), - secret_access_key: secret_key.to_string(), - path_style: true, - } - } - - /// Create Backblaze B2 configuration - pub fn b2(bucket: &str, region: &str, key_id: &str, app_key: &str) -> Self { - Self { - endpoint: format!("https://s3.{}.backblazeb2.com", region), - bucket: bucket.to_string(), - region: region.to_string(), - access_key_id: key_id.to_string(), - secret_access_key: app_key.to_string(), - path_style: false, - } - } - - /// Create DigitalOcean Spaces configuration - pub fn digitalocean(region: &str, space: &str, key: &str, secret: &str) -> Self { - Self { - endpoint: format!("https://{}.digitaloceanspaces.com", region), - bucket: space.to_string(), - region: region.to_string(), - access_key_id: key.to_string(), - secret_access_key: secret.to_string(), - path_style: false, - } - } - - /// Create Wasabi configuration - pub fn wasabi(bucket: &str, region: &str, access_key: &str, secret_key: &str) -> Self { - Self { - endpoint: format!("https://s3.{}.wasabisys.com", region), - bucket: bucket.to_string(), - region: region.to_string(), - access_key_id: access_key.to_string(), - secret_access_key: secret_key.to_string(), - path_style: false, - } - } -} - -/// S3 storage backend -pub struct S3Backend { - id: String, - config: Arc>, - http: Client, - capabilities: StorageCapabilities, -} - -impl S3Backend { - pub fn new(id: impl Into, config: S3Config) -> Self { - Self { - id: id.into(), - config: Arc::new(RwLock::new(config)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: false, // S3 doesn't support rename, need copy+delete - copy: true, - list: true, - search: false, - versioning: true, - sharing: true, // Presigned URLs - streaming: true, - resume: true, // Multipart upload - watch: false, - metadata: true, - thumbnails: false, - max_file_size: Some(5 * 1024 * 1024 * 1024 * 1024), // 5TB - }, - } - } - - /// Build URL for an object - async fn object_url(&self, key: &str) -> String { - let config = self.config.read().await; - - if config.path_style { - format!( - "{}/{}/{}", - config.endpoint.trim_end_matches('/'), - config.bucket, - key.trim_start_matches('/') - ) - } else { - // Virtual-hosted style - let endpoint = config.endpoint.replace("://", &format!("://{}.bucket.", config.bucket)); - format!("{}/{}", endpoint.trim_end_matches('/'), key.trim_start_matches('/')) - } - } - - /// Build URL for bucket operations - async fn bucket_url(&self) -> String { - let config = self.config.read().await; - - if config.path_style { - format!( - "{}/{}", - config.endpoint.trim_end_matches('/'), - config.bucket - ) - } else { - config.endpoint.replace("://", &format!("://{}.bucket.", config.bucket)) - } - } - - /// Sign request with AWS Signature Version 4 - async fn sign_request( - &self, - method: &Method, - url: &str, - headers: &mut BTreeMap, - payload_hash: &str, - ) -> CfkResult { - let config = self.config.read().await; - let now = Utc::now(); - let date_stamp = now.format("%Y%m%d").to_string(); - let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string(); - - headers.insert("x-amz-date".to_string(), amz_date.clone()); - headers.insert("x-amz-content-sha256".to_string(), payload_hash.to_string()); - - // Parse URL - let parsed = url::Url::parse(url).map_err(|e| CfkError::InvalidPath(e.to_string()))?; - let host = parsed.host_str().unwrap_or(""); - let path = parsed.path(); - let query = parsed.query().unwrap_or(""); - - headers.insert("host".to_string(), host.to_string()); - - // Create canonical request - let signed_headers: Vec<&str> = headers.keys().map(|s| s.as_str()).collect(); - let signed_headers_str = signed_headers.join(";"); - - let canonical_headers: String = headers - .iter() - .map(|(k, v)| format!("{}:{}\n", k.to_lowercase(), v.trim())) - .collect(); - - let canonical_request = format!( - "{}\n{}\n{}\n{}\n{}\n{}", - method.as_str(), - path, - query, - canonical_headers, - signed_headers_str, - payload_hash - ); - - let canonical_request_hash = sha256_hex(canonical_request.as_bytes()); - - // Create string to sign - let credential_scope = format!("{}/{}/s3/aws4_request", date_stamp, config.region); - let string_to_sign = format!( - "AWS4-HMAC-SHA256\n{}\n{}\n{}", - amz_date, credential_scope, canonical_request_hash - ); - - // Calculate signature - let k_date = hmac_sha256( - format!("AWS4{}", config.secret_access_key).as_bytes(), - date_stamp.as_bytes(), - ); - let k_region = hmac_sha256(&k_date, config.region.as_bytes()); - let k_service = hmac_sha256(&k_region, b"s3"); - let k_signing = hmac_sha256(&k_service, b"aws4_request"); - let signature = hex::encode(hmac_sha256(&k_signing, string_to_sign.as_bytes())); - - // Build authorization header - let authorization = format!( - "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}", - config.access_key_id, credential_scope, signed_headers_str, signature - ); - - Ok(authorization) - } - - /// Make signed request - async fn request( - &self, - method: Method, - key: &str, - body: Option, - ) -> CfkResult { - let url = if key.is_empty() { - self.bucket_url().await - } else { - self.object_url(key).await - }; - - let payload_hash = if let Some(ref data) = body { - sha256_hex(data) - } else { - sha256_hex(b"") - }; - - let mut headers = BTreeMap::new(); - let auth = self.sign_request(&method, &url, &mut headers, &payload_hash).await?; - - let mut request = self.http.request(method, &url); - - for (k, v) in &headers { - request = request.header(k, v); - } - request = request.header(header::AUTHORIZATION, auth); - - if let Some(data) = body { - request = request.body(data.to_vec()); - } - - request - .send() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - /// List objects with prefix - async fn list_objects( - &self, - prefix: &str, - delimiter: Option<&str>, - ) -> CfkResult { - let config = self.config.read().await; - let mut url = format!( - "{}/{}?list-type=2", - config.endpoint.trim_end_matches('/'), - config.bucket - ); - - if !prefix.is_empty() { - url.push_str(&format!("&prefix={}", urlencoding::encode(prefix))); - } - if let Some(d) = delimiter { - url.push_str(&format!("&delimiter={}", urlencoding::encode(d))); - } - - drop(config); - - let payload_hash = sha256_hex(b""); - let mut headers = BTreeMap::new(); - let auth = self.sign_request(&Method::GET, &url, &mut headers, &payload_hash).await?; - - let mut request = self.http.get(&url); - for (k, v) in &headers { - request = request.header(k, v); - } - request = request.header(header::AUTHORIZATION, auth); - - let response = request - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let text = response.text().await.map_err(|e| CfkError::Network(e.to_string()))?; - parse_list_objects_v2(&text) - } - - /// Convert VirtualPath to S3 key - fn to_key(&self, path: &VirtualPath) -> String { - path.segments.join("/") - } -} - -/// S3 object metadata -#[derive(Debug, Clone, Default)] -struct S3Object { - key: String, - size: u64, - last_modified: Option>, - etag: Option, - storage_class: Option, -} - -/// Common prefix (directory) in listing -#[derive(Debug, Clone)] -struct CommonPrefix { - prefix: String, -} - -/// List objects result -#[derive(Debug, Clone, Default)] -struct ListObjectsResult { - objects: Vec, - common_prefixes: Vec, - is_truncated: bool, - continuation_token: Option, -} - -/// Parse ListObjectsV2 XML response -fn parse_list_objects_v2(xml: &str) -> CfkResult { - let mut result = ListObjectsResult::default(); - let mut in_contents = false; - let mut current_object = S3Object::default(); - - for line in xml.lines() { - let line = line.trim(); - - if line.contains("") { - in_contents = true; - current_object = S3Object::default(); - } else if line.contains("") { - in_contents = false; - result.objects.push(current_object.clone()); - } else if in_contents { - if let Some(key) = extract_xml_value(line, "Key") { - current_object.key = key; - } - if let Some(size) = extract_xml_value(line, "Size") { - current_object.size = size.parse().unwrap_or(0); - } - if let Some(modified) = extract_xml_value(line, "LastModified") { - current_object.last_modified = DateTime::parse_from_rfc3339(&modified) - .ok() - .map(|dt| dt.with_timezone(&Utc)); - } - if let Some(etag) = extract_xml_value(line, "ETag") { - current_object.etag = Some(etag.trim_matches('"').to_string()); - } - if let Some(class) = extract_xml_value(line, "StorageClass") { - current_object.storage_class = Some(class); - } - } - - if let Some(prefix) = extract_xml_value(line, "Prefix") { - if line.contains("") || xml.contains("") { - result.common_prefixes.push(CommonPrefix { prefix }); - } - } - - if let Some(truncated) = extract_xml_value(line, "IsTruncated") { - result.is_truncated = truncated == "true"; - } - - if let Some(token) = extract_xml_value(line, "NextContinuationToken") { - result.continuation_token = Some(token); - } - } - - Ok(result) -} - -/// Extract value from XML element -fn extract_xml_value(line: &str, tag: &str) -> Option { - let start_tag = format!("<{}>", tag); - let end_tag = format!("", tag); - - if let Some(start) = line.find(&start_tag) { - let content_start = start + start_tag.len(); - if let Some(end) = line[content_start..].find(&end_tag) { - return Some(line[content_start..content_start + end].to_string()); - } - } - None -} - -/// SHA-256 hash as hex string -fn sha256_hex(data: &[u8]) -> String { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(data); - hex::encode(hasher.finalize()) -} - -/// HMAC-SHA256 -fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec { - use hmac::{Hmac, Mac}; - use sha2::Sha256; - - type HmacSha256 = Hmac; - let mut mac = HmacSha256::new_from_slice(key).expect("HMAC can take key of any size"); - mac.update(data); - mac.finalize().into_bytes().to_vec() -} - -#[async_trait] -impl StorageBackend for S3Backend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "S3" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.list_objects("", Some("/")).await.is_ok() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let key = self.to_key(path); - - if key.is_empty() { - // Root - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - - // HEAD request - let response = self.request(Method::HEAD, &key, None).await?; - - if response.status() == StatusCode::NOT_FOUND { - // Check if it's a directory (prefix) - let prefix = format!("{}/", key); - let list = self.list_objects(&prefix, Some("/")).await?; - if !list.objects.is_empty() || !list.common_prefixes.is_empty() { - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - return Err(CfkError::NotFound(path.to_string())); - } - - if !response.status().is_success() { - let status = response.status(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}", status), - }); - } - - let headers = response.headers(); - let mut metadata = Metadata::default(); - - if let Some(len) = headers.get(header::CONTENT_LENGTH) { - metadata.size = len.to_str().ok().and_then(|s| s.parse().ok()); - } - - if let Some(modified) = headers.get(header::LAST_MODIFIED) { - if let Ok(s) = modified.to_str() { - metadata.modified = DateTime::parse_from_rfc2822(s) - .ok() - .map(|dt| dt.with_timezone(&Utc)); - } - } - - if let Some(etag) = headers.get(header::ETAG) { - metadata.checksum = etag.to_str().ok().map(|s| s.trim_matches('"').to_string()); - } - - if let Some(ct) = headers.get(header::CONTENT_TYPE) { - metadata.mime_type = ct.to_str().ok().map(String::from); - } - - Ok(Entry { - path: path.clone(), - kind: EntryKind::File, - metadata, - }) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let mut prefix = self.to_key(path); - if !prefix.is_empty() && !prefix.ends_with('/') { - prefix.push('/'); - } - - let result = self.list_objects(&prefix, Some("/")).await?; - - let mut entries = Vec::new(); - - // Add objects - for obj in result.objects { - let key = obj.key.trim_start_matches(&prefix); - if key.is_empty() || key == "/" { - continue; - } - - let mut metadata = Metadata::default(); - metadata.size = Some(obj.size); - metadata.modified = obj.last_modified; - metadata.checksum = obj.etag; - - entries.push(Entry { - path: VirtualPath::new(&self.id, &obj.key), - kind: EntryKind::File, - metadata, - }); - } - - // Add directories (common prefixes) - for cp in result.common_prefixes { - let dir_name = cp.prefix.trim_end_matches('/'); - entries.push(Entry { - path: VirtualPath::new(&self.id, dir_name), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - - Ok(entries) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let key = self.to_key(path); - let response = self.request(Method::GET, &key, None).await?; - - if !response.status().is_success() { - let status = response.status(); - if status == StatusCode::NOT_FOUND { - return Err(CfkError::NotFound(path.to_string())); - } - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let key = self.to_key(path); - let response = self.request(Method::PUT, &key, Some(data)).await?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(path).await - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let key = self.to_key(path); - let response = self.request(Method::DELETE, &key, None).await?; - - if !response.status().is_success() && response.status() != StatusCode::NO_CONTENT { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - // S3 doesn't have real directories, create a zero-byte object with trailing slash - let mut key = self.to_key(path); - if !key.ends_with('/') { - key.push('/'); - } - - let response = self.request(Method::PUT, &key, Some(Bytes::new())).await?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_key = self.to_key(from); - let to_key = self.to_key(to); - let config = self.config.read().await; - - let copy_source = format!("{}/{}", config.bucket, from_key); - drop(config); - - // Build copy request with x-amz-copy-source header - let url = self.object_url(&to_key).await; - let payload_hash = sha256_hex(b""); - - let mut headers = BTreeMap::new(); - headers.insert("x-amz-copy-source".to_string(), copy_source); - - let auth = self.sign_request(&Method::PUT, &url, &mut headers, &payload_hash).await?; - - let mut request = self.http.put(&url); - for (k, v) in &headers { - request = request.header(k, v); - } - request = request.header(header::AUTHORIZATION, auth); - - let response = request - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "s3".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(to).await - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - // S3 doesn't support rename, use copy + delete - let entry = self.copy(from, to).await?; - self.delete(from).await?; - Ok(entry) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // S3 doesn't have quota concept, return 0 - Ok((0, 0)) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/sftp.rs b/czech-file-knife/src/cfk-providers/src/sftp.rs deleted file mode 100644 index 9a278f2f5..000000000 --- a/czech-file-knife/src/cfk-providers/src/sftp.rs +++ /dev/null @@ -1,326 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! SFTP storage backend -//! -//! SSH File Transfer Protocol implementation. -//! Supports password, key-based, and agent authentication. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use std::path::PathBuf; - -/// SFTP authentication method -#[derive(Debug, Clone)] -pub enum SftpAuth { - /// Password authentication - Password { username: String, password: String }, - /// Private key authentication - PrivateKey { - username: String, - private_key_path: PathBuf, - passphrase: Option, - }, - /// SSH agent authentication - Agent { username: String }, -} - -/// SFTP backend configuration -#[derive(Debug, Clone)] -pub struct SftpConfig { - /// Host address - pub host: String, - /// Port (default: 22) - pub port: u16, - /// Authentication method - pub auth: SftpAuth, - /// Known hosts file path - pub known_hosts: Option, - /// Skip host key verification (insecure!) - pub skip_host_key_check: bool, - /// Remote base path - pub base_path: String, -} - -impl Default for SftpConfig { - fn default() -> Self { - Self { - host: "localhost".to_string(), - port: 22, - auth: SftpAuth::Agent { - username: whoami::username(), - }, - known_hosts: None, - skip_host_key_check: false, - base_path: "/".to_string(), - } - } -} - -/// SFTP storage backend -/// -/// Note: This is a stub implementation. Full implementation would use -/// the `ssh2` or `russh` crate for SSH/SFTP protocol support. -pub struct SftpBackend { - id: String, - config: SftpConfig, - capabilities: StorageCapabilities, - // In a full implementation: - // session: Option, - // sftp: Option, -} - -impl SftpBackend { - pub fn new(id: impl Into, config: SftpConfig) -> Self { - Self { - id: id.into(), - config, - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: false, // SFTP doesn't have native copy - list: true, - search: false, - versioning: false, - sharing: false, - streaming: true, - resume: true, // With SEEK - watch: false, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - } - } - - /// Create from SSH URL: sftp://user@host:port/path - pub fn from_url(id: impl Into, url: &str) -> CfkResult { - let parsed = url::Url::parse(url) - .map_err(|e| CfkError::InvalidPath(format!("Invalid URL: {}", e)))?; - - if parsed.scheme() != "sftp" { - return Err(CfkError::InvalidPath("URL scheme must be sftp".into())); - } - - let host = parsed - .host_str() - .ok_or_else(|| CfkError::InvalidPath("Missing host".into()))? - .to_string(); - - let port = parsed.port().unwrap_or(22); - let username = if parsed.username().is_empty() { - whoami::username() - } else { - parsed.username().to_string() - }; - - let base_path = if parsed.path().is_empty() { - "/".to_string() - } else { - parsed.path().to_string() - }; - - let auth = if let Some(password) = parsed.password() { - SftpAuth::Password { - username, - password: password.to_string(), - } - } else { - SftpAuth::Agent { username } - }; - - Ok(Self::new( - id, - SftpConfig { - host, - port, - auth, - base_path, - ..Default::default() - }, - )) - } - - /// Convert VirtualPath to remote path - fn to_remote_path(&self, path: &VirtualPath) -> String { - let base = self.config.base_path.trim_end_matches('/'); - if path.segments.is_empty() { - base.to_string() - } else { - format!("{}/{}", base, path.segments.join("/")) - } - } - - /// Connect to SFTP server - pub async fn connect(&self) -> CfkResult<()> { - // In a full implementation, this would: - // 1. Create TCP connection - // 2. Perform SSH handshake - // 3. Authenticate - // 4. Initialize SFTP subsystem - - Err(CfkError::Unsupported( - "SFTP backend requires ssh2 or russh crate. Stub implementation.".into(), - )) - } -} - -#[async_trait] -impl StorageBackend for SftpBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "SFTP" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - // Would check SSH connection - false - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let _remote_path = self.to_remote_path(path); - - // Would use SFTP stat() call - // let attrs = sftp.stat(&remote_path)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let _remote_path = self.to_remote_path(path); - - // Would use SFTP readdir() call - // let entries = sftp.readdir(&remote_path)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let _remote_path = self.to_remote_path(path); - - // Would open file and read: - // let mut file = sftp.open(&remote_path)?; - // let mut data = Vec::new(); - // file.read_to_end(&mut data)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn write_file(&self, path: &VirtualPath, _data: Bytes) -> CfkResult { - let _remote_path = self.to_remote_path(path); - - // Would create/open file and write: - // let mut file = sftp.create(&remote_path)?; - // file.write_all(&data)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let _remote_path = self.to_remote_path(path); - - // Would use SFTP unlink() or rmdir(): - // sftp.unlink(&remote_path)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let _remote_path = self.to_remote_path(path); - - // Would use SFTP mkdir(): - // sftp.mkdir(&remote_path, 0o755)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn copy(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - // SFTP doesn't support server-side copy - // Would need to read + write - Err(CfkError::Unsupported( - "SFTP doesn't support native copy".into(), - )) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let _from_path = self.to_remote_path(from); - let _to_path = self.to_remote_path(to); - - // Would use SFTP rename(): - // sftp.rename(&from_path, &to_path, None)?; - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // SFTP has statvfs extension (OpenSSH) - // Would use sftp.statvfs() - - Err(CfkError::Unsupported("SFTP stub - use ssh2 crate".into())) - } -} - -/// Helper to get username -mod whoami { - pub fn username() -> String { - std::env::var("USER") - .or_else(|_| std::env::var("USERNAME")) - .unwrap_or_else(|_| "nobody".to_string()) - } -} - -/// SFTP file attributes (mirrors ssh2::FileStat) -#[derive(Debug, Clone, Default)] -pub struct FileAttributes { - pub size: Option, - pub uid: Option, - pub gid: Option, - pub permissions: Option, - pub atime: Option, - pub mtime: Option, -} - -impl FileAttributes { - pub fn is_dir(&self) -> bool { - self.permissions - .map(|p| (p & 0o40000) != 0) - .unwrap_or(false) - } - - pub fn is_symlink(&self) -> bool { - self.permissions - .map(|p| (p & 0o120000) == 0o120000) - .unwrap_or(false) - } - - pub fn is_file(&self) -> bool { - self.permissions - .map(|p| (p & 0o100000) != 0) - .unwrap_or(false) - } - - pub fn to_metadata(&self) -> Metadata { - let mut meta = Metadata::default(); - meta.size = self.size; - meta.permissions = self.permissions; - meta.uid = self.uid; - meta.gid = self.gid; - - if let Some(mtime) = self.mtime { - meta.modified = chrono::DateTime::from_timestamp(mtime as i64, 0); - } - - meta - } -} diff --git a/czech-file-knife/src/cfk-providers/src/smb.rs b/czech-file-knife/src/cfk-providers/src/smb.rs deleted file mode 100644 index 538639e4e..000000000 --- a/czech-file-knife/src/cfk-providers/src/smb.rs +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! SMB/CIFS storage backend -//! -//! Server Message Block / Common Internet File System protocol. -//! Compatible with Windows shares, Samba, and macOS file sharing. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use std::path::PathBuf; - -/// SMB protocol version -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SmbVersion { - /// SMB 1.0 (legacy, insecure) - Smb1, - /// SMB 2.0 - Smb2, - /// SMB 2.1 - Smb21, - /// SMB 3.0 - Smb3, - /// SMB 3.0.2 - Smb302, - /// SMB 3.1.1 - Smb311, -} - -impl Default for SmbVersion { - fn default() -> Self { - Self::Smb3 // Secure default - } -} - -/// SMB authentication -#[derive(Debug, Clone)] -pub enum SmbAuth { - /// Anonymous/Guest access - Anonymous, - /// NTLM authentication - Ntlm { username: String, password: String, domain: Option }, - /// Kerberos authentication - Kerberos { principal: String }, -} - -impl Default for SmbAuth { - fn default() -> Self { - Self::Anonymous - } -} - -/// SMB backend configuration -#[derive(Debug, Clone)] -pub struct SmbConfig { - /// Server hostname or IP - pub server: String, - /// Share name - pub share: String, - /// SMB protocol version - pub version: SmbVersion, - /// Authentication - pub auth: SmbAuth, - /// Port (default: 445, legacy: 139) - pub port: u16, - /// Encrypt traffic (SMB 3.0+) - pub encryption: bool, - /// Sign messages - pub signing: bool, -} - -impl Default for SmbConfig { - fn default() -> Self { - Self { - server: "localhost".to_string(), - share: "share".to_string(), - version: SmbVersion::default(), - auth: SmbAuth::default(), - port: 445, - encryption: true, - signing: true, - } - } -} - -/// SMB tree connection ID -#[derive(Debug, Clone, Copy, Default)] -struct TreeId(u32); - -/// SMB session ID -#[derive(Debug, Clone, Copy, Default)] -struct SessionId(u64); - -/// SMB file ID -#[derive(Debug, Clone, Copy, Default)] -struct FileId { - persistent: u64, - volatile: u64, -} - -/// SMB storage backend -/// -/// Note: This is a stub implementation. Full implementation would require -/// the SMB protocol which is complex. Consider using `pavao` or `smb` crate, -/// or system mount. -pub struct SmbBackend { - id: String, - config: SmbConfig, - capabilities: StorageCapabilities, - session: Option, - tree_id: Option, -} - -impl SmbBackend { - pub fn new(id: impl Into, config: SmbConfig) -> Self { - let mut caps = StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, // SMB2+ has server-side copy - list: true, - search: true, // SMB has FIND - versioning: false, - sharing: true, // Windows ACLs - streaming: true, - resume: true, - watch: true, // Change notifications - metadata: true, - thumbnails: false, - max_file_size: None, - }; - - // Adjust capabilities based on version - if config.version == SmbVersion::Smb1 { - caps.copy = false; // SMB1 doesn't have server-side copy - caps.watch = false; - } - - Self { - id: id.into(), - config, - capabilities: caps, - session: None, - tree_id: None, - } - } - - /// Create from SMB URL: smb://user:pass@server/share - pub fn from_url(id: impl Into, url: &str) -> CfkResult { - let parsed = url::Url::parse(url) - .map_err(|e| CfkError::InvalidPath(format!("Invalid URL: {}", e)))?; - - if parsed.scheme() != "smb" { - return Err(CfkError::InvalidPath("URL scheme must be smb".into())); - } - - let server = parsed - .host_str() - .ok_or_else(|| CfkError::InvalidPath("Missing server".into()))? - .to_string(); - - let share = parsed - .path() - .trim_start_matches('/') - .split('/') - .next() - .unwrap_or("share") - .to_string(); - - let port = parsed.port().unwrap_or(445); - - let auth = if !parsed.username().is_empty() { - SmbAuth::Ntlm { - username: parsed.username().to_string(), - password: parsed.password().unwrap_or("").to_string(), - domain: None, - } - } else { - SmbAuth::Anonymous - }; - - Ok(Self::new( - id, - SmbConfig { - server, - share, - port, - auth, - ..Default::default() - }, - )) - } - - /// Connect to SMB server - pub async fn connect(&mut self) -> CfkResult<()> { - // SMB2/3 connection sequence: - // 1. TCP connect to port 445 - // 2. NEGOTIATE (select protocol version) - // 3. SESSION_SETUP (authenticate) - // 4. TREE_CONNECT (connect to share) - - Err(CfkError::Unsupported( - "SMB backend is a stub. Use system mount, pavao, or smb crate.".into(), - )) - } - - /// Disconnect from SMB server - pub async fn disconnect(&mut self) -> CfkResult<()> { - // 1. TREE_DISCONNECT - // 2. LOGOFF - // 3. Close TCP connection - - self.tree_id = None; - self.session = None; - Ok(()) - } - - /// Convert VirtualPath to SMB path (backslashes) - fn to_smb_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - "\\".to_string() - } else { - format!("\\{}", path.segments.join("\\")) - } - } -} - -#[async_trait] -impl StorageBackend for SmbBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - match self.config.version { - SmbVersion::Smb1 => "SMB1/CIFS", - SmbVersion::Smb2 => "SMB2", - SmbVersion::Smb21 => "SMB2.1", - SmbVersion::Smb3 => "SMB3", - SmbVersion::Smb302 => "SMB3.0.2", - SmbVersion::Smb311 => "SMB3.1.1", - } - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.session.is_some() && self.tree_id.is_some() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let _smb_path = self.to_smb_path(path); - // Would use QUERY_INFO with FileAllInformation class - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let _smb_path = self.to_smb_path(path); - // Would use QUERY_DIRECTORY (SMB2) or FIND_FIRST2/FIND_NEXT2 (SMB1) - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let _smb_path = self.to_smb_path(path); - // Would use CREATE (open) + READ + CLOSE - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn write_file(&self, path: &VirtualPath, _data: Bytes) -> CfkResult { - let _smb_path = self.to_smb_path(path); - // Would use CREATE + WRITE + CLOSE - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let _smb_path = self.to_smb_path(path); - // Would use CREATE with DELETE_ON_CLOSE or SET_INFO with FileDispositionInfo - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let _smb_path = self.to_smb_path(path); - // Would use CREATE with FILE_DIRECTORY_FILE - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - if self.config.version == SmbVersion::Smb1 { - return Err(CfkError::Unsupported("SMB1 doesn't support server-side copy".into())); - } - - let _from_path = self.to_smb_path(from); - let _to_path = self.to_smb_path(to); - // Would use IOCTL with FSCTL_SRV_COPYCHUNK - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let _from_path = self.to_smb_path(from); - let _to_path = self.to_smb_path(to); - // Would use SET_INFO with FileRenameInformation - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // Would use QUERY_INFO with FileFsFullSizeInformation - - Err(CfkError::Unsupported("SMB stub - use system mount".into())) - } -} - -/// SMB file attributes -#[derive(Debug, Clone, Copy, Default)] -pub struct SmbFileAttributes(u32); - -impl SmbFileAttributes { - pub const READONLY: u32 = 0x0001; - pub const HIDDEN: u32 = 0x0002; - pub const SYSTEM: u32 = 0x0004; - pub const DIRECTORY: u32 = 0x0010; - pub const ARCHIVE: u32 = 0x0020; - pub const NORMAL: u32 = 0x0080; - pub const TEMPORARY: u32 = 0x0100; - pub const SPARSE: u32 = 0x0200; - pub const REPARSE_POINT: u32 = 0x0400; - pub const COMPRESSED: u32 = 0x0800; - pub const ENCRYPTED: u32 = 0x4000; - - pub fn is_directory(&self) -> bool { - self.0 & Self::DIRECTORY != 0 - } - - pub fn is_hidden(&self) -> bool { - self.0 & Self::HIDDEN != 0 - } - - pub fn is_readonly(&self) -> bool { - self.0 & Self::READONLY != 0 - } - - pub fn is_symlink(&self) -> bool { - self.0 & Self::REPARSE_POINT != 0 - } -} - -/// SMB file information -#[derive(Debug, Clone, Default)] -pub struct SmbFileInfo { - pub creation_time: u64, - pub last_access_time: u64, - pub last_write_time: u64, - pub change_time: u64, - pub attributes: SmbFileAttributes, - pub allocation_size: u64, - pub end_of_file: u64, - pub file_id: u64, -} - -impl SmbFileInfo { - /// Convert Windows FILETIME to Unix timestamp - fn filetime_to_unix(ft: u64) -> Option { - // FILETIME is 100-nanosecond intervals since Jan 1, 1601 - // Unix epoch is Jan 1, 1970 - const FILETIME_UNIX_DIFF: u64 = 116444736000000000; - if ft > FILETIME_UNIX_DIFF { - Some(((ft - FILETIME_UNIX_DIFF) / 10000000) as i64) - } else { - None - } - } - - pub fn to_entry(&self, backend_id: &str, path: &str) -> Entry { - let kind = if self.attributes.is_directory() { - EntryKind::Directory - } else if self.attributes.is_symlink() { - EntryKind::Symlink - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = Some(self.end_of_file); - - if let Some(ts) = Self::filetime_to_unix(self.last_write_time) { - metadata.modified = chrono::DateTime::from_timestamp(ts, 0); - } - if let Some(ts) = Self::filetime_to_unix(self.creation_time) { - metadata.created = chrono::DateTime::from_timestamp(ts, 0); - } - - metadata.custom.insert( - "readonly".to_string(), - self.attributes.is_readonly().to_string(), - ); - metadata.custom.insert( - "hidden".to_string(), - self.attributes.is_hidden().to_string(), - ); - - Entry { - path: VirtualPath::new(backend_id, path), - kind, - metadata, - } - } -} - -/// Helper to use system mount -impl SmbBackend { - /// Mount using system mount.cifs (Linux) or mount_smbfs (macOS) - pub fn mount_system(&self, mount_point: &PathBuf) -> CfkResult<()> { - use std::process::Command; - - let source = format!("//{}/{}", self.config.server, self.config.share); - - #[cfg(target_os = "linux")] - { - let (username, password) = match &self.config.auth { - SmbAuth::Anonymous => ("guest".to_string(), String::new()), - SmbAuth::Ntlm { username, password, .. } => (username.clone(), password.clone()), - SmbAuth::Kerberos { .. } => { - return Err(CfkError::Unsupported( - "Kerberos mount requires system configuration".into(), - )) - } - }; - - let options = format!( - "username={},password={},vers={}", - username, - password, - match self.config.version { - SmbVersion::Smb1 => "1.0", - SmbVersion::Smb2 => "2.0", - SmbVersion::Smb21 => "2.1", - SmbVersion::Smb3 | SmbVersion::Smb302 | SmbVersion::Smb311 => "3.0", - } - ); - - let status = Command::new("mount") - .args([ - "-t", "cifs", - "-o", &options, - &source, - mount_point.to_str().unwrap_or("/mnt"), - ]) - .status() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !status.success() { - return Err(CfkError::ProviderApi { - provider: "smb".into(), - message: "mount.cifs failed".into(), - }); - } - } - - #[cfg(target_os = "macos")] - { - let status = Command::new("mount_smbfs") - .args([&source, mount_point.to_str().unwrap_or("/mnt")]) - .status() - .map_err(|e| CfkError::Io(e.to_string()))?; - - if !status.success() { - return Err(CfkError::ProviderApi { - provider: "smb".into(), - message: "mount_smbfs failed".into(), - }); - } - } - - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - return Err(CfkError::Unsupported( - "System SMB mount not supported on this platform".into(), - )); - } - - Ok(()) - } -} diff --git a/czech-file-knife/src/cfk-providers/src/syncthing.rs b/czech-file-knife/src/cfk-providers/src/syncthing.rs deleted file mode 100644 index f6b832ba0..000000000 --- a/czech-file-knife/src/cfk-providers/src/syncthing.rs +++ /dev/null @@ -1,532 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Syncthing storage backend -//! -//! Connects to Syncthing's REST API to expose synced folders. -//! Note: Syncthing folders are local, this backend provides folder discovery and sync status. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::PathBuf; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// Syncthing connection configuration -#[derive(Debug, Clone)] -pub struct SyncthingConfig { - pub api_url: String, - pub api_key: String, -} - -/// Syncthing folder information -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FolderConfig { - pub id: String, - pub label: String, - pub path: String, - #[serde(rename = "type")] - pub folder_type: String, - pub paused: bool, -} - -/// Syncthing folder status -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FolderStatus { - pub state: String, - pub local_files: u64, - pub local_bytes: u64, - pub global_files: u64, - pub global_bytes: u64, - pub need_files: u64, - pub need_bytes: u64, -} - -/// Syncthing storage backend -pub struct SyncthingBackend { - id: String, - http: Client, - config: Arc>, - capabilities: StorageCapabilities, - /// Cache of folder configs - folders: Arc>>, -} - -impl SyncthingBackend { - pub fn new(id: impl Into, config: SyncthingConfig) -> Self { - Self { - id: id.into(), - http: Client::new(), - config: Arc::new(RwLock::new(config)), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: false, - versioning: true, - sharing: false, - streaming: true, - resume: true, - watch: true, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - folders: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Make API GET request - async fn api_get Deserialize<'de>>(&self, endpoint: &str) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/rest/{}", config.api_url, endpoint); - - let response = self - .http - .get(&url) - .header("X-API-Key", &config.api_key) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - return Err(CfkError::ProviderApi { - provider: "syncthing".into(), - message: response.text().await.unwrap_or_default(), - }); - } - - response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string())) - } - - /// Make API POST request - async fn api_post Deserialize<'de>>( - &self, - endpoint: &str, - body: impl Serialize, - ) -> CfkResult { - let config = self.config.read().await; - let url = format!("{}/rest/{}", config.api_url, endpoint); - - let response = self - .http - .post(&url) - .header("X-API-Key", &config.api_key) - .json(&body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - return Err(CfkError::ProviderApi { - provider: "syncthing".into(), - message: response.text().await.unwrap_or_default(), - }); - } - - response - .json() - .await - .map_err(|e| CfkError::Serialization(e.to_string())) - } - - /// Refresh folder list - pub async fn refresh_folders(&self) -> CfkResult<()> { - #[derive(Deserialize)] - struct ConfigResponse { - folders: Vec, - } - - let config: ConfigResponse = self.api_get("config").await?; - - let mut folders = self.folders.write().await; - folders.clear(); - for folder in config.folders { - folders.insert(folder.id.clone(), folder); - } - - Ok(()) - } - - /// Get folder status - pub async fn get_folder_status(&self, folder_id: &str) -> CfkResult { - self.api_get(&format!("db/status?folder={}", folder_id)) - .await - } - - /// Trigger rescan of a folder - pub async fn rescan_folder(&self, folder_id: &str) -> CfkResult<()> { - let config = self.config.read().await; - let url = format!( - "{}/rest/db/scan?folder={}", - config.api_url, folder_id - ); - - let response = self - .http - .post(&url) - .header("X-API-Key", &config.api_key) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - return Err(CfkError::ProviderApi { - provider: "syncthing".into(), - message: response.text().await.unwrap_or_default(), - }); - } - - Ok(()) - } - - /// Get local path for a virtual path - fn get_local_path(&self, folder_id: &str, subpath: &str) -> CfkResult { - // This would need the folder cache to be populated - // For now, return an error indicating the need for local backend - Err(CfkError::Unsupported(format!( - "Use LocalBackend for actual file operations on folder {} subpath {}", - folder_id, subpath - ))) - } - - /// Parse path into folder ID and subpath - fn parse_path(&self, path: &VirtualPath) -> (Option, String) { - if path.segments.is_empty() { - (None, String::new()) - } else { - let folder_id = path.segments[0].clone(); - let subpath = if path.segments.len() > 1 { - path.segments[1..].join("/") - } else { - String::new() - }; - (Some(folder_id), subpath) - } - } -} - -#[async_trait] -impl StorageBackend for SyncthingBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "Syncthing" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - #[derive(Deserialize)] - struct SystemStatus { - #[serde(rename = "myID")] - _my_id: String, - } - - self.api_get::("system/status").await.is_ok() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() { - // Root - return as directory - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata: Metadata::default(), - }); - } - - let folder_id = folder_id.unwrap(); - - if subpath.is_empty() { - // Folder root - let status = self.get_folder_status(&folder_id).await?; - - let mut metadata = Metadata::default(); - metadata.size = Some(status.local_bytes); - - return Ok(Entry { - path: path.clone(), - kind: EntryKind::Directory, - metadata, - }); - } - - // For subpaths, delegate to local backend - Err(CfkError::Unsupported( - "Use LocalBackend for file metadata in Syncthing folders".into(), - )) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() { - // List all synced folders - self.refresh_folders().await?; - - let folders = self.folders.read().await; - let entries: Vec = folders - .values() - .map(|f| { - let mut metadata = Metadata::default(); - // Use label as display name in custom metadata - metadata.custom - .insert("label".to_string(), f.label.clone()); - metadata - .custom - .insert("type".to_string(), f.folder_type.clone()); - metadata.custom.insert( - "paused".to_string(), - if f.paused { "true" } else { "false" }.to_string(), - ); - - Entry { - path: VirtualPath::new(&self.id, &f.id), - kind: EntryKind::Directory, - metadata, - } - }) - .collect(); - - return Ok(entries); - } - - if !subpath.is_empty() { - return Err(CfkError::Unsupported( - "Use LocalBackend to list files in Syncthing folders".into(), - )); - } - - // For folder contents, we need to use the database browse API - #[derive(Deserialize)] - struct BrowseEntry { - name: String, - #[serde(rename = "type")] - entry_type: String, - size: Option, - #[serde(rename = "modTime")] - mod_time: Option, - } - - let folder_id = folder_id.unwrap(); - let entries: Vec = self - .api_get(&format!("db/browse?folder={}&levels=1", folder_id)) - .await?; - - let base_path = path.segments.join("/"); - - Ok(entries - .iter() - .map(|e| { - let path_str = if base_path.is_empty() { - e.name.clone() - } else { - format!("{}/{}", base_path, e.name) - }; - - let kind = if e.entry_type == "d" { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = e.size; - - Entry { - path: VirtualPath::new(&self.id, &path_str), - kind, - metadata, - } - }) - .collect()) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() || subpath.is_empty() { - return Err(CfkError::InvalidPath("Cannot read folder as file".into())); - } - - // Syncthing doesn't provide file content via API - // Files must be accessed through local filesystem - Err(CfkError::Unsupported( - "Use LocalBackend to read files in Syncthing folders".into(), - )) - } - - async fn write_file(&self, path: &VirtualPath, _data: Bytes) -> CfkResult { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() || subpath.is_empty() { - return Err(CfkError::InvalidPath("Cannot write to folder".into())); - } - - Err(CfkError::Unsupported( - "Use LocalBackend to write files in Syncthing folders, then trigger rescan".into(), - )) - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() { - return Err(CfkError::InvalidPath("Cannot delete root".into())); - } - - if subpath.is_empty() { - return Err(CfkError::Unsupported( - "Cannot delete Syncthing folder via this API".into(), - )); - } - - Err(CfkError::Unsupported( - "Use LocalBackend to delete files in Syncthing folders".into(), - )) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let (folder_id, subpath) = self.parse_path(path); - - if folder_id.is_none() { - return Err(CfkError::Unsupported( - "Create new Syncthing folder via Syncthing UI".into(), - )); - } - - if subpath.is_empty() { - return Err(CfkError::AlreadyExists(path.to_string())); - } - - Err(CfkError::Unsupported( - "Use LocalBackend to create directories in Syncthing folders".into(), - )) - } - - async fn copy(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - Err(CfkError::Unsupported( - "Use LocalBackend to copy files in Syncthing folders".into(), - )) - } - - async fn rename(&self, _from: &VirtualPath, _to: &VirtualPath) -> CfkResult { - Err(CfkError::Unsupported( - "Use LocalBackend to rename files in Syncthing folders".into(), - )) - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // Sum up space from all folders - self.refresh_folders().await?; - - let folders = self.folders.read().await; - let mut total_bytes = 0u64; - - for folder in folders.values() { - if let Ok(status) = self.get_folder_status(&folder.id).await { - total_bytes += status.local_bytes; - } - } - - // Syncthing doesn't track quota, return local bytes as both - Ok((total_bytes, total_bytes)) - } -} - -/// Syncthing device information -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DeviceConfig { - pub device_id: String, - pub name: String, - pub addresses: Vec, - pub paused: bool, -} - -/// Extension methods for Syncthing-specific operations -impl SyncthingBackend { - /// List connected devices - pub async fn list_devices(&self) -> CfkResult> { - #[derive(Deserialize)] - struct ConfigResponse { - devices: Vec, - } - - let config: ConfigResponse = self.api_get("config").await?; - Ok(config.devices) - } - - /// Get system connections - pub async fn get_connections(&self) -> CfkResult> { - #[derive(Deserialize)] - struct ConnectionsResponse { - connections: HashMap, - } - - let resp: ConnectionsResponse = self.api_get("system/connections").await?; - Ok(resp.connections) - } - - /// Pause syncing - pub async fn pause(&self) -> CfkResult<()> { - let config = self.config.read().await; - let url = format!("{}/rest/system/pause", config.api_url); - - self.http - .post(&url) - .header("X-API-Key", &config.api_key) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - Ok(()) - } - - /// Resume syncing - pub async fn resume(&self) -> CfkResult<()> { - let config = self.config.read().await; - let url = format!("{}/rest/system/resume", config.api_url); - - self.http - .post(&url) - .header("X-API-Key", &config.api_key) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - Ok(()) - } -} - -/// Connection information -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ConnectionInfo { - pub connected: bool, - pub paused: bool, - pub address: String, - pub client_version: String, - #[serde(rename = "type")] - pub connection_type: String, -} diff --git a/czech-file-knife/src/cfk-providers/src/transport.rs b/czech-file-knife/src/cfk-providers/src/transport.rs deleted file mode 100644 index bb169c761..000000000 --- a/czech-file-knife/src/cfk-providers/src/transport.rs +++ /dev/null @@ -1,308 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Transport layer support -//! -//! Low-level transport protocols: -//! - TCP: Traditional reliable transport -//! - QUIC: Modern UDP-based transport (HTTP/3) -//! - UDP: Unreliable datagram -//! - Unix sockets: Local IPC -//! - Named pipes: Windows IPC - -#![allow(dead_code)] // Placeholder structs for future implementation - -use cfk_core::{CfkError, CfkResult}; -use std::net::SocketAddr; -use tokio::net::TcpStream; - -/// Transport type -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Transport { - Tcp, - Quic, - Udp, - Unix, - Pipe, - // Multicast transports - Pgm, // Pragmatic General Multicast (RFC 3208) - Norm, // NACK-Oriented Reliable Multicast (RFC 5740) - Rmtp, // Reliable Multicast Transport Protocol -} - -/// Connection configuration -#[derive(Debug, Clone)] -pub struct ConnectionConfig { - pub transport: Transport, - pub addr: String, - pub port: u16, - pub timeout_ms: u64, - pub keepalive: bool, - pub nodelay: bool, // TCP_NODELAY - pub buffer_size: usize, -} - -impl Default for ConnectionConfig { - fn default() -> Self { - Self { - transport: Transport::Tcp, - addr: "127.0.0.1".into(), - port: 0, - timeout_ms: 30000, - keepalive: true, - nodelay: true, - buffer_size: 65536, - } - } -} - -/// TCP connection wrapper -pub struct TcpConnection { - stream: TcpStream, - config: ConnectionConfig, -} - -impl TcpConnection { - pub async fn connect(config: ConnectionConfig) -> CfkResult { - let addr = format!("{}:{}", config.addr, config.port); - let stream = TcpStream::connect(&addr).await - .map_err(|e| CfkError::Network(e.to_string()))?; - - stream.set_nodelay(config.nodelay) - .map_err(|e| CfkError::Network(e.to_string()))?; - - Ok(Self { stream, config }) - } - - pub fn inner(&self) -> &TcpStream { - &self.stream - } - - pub fn into_inner(self) -> TcpStream { - self.stream - } -} - -/// QUIC configuration -#[derive(Debug, Clone)] -pub struct QuicConfig { - pub alpn_protocols: Vec, - pub max_idle_timeout_ms: u64, - pub keep_alive_interval_ms: Option, - pub max_concurrent_streams: u32, - pub initial_window_size: u32, -} - -impl Default for QuicConfig { - fn default() -> Self { - Self { - alpn_protocols: vec!["h3".into()], - max_idle_timeout_ms: 30000, - keep_alive_interval_ms: Some(15000), - max_concurrent_streams: 100, - initial_window_size: 1048576, // 1MB - } - } -} - -/// QUIC connection (stub - would use quinn crate) -pub struct QuicConnection { - config: QuicConfig, - // In real impl: quinn::Connection -} - -impl QuicConnection { - pub async fn connect(_addr: SocketAddr, _server_name: &str, config: QuicConfig) -> CfkResult { - // TODO: Implement with quinn crate - // let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?; - // let connection = endpoint.connect(addr, server_name)?.await?; - Ok(Self { config }) - } - - /// Open a new bidirectional stream - pub async fn open_stream(&self) -> CfkResult { - Err(CfkError::Unsupported("QUIC not yet implemented".into())) - } -} - -/// QUIC bidirectional stream -pub struct QuicStream { - // In real impl: quinn::SendStream + quinn::RecvStream -} - -/// Multi-transport connector -pub struct MultiTransport { - preferred: Transport, - fallback: Option, -} - -impl MultiTransport { - pub fn new(preferred: Transport) -> Self { - Self { preferred, fallback: None } - } - - pub fn with_fallback(mut self, fallback: Transport) -> Self { - self.fallback = Some(fallback); - self - } - - /// Connect using preferred transport, fall back if needed - pub async fn connect(&self, addr: &str, port: u16) -> CfkResult> { - let config = ConnectionConfig { - transport: self.preferred, - addr: addr.into(), - port, - ..Default::default() - }; - - match self.preferred { - Transport::Tcp => { - let conn = TcpConnection::connect(config).await?; - Ok(Box::new(conn)) - } - Transport::Quic => { - // Try QUIC, fall back to TCP if configured - if let Some(Transport::Tcp) = self.fallback { - let tcp_config = ConnectionConfig { - transport: Transport::Tcp, - addr: addr.into(), - port, - ..Default::default() - }; - let conn = TcpConnection::connect(tcp_config).await?; - Ok(Box::new(conn)) - } else { - Err(CfkError::Unsupported("QUIC not yet implemented".into())) - } - } - _ => Err(CfkError::Unsupported(format!("{:?} not implemented", self.preferred))), - } - } -} - -/// Abstract transport stream trait -pub trait TransportStream: Send + Sync { - fn transport_type(&self) -> Transport; -} - -impl TransportStream for TcpConnection { - fn transport_type(&self) -> Transport { - Transport::Tcp - } -} - -/// Reliable multicast support -pub mod multicast { - use super::*; - use std::net::Ipv4Addr; - - /// Multicast group configuration - #[derive(Debug, Clone)] - pub struct MulticastGroup { - pub group_addr: Ipv4Addr, - pub port: u16, - pub interface: Option, - pub ttl: u8, - pub loopback: bool, - } - - impl Default for MulticastGroup { - fn default() -> Self { - Self { - group_addr: Ipv4Addr::new(239, 255, 0, 1), // Local scope - port: 5000, - interface: None, - ttl: 1, - loopback: false, - } - } - } - - /// PGM (Pragmatic General Multicast) configuration - #[derive(Debug, Clone)] - pub struct PgmConfig { - pub group: MulticastGroup, - pub rate_limit_kbps: u32, - pub window_size: u32, - pub nak_rdata_ivl_ms: u32, // NAK repeat interval - } - - impl Default for PgmConfig { - fn default() -> Self { - Self { - group: MulticastGroup::default(), - rate_limit_kbps: 10000, // 10 Mbps - window_size: 1024, - nak_rdata_ivl_ms: 200, - } - } - } - - /// NORM (NACK-Oriented Reliable Multicast) configuration - #[derive(Debug, Clone)] - pub struct NormConfig { - pub group: MulticastGroup, - pub rate_kbps: u32, - pub buffer_size: usize, - pub segment_size: u16, - pub fec_enabled: bool, // Forward Error Correction - } - - impl Default for NormConfig { - fn default() -> Self { - Self { - group: MulticastGroup::default(), - rate_kbps: 10000, - buffer_size: 1048576, // 1MB - segment_size: 1400, - fec_enabled: true, - } - } - } - - /// Reliable multicast sender - pub struct MulticastSender { - transport: Transport, - // In real impl: PGM/NORM socket - } - - impl MulticastSender { - pub async fn new_pgm(_config: PgmConfig) -> CfkResult { - // TODO: Implement PGM sender - Err(CfkError::Unsupported("PGM multicast not yet implemented".into())) - } - - pub async fn new_norm(_config: NormConfig) -> CfkResult { - // TODO: Implement NORM sender - Err(CfkError::Unsupported("NORM multicast not yet implemented".into())) - } - - /// Send data to all group members - pub async fn send(&self, _data: &[u8]) -> CfkResult<()> { - Err(CfkError::Unsupported("Multicast send not implemented".into())) - } - - /// Send file to all group members with progress - pub async fn send_file(&self, _path: &std::path::Path) -> CfkResult<()> { - Err(CfkError::Unsupported("Multicast file send not implemented".into())) - } - } - - /// Reliable multicast receiver - pub struct MulticastReceiver { - transport: Transport, - } - - impl MulticastReceiver { - pub async fn join_pgm(_config: PgmConfig) -> CfkResult { - Err(CfkError::Unsupported("PGM multicast not yet implemented".into())) - } - - pub async fn join_norm(_config: NormConfig) -> CfkResult { - Err(CfkError::Unsupported("NORM multicast not yet implemented".into())) - } - - /// Receive data from group - pub async fn recv(&self) -> CfkResult> { - Err(CfkError::Unsupported("Multicast recv not implemented".into())) - } - } -} diff --git a/czech-file-knife/src/cfk-providers/src/webdav.rs b/czech-file-knife/src/cfk-providers/src/webdav.rs deleted file mode 100644 index 3b5e8fb29..000000000 --- a/czech-file-knife/src/cfk-providers/src/webdav.rs +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! WebDAV storage backend -//! -//! HTTP-based distributed authoring and versioning protocol. -//! Compatible with NextCloud, ownCloud, SharePoint, Apache mod_dav, etc. - -use async_trait::async_trait; -use bytes::Bytes; -use cfk_core::{ - CfkError, CfkResult, Entry, EntryKind, Metadata, StorageBackend, StorageCapabilities, - VirtualPath, -}; -use chrono::{DateTime, Utc}; -use reqwest::{header, Client, Method, StatusCode}; -use serde::Deserialize; -use std::sync::Arc; -use tokio::sync::RwLock; - -/// WebDAV authentication method -#[derive(Debug, Clone)] -pub enum WebDavAuth { - /// No authentication - None, - /// Basic authentication - Basic { username: String, password: String }, - /// Bearer token (OAuth) - Bearer(String), - /// Digest authentication (handled by reqwest) - Digest { username: String, password: String }, -} - -/// WebDAV backend configuration -#[derive(Debug, Clone)] -pub struct WebDavConfig { - /// Base URL (e.g., "https://cloud.example.com/remote.php/dav/files/username") - pub base_url: String, - /// Authentication method - pub auth: WebDavAuth, - /// Custom headers - pub headers: Vec<(String, String)>, -} - -/// WebDAV storage backend -pub struct WebDavBackend { - id: String, - config: Arc>, - http: Client, - capabilities: StorageCapabilities, -} - -impl WebDavBackend { - pub fn new(id: impl Into, config: WebDavConfig) -> Self { - Self { - id: id.into(), - config: Arc::new(RwLock::new(config)), - http: Client::new(), - capabilities: StorageCapabilities { - read: true, - write: true, - delete: true, - rename: true, - copy: true, - list: true, - search: false, - versioning: false, // Some servers support it - sharing: false, - streaming: true, - resume: true, - watch: false, - metadata: true, - thumbnails: false, - max_file_size: None, - }, - } - } - - /// Build authenticated request - async fn request(&self, method: Method, path: &str) -> reqwest::RequestBuilder { - let config = self.config.read().await; - let url = format!("{}/{}", config.base_url.trim_end_matches('/'), path.trim_start_matches('/')); - - let mut request = self.http.request(method, &url); - - match &config.auth { - WebDavAuth::None => {} - WebDavAuth::Basic { username, password } => { - request = request.basic_auth(username, Some(password)); - } - WebDavAuth::Bearer(token) => { - request = request.bearer_auth(token); - } - WebDavAuth::Digest { username, password } => { - // reqwest handles digest auth automatically - request = request.basic_auth(username, Some(password)); - } - } - - for (key, value) in &config.headers { - request = request.header(key, value); - } - - request - } - - /// Convert VirtualPath to URL path - fn to_url_path(&self, path: &VirtualPath) -> String { - if path.segments.is_empty() { - String::new() - } else { - path.segments - .iter() - .map(|s| urlencoding::encode(s).to_string()) - .collect::>() - .join("/") - } - } - - /// PROPFIND request for listing/metadata - async fn propfind(&self, path: &str, depth: &str) -> CfkResult> { - let body = r#" - - - - - - - - - -"#; - - let response = self - .request(Method::from_bytes(b"PROPFIND").unwrap(), path) - .await - .header("Depth", depth) - .header(header::CONTENT_TYPE, "application/xml") - .body(body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != StatusCode::MULTI_STATUS { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - let text = response - .text() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - parse_multistatus(&text) - } -} - -/// DAV response from PROPFIND -#[derive(Debug, Clone, Default)] -struct DavResponse { - href: String, - is_collection: bool, - content_length: Option, - last_modified: Option>, - creation_date: Option>, - etag: Option, - content_type: Option, -} - -/// Parse WebDAV multistatus XML response -fn parse_multistatus(xml: &str) -> CfkResult> { - // Simple XML parsing without full XML crate - let mut responses = Vec::new(); - let mut current: Option = None; - - for line in xml.lines() { - let line = line.trim(); - - if line.contains("") || line.contains("") { - current = Some(DavResponse::default()); - } else if line.contains("") || line.contains("") { - if let Some(resp) = current.take() { - responses.push(resp); - } - } else if let Some(ref mut resp) = current { - // Parse href - if let Some(href) = extract_tag_content(line, "href") { - resp.href = urlencoding::decode(&href).unwrap_or(href.into()).to_string(); - } - - // Parse resourcetype - if line.contains(" Option { - let patterns = [ - format!("", tag), - format!("", tag), - format!("<{}:", tag), - ]; - - for pattern in &patterns { - if let Some(start) = line.find(pattern) { - let content_start = start + pattern.len(); - let end_patterns = [ - format!("", tag), - format!("", tag), - format!(" Option> { - // Try RFC 2822 first (most common) - if let Ok(dt) = DateTime::parse_from_rfc2822(s) { - return Some(dt.with_timezone(&Utc)); - } - - // Try RFC 3339 - if let Ok(dt) = DateTime::parse_from_rfc3339(s) { - return Some(dt.with_timezone(&Utc)); - } - - // Try common HTTP date format - let formats = [ - "%a, %d %b %Y %H:%M:%S GMT", - "%A, %d-%b-%y %H:%M:%S GMT", - "%a %b %e %H:%M:%S %Y", - ]; - - for fmt in &formats { - if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, fmt) { - return Some(DateTime::from_naive_utc_and_offset(dt, Utc)); - } - } - - None -} - -impl DavResponse { - fn to_entry(&self, backend_id: &str, base_href: &str) -> Entry { - // Extract relative path from href - let relative_path = self - .href - .trim_start_matches(base_href) - .trim_start_matches('/') - .trim_end_matches('/'); - - let kind = if self.is_collection { - EntryKind::Directory - } else { - EntryKind::File - }; - - let mut metadata = Metadata::default(); - metadata.size = self.content_length; - metadata.modified = self.last_modified; - metadata.created = self.creation_date; - metadata.checksum = self.etag.clone(); - metadata.mime_type = self.content_type.clone(); - - Entry { - path: VirtualPath::new(backend_id, relative_path), - kind, - metadata, - } - } -} - -#[async_trait] -impl StorageBackend for WebDavBackend { - fn id(&self) -> &str { - &self.id - } - - fn display_name(&self) -> &str { - "WebDAV" - } - - fn capabilities(&self) -> &StorageCapabilities { - &self.capabilities - } - - async fn is_available(&self) -> bool { - self.propfind("", "0").await.is_ok() - } - - async fn get_metadata(&self, path: &VirtualPath) -> CfkResult { - let url_path = self.to_url_path(path); - let responses = self.propfind(&url_path, "0").await?; - - responses - .first() - .map(|r| r.to_entry(&self.id, "")) - .ok_or_else(|| CfkError::NotFound(path.to_string())) - } - - async fn list_directory(&self, path: &VirtualPath) -> CfkResult> { - let url_path = self.to_url_path(path); - let responses = self.propfind(&url_path, "1").await?; - - // First response is the directory itself, skip it - Ok(responses - .iter() - .skip(1) - .map(|r| r.to_entry(&self.id, "")) - .collect()) - } - - async fn read_file(&self, path: &VirtualPath) -> CfkResult { - let url_path = self.to_url_path(path); - - let response = self - .request(Method::GET, &url_path) - .await - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - if status == StatusCode::NOT_FOUND { - return Err(CfkError::NotFound(path.to_string())); - } - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - response - .bytes() - .await - .map_err(|e| CfkError::Network(e.to_string())) - } - - async fn write_file(&self, path: &VirtualPath, data: Bytes) -> CfkResult { - let url_path = self.to_url_path(path); - - let response = self - .request(Method::PUT, &url_path) - .await - .header(header::CONTENT_TYPE, "application/octet-stream") - .body(data.to_vec()) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(path).await - } - - async fn delete(&self, path: &VirtualPath) -> CfkResult<()> { - let url_path = self.to_url_path(path); - - let response = self - .request(Method::DELETE, &url_path) - .await - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != StatusCode::NO_CONTENT { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - Ok(()) - } - - async fn create_directory(&self, path: &VirtualPath) -> CfkResult { - let url_path = self.to_url_path(path); - - let response = self - .request(Method::from_bytes(b"MKCOL").unwrap(), &url_path) - .await - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() && response.status() != StatusCode::CREATED { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(path).await - } - - async fn copy(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_url_path(from); - let to_path = self.to_url_path(to); - - let config = self.config.read().await; - let dest_url = format!( - "{}/{}", - config.base_url.trim_end_matches('/'), - to_path.trim_start_matches('/') - ); - - let response = self - .request(Method::from_bytes(b"COPY").unwrap(), &from_path) - .await - .header("Destination", &dest_url) - .header("Overwrite", "T") - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() - && response.status() != StatusCode::CREATED - && response.status() != StatusCode::NO_CONTENT - { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(to).await - } - - async fn rename(&self, from: &VirtualPath, to: &VirtualPath) -> CfkResult { - let from_path = self.to_url_path(from); - let to_path = self.to_url_path(to); - - let config = self.config.read().await; - let dest_url = format!( - "{}/{}", - config.base_url.trim_end_matches('/'), - to_path.trim_start_matches('/') - ); - - let response = self - .request(Method::from_bytes(b"MOVE").unwrap(), &from_path) - .await - .header("Destination", &dest_url) - .header("Overwrite", "T") - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - if !response.status().is_success() - && response.status() != StatusCode::CREATED - && response.status() != StatusCode::NO_CONTENT - { - let status = response.status(); - let error_text = response.text().await.unwrap_or_default(); - return Err(CfkError::ProviderApi { - provider: "webdav".into(), - message: format!("{}: {}", status, error_text), - }); - } - - self.get_metadata(to).await - } - - async fn get_space_info(&self) -> CfkResult<(u64, u64)> { - // WebDAV quota requires RFC 4331 support - let body = r#" - - - - - -"#; - - let response = self - .request(Method::from_bytes(b"PROPFIND").unwrap(), "") - .await - .header("Depth", "0") - .header(header::CONTENT_TYPE, "application/xml") - .body(body) - .send() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let text = response - .text() - .await - .map_err(|e| CfkError::Network(e.to_string()))?; - - let available = extract_tag_content(&text, "quota-available-bytes") - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - - let used = extract_tag_content(&text, "quota-used-bytes") - .and_then(|s| s.parse().ok()) - .unwrap_or(0); - - let total = available + used; - Ok((available, total)) - } -} - -/// NextCloud-specific extensions -impl WebDavBackend { - /// Create a NextCloud backend with standard configuration - pub fn nextcloud( - id: impl Into, - server_url: &str, - username: &str, - password: &str, - ) -> Self { - let base_url = format!( - "{}/remote.php/dav/files/{}", - server_url.trim_end_matches('/'), - username - ); - - Self::new( - id, - WebDavConfig { - base_url, - auth: WebDavAuth::Basic { - username: username.to_string(), - password: password.to_string(), - }, - headers: vec![], - }, - ) - } - - /// Create an ownCloud backend - pub fn owncloud( - id: impl Into, - server_url: &str, - username: &str, - password: &str, - ) -> Self { - let base_url = format!( - "{}/remote.php/webdav", - server_url.trim_end_matches('/') - ); - - Self::new( - id, - WebDavConfig { - base_url, - auth: WebDavAuth::Basic { - username: username.to_string(), - password: password.to_string(), - }, - headers: vec![], - }, - ) - } -} diff --git a/czech-file-knife/src/cfk-search/Cargo.toml b/czech-file-knife/src/cfk-search/Cargo.toml deleted file mode 100644 index 7047c43cb..000000000 --- a/czech-file-knife/src/cfk-search/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "cfk-search" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Full-text search for Czech File Knife" - -[features] -default = [] -tantivy = ["dep:tantivy"] - -[dependencies] -cfk-core = { path = "../cfk-core" } - -# Search -tantivy = { workspace = true, optional = true } -regex.workspace = true - -# Async -tokio.workspace = true -async-trait.workspace = true - -# Serialization -serde.workspace = true - -# Time -chrono.workspace = true - -# Error handling -thiserror.workspace = true diff --git a/czech-file-knife/src/cfk-search/src/lib.rs b/czech-file-knife/src/cfk-search/src/lib.rs deleted file mode 100644 index a6d3e17d9..000000000 --- a/czech-file-knife/src/cfk-search/src/lib.rs +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Full-text search for Czech File Knife -//! -//! This module provides full-text search capabilities using Tantivy. -//! Currently a stub - full implementation coming in a future release. - -use async_trait::async_trait; -use cfk_core::{CfkResult, Entry, VirtualPath}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -/// Search index errors -#[derive(Error, Debug)] -pub enum SearchError { - #[error("Index not found: {0}")] - IndexNotFound(String), - - #[error("Index error: {0}")] - IndexError(String), - - #[error("Query parse error: {0}")] - QueryError(String), - - #[error("IO error: {0}")] - Io(#[from] std::io::Error), -} - -/// Search result with relevance score -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SearchResult { - /// The matching entry - pub entry: Entry, - /// Relevance score (0.0 - 1.0) - pub score: f32, - /// Matching snippets with highlights - pub snippets: Vec, -} - -/// Search query options -#[derive(Debug, Clone, Default)] -pub struct SearchQuery { - /// The search query string - pub query: String, - /// Limit search to specific backends - pub backends: Option>, - /// Limit search to specific path prefixes - pub paths: Option>, - /// Maximum number of results - pub limit: Option, - /// Offset for pagination - pub offset: Option, - /// File type filters (e.g., "pdf", "txt") - pub file_types: Option>, - /// Search in file contents (not just names) - pub search_contents: bool, -} - -/// Search index trait -#[async_trait] -pub trait SearchIndex: Send + Sync { - /// Index a file or directory - async fn index(&self, entry: &Entry, content: Option<&[u8]>) -> CfkResult<()>; - - /// Remove an entry from the index - async fn remove(&self, path: &VirtualPath) -> CfkResult<()>; - - /// Search the index - async fn search(&self, query: &SearchQuery) -> CfkResult>; - - /// Clear the entire index - async fn clear(&self) -> CfkResult<()>; - - /// Get index statistics - async fn stats(&self) -> CfkResult; -} - -/// Index statistics -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct IndexStats { - /// Number of indexed documents - pub document_count: u64, - /// Index size in bytes - pub size_bytes: u64, - /// Last update timestamp - pub last_updated: Option>, -} - -/// Tantivy-based search index (stub) -/// Enable the `tantivy` feature to use this. -#[cfg(feature = "tantivy")] -pub struct TantivyIndex { - _path: PathBuf, -} - -#[cfg(feature = "tantivy")] -impl TantivyIndex { - /// Create a new Tantivy index at the given path - pub fn new(_path: impl Into) -> CfkResult { - Err(CfkError::Unsupported( - "Tantivy search index not yet implemented".into(), - )) - } - - /// Open an existing index - pub fn open(_path: impl Into) -> CfkResult { - Err(CfkError::Unsupported( - "Tantivy search index not yet implemented".into(), - )) - } -} - -#[cfg(feature = "tantivy")] -#[async_trait] -impl SearchIndex for TantivyIndex { - async fn index(&self, _entry: &Entry, _content: Option<&[u8]>) -> CfkResult<()> { - Err(CfkError::Unsupported("Search indexing not yet implemented".into())) - } - - async fn remove(&self, _path: &VirtualPath) -> CfkResult<()> { - Err(CfkError::Unsupported("Search indexing not yet implemented".into())) - } - - async fn search(&self, _query: &SearchQuery) -> CfkResult> { - Err(CfkError::Unsupported("Search not yet implemented".into())) - } - - async fn clear(&self) -> CfkResult<()> { - Err(CfkError::Unsupported("Search indexing not yet implemented".into())) - } - - async fn stats(&self) -> CfkResult { - Err(CfkError::Unsupported("Search indexing not yet implemented".into())) - } -} - -/// Simple filename-based search (works without full-text index) -pub async fn search_by_name( - pattern: &str, - entries: impl IntoIterator, -) -> Vec { - let pattern_lower = pattern.to_lowercase(); - entries - .into_iter() - .filter(|e| { - e.name() - .map(|n| n.to_lowercase().contains(&pattern_lower)) - .unwrap_or(false) - }) - .collect() -} - -/// Glob-style pattern matching -pub fn matches_glob(pattern: &str, name: &str) -> bool { - let pattern = pattern.to_lowercase(); - let name = name.to_lowercase(); - - if pattern == "*" { - return true; - } - - if let Some(suffix) = pattern.strip_prefix("*.") { - return name.ends_with(&format!(".{}", suffix)); - } - - if let Some(prefix) = pattern.strip_suffix(".*") { - return name.starts_with(prefix); - } - - name.contains(&pattern) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_matches_glob() { - assert!(matches_glob("*", "anything.txt")); - assert!(matches_glob("*.txt", "file.txt")); - assert!(matches_glob("*.TXT", "file.txt")); - assert!(!matches_glob("*.txt", "file.pdf")); - assert!(matches_glob("file.*", "file.txt")); - assert!(matches_glob("test", "my_test_file.txt")); - } -} diff --git a/czech-file-knife/src/cfk-tui/cfk_tui.gpr b/czech-file-knife/src/cfk-tui/cfk_tui.gpr deleted file mode 100644 index b1fb1ef3f..000000000 --- a/czech-file-knife/src/cfk-tui/cfk_tui.gpr +++ /dev/null @@ -1,38 +0,0 @@ --- Czech File Knife TUI --- Ada/SPARK Terminal User Interface - -project Cfk_Tui is - - for Source_Dirs use ("src", "spark"); - for Object_Dir use "obj"; - for Exec_Dir use "bin"; - for Main use ("cfk_tui_main.adb"); - - type Build_Mode is ("debug", "release", "spark"); - Mode : Build_Mode := external ("MODE", "debug"); - - package Compiler is - case Mode is - when "debug" => - for Default_Switches ("Ada") use - ("-g", "-gnata", "-gnatwa", "-gnatVa", "-gnatQ", - "-gnat2022", "-gnatyM120"); - when "release" => - for Default_Switches ("Ada") use - ("-O2", "-gnatn", "-gnatp", "-gnat2022"); - when "spark" => - for Default_Switches ("Ada") use - ("-g", "-gnata", "-gnat2022"); - end case; - end Compiler; - - package Prove is - for Proof_Switches ("Ada") use - ("--level=2", "--timeout=60", "--memlimit=2000"); - end Prove; - - package Binder is - for Default_Switches ("Ada") use ("-E"); - end Binder; - -end Cfk_Tui; diff --git a/czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads b/czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads deleted file mode 100644 index 7b7b2ce53..000000000 --- a/czech-file-knife/src/cfk-tui/src/cfk-tui-application.ads +++ /dev/null @@ -1,39 +0,0 @@ --- CFK TUI Application Package --- Main application state and lifecycle - -with CFK.TUI.Config; -with CFK.TUI.Screen; -with CFK.TUI.Input; - -package CFK.TUI.Application is - - type Application_State is (Initializing, Running, Paused, Exiting); - - type Application_Type is record - State : Application_State := Initializing; - Config : Config.Config_Type; - Screen : Screen.Screen_Type; - Input : Input.Input_Handler; - Exit_Code : Integer := 0; - end record; - - -- Lifecycle procedures - procedure Initialize - (App : in out Application_Type; - Config : Config.Config_Type); - - procedure Run (App : in out Application_Type); - - procedure Pause (App : in out Application_Type); - - procedure Resume (App : in out Application_Type); - - procedure Request_Exit (App : in out Application_Type; Code : Integer := 0); - - procedure Finalize (App : in out Application_Type); - - -- State queries - function Is_Running (App : Application_Type) return Boolean; - function Get_Exit_Code (App : Application_Type) return Integer; - -end CFK.TUI.Application; diff --git a/czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb b/czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb deleted file mode 100644 index b3dc5fe4f..000000000 --- a/czech-file-knife/src/cfk-tui/src/cfk_tui_main.adb +++ /dev/null @@ -1,36 +0,0 @@ --- Czech File Knife TUI Main --- Ada terminal user interface for CFK - -with Ada.Text_IO; -with Ada.Command_Line; -with CFK.TUI.Application; -with CFK.TUI.Config; - -procedure CFK_TUI_Main is - use Ada.Text_IO; - use Ada.Command_Line; - - App : CFK.TUI.Application.Application_Type; - Config : CFK.TUI.Config.Config_Type; -begin - -- Parse command line arguments - if Argument_Count > 0 then - Config := CFK.TUI.Config.Parse_Args; - else - Config := CFK.TUI.Config.Default_Config; - end if; - - -- Initialize application - CFK.TUI.Application.Initialize (App, Config); - - -- Run main loop - CFK.TUI.Application.Run (App); - - -- Cleanup - CFK.TUI.Application.Finalize (App); - -exception - when E : others => - Put_Line (Standard_Error, "Fatal error in CFK TUI"); - Set_Exit_Status (Failure); -end CFK_TUI_Main; diff --git a/czech-file-knife/src/cfk-vfs/Cargo.toml b/czech-file-knife/src/cfk-vfs/Cargo.toml deleted file mode 100644 index 1f24a9de2..000000000 --- a/czech-file-knife/src/cfk-vfs/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cfk-vfs" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "FUSE virtual filesystem for Czech File Knife" - -[features] -default = [] -fuse = ["dep:fuser", "dep:parking_lot", "dep:dashmap"] - -[dependencies] -cfk-core = { path = "../cfk-core" } -cfk-providers = { path = "../cfk-providers" } - -# FUSE (optional - requires libfuse on system) -fuser = { workspace = true, optional = true } -parking_lot = { workspace = true, optional = true } -dashmap = { workspace = true, optional = true } - -# Async -tokio.workspace = true -async-trait.workspace = true - -# Error handling -thiserror.workspace = true - -# Logging -tracing.workspace = true diff --git a/czech-file-knife/src/cfk-vfs/src/lib.rs b/czech-file-knife/src/cfk-vfs/src/lib.rs deleted file mode 100644 index 467746f10..000000000 --- a/czech-file-knife/src/cfk-vfs/src/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! FUSE virtual filesystem for Czech File Knife -//! -//! This module provides FUSE mounting capabilities to access -//! any CFK backend as a local filesystem. -//! Currently a stub - full implementation coming in a future release. - -use cfk_core::{CfkError, CfkResult}; -use std::path::PathBuf; -use thiserror::Error; - -/// VFS errors -#[derive(Error, Debug)] -pub enum VfsError { - #[error("Mount point does not exist: {0}")] - MountPointNotFound(String), - - #[error("Mount point is not a directory: {0}")] - MountPointNotDirectory(String), - - #[error("Already mounted at: {0}")] - AlreadyMounted(String), - - #[error("Not mounted")] - NotMounted, - - #[error("FUSE error: {0}")] - Fuse(String), - - #[error("IO error: {0}")] - Io(#[from] std::io::Error), -} - -/// Mount options -#[derive(Debug, Clone, Default)] -pub struct MountOptions { - /// Allow other users to access the mount - pub allow_other: bool, - /// Allow root to access the mount - pub allow_root: bool, - /// Read-only mount - pub read_only: bool, - /// Enable caching - pub cache: bool, - /// Cache timeout in seconds - pub cache_timeout_secs: Option, - /// Debug mode - pub debug: bool, -} - -/// VFS mount handle -pub struct VfsMount { - mount_point: PathBuf, - _options: MountOptions, -} - -impl VfsMount { - /// Mount a CFK backend at the given path - /// - /// # Arguments - /// * `backend_id` - The backend to mount (e.g., "local", "dropbox") - /// * `mount_point` - The local path to mount at - /// * `options` - Mount options - pub fn mount( - _backend_id: &str, - _mount_point: impl Into, - _options: MountOptions, - ) -> CfkResult { - Err(CfkError::Unsupported( - "FUSE VFS mounting not yet implemented".into(), - )) - } - - /// Get the mount point path - pub fn mount_point(&self) -> &PathBuf { - &self.mount_point - } - - /// Check if the mount is still active - pub fn is_mounted(&self) -> bool { - false - } - - /// Unmount the filesystem - pub fn unmount(self) -> CfkResult<()> { - Err(CfkError::Unsupported( - "FUSE VFS mounting not yet implemented".into(), - )) - } -} - -impl Drop for VfsMount { - fn drop(&mut self) { - // Attempt to unmount on drop - // In real implementation, this would call fuser::unmount - } -} - -/// List active mounts -pub fn list_mounts() -> Vec { - Vec::new() -} - -/// Check if FUSE is available on this system -pub fn is_fuse_available() -> bool { - #[cfg(target_os = "linux")] - { - std::path::Path::new("/dev/fuse").exists() - } - - #[cfg(target_os = "macos")] - { - // Check for macFUSE - std::path::Path::new("/Library/Filesystems/macfuse.fs").exists() - } - - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - { - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_fuse_available() { - // Just make sure it doesn't panic - let _ = is_fuse_available(); - } - - #[test] - fn test_mount_not_implemented() { - let result = VfsMount::mount("local", "/tmp/test", MountOptions::default()); - assert!(result.is_err()); - } -} diff --git a/czech-file-knife/src/contracts/README.adoc b/czech-file-knife/src/contracts/README.adoc deleted file mode 100644 index 657be1fe8..000000000 --- a/czech-file-knife/src/contracts/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Contracts Unit diff --git a/czech-file-knife/src/core/.gitkeep b/czech-file-knife/src/core/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/czech-file-knife/src/definitions/README.adoc b/czech-file-knife/src/definitions/README.adoc deleted file mode 100644 index 02ecc4fd7..000000000 --- a/czech-file-knife/src/definitions/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Definitions Unit diff --git a/czech-file-knife/src/errors/README.adoc b/czech-file-knife/src/errors/README.adoc deleted file mode 100644 index b03a1c45c..000000000 --- a/czech-file-knife/src/errors/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Errors Unit diff --git a/czech-file-knife/tests/aspect_tests.sh b/czech-file-knife/tests/aspect_tests.sh deleted file mode 100755 index e5ca45831..000000000 --- a/czech-file-knife/tests/aspect_tests.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# RSR Standard Aspect Test Template -# -# Aspect tests validate cross-cutting architectural invariants that span -# the entire codebase. These are NOT functional tests — they verify that -# coding standards, safety rules, and structural contracts hold. -# -# Usage: -# bash tests/aspect_tests.sh -# just aspect -# -# Standard aspects (enable what applies to your project): -# 1. SPDX compliance — all source files have license headers -# 2. Dangerous patterns — no believe_me, assert_total, sorry, unsafeCoerce, etc. -# 3. ABI/FFI contract — declarations match exports -# 4. Thread safety — mutex in FFI modules -# 5. Error handling — no panic/unreachable in production paths - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -cd "$PROJECT_DIR" - -PASS=0 -FAIL=0 -WARN=0 - -green() { printf '\033[32m%s\033[0m\n' "$*"; } -red() { printf '\033[31m%s\033[0m\n' "$*"; } -yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } -bold() { printf '\033[1m%s\033[0m\n' "$*"; } - -pass() { green " PASS: $1"; PASS=$((PASS + 1)); } -fail() { red " FAIL: $1"; FAIL=$((FAIL + 1)); } -warn() { yellow " WARN: $1"; WARN=$((WARN + 1)); } - -echo "═══════════════════════════════════════════════════════════════" -echo " CZECH_FILE_KNIFE — Aspect Tests (Cross-Cutting Concerns)" -echo "═══════════════════════════════════════════════════════════════" -echo "" - -# ═══════════════════════════════════════════════════════════════════════ -# Aspect 1: SPDX License Headers -# ═══════════════════════════════════════════════════════════════════════ -bold "Aspect 1: SPDX license headers" - -MISSING_SPDX=0 -while IFS= read -r -d '' f; do - if ! head -5 "$f" | grep -q "SPDX-License-Identifier"; then - warn "Missing SPDX header: $f" - MISSING_SPDX=$((MISSING_SPDX + 1)) - fi -done < <(find src/ -type f \( -name "*.rs" -o -name "*.zig" -o -name "*.res" -o -name "*.ex" -o -name "*.exs" -o -name "*.gleam" -o -name "*.idr" -o -name "*.sh" \) -print0 2>/dev/null) - -if [ "$MISSING_SPDX" -eq 0 ]; then - pass "All source files have SPDX headers" -else - fail "$MISSING_SPDX files missing SPDX headers" -fi - -# ═══════════════════════════════════════════════════════════════════════ -# Aspect 2: Dangerous Patterns (BANNED) -# ═══════════════════════════════════════════════════════════════════════ -bold "Aspect 2: Dangerous patterns" - -# Idris2 dangerous patterns -DANGEROUS_IDRIS=$(grep -rn 'believe_me\|assert_total\|really_believe_me' src/abi/ 2>/dev/null | grep -v "^Binary" | grep -v "test" || true) -if [ -n "$DANGEROUS_IDRIS" ]; then - fail "Dangerous Idris2 patterns found:" - echo "$DANGEROUS_IDRIS" | head -5 -else - pass "No dangerous Idris2 patterns (believe_me, assert_total)" -fi - -# Coq/Lean dangerous patterns -DANGEROUS_PROOF=$(grep -rn '\bAdmitted\b\|\bsorry\b\|\bunsafeCoerce\b\|\bObj\.magic\b' src/ verification/ 2>/dev/null | grep -v "test" | grep -v "comment" || true) -if [ -n "$DANGEROUS_PROOF" ]; then - fail "Dangerous proof patterns found:" - echo "$DANGEROUS_PROOF" | head -5 -else - pass "No dangerous proof patterns (Admitted, sorry, unsafeCoerce)" -fi - -# ═══════════════════════════════════════════════════════════════════════ -# Aspect 3: ABI/FFI Contract (if applicable) -# ═══════════════════════════════════════════════════════════════════════ -# Uncomment if your project has Idris2 ABI + Zig FFI: - -# bold "Aspect 3: ABI/FFI contract" -# if [ -d "src/abi" ] && [ -d "ffi/zig" ]; then -# # Check that every exported function in Idris2 ABI has a Zig FFI implementation -# ABI_EXPORTS=$(grep -h 'export' src/abi/*.idr 2>/dev/null | wc -l) -# FFI_EXPORTS=$(grep -h 'pub export fn' ffi/zig/src/*.zig 2>/dev/null | wc -l) -# if [ "$ABI_EXPORTS" -gt 0 ] && [ "$FFI_EXPORTS" -gt 0 ]; then -# pass "ABI ($ABI_EXPORTS exports) and FFI ($FFI_EXPORTS exports) both present" -# else -# fail "ABI/FFI mismatch: $ABI_EXPORTS ABI exports, $FFI_EXPORTS FFI exports" -# fi -# else -# pass "ABI/FFI not applicable (no src/abi or ffi/zig)" -# fi - -# ═══════════════════════════════════════════════════════════════════════ -# Aspect 4: Error Handling (no raw panic in production code) -# ═══════════════════════════════════════════════════════════════════════ -# Uncomment for Rust projects: - -# bold "Aspect 4: Error handling" -# UNWRAP_COUNT=$(grep -rn '\.unwrap()' src/ 2>/dev/null | grep -v "test" | grep -v "example" | wc -l) -# if [ "$UNWRAP_COUNT" -gt 20 ]; then -# warn "$UNWRAP_COUNT .unwrap() calls in src/ — consider replacing with ? or expect()" -# else -# pass "Acceptable unwrap count: $UNWRAP_COUNT" -# fi - -# ═══════════════════════════════════════════════════════════════════════ -# Summary -# ═══════════════════════════════════════════════════════════════════════ -echo "" -echo "═══════════════════════════════════════════════════════════════" -printf " Results: " -green "PASS=$PASS" | tr -d '\n' -echo -n " " -if [ "$FAIL" -gt 0 ]; then red "FAIL=$FAIL" | tr -d '\n'; else echo -n "FAIL=0"; fi -echo -n " " -if [ "$WARN" -gt 0 ]; then yellow "WARN=$WARN"; else echo "WARN=0"; fi -echo "" -echo "═══════════════════════════════════════════════════════════════" - -exit "$FAIL" diff --git a/czech-file-knife/tests/e2e.sh b/czech-file-knife/tests/e2e.sh deleted file mode 100755 index 14f65edef..000000000 --- a/czech-file-knife/tests/e2e.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# End-to-end: build the `cfk` binary, drive it against a scratch directory, -# and verify the reversible journal restores every destructive operation. -# -# Usage: bash tests/e2e.sh (or: just e2e) - -set -euo pipefail - -PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PASS=0 -FAIL=0 - -ok() { printf ' PASS: %s\n' "$1"; PASS=$((PASS + 1)); } -bad() { printf ' FAIL: %s\n' "$1"; FAIL=$((FAIL + 1)); } -expect_eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (expected '$2', got '$3')"; fi; } - -echo "Building cfk..." -cargo build --quiet --manifest-path "$PROJECT_DIR/Cargo.toml" -p cfk-cli -CFK="$PROJECT_DIR/target/debug/cfk" - -WORK="$(mktemp -d)" -trap 'rm -rf "$WORK"' EXIT -export CFK_JOURNAL_DIR="$WORK/.journal" -cd "$WORK" - -echo "one" > a.txt - -# 1. rm + undo restores content -"$CFK" rm a.txt -[ ! -e a.txt ] && ok "rm removes file" || bad "rm removes file" -"$CFK" undo -expect_eq "undo restores deleted file" "one" "$(cat a.txt 2>/dev/null || true)" - -# 2. mv + undo restores original name -"$CFK" mv a.txt b.txt -"$CFK" undo -[ -e a.txt ] && [ ! -e b.txt ] && ok "undo reverses move" || bad "undo reverses move" - -# 3. cp over existing file + undo restores the overwritten content -echo "two" > c.txt -"$CFK" cp --force a.txt c.txt -expect_eq "cp overwrote destination" "one" "$(cat c.txt)" -"$CFK" undo -expect_eq "undo restores overwritten destination" "two" "$(cat c.txt)" - -# 4. recursive rm + undo restores tree -mkdir -p d/sub && echo x > d/x && echo y > d/sub/y -"$CFK" rm -r d -"$CFK" undo -expect_eq "undo restores nested file" "y" "$(cat d/sub/y 2>/dev/null || true)" - -# 5. history records operations and undos -HIST="$("$CFK" history -n 50)" -printf '%s\n' "$HIST" | grep -q "(undone)" && ok "history marks undone ops" || bad "history marks undone ops" - -# 6. nothing left to undo is an error, not a silent success -if "$CFK" undo >/dev/null 2>&1; then bad "empty undo fails"; else ok "empty undo fails"; fi - -echo -echo "E2E: $PASS passed, $FAIL failed" -[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/e2e/julia_mint_test.sh b/czech-file-knife/tests/e2e/julia_mint_test.sh deleted file mode 100644 index 9c10b2e27..000000000 --- a/czech-file-knife/tests/e2e/julia_mint_test.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# julia_mint_test.sh — e2e: the julia-library overlay mints a working package. -# -# Simulates `just repo-init julia-library` end to end (overlay copy -> -# token substitution -> .in renames -> placeholder gate -> lock coverage -> -# Pkg.instantiate -> Pkg.test -> uuid derivation checks), on the real -# toolchain. This is the acceptance test for the overlay and for the two -# repo-init hunks it depends on (PACKAGE_UUID derivation; .in renames). -# -# Owner rulings exercised here (2026-09-19): -# D7 - the package uuid is generated at mint (derived, stable, v5) -# D6 - the minted workflows are lock-SSOT compliant (actions.lock ships) -# -# Usage: -# JULIA_BIN=/path/to/julia bash tests/e2e/julia_mint_test.sh -# (JULIA_BIN defaults to `julia` on PATH; the estate runner pins it via -# julia-actions/setup-julia) - -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -OVERLAY="$REPO_DIR/archetypes/julia-library/overlay" -JULIA="${JULIA_BIN:-julia}" -SCRATCH="${SCRATCH:-$(mktemp -d /tmp/julia-mint-test.XXXXXX)}" -trap 'rm -rf "$SCRATCH"' EXIT - -PASS=0; FAIL=0 -ok() { echo " PASS: $1"; PASS=$((PASS+1)); } -bad() { echo " FAIL: $1"; FAIL=$((FAIL+1)); } - -command -v "$JULIA" >/dev/null 2>&1 || { echo "SKIP: no julia binary (JULIA_BIN)"; exit 0; } -[ -d "$OVERLAY" ] || { echo "FAIL: overlay not found at $OVERLAY"; exit 1; } - -cd "$SCRATCH" - -# ── 1. overlay copy (as repo-init does) ──────────────────────────────── -cp -a "$OVERLAY/." . - -# ── 2. token substitution (as repo-init does) + hunk A (uuid derivation) -OWNER=hyperpolymath; REPO=mintcheck; PROJECT_NAME=MintCheck -AUTHOR="Jonathan D.A. Jewell"; AUTHOR_EMAIL="owner@hyperpolymath.dev" -FORGE=https://github.com; CURRENT_YEAR=$(date +%Y) -LB='{{'; RB='}}' -REPO_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, '${FORGE}/${OWNER}/${REPO}'))") -if command -v uuidgen >/dev/null 2>&1; then - PACKAGE_UUID=$(uuidgen --sha1 --namespace "$REPO_UUID" --name "julia:${REPO}") -else - PACKAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") -fi -for f in $(grep -rl "{{" . 2>/dev/null || true); do - tmp=$(mktemp) - sed -e "s|${LB}PROJECT_NAME${RB}|${PROJECT_NAME}|g" \ - -e "s|${LB}OWNER${RB}|${OWNER}|g" \ - -e "s|${LB}CURRENT_YEAR${RB}|${CURRENT_YEAR}|g" \ - -e "s|${LB}PACKAGE_UUID${RB}|${PACKAGE_UUID}|g" \ - -e "s|${LB}AUTHOR${RB}|${AUTHOR}|g" \ - -e "s|${LB}AUTHOR_EMAIL${RB}|${AUTHOR_EMAIL}|g" \ - -e "s|${LB}PROJECT_DESCRIPTION${RB}|Mint check of the julia-library archetype overlay.|g" \ - "$f" > "$tmp" && mv "$tmp" "$f" -done - -# ── 3. rename rule (patch hunk B) ────────────────────────────────────── -TOML_FILE=Project.toml -[ -f "$TOML_FILE" ] || TOML_FILE=Project.toml.in -if [ -f src/PACKAGE.jl.in ] && [ -f "$TOML_FILE" ]; then - PKG_NAME=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' "$TOML_FILE" | head -1) - [ -n "$PKG_NAME" ] && [ "$PKG_NAME" != "UNASSIGNED" ] && mv "src/PACKAGE.jl.in" "src/${PKG_NAME}.jl" -fi -for f in Project.toml.in .github/workflows/julia-ci.yml.in .github/workflows/julia-docs.yml.in; do - [ -f "$f" ] && mv "$f" "${f%.in}" -done - -# ── 4. placeholder gate (roster only — GHA ${{ }} is not a token) ───── -if grep -rnE "${LB}(PROJECT_NAME|PROJECT_DESCRIPTION|OWNER|AUTHOR|AUTHOR_EMAIL|CURRENT_YEAR|PACKAGE_UUID|REPO|FORGE)${RB}" . 2>/dev/null; then - bad "unfilled template tokens remain" -else - ok "no unfilled tokens" -fi - -# ── 4b. lock coverage (D6: actions.lock is the pin truth) ────────────── -if python3 - <<'PY' -import re, sys, pathlib -wfdir = pathlib.Path(".github/workflows") -lock_text = (wfdir / "actions.lock").read_text() -for wf in sorted(wfdir.glob("*.yml")): - uses = {m.group(1) for line in wf.read_text().splitlines() - if (m := re.match(r'\s*uses:\s*(\S+)', line))} - sect = re.search(r"'" + re.escape(str(wf)) + r"':\s*\n((?:\s+-\s+'[^']+'\n?)*)", lock_text) - locked = set(re.findall(r"-\s+'([^']+)'", sect.group(1))) if sect else set() - if uses - locked: - print(f" MISSING in lock: {wf.name}: {sorted(uses - locked)}") - sys.exit(1) -PY -then ok "every uses: ref is in actions.lock"; else bad "lockfile coverage gap"; fi - -# ── 5. Pkg.instantiate + Pkg.test (Test + Aqua) ──────────────────────── -if "$JULIA" --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.precompile()' >/tmp/julia-mint-inst.log 2>&1; then - ok "Pkg.instantiate" -else - bad "Pkg.instantiate (see /tmp/julia-mint-inst.log)"; tail -5 /tmp/julia-mint-inst.log -fi -if "$JULIA" --project=. -e 'using Pkg; Pkg.test()' >/tmp/julia-mint-test.log 2>&1; then - ok "Pkg.test (Test + Aqua)" -else - bad "Pkg.test (see /tmp/julia-mint-test.log)"; tail -8 /tmp/julia-mint-test.log -fi - -# ── 6. uuid derivation (D7) ──────────────────────────────────────────── -MINTED_UUID=$(sed -n 's/^uuid = "\(.*\)".*/\1/p' Project.toml | head -1) -if python3 - "$MINTED_UUID" "$PACKAGE_UUID" <<'PY' -import sys, uuid -minted, derived = sys.argv[1], sys.argv[2] -assert minted == derived, f"minted {minted} != derived {derived}" -assert uuid.UUID(minted).version == 5 -PY -then ok "package uuid is the derived v5"; else bad "package uuid derivation"; fi -RE_MINT=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") -[ "$RE_MINT" = "$MINTED_UUID" ] && ok "re-mint derives the same uuid (stable identity)" || bad "uuid not stable across re-mints" - -# ── 7. JET analysis — the SHIPPED invocation, actually executed ──────── -# -# Issue #202: the overlay emitted `JET.test_package(path=".", julia_version="1")` -# — the path/string form REMOVED in JET v0.12.0. It threw MethodError before -# analysing anything, and nothing in this repository ever executed it, so it -# shipped to every minted Julia repo. Syntax-checking the YAML could not catch -# it; only running it can. -# -# Both controls are mandatory: -# positive — the clean minted package passes. -# negative — an injected inference error IS detected. If the negative control -# passes, this test is vacuous and its "PASS" means nothing. -# -# JET goes in its own environment so the package's Project.toml is untouched, -# mirroring what the shipped workflow now does. -JET_PKG=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' Project.toml | head -1) -JET_SRC="src/${JET_PKG}.jl" -JET_ENV="$SCRATCH/.jet-env" - -run_jet() { - "$JULIA" -e " - using Pkg - Pkg.activate(\"$JET_ENV\") - Pkg.develop(path=\"$SCRATCH\") - Pkg.add(Pkg.PackageSpec(name=\"JET\", version=\"0.12\")) - using ${JET_PKG}, JET - if !hasmethod(JET.test_package, (Module,)) - error(\"JET \", pkgversion(JET), - \" has no test_package(::Module); the overlay pins an API that moved\") - end - JET.test_package(${JET_PKG}) - " -} - -if [ -n "$JET_PKG" ] && [ -f "$JET_SRC" ]; then - # positive control - if run_jet >/tmp/julia-mint-jet.log 2>&1; then - ok "JET: clean package passes (positive control)" - else - bad "JET: clean package failed — inspect; if this is an API/infra failure it is NOT a findings failure" - tail -12 /tmp/julia-mint-jet.log | sed 's/^/ /' - fi - - # negative control — inject a concrete inference error INSIDE the module. - # Inside a function body, so the module still loads and JET can analyse it; - # a bare top-level call would throw at load time and prove nothing. - cp "$JET_SRC" "$JET_SRC.bak" - sed -i '/^end # module/i neg_control() = "string" + 1' "$JET_SRC" - if run_jet >/tmp/julia-mint-jet-neg.log 2>&1; then - bad "JET: injected inference error was NOT detected — this canary cannot fail, so its PASS is vacuous" - else - ok "JET: injected inference error detected (negative control)" - fi - mv "$JET_SRC.bak" "$JET_SRC" -else - bad "JET: could not locate the minted package module (name=${JET_PKG:-}, src=$JET_SRC)" -fi - -echo -echo "julia mint test: $PASS passed, $FAIL failed" -[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/e2e/template_instantiation_test.sh b/czech-file-knife/tests/e2e/template_instantiation_test.sh deleted file mode 100755 index decbdb2f2..000000000 --- a/czech-file-knife/tests/e2e/template_instantiation_test.sh +++ /dev/null @@ -1,422 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# E2E Test: Template Instantiation -# Verifies that the template can be cloned and instantiated into a working project -# -# This test: -# 1. Clones the template to a temp directory -# 2. Replaces all placeholder tokens with test values -# 3. Validates the resulting repository structure -# 4. Verifies builds work after instantiation -# 5. Cleans up - -# Test configuration -TEMPLATE_ROOT="${1:-.}" -TEST_DIR="${TMPDIR:-/tmp}/rsr-template-test-$$" -TEST_REPO_NAME="test-instantiated-repo" -TEST_OWNER="test-owner" -TEST_FORGE="github" -TEST_AUTHOR="Test Author" -TEST_AUTHOR_EMAIL="test@example.com" -TEST_PROJECT_NAME="Test Project" -TEST_DESCRIPTION="A test project instantiated from the RSR template" -TEST_PRIMARY_LANGUAGE="Rust" - -if [ ! -d "$TEMPLATE_ROOT/archetypes" ]; then - echo "Self-skipping: archetypes/ not found (repo is already instantiated)." - exit 0 -fi - -# ANSI colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Helper functions -log_step() { - echo "" - echo -e "${BLUE}→${NC} $*" -} - -log_pass() { - echo -e "${GREEN}✓${NC} $*" -} - -log_error() { - echo -e "${RED}✗${NC} $*" >&2 -} - -log_warn() { - echo -e "${YELLOW}!${NC} $*" >&2 -} - -cleanup() { - if [ -d "$TEST_DIR" ]; then - log_step "Cleaning up test directory: $TEST_DIR" - rm -rf "$TEST_DIR" - log_pass "Cleanup complete" - fi -} - -trap cleanup EXIT - -#============================================================================== -# PHASE 1: SETUP -#============================================================================== - -echo "" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "E2E TEST: Template Instantiation" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" - -log_step "Creating test directory: $TEST_DIR" -mkdir -p "$TEST_DIR" -log_pass "Test directory created" - -#============================================================================== -# PHASE 2: CLONE TEMPLATE -#============================================================================== - -log_step "Cloning template from $TEMPLATE_ROOT" - -# Copy template to test location (simulating git clone) -TEST_REPO_PATH="$TEST_DIR/$TEST_REPO_NAME" -cp -r "$TEMPLATE_ROOT" "$TEST_REPO_PATH" -log_pass "Template cloned to $TEST_REPO_PATH" - -# Local ignored agent state is not part of the template product. A filesystem -# copy sees it anyway, unlike a real GitHub-template instantiation, and can make -# the placeholder gate judge unrelated nested worktrees. Remove it from the -# fixture before exercising the mint. -rm -rf "$TEST_REPO_PATH/.claude" - -# Remove .git directory for clean state, then re-init as the INSTANTIATED repo. -# -# The re-init is not cosmetic. check-no-placeholders.sh identifies the repo from -# `git config --get remote.origin.url`, so that a worktree whose basename is not -# `*-template-repo` still gets the template exemption. With no git repo at all -# that lookup fails and the script exits 1 having printed NOTHING — so this test -# reported "left unfilled placeholder tokens (see above)" with nothing above, -# and had been failing for a reason that has nothing to do with placeholders. -# -# That is worth stating plainly: the one gate whose whole job is "no placeholder -# may survive instantiation" was inoperative, which is exactly consistent with -# 211 estate repos shipping an un-deleted template instruction block. -# -# The origin is set to the INSTANTIATED name deliberately, for the same reason -# the check below clears GITHUB_REPOSITORY: we want the checker to judge this as -# a real minted repo, not to exempt itself as a template. -if [ -d "$TEST_REPO_PATH/.git" ]; then - rm -rf "$TEST_REPO_PATH/.git" - log_pass ".git directory removed (fresh clone)" -fi -if git -C "$TEST_REPO_PATH" init -q 2>/dev/null \ - && git -C "$TEST_REPO_PATH" remote add origin \ - "git@github.com:${TEST_OWNER}/${TEST_REPO_NAME}.git" 2>/dev/null; then - log_pass "re-initialised as ${TEST_OWNER}/${TEST_REPO_NAME} (checker needs a remote)" -else - log_error "could not re-init the test repo — check-no-placeholders.sh will exit 1 silently" - exit 1 -fi - -#============================================================================== -# PHASE 3: PLACEHOLDER REPLACEMENT -#============================================================================== - -log_step "Replacing placeholder tokens" - -# Guix package names cannot contain spaces, capitals, underscores, or doubled -# separators. Fail before mutating the fixture when the repository slug cannot -# be used as a valid Guix name. -if (cd "$TEST_REPO_PATH" && \ - RSR_NON_INTERACTIVE=1 \ - PROJECT_NAME="$TEST_PROJECT_NAME" \ - REPO="Invalid Guix_Name" \ - OWNER="$TEST_OWNER" \ - AUTHOR="$TEST_AUTHOR" \ - AUTHOR_EMAIL="$TEST_AUTHOR_EMAIL" \ - just repo-init) > "$TEST_DIR/invalid-slug.log" 2>&1; then - log_error "just repo-init accepted a repository slug that is invalid for Guix" - exit 1 -fi -if ! grep -q 'repo slug must be lowercase alphanumeric words separated by single hyphens' \ - "$TEST_DIR/invalid-slug.log"; then - log_error "invalid repository slug failed for the wrong reason" - cat "$TEST_DIR/invalid-slug.log" >&2 - exit 1 -fi -log_pass "Invalid Guix package slug rejected before instantiation" - -# Substitution is `just repo-init`'s job. This test MUST drive the real recipe: -# a second, hand-rolled replacement list here would be a mock that silently -# diverges from init.just (it did — it carried {{REPO_DESCRIPTION}} and -# {{PRIMARY_LANGUAGE}}, tokens init has never defined), so the test passed -# while real instantiation leaked placeholders into every new repo. -if ! command -v just >/dev/null 2>&1; then - log_error "just is not installed — cannot exercise the real init recipe" - exit 1 -fi - -# Answers, in the exact order init.just prompts for them. -INIT_ANSWERS=( - "$TEST_PROJECT_NAME" # Project name - "$TEST_REPO_NAME" # Repository slug - "$TEST_OWNER" # Owner - "$TEST_AUTHOR" # Author full name - "$TEST_AUTHOR_EMAIL" # Author email - "" # Author organization - "" # Previous/alt email - "$TEST_DESCRIPTION" # Project description - "" # Forge domain -> default - "" # Security email -> default - "" # Conduct email -> default - "library" # Project type - "" # Website URL -> default - "" # OpenSSF BP ID -) -# init only asks the container questions when build/container/ exists. -# -# The path here must match the recipe's own guard in build/just/repo-init.just -# exactly. It did not: the recipe tests `build/container`, this test tested -# `container`. On a full checkout — which is what CI clones — the recipe -# therefore asked three questions this test had no answers for, `read` hit -# EOF, and the recipe exited 1. The clone is complete here, so the mismatch -# is invisible on a tree missing build/ and unavoidable on one that has it. -if [ -d "$TEST_REPO_PATH/build/container" ]; then - INIT_ANSWERS+=("" "" "") # service name, port, registry -> defaults -fi -INIT_ANSWERS+=("Y") # Proceed? - -if ! (cd "$TEST_REPO_PATH" && printf '%s\n' "${INIT_ANSWERS[@]}" | just repo-init) > "$TEST_DIR/init.log" 2>&1; then - log_error "just repo-init failed:" - cat "$TEST_DIR/init.log" >&2 - exit 1 -fi - -log_pass "just repo-init completed" - -#============================================================================== -# PHASE 3a: GUIX IDENTITY MUST BE RENDERED FROM THE REPOSITORY SLUG -#============================================================================== - -log_step "Checking rendered Guix package identity" - -for guix_file in build/guix.scm; do - if [ ! -f "$TEST_REPO_PATH/$guix_file" ]; then - log_error "$guix_file is missing after instantiation" - exit 1 - fi - if ! grep -qF "(name \"$TEST_REPO_NAME\")" "$TEST_REPO_PATH/$guix_file"; then - log_error "$guix_file does not use the lowercase repository slug as its Guix name" - exit 1 - fi - if ! grep -qF "(home-page \"https://github.com/$TEST_OWNER/$TEST_REPO_NAME\")" "$TEST_REPO_PATH/$guix_file"; then - log_error "$guix_file does not contain the rendered project home page" - exit 1 - fi - if grep -q 'czech-file-knife' "$TEST_REPO_PATH/$guix_file"; then - log_error "$guix_file still contains the template repository identity" - exit 1 - fi -done - -log_pass "Guix package names and home pages were rendered correctly" - -#============================================================================== -# PHASE 3b: NO PLACEHOLDER MAY SURVIVE INSTANTIATION -#============================================================================== - -log_step "Checking for placeholders that survived instantiation" - -# Same script the openssf-compliance workflow runs, deliberately: a second -# copy of this logic here is what let the two drift last time. GITHUB_REPOSITORY -# is cleared so the check does not mistake the instantiated repo for a template -# repo and skip itself — the instantiated name is what we want it to judge. -if ! env -u GITHUB_REPOSITORY bash "$TEMPLATE_ROOT/scripts/check-no-placeholders.sh" "$TEST_REPO_PATH"; then - log_error "just repo-init left unfilled placeholder tokens (see above)" - exit 1 -fi - -log_pass "No placeholders survived instantiation" - -#============================================================================== -# PHASE 3c: NO TEMPLATE INSTRUCTION BLOCK MAY SURVIVE INSTANTIATION -#============================================================================== - -log_step "Checking for un-deleted template instruction blocks" - -# This is a SEPARATE assertion from 3b on purpose, and the two cannot be merged. -# -# check-no-placeholders.sh exempts metasyntactic tokens (PLACEHOLDER, TOKEN, -# ANYTHING, UPPER_SNAKE) so that prose ABOUT tokens does not fail the build. It -# has to: without that exemption README.adoc, EXPLAINME.adoc and both -# descriptiles fail on every mint, and a gate that always fires gets switched -# off. But the instruction block's ONLY doubled-brace text is the metasyntactic -# PLACEHOLDER token, so 3b is structurally incapable of seeing it. -# -# That gap is why 211 estate repos shipped the block, 206 of them in -# CODE_OF_CONDUCT.md, each one naming "Squisher Corpus" as the project it -# protects and routing conduct reports to the wrong repository. Measured -# 2026-08-04. Catch it by name instead. -LEFTOVER=$(grep -rl 'TEMPLATE INSTRUCTIONS' "$TEST_REPO_PATH" \ - --exclude-dir=.git 2>/dev/null \ - | grep -vE '/(scripts/strip-instruction-blocks\.rs|build/just/repo-init\.just|tests/e2e/template_instantiation_test\.sh|tests/workflows/mint_cleanup_test\.sh)$' || true) -if [ -n "$LEFTOVER" ]; then - log_error "just repo-init left a TEMPLATE INSTRUCTIONS block in:" - echo "$LEFTOVER" | sed 's/^/ /' >&2 - exit 1 -fi - -log_pass "No template instruction blocks survived instantiation" - -#============================================================================== -# PHASE 4: VALIDATE STRUCTURE -#============================================================================== - -log_step "Validating instantiated repository structure" - -# Run validation script on the instantiated repo -if [ -f "$TEMPLATE_ROOT/scripts/validate-template.sh" ]; then - bash "$TEMPLATE_ROOT/scripts/validate-template.sh" "$TEST_REPO_PATH" 0 - log_pass "Repository structure validation passed" -else - log_error "Validation script not found" - exit 1 -fi - -#============================================================================== -# PHASE 5: VERIFY BUILD -#============================================================================== - -log_step "Verifying build system works after instantiation" - -if [ -f "$TEST_REPO_PATH/src/interface/ffi/build.zig" ]; then - if command -v zig &> /dev/null; then - cd "$TEST_REPO_PATH/src/interface/ffi" - if ZIG_GLOBAL_CACHE_DIR="$TEST_DIR/zig-global-cache" \ - ZIG_LOCAL_CACHE_DIR="$TEST_DIR/zig-local-cache" zig build 2>&1; then - log_pass "Zig build successful" - else - log_error "Zig build failed" - exit 1 - fi - cd - > /dev/null - else - # Zig is OPTIONAL at this point. Everything this e2e exists to prove - # about instantiation - placeholders, instruction blocks, structure - - # is already established by the phases above. Hard-failing on an - # absent Zig toolchain means the e2e can only ever pass on a machine - # that has every FFI compiler, which is how it came to sit red: it - # reported "no zig" as though it were an instantiation failure. - # Warn loudly and continue. A Zig build that RUNS and fails is fatal. - log_warn "zig not installed - FFI build verification SKIPPED" - log_warn " instantiation verified; install zig to check src/interface/ffi" - fi -fi - -#============================================================================== -# PHASE 7: VERIFY CRITICAL FILES ARE NOT TEMPLATES -#============================================================================== - -log_step "Verifying critical files have been instantiated" - -CRITICAL_FILES=( - "README.adoc" - "docs/EXPLAINME.adoc" - "Justfile" -) - -for file in "${CRITICAL_FILES[@]}"; do - if [ -f "$TEST_REPO_PATH/$file" ]; then - # Check that it's not just a template (contains some actual content) - if grep -q "$TEST_PROJECT_NAME\|$TEST_AUTHOR\|$TEST_REPO_NAME" "$TEST_REPO_PATH/$file" 2>/dev/null || \ - [ $(wc -l < "$TEST_REPO_PATH/$file") -gt 10 ]; then - log_pass "File instantiated: $file" - else - log_error "File appears to be a template: $file" - exit 1 - fi - else - log_error "Critical file missing: $file" - exit 1 - fi -done - -#============================================================================== -# PHASE 8: VERIFY METADATA -#============================================================================== - -log_step "Verifying machine-readable metadata" - -METADATA_FILES=( - ".machine_readable/descriptiles/STATE.a2ml" - ".machine_readable/descriptiles/META.a2ml" -) - -for file in "${METADATA_FILES[@]}"; do - if [ -f "$TEST_REPO_PATH/$file" ]; then - log_pass "Metadata file exists: $file" - else - log_error "Metadata file missing: $file" - exit 1 - fi -done - -#============================================================================== -# WWW SITE-OPERATIONS BUNDLE (issue #53) -#============================================================================== - -# The mint must carry the bundle, and the legacy root location must be gone. -if [ -d "$TEST_REPO_PATH/.well-known" ]; then - log_error "minted repo still has root .well-known/ — canonical location is www/.well-known/" - exit 1 -fi -WWW_FILES=( - "README.adoc" - ".well-known/security.txt" - ".well-known/ai.txt" - ".well-known/humans.txt" - "schemas/publishable-paths.txt" - "tests/run-all.sh" -) -for file in "${WWW_FILES[@]}"; do - if [ -f "$TEST_REPO_PATH/www/$file" ]; then - log_pass "www bundle file exists: www/$file" - else - log_error "www bundle file missing: www/$file" - exit 1 - fi -done - -# The minted bundle must pass its own planted-control tests. -if (cd "$TEST_REPO_PATH" && bash www/tests/run-all.sh); then - log_pass "www bundle self-test passed in the minted repo" -else - log_error "www/tests/run-all.sh failed in the minted repo" - exit 1 -fi - -#============================================================================== -# SUMMARY -#============================================================================== - -echo "" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo -e "${GREEN}✓ E2E TEMPLATE INSTANTIATION TEST PASSED${NC}" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" -echo "Summary:" -echo " - Template cloned successfully" -echo " - All placeholders replaced" -echo " - Repository structure valid" -echo " - Build system works" -echo " - No remaining placeholders" -echo " - Metadata intact" -echo "" -echo "Test repository: $TEST_REPO_PATH (will be cleaned up)" -echo "" diff --git a/czech-file-knife/tests/fuzz/Cargo.toml b/czech-file-knife/tests/fuzz/Cargo.toml deleted file mode 100644 index 4ea590441..000000000 --- a/czech-file-knife/tests/fuzz/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -[package] -name = "czech-file-knife-fuzz" -version = "0.0.0" -authors = ["hyperpolymath"] -publish = false -edition = "2021" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.4" -arbitrary = { version = "1", features = ["derive"] } - -[dependencies.cfk-core] -path = "../../src/cfk-core" - -[[bin]] -name = "fuzz_path" -path = "fuzz_targets/fuzz_path.rs" -test = false -doc = false -bench = false diff --git a/czech-file-knife/tests/fuzz/README.adoc b/czech-file-knife/tests/fuzz/README.adoc deleted file mode 100644 index 00188a01b..000000000 --- a/czech-file-knife/tests/fuzz/README.adoc +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Fuzz Testing -:toc: - -== Status - -This directory is a scaffold for fuzz tests. -**No fuzz harness is configured yet.** Add one when your project has parsers, -deserializers, protocol handlers, or other input-processing code worth fuzzing. - -== Adding Fuzz Tests - -Choose the harness that matches your project's primary language: - -=== Rust (cargo-fuzz / libFuzzer) - -[source,bash] ----- -# Install cargo-fuzz (one-time) -cargo install cargo-fuzz - -# Initialise fuzz targets in this repo -cargo fuzz init - -# Create a target -cargo fuzz add my_target - -# Run -cargo fuzz run my_target -- -max_total_time=300 ----- - -The `cargo fuzz init` command creates `fuzz/Cargo.toml` and `fuzz/fuzz_targets/`. -Move or symlink those into `tests/fuzz/` to keep the RSR directory layout. - -=== Zig (built-in fuzzing, Zig 0.14+) - -[source,zig] ----- -// tests/fuzz/fuzz_parser.zig -const std = @import("std"); - -test "fuzz parser" { - // Zig's built-in fuzz testing - const input = std.testing.fuzzInput(.{}); - // Call your parser with arbitrary input - _ = mylib.parse(input) catch {}; -} ----- - -[source,bash] ----- -zig build test --fuzz ----- - -=== Elixir (stream_data property-based testing) - -[source,elixir] ----- -# mix.exs — add {:stream_data, "~> 1.0", only: :test} - -# tests/fuzz/my_property_test.exs -defmodule MyPropertyTest do - use ExUnit.Case - use ExUnitProperties - - property "parser never crashes on arbitrary input" do - check all input <- binary() do - # Should not raise - MyApp.Parser.parse(input) - end - end -end ----- - -=== / (fast-check) - -[source,javascript] ----- -// tests/fuzz/fuzz_parser.test.mjs -import fc from "npm:fast-check"; -import { parse } from "../../src/parser.mjs"; - -.test("parser handles arbitrary strings", () => { - fc.assert( - fc.property(fc.string(), (input) => { - // Should not throw - try { parse(input); } catch (_) { /* parse errors OK */ } - }), - { numRuns: 10000 } - ); -}); ----- - -== When to Add Fuzzing - -Fuzz testing is most valuable for code that: - -* Parses untrusted input (file formats, network protocols, user data) -* Deserializes structured data (JSON, binary formats, ASN.1) -* Performs complex string/byte manipulation -* Has safety-critical invariants - -If your project is purely a library of pure functions with typed inputs, -property-based testing (see `tests/property/`) may be more appropriate than -byte-level fuzzing. - -== CI Integration - -Once you have a fuzz harness, add a CI job that runs it for a bounded time -(e.g., 5 minutes) on each PR. This catches regressions without blocking merges -for hours. diff --git a/czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs b/czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs deleted file mode 100644 index 4da762fde..000000000 --- a/czech-file-knife/tests/fuzz/fuzz_targets/fuzz_path.rs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -//! Fuzz target for VirtualPath parsing and manipulation - -#![no_main] - -use libfuzzer_sys::fuzz_target; -use cfk_core::path::VirtualPath; - -fuzz_target!(|data: &[u8]| { - // Convert bytes to string for path operations - if let Ok(input) = std::str::from_utf8(data) { - // Fuzz URI parsing - let _ = VirtualPath::parse_uri(input); - - // Fuzz path construction and manipulation - if let Some((backend, path)) = input.split_once('/') { - let vpath = VirtualPath::new(backend, path); - - // Exercise various operations - let _ = vpath.to_uri(); - let _ = vpath.to_path_string(); - let _ = vpath.name(); - let _ = vpath.extension(); - let _ = vpath.parent(); - let _ = vpath.is_root(); - - // Fuzz join with remaining data - if input.len() > 10 { - let _ = vpath.join(&input[..10]); - } - } - } -}); diff --git a/czech-file-knife/tests/invisible-characters-test.sh b/czech-file-knife/tests/invisible-characters-test.sh deleted file mode 100755 index f73e3f2a1..000000000 --- a/czech-file-knife/tests/invisible-characters-test.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -set -euo pipefail - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -fixture_root="$(mktemp -d)" # TMPDIR-respecting; Hypatia hardcoded_tmp (alerts #125/#126) -# cleanup removes the temporary fixture directory only when its path is an -# existing absolute directory (the exact path mktemp -d just created). -cleanup() { - case "$fixture_root" in - /*) [ -d "$fixture_root" ] && rm -rf -- "$fixture_root" ;; - *) echo "refusing unsafe cleanup target: $fixture_root" >&2 ;; - esac -} -trap cleanup EXIT - -scanner="$repo_root/scripts/check-invisible-characters.sh" -results="$fixture_root/results.bin" -blocking_results="$fixture_root/blocking-results.bin" -fixtures="$fixture_root/fixtures" -mkdir -p "$fixtures" - -printf 'tab\tline\ncarriage\rreturn\n' > "$fixtures/safe.md" -printf 'nbsp:\302\240\n' > "$fixtures/nbsp.md" -printf 'soft-hyphen:\302\255\n' > "$fixtures/soft-hyphen.adoc" -printf 'zero-width:\342\200\213\n' > "$fixtures/zero-width.json" -printf 'bidi:\342\200\256\n' > "$fixtures/bidi.toml" -printf 'word-joiner:\342\201\240\n' > "$fixtures/word-joiner.yml" -printf '\357\273\277leading bom\n' > "$fixtures/bom.sh" -printf 'nul:\000byte\n' > "$fixtures/nul.rs" -printf 'backspace:\010byte\n' > "$fixtures/backspace.rs" -printf 'invalid:\377 then nbsp:\302\240\n' > "$fixtures/invalid-utf8.md" -printf 'newline name:\302\240\n' > "$fixtures/with -newline.md" - -# Estate-shaped files that the extension filter did not cover until 2026-09-02: -# .a2ml carries repo IDENTITY (uuid, forge, lineage), and the recipe files are -# extensionless. An invisible character in either is exactly the kind of damage -# this scanner exists to find, and both were silently skipped. -printf 'uuid\302\240= "x"\n' > "$fixtures/IDENTITY.a2ml" -printf 'test:\n\techo\302\240hi\n' > "$fixtures/Justfile" - -"$scanner" "$fixtures" "$results" "$blocking_results" - -count=0 -safe_seen=false -newline_seen=false -while IFS= read -r -d '' filepath; do - count=$((count + 1)) - [[ "$filepath" == "$fixtures/safe.md" ]] && safe_seen=true - [[ "$filepath" == "$fixtures/with"$'\n'"newline.md" ]] && newline_seen=true -done < "$results" - -[[ "$count" -eq 12 ]] || { - echo "expected 12 findings, got $count" >&2 - exit 1 -} -[[ "$safe_seen" == false ]] || { - echo "TAB/LF/CR-only safe fixture was incorrectly reported" >&2 - exit 1 -} -[[ "$newline_seen" == true ]] || { - echo "newline-containing filename was not preserved as one record" >&2 - exit 1 -} - -blocking_count=0 -nul_blocked=false -backspace_blocked=false -while IFS= read -r -d '' filepath; do - blocking_count=$((blocking_count + 1)) - [[ "$filepath" == "$fixtures/nul.rs" ]] && nul_blocked=true - [[ "$filepath" == "$fixtures/backspace.rs" ]] && backspace_blocked=true -done < "$blocking_results" -[[ "$blocking_count" -eq 2 && "$nul_blocked" == true && "$backspace_blocked" == true ]] || { - echo "expected only NUL and backspace fixtures in the blocking set" >&2 - exit 1 -} - -if "$scanner" "$fixture_root/missing" "$results"; then - echo "missing scan root did not fail closed" >&2 - exit 1 -fi - -failing_grep="$fixture_root/failing-grep" -printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_grep" -chmod +x "$failing_grep" -if INVISIBLE_GREP_BIN="$failing_grep" "$scanner" "$fixtures" "$results"; then - echo "grep execution errors did not fail closed" >&2 - exit 1 -fi - -failing_find="$fixture_root/failing-find" -printf '#!/usr/bin/env sh\nexit 2\n' > "$failing_find" -chmod +x "$failing_find" -if INVISIBLE_FIND_BIN="$failing_find" "$scanner" "$fixtures" "$results"; then - echo "find execution errors did not fail closed" >&2 - exit 1 -fi - -echo "invisible-character scanner positive and negative controls passed" diff --git a/czech-file-knife/tests/shape/check_root_shape_test.sh b/czech-file-knife/tests/shape/check_root_shape_test.sh deleted file mode 100755 index 83fe22b48..000000000 --- a/czech-file-knife/tests/shape/check_root_shape_test.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check_root_shape_test.sh — prove scripts/check-root-shape.sh can FAIL. -# -# The gate went one-directional for months and nobody noticed, because a gate -# that only ever passes looks identical to a gate that works. These cases pin -# both directions and the '?' optional marker. - -set -euo pipefail - -CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-root-shape.sh" -FIXTURE=$(mktemp -d) -trap 'rm -rf "$FIXTURE"' EXIT - -git -C "$FIXTURE" init -q -git -C "$FIXTURE" config user.name "RSR fixture" -git -C "$FIXTURE" config user.email "fixture@example.invalid" - -mkdir -p "$FIXTURE/machine-readable" -cat > "$FIXTURE/machine-readable/root-allow.txt" <<'ALLOW' -# fixture allowlist -.git/ -machine-readable/ -README.adoc -?OPTIONAL-THING.adoc -ALLOW -printf 'fixture\n' > "$FIXTURE/README.adoc" - -fail() { echo "FAIL: $1" >&2; exit 1; } - -# 1. A conforming root passes. -bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "conforming fixture did not pass" - -# 2. An entry at root that is not allow-listed fails, and is named. -printf 'stray\n' > "$FIXTURE/STRAY.adoc" -if out=$(bash "$CHECKER" "$FIXTURE" 2>&1); then - fail "stray root entry did not fail the gate" -fi -grep -q 'STRAY.adoc' <<<"$out" || fail "failure did not name the stray entry" -rm "$FIXTURE/STRAY.adoc" - -# 3. A REQUIRED allowlist entry that is absent fails, and is named. -# This is the direction that was missing, and the reason the allowlist rotted. -mv "$FIXTURE/README.adoc" "$FIXTURE/machine-readable/README.adoc.parked" -if out=$(bash "$CHECKER" "$FIXTURE" 2>&1); then - fail "missing required entry did not fail the gate" -fi -grep -q 'README.adoc' <<<"$out" || fail "failure did not name the missing entry" -mv "$FIXTURE/machine-readable/README.adoc.parked" "$FIXTURE/README.adoc" - -# 4. An OPTIONAL entry that is absent passes. Capability-gated and template-only -# material is legitimately missing in a conforming repo. -bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "absent ?optional entry wrongly failed" - -# 5. A git-ignored root entry is not drift: the allowlist governs tracked shape, -# not build output. (A .tmp probe once made this gate look broken when it -# was the probe that was wrong.) -printf '*.tmp\n' > "$FIXTURE/.gitignore" -printf 'x\n' > "$FIXTURE/build-output.tmp" -sed -i 's|^README.adoc$|README.adoc\n.gitignore|' "$FIXTURE/machine-readable/root-allow.txt" -bash "$CHECKER" "$FIXTURE" | grep -q '^PASS:' || fail "git-ignored root entry was wrongly treated as drift" - - -# --------------------------------------------------------------------------- -# Both-path resolution. The estate contract admits two spellings of the -# machine-readable directory, and the great majority of repos use the dotted -# one. A gate that reads only the hyphenated path exits 2 on those repos -- -# indistinguishable, to a caller, from a broken setup. -# --------------------------------------------------------------------------- - -DOTTED=$(mktemp -d) -trap 'rm -rf "$FIXTURE" "$DOTTED"' EXIT -git -C "$DOTTED" init -q -git -C "$DOTTED" config user.name "RSR fixture" -git -C "$DOTTED" config user.email "fixture@example.invalid" -mkdir -p "$DOTTED/.machine_readable" -cat > "$DOTTED/.machine_readable/root-allow.txt" <<'ALLOW' -# fixture allowlist, dotted spelling -.git/ -.machine_readable/ -README.adoc -ALLOW -printf 'fixture\n' > "$DOTTED/README.adoc" - -# 6. The dotted spelling resolves and a conforming root passes. -bash "$CHECKER" "$DOTTED" | grep -q '^PASS:' || fail "dotted .machine_readable/ allowlist was not resolved" - -# 7. The dotted spelling still FAILS on drift. Resolving the file is not the -# same as enforcing against it; without this case, case 6 would also pass -# for a gate that found the allowlist and then ignored it. -printf 'stray\n' > "$DOTTED/STRAY.adoc" -if out=$(bash "$CHECKER" "$DOTTED" 2>&1); then - fail "dotted-spelling repo did not fail on a stray root entry" -fi -grep -q 'STRAY.adoc' <<<"$out" || fail "dotted-spelling failure did not name the stray entry" -rm "$DOTTED/STRAY.adoc" - -# 8. BOTH spellings present is refused as setup error (2), not silently -# resolved: two allowlists cannot both be canonical. -mkdir -p "$DOTTED/machine-readable" -cp "$DOTTED/.machine_readable/root-allow.txt" "$DOTTED/machine-readable/root-allow.txt" -set +e -bash "$CHECKER" "$DOTTED" >/dev/null 2>&1 -rc=$? -set -e -[ "$rc" -eq 2 ] || fail "two competing allowlists returned $rc, expected 2" -rm -rf "$DOTTED/machine-readable" - -# 9. NEITHER spelling present is a setup error (2), and the message names both -# paths so the operator knows which two were tried. -rm -rf "$DOTTED/.machine_readable" -set +e -out=$(bash "$CHECKER" "$DOTTED" 2>&1) -rc=$? -set -e -[ "$rc" -eq 2 ] || fail "absent allowlist returned $rc, expected 2" -grep -q '.machine_readable/root-allow.txt' <<<"$out" || fail "error did not name the dotted path" -grep -q 'machine-readable/root-allow.txt' <<<"$out" || fail "error did not name the hyphenated path" - -echo "PASS: check-root-shape.sh fails in both directions, honours '?', and resolves both spellings" diff --git a/czech-file-knife/tests/shape/repo_map_determinism_test.sh b/czech-file-knife/tests/shape/repo_map_determinism_test.sh deleted file mode 100755 index 66175a859..000000000 --- a/czech-file-knife/tests/shape/repo_map_determinism_test.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# repo_map_determinism_test.sh — the repository map must generate identically -# in every environment, not merely repeatably in one. -# -# The CI freshness check compares a committed map against a freshly generated -# one. That check is only meaningful if the generator is environment-independent. -# It was not: `sort` is locale-dependent, so under en_US.UTF-8 dotfiles -# interleaved with ordinary names while under LC_ALL=C they sorted first. The -# generator produced stable output when run twice in one shell and DIFFERENT -# output in CI — which the freshness gate caught on its first real run. -# -# Running the generator twice cannot detect that. The locale must be varied. - -set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -GEN="$ROOT/scripts/gen-repo-map.sh" -MAP="$ROOT/docs/architecture/REPOSITORY-MAP.adoc" - -[ -x "$GEN" ] || [ -f "$GEN" ] || { echo "FAIL: generator not found at $GEN" >&2; exit 1; } - -ORIGINAL=$(mktemp); trap 'cp "$ORIGINAL" "$MAP" 2>/dev/null; rm -f "$ORIGINAL"' EXIT -cp "$MAP" "$ORIGINAL" - -prev="" -for loc in C en_US.UTF-8 C.UTF-8 POSIX; do - LC_ALL="$loc" bash "$GEN" "$ROOT" >/dev/null 2>&1 || { - echo "FAIL: generator errored under LC_ALL=$loc" >&2; exit 1; } - sum=$(sha256sum "$MAP" | cut -d' ' -f1) - if [ -n "$prev" ] && [ "$sum" != "$prev" ]; then - echo "FAIL: map differs under LC_ALL=$loc — the generator is locale-dependent." >&2 - echo " Sort with LC_ALL=C (or export it) so output is byte-identical everywhere." >&2 - exit 1 - fi - prev="$sum" -done - -# And the committed map must match a fresh generation, or CI is already stale. -if ! diff -q "$ORIGINAL" "$MAP" >/dev/null 2>&1; then - echo "FAIL: committed REPOSITORY-MAP.adoc is stale — run: just repo-map" >&2 - exit 1 -fi - -echo "PASS: repository map is byte-identical across locales, and committed copy is current" diff --git a/czech-file-knife/tests/workflows/check_adoc_renders_test.sh b/czech-file-knife/tests/workflows/check_adoc_renders_test.sh deleted file mode 100755 index 303280895..000000000 --- a/czech-file-knife/tests/workflows/check_adoc_renders_test.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 - -# Self-test for scripts/check-adoc-renders.sh. -# -# Four of the six fixtures below are defect classes found in this very repo. -# EVERY ONE OF THEM EXITS 0 UNDER BARE asciidoctor, because --failure-level -# defaults to FATAL. That is why each defective fixture is asserted twice: -# once to prove bare asciidoctor is blind to it, and once to prove the checker -# catches it. If the first assertion ever fails, the --failure-level flag has -# stopped being the load-bearing part and this gate needs rereading. - -set -euo pipefail - -CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-adoc-renders.sh" -FIXTURE=$(mktemp -d) -trap 'rm -rf "$FIXTURE"' EXIT - -git -C "$FIXTURE" init -q -git -C "$FIXTURE" config user.name "RSR fixture" -git -C "$FIXTURE" config user.email "fixture@example.invalid" - -# A defective fixture must be invisible to bare asciidoctor, or it is measuring -# something other than the flag. -assert_bare_asciidoctor_is_blind() { - local f="$1" - if ! asciidoctor --backend=html5 -o /dev/null "$FIXTURE/$f" >/dev/null 2>&1; then - echo "FAIL: bare asciidoctor already rejects $f; fixture no longer proves the flag" >&2 - exit 1 - fi -} - -# Replace the single defective file under test, so each case is isolated. -stage_only() { - local f="$1" - rm -f "$FIXTURE"/defect-*.adoc - git -C "$FIXTURE" rm -q --cached --ignore-unmatch 'defect-*.adoc' >/dev/null - cat > "$FIXTURE/$f" - git -C "$FIXTURE" add "$f" -} - -expect_rejected() { - local f="$1" label="$2" - assert_bare_asciidoctor_is_blind "$f" - if "$CHECKER" "$FIXTURE" > "$FIXTURE/out" 2>&1; then - echo "FAIL: checker accepted $label ($f)" >&2 - exit 1 - fi - grep -q "$f" "$FIXTURE/out" -} - -# 1. An empty repository is a PASS, not a failure. A stripped mint of this -# template may legitimately carry no .adoc at all. -"$CHECKER" "$FIXTURE" | grep -q '^PASS: no tracked .adoc' - -# 2. A well-formed document passes. -cat > "$FIXTURE/README.adoc" <<'EOF' -= Well Formed Fixture - -A paragraph of prose. - -* one -* two -EOF -git -C "$FIXTURE" add README.adoc -"$CHECKER" "$FIXTURE" | grep -q '^PASS:' - -# 3. A stray cell separator shifts the row width. The blank line after the -# title is load-bearing: without it, [cols=] is swallowed into the document -# header and the fixture measures the swallowed-attribute defect instead. -stage_only defect-table.adoc <<'EOF' -= Table Fixture - -[cols="2*"] -|=== -| one | two -| three | four | five -|=== -EOF -expect_rejected defect-table.adoc "a table dropping cells" - -# 4. A Markdown body inside a .adoc: '# H' parses as a level 0 section, which -# collides with the '= Title' the file already has. -stage_only defect-markdown.adoc <<'EOF' -= Markdown Body Fixture - -# Heading One - -## Sub Heading - -Some prose. -EOF -expect_rejected defect-markdown.adoc "a Markdown body carried inside a .adoc" - -# 5. An attribute line flush against '= Title' is read as the author line and -# the FIRST '----' as the revision line, so the SECOND '----' opens a block -# nothing closes. The closing delimiter is what makes this fixture bite: -# without it the file renders clean and the case is vacuous. -stage_only defect-swallowed.adoc <<'EOF' -= Swallowed Attribute Fixture -[source,bash] ----- -echo hello ----- - -Prose after the block. -EOF -expect_rejected defect-swallowed.adoc "a listing block swallowed into the header" - -# 6. [[word]] in prose is an inline anchor, processed even inside backticks, -# so the second occurrence redefines an id that is already in use. -stage_only defect-anchor.adoc <<'EOF' -= Anchor Fixture - -The token [[widget]] is used here. - -The token [[widget]] is used again here. -EOF -expect_rejected defect-anchor.adoc "a repeated prose anchor" - -# The repository is clean again once the defective file is withdrawn. -rm -f "$FIXTURE"/defect-*.adoc -git -C "$FIXTURE" rm -q --cached --ignore-unmatch 'defect-*.adoc' >/dev/null -"$CHECKER" "$FIXTURE" | grep -q '^PASS:' - -echo "PASS: clean and empty repos accepted; four defect classes rejected that bare asciidoctor exits 0 on" diff --git a/czech-file-knife/tests/workflows/check_no_vlang_test.sh b/czech-file-knife/tests/workflows/check_no_vlang_test.sh deleted file mode 100755 index cb4b477dc..000000000 --- a/czech-file-knife/tests/workflows/check_no_vlang_test.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 - -set -euo pipefail - -CHECKER="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/scripts/check-no-vlang.sh" -FIXTURE=$(mktemp -d) -trap 'rm -rf "$FIXTURE"' EXIT - -git -C "$FIXTURE" init -q -git -C "$FIXTURE" config user.name "RSR fixture" -git -C "$FIXTURE" config user.email "fixture@example.invalid" - -printf '%s\n' 'pub fn main() void {}' > "$FIXTURE/build.zig" -printf '%s\n' 'Zig is the supported FFI language.' > "$FIXTURE/README.adoc" -git -C "$FIXTURE" add build.zig README.adoc - -"$CHECKER" "$FIXTURE" | grep -q '^PASS:' - -printf '%s\n' 'import vweb' > "$FIXTURE/legacy.txt" -git -C "$FIXTURE" add legacy.txt -if "$CHECKER" "$FIXTURE" > "$FIXTURE/content.out" 2>&1; then - echo "FAIL: checker accepted tracked V-language content" >&2 - exit 1 -fi -grep -q 'legacy.txt' "$FIXTURE/content.out" - -git -C "$FIXTURE" reset -q legacy.txt -rm "$FIXTURE/legacy.txt" -printf '%s\n' 'Module {}' > "$FIXTURE/v.mod" -git -C "$FIXTURE" add v.mod -if "$CHECKER" "$FIXTURE" > "$FIXTURE/module.out" 2>&1; then - echo "FAIL: checker accepted a tracked v.mod" >&2 - exit 1 -fi -grep -q 'tracked module file: v.mod' "$FIXTURE/module.out" - -git -C "$FIXTURE" reset -q v.mod -rm "$FIXTURE/v.mod" - -# A NEW call site must not be reported as a V-language reference. The -# :(exclude) list in the checker can only name call sites that already exist, -# so a repo that invokes the gate from its Justfile (or a hook, or another -# workflow) used to fail with a false positive on the invocation line itself. -printf 'test:\n\tbash scripts/check-no-vlang.sh .\n' > "$FIXTURE/Justfile" -git -C "$FIXTURE" add Justfile -if ! "$CHECKER" "$FIXTURE" | grep -q '^PASS:'; then - echo "FAIL: checker false-positived on its own invocation line" >&2 - exit 1 -fi - -# ...but a real reference on the SAME line as an invocation is still caught, -# so the self-reference filter cannot be used to smuggle V past the gate. -printf 'test:\n\tbash scripts/check-no-vlang.sh . && vlang build\n' > "$FIXTURE/Justfile" -if "$CHECKER" "$FIXTURE" > "$FIXTURE/mixed.out" 2>&1; then - echo "FAIL: checker missed a V reference sharing a line with its invocation" >&2 - exit 1 -fi -grep -q 'Justfile' "$FIXTURE/mixed.out" - -echo "PASS: Zig allowed; V content and v.mod rejected; new call sites not false-positived" diff --git a/czech-file-knife/tests/workflows/k9_typecheck_test.sh b/czech-file-knife/tests/workflows/k9_typecheck_test.sh deleted file mode 100644 index d1e80a48f..000000000 --- a/czech-file-knife/tests/workflows/k9_typecheck_test.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Verify plain Nickel and K9!-enveloped inputs for validate-session-contracts.sh. -set -euo pipefail -root="$(cd "$(dirname "$0")/../.." && pwd)" -fixture="$(mktemp -d)" -trap 'rm -rf "$fixture"' EXIT -printf '%s\n' '{ value = 1 }' > "$fixture/plain.ncl" -printf '%s\n' 'K9!' '{ value = 1 }' > "$fixture/wrapped.k9.ncl" -printf '%s\n' '' ' ' 'K9!' '{ value = 1 }' > "$fixture/leading-blank-wrapped.k9.ncl" -printf '%s\n' 'K9!' '{ value = }' > "$fixture/bad.k9.ncl" -bash "$root/scripts/validate-session-contracts.sh" --typecheck "$fixture/plain.ncl" "$fixture/wrapped.k9.ncl" "$fixture/leading-blank-wrapped.k9.ncl" -if bash "$root/scripts/validate-session-contracts.sh" --typecheck "$fixture/bad.k9.ncl"; then - echo 'Invalid Nickel was accepted' >&2 - exit 1 -fi -echo 'PASS: plain and wrapped Nickel accepted, including leading blanks; malformed Nickel rejected' diff --git a/czech-file-knife/tests/workflows/mint_cleanup_test.sh b/czech-file-knife/tests/workflows/mint_cleanup_test.sh deleted file mode 100644 index 398c63da7..000000000 --- a/czech-file-knife/tests/workflows/mint_cleanup_test.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -set -euo pipefail -repo=$(git rev-parse --show-toplevel) -fixture=$(mktemp -d) -trap 'rm -rf "$fixture"' EXIT -cat > "$fixture/dependabot.yml" <<'YAML' -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - - package-ecosystem: nix - directory: / - - package-ecosystem: cargo - directory: / -YAML -bash "$repo/scripts/rust-tool.sh" prune-dependabot-ecosystems "$fixture/dependabot.yml" github-actions -grep -q 'github-actions' "$fixture/dependabot.yml" -if grep -qE 'nix|cargo' "$fixture/dependabot.yml"; then exit 1; fi -cp "$fixture/dependabot.yml" "$fixture/before" -bash "$repo/scripts/rust-tool.sh" prune-dependabot-ecosystems "$fixture/dependabot.yml" nix -cmp "$fixture/dependabot.yml" "$fixture/before" -cat > "$fixture/policy.md" <<'DOC' - -# Policy -Prose mentions TEMPLATE INSTRUCTIONS and must survive. -Actual policy. -DOC -bash "$repo/scripts/rust-tool.sh" strip-instruction-blocks "$fixture" -grep -q SPDX "$fixture/policy.md" -grep -q '^# Policy' "$fixture/policy.md" -grep -q '^Prose mentions' "$fixture/policy.md" -if grep -q 'delete only this' "$fixture/policy.md"; then exit 1; fi -cp "$fixture/policy.md" "$fixture/before" -bash "$repo/scripts/rust-tool.sh" strip-instruction-blocks "$fixture" -cmp "$fixture/policy.md" "$fixture/before" -echo 'PASS: selected ecosystems, nonempty updates, comment boundaries, and idempotence' diff --git a/czech-file-knife/tests/workflows/session_contracts_test.sh b/czech-file-knife/tests/workflows/session_contracts_test.sh deleted file mode 100644 index 116fa2d48..000000000 --- a/czech-file-knife/tests/workflows/session_contracts_test.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Exercise envelope handling using the actual Nickel evaluator. -set -euo pipefail -repo=$(git rev-parse --show-toplevel) -fixture=$(mktemp -d) -trap 'rm -rf "$fixture"' EXIT -mkdir -p "$fixture/session" -cp "$repo/coordination.k9.ncl" "$fixture/coordination.k9.ncl" -cp "$repo/session/custom-checks.k9.ncl" "$fixture/session/custom-checks.k9.ncl" -cd "$fixture" -bash "$repo/scripts/validate-session-contracts.sh" -{ printf '\n \n'; cat "$repo/coordination.k9.ncl"; } > coordination.k9.ncl -bash "$repo/scripts/validate-session-contracts.sh" -printf '\n{ value = 1 }\n' > coordination.k9.ncl -if bash "$repo/scripts/validate-session-contracts.sh"; then - echo 'FAIL: missing envelope accepted' >&2 - exit 1 -fi -printf '\nK9!\n{ invalid = }\n' > coordination.k9.ncl -if bash "$repo/scripts/validate-session-contracts.sh"; then - echo 'FAIL: invalid Nickel accepted' >&2 - exit 1 -fi -echo 'PASS: leading blanks, missing envelope, and invalid Nickel controls' diff --git a/czech-file-knife/tests/workflows/template_conformance_test.sh b/czech-file-knife/tests/workflows/template_conformance_test.sh deleted file mode 100755 index 36159e863..000000000 --- a/czech-file-knife/tests/workflows/template_conformance_test.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# template_conformance_test.sh — prove check-template-conformance.sh can fail. -# -# The estate has twice shipped a gate that could never fire (#49: the -# invisible-character gate matched nothing; #64: the Hypatia gate was -# unconditionally vacuous). A gate whose only evidence is that it passes is not -# evidence. Every check below therefore has a NEGATIVE control: a fixture that -# MUST be rejected. If a negative control passes, this test fails. -# -# Usage: bash tests/workflows/template_conformance_test.sh [--keep] - -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -CHECK="$REPO_DIR/scripts/check-template-conformance.sh" -KEEP="${1:-}" - -SCRATCH="$(mktemp -d /tmp/tmpl-conformance.XXXXXX)" -[ "$KEEP" = "--keep" ] || trap 'rm -rf "$SCRATCH"' EXIT - -PASS=0; FAIL=0 -ok() { echo " PASS: $1"; PASS=$((PASS+1)); } -bad() { echo " FAIL: $1"; FAIL=$((FAIL+1)); } - -command -v git >/dev/null 2>&1 || { echo "SKIP: no git"; exit 0; } -[ -x "$CHECK" ] || [ -f "$CHECK" ] || { echo "FAIL: checker not found at $CHECK"; exit 1; } - -# ── fixture: a template and a minted child ─────────────────────────────────── -make_template() { - local d="$1" - mkdir -p "$d/.github" "$d/scripts" - printf 'name: demo\n' > "$d/Justfile" - printf 'body\n' > "$d/README.adoc" - printf 'ci: []\n' > "$d/.github/workflows.yml" - git -C "$d" init -q -b main - git -C "$d" -c user.email=t@t -c user.name=t add -A - git -C "$d" -c user.email=t@t -c user.name=t commit -qm "template" -} - -make_child() { - local d="$1" parent_slug="$2" branch="$3" commit="$4" tree="$5" - mkdir -p "$d/.machine_readable" "$d/.github" "$d/scripts" - cp "$2_README" /dev/null 2>/dev/null || true - printf 'name: demo\n' > "$d/Justfile" - printf 'body\n' > "$d/README.adoc" - printf 'ci: []\n' > "$d/.github/workflows.yml" - cp "$CHECK" "$d/scripts/check-template-conformance.sh" - cat > "$d/.machine_readable/PROVENANCE.a2ml" <"$SCRATCH/out" 2>&1 - else - bash "$CHECK" --repo "$repo" --quiet >"$SCRATCH/out" 2>&1 - fi - echo $? -} - -say() { echo; echo "── $1 ──"; } - -TMPL="$SCRATCH/template"; make_template "$TMPL" -T_COMMIT="$(git -C "$TMPL" rev-parse HEAD)" -T_TREE="$(git -C "$TMPL" rev-parse 'HEAD^{tree}')" - -# ───────────────────────────────────────────────────────────────────────────── -say "control: a conforming child must PASS" -CHILD="$SCRATCH/child-good" -make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" -rc=$(run_check "$CHILD") -if [ "$rc" -eq 0 ]; then ok "conforming child accepted (positive control)"; else bad "conforming child was rejected (rc=$rc)"; cat "$SCRATCH/out"; fi - -# ───────────────────────────────────────────────────────────────────────────── -say "negative control 1: missing provenance must FAIL (#203)" -CHILD="$SCRATCH/child-noprov" -make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" -rm "$CHILD/.machine_readable/PROVENANCE.a2ml" -rc=$(run_check "$CHILD") -if [ "$rc" -ne 0 ]; then ok "child with no provenance rejected (T1)"; else bad "child with no provenance ACCEPTED — T1 is vacuous"; fi - -# ───────────────────────────────────────────────────────────────────────────── -say "negative control 2: self-parent must FAIL (#200/#201 class)" -CHILD="$SCRATCH/child-self" -make_child "$CHILD" "metadatastician/knot-knot" "main" "$T_COMMIT" "$T_TREE" -rc=$(run_check "$CHILD") -if [ "$rc" -ne 0 ]; then ok "self-parenting repo rejected (T2)"; else bad "self-parenting repo ACCEPTED — T2 is vacuous"; fi - -# ───────────────────────────────────────────────────────────────────────────── -say "negative control 3: UNASSIGNED pin must FAIL" -CHILD="$SCRATCH/child-unassigned" -make_child "$CHILD" "hyperpolymath/czech-file-knife" "UNASSIGNED" "UNASSIGNED" "UNASSIGNED" -rc=$(run_check "$CHILD") -if [ "$rc" -ne 0 ]; then ok "unresolved parent pin rejected (T3)"; else bad "unresolved parent pin ACCEPTED — T3 is vacuous"; fi - -# ───────────────────────────────────────────────────────────────────────────── -say "negative control 4: a leaked work branch must FAIL (#203)" -CHILD="$SCRATCH/child-leak" -make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" -# reproduce the knot-knot shape: a parentless branch whose tree is copied -git -C "$CHILD" checkout -q --orphan coderabbit/fix-hypatia-scan-failures/f36ac704 -git -C "$CHILD" -c user.email=t@t -c user.name=t commit -qm "Initialize coderabbit/fix-hypatia-scan-failures/f36ac704" -git -C "$CHILD" checkout -q main -rc=$(run_check "$CHILD") -if [ "$rc" -ne 0 ]; then ok "leaked work branch rejected (T4)"; else bad "leaked work branch ACCEPTED — T4 is vacuous"; fi -if grep -qi "no common ancestor" "$SCRATCH/out"; then ok "unrelated-history signature reported (T4b)"; else bad "unrelated-history signature not reported"; fi - -# ───────────────────────────────────────────────────────────────────────────── -say "negative control 5: drift must be REPORTED (advisory) and visibly found" -CHILD="$SCRATCH/child-drift" -make_child "$CHILD" "hyperpolymath/czech-file-knife" "main" "$T_COMMIT" "$T_TREE" -printf 'tampered\n' > "$CHILD/README.adoc" # a template-owned file changed -rm "$CHILD/.github/workflows.yml" # a template-owned file deleted -rc=$(run_check "$CHILD" "$TMPL") -if grep -q "MISSING" "$SCRATCH/out" && grep -q "differs" "$SCRATCH/out"; then - ok "drift report found both a missing and a changed template-owned path (D1/D2)" -else - bad "drift report missed the injected divergence"; cat "$SCRATCH/out" -fi -[ "$rc" -eq 0 ] && ok "drift alone does not fail the gate (advisory, by design)" || bad "drift failed the gate — that is how a gate becomes unpassable, then continue-on-error'd" - -# ───────────────────────────────────────────────────────────────────────────── -say "self-skip: the template itself must not be checked" -SKIPDIR="$SCRATCH/template-with-archetypes" -mkdir -p "$SKIPDIR/archetypes" -out=$(bash "$CHECK" --repo "$SKIPDIR" 2>&1); rc=$? -if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "SKIP"; then ok "template self-skips"; else bad "template did not self-skip (rc=$rc): $out"; fi - -echo -echo "template conformance test: $PASS passed, $FAIL failed" -[ "$FAIL" -eq 0 ] diff --git a/czech-file-knife/tests/workflows/validate_workflows_test.sh b/czech-file-knife/tests/workflows/validate_workflows_test.sh deleted file mode 100755 index 4e6e32172..000000000 --- a/czech-file-knife/tests/workflows/validate_workflows_test.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Test: GitHub Workflows Validation -# Verifies that all workflows follow the standards - -set -euo pipefail - -WORKFLOWS_DIR="${1:-.github/workflows}" -ERRORS=0 -WARNINGS=0 - -# ANSI colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -log_error() { - echo -e "${RED}ERROR${NC}: $*" >&2 - ERRORS=$((ERRORS + 1)) -} - -log_warning() { - echo -e "${YELLOW}WARN${NC}: $*" >&2 - WARNINGS=$((WARNINGS + 1)) -} - -log_pass() { - echo -e "${GREEN}PASS${NC}: $*" >&2 -} - -log_info() { - echo -e "${BLUE}INFO${NC}: $*" >&2 -} - -# Verify workflows directory exists -if [ ! -d "$WORKFLOWS_DIR" ]; then - log_error "Workflows directory not found: $WORKFLOWS_DIR" - exit 1 -fi - -echo "" -log_info "Validating workflows in: $WORKFLOWS_DIR" -echo "" - -#============================================================================== -# TEST 1: CHECK EACH WORKFLOW FILE -#============================================================================== - -WORKFLOW_COUNT=0 -while IFS= read -r workflow_file; do - [ -z "$workflow_file" ] && continue - WORKFLOW_COUNT=$((WORKFLOW_COUNT + 1)) -done < <(find "$WORKFLOWS_DIR" \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | sort) - -echo "Found $WORKFLOW_COUNT workflow file(s)" -echo "" - -while IFS= read -r workflow_file; do - [ -z "$workflow_file" ] && continue - - WORKFLOW_NAME=$(basename "$workflow_file") - - # TEST 1a: SPDX Header - if head -10 "$workflow_file" 2>/dev/null | grep -q "SPDX-License-Identifier"; then - log_pass " $WORKFLOW_NAME: SPDX header present" - else - log_warning " $WORKFLOW_NAME: No SPDX header" - fi - - # TEST 1b: Has 'name' field - if grep -q "^name:" "$workflow_file" 2>/dev/null; then - log_pass " $WORKFLOW_NAME: Has 'name' field" - else - log_error " $WORKFLOW_NAME: Missing 'name' field" - fi - -done < <(find "$WORKFLOWS_DIR" \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | sort) - -#============================================================================== -# TEST 2: REQUIRED WORKFLOWS -#============================================================================== - -echo "" -log_info "Checking for required workflows" -echo "" - -REQUIRED_WORKFLOWS=( - "hypatia-scan.yml" - "codeql.yml" - "scorecard.yml" - "quality.yml" - "mirror.yml" - "instant-sync.yml" - "guix-policy.yml" - "security-policy.yml" - "wellknown-enforcement.yml" - "workflow-linter.yml" - # Retired in #14, replaced by runtime-policy.yml — see the note in - # scripts/validate-template.sh. Keeping the old names here required two - # files the template no longer ships. - "runtime-policy.yml" - "secret-scanner.yml" -) - -FOUND_COUNT=0 -for required in "${REQUIRED_WORKFLOWS[@]}"; do - if [ -f "$WORKFLOWS_DIR/$required" ]; then - log_pass "Found: $required" - FOUND_COUNT=$((FOUND_COUNT + 1)) - else - log_warning "Missing: $required" - WARNINGS=$((WARNINGS + 1)) - fi -done - -echo "" -echo "Found $FOUND_COUNT/${#REQUIRED_WORKFLOWS[@]} required workflows" -echo "" - -#============================================================================== -# SUMMARY -#============================================================================== - -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "WORKFLOW VALIDATION SUMMARY" -echo "═══════════════════════════════════════════════════════════════════════════════" -echo "" -echo -e "Errors: ${RED}${ERRORS}${NC}" -echo -e "Warnings: ${YELLOW}${WARNINGS}${NC}" -echo "" - -if [ "$ERRORS" -eq 0 ]; then - echo -e "${GREEN}✓ Workflow validation PASSED${NC}" - [ "$WARNINGS" -gt 0 ] && echo -e " (with $WARNINGS recommendations)" - exit 0 -else - echo -e "${RED}✗ Workflow validation FAILED${NC}" - echo " Please fix the errors above." - exit 1 -fi diff --git a/czech-file-knife/verification/README.adoc b/czech-file-knife/verification/README.adoc deleted file mode 100644 index f0e6aa767..000000000 --- a/czech-file-knife/verification/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Verification Pillar diff --git a/czech-file-knife/verification/benchmarks/README.adoc b/czech-file-knife/verification/benchmarks/README.adoc deleted file mode 100644 index 3f4ce4f58..000000000 --- a/czech-file-knife/verification/benchmarks/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Benchmarks Unit diff --git a/czech-file-knife/verification/coverage/README.adoc b/czech-file-knife/verification/coverage/README.adoc deleted file mode 100644 index 60b580482..000000000 --- a/czech-file-knife/verification/coverage/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Coverage Unit diff --git a/czech-file-knife/verification/fuzzing/README.adoc b/czech-file-knife/verification/fuzzing/README.adoc deleted file mode 100644 index 48036b6de..000000000 --- a/czech-file-knife/verification/fuzzing/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Fuzzing Unit diff --git a/czech-file-knife/verification/proofs/README.adoc b/czech-file-knife/verification/proofs/README.adoc deleted file mode 100644 index eb22d4c40..000000000 --- a/czech-file-knife/verification/proofs/README.adoc +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Formal Verification Proofs - -This directory contains formal proofs organised by proof assistant. - -== Directory Structure - -[source] ----- -proofs/ -├── idris2/ # Idris2 proofs (ABI, dependent types) -│ ├── ABI/ # ABI-specific proofs (mandatory) -│ │ ├── Pointers.idr # Non-null pointer safety -│ │ ├── Layout.idr # Memory layout correctness -│ │ ├── Platform.idr # Platform type size proofs -│ │ ├── Foreign.idr # FFI return type proofs -│ │ └── Compliance.idr # C ABI compliance -│ └── Types.idr # Core data type well-formedness -├── lean4/ # Lean4 proofs (algebra, lattices) -│ └── ApiTypes.lean -├── agda/ # Agda proofs (induction, metatheory) -│ └── Properties.agda -├── coq/ # Coq proofs (type systems, compilation) -│ └── TypeSafety.v -└── tlaplus/ # TLA+ specs (distributed protocols) - └── StateMachine.tla ----- - -== Verification Commands - -[source,bash] ----- -just proof-check-all # Run all proof checkers -just proof-check-idris2 # Idris2 only -just proof-check-lean4 # Lean4 only -just proof-check-agda # Agda only -just proof-check-coq # Coq only ----- - -== Banned Patterns - -The following MUST NOT appear in any proof file: - -- `believe_me` (Idris2) -- `assert_total` (Idris2) -- `postulate` (Idris2/Agda) -- `sorry` (Lean4) -- `Admitted` (Coq) -- `unsafeCoerce` (Haskell) - -CI enforces this via `panic-attack assail --proofs-only`. - -== Adding New Proofs - -1. Choose the appropriate prover (see PROOF-NEEDS.md) -2. Create the `.idr`/`.lean`/`.agda`/`.v`/`.tla` file in the right directory -3. Ensure `%default total` (Idris2) or equivalent -4. Run the verification command -5. Update PROOF-STATUS.md diff --git a/czech-file-knife/verification/proofs/agda/MANIFEST b/czech-file-knife/verification/proofs/agda/MANIFEST deleted file mode 100644 index 2e18dc332..000000000 --- a/czech-file-knife/verification/proofs/agda/MANIFEST +++ /dev/null @@ -1,4 +0,0 @@ -# Agda proof manifest — read by scripts/check-proofs.sh agda -# Format: ||gated|quarantine| -# Ground-truthed 2026-07-17 with agda (agda --safe). -verification/proofs/agda|Properties.agda|gated| compiles under `agda --safe` diff --git a/czech-file-knife/verification/proofs/agda/Properties.agda b/czech-file-knife/verification/proofs/agda/Properties.agda deleted file mode 100644 index d78d9d0f7..000000000 --- a/czech-file-knife/verification/proofs/agda/Properties.agda +++ /dev/null @@ -1,37 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) --- --- Agda Proof Template: Inductive and coinductive properties --- Replace with your project's domain-specific proofs. --- All proofs must be total (no postulate, no {-# TERMINATING #-}). - -module Properties where - -open import Data.Nat using (ℕ; zero; suc; _+_; _≤_; z≤n; s≤s; _<_) -open import Data.Nat.Properties using (+-comm; +-assoc; ≤-refl; ≤-trans) -open import Data.List using (List; []; _∷_; length; _++_) -open import Data.List.Properties using (length-++ ) -open import Relation.Binary.PropositionalEquality using (_≡_; refl; cong; sym; trans) - --- Example: Proof that list append preserves total length --- Replace with your project's domain proofs. - -append-length : ∀ {A : Set} (xs ys : List A) → - length (xs ++ ys) ≡ length xs + length ys -append-length xs ys = length-++ xs - --- Example: Monotonicity proof template --- Use for state machines, confidence scores, trust levels -record Monotone {A : Set} (_≤A_ : A → A → Set) (f : A → A) : Set where - field - preserves : ∀ {x y} → x ≤A y → f x ≤A f y - --- Example: Idempotence proof template --- Use for normalisation, deduplication, formatting -record Idempotent {A : Set} (_≡A_ : A → A → Set) (f : A → A) : Set where - field - idem : ∀ (x : A) → f (f x) ≡A f x - --- Example: Natural number successor is monotone -suc-monotone : Monotone _≤_ suc -suc-monotone = record { preserves = s≤s } diff --git a/czech-file-knife/verification/proofs/coq/MANIFEST b/czech-file-knife/verification/proofs/coq/MANIFEST deleted file mode 100644 index aa71524e6..000000000 --- a/czech-file-knife/verification/proofs/coq/MANIFEST +++ /dev/null @@ -1,4 +0,0 @@ -# Coq proof manifest — read by scripts/check-proofs.sh coq -# Format: ||gated|quarantine| -# Ground-truthed 2026-07-17 with coqc. -verification/proofs/coq|TypeSafety.v|gated| compiles under `coqc` diff --git a/czech-file-knife/verification/proofs/coq/TypeSafety.v b/czech-file-knife/verification/proofs/coq/TypeSafety.v deleted file mode 100644 index 8f5b41894..000000000 --- a/czech-file-knife/verification/proofs/coq/TypeSafety.v +++ /dev/null @@ -1,73 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) *) -(* - Coq Proof Template: Type system soundness - Replace with your project's type system proofs. - All proofs must be complete — NO Admitted allowed. -*) - -Require Import Coq.Lists.List. -Require Import Coq.Arith.Arith. -Require Import Coq.Bool.Bool. -Import ListNotations. - -(** * Example: Simple expression language with type safety *) -(** Replace this entire section with your project's type system. *) - -(** Types *) -Inductive ty : Type := - | TyNat : ty - | TyBool : ty. - -(** Expressions *) -Inductive expr : Type := - | EConst : nat -> expr - | ETrue : expr - | EFalse : expr - | EPlus : expr -> expr -> expr - | EEq : expr -> expr -> expr. - -(** Values *) -Inductive value : Type := - | VNat : nat -> value - | VBool : bool -> value. - -(** Typing relation *) -Inductive has_type : expr -> ty -> Prop := - | T_Const : forall n, has_type (EConst n) TyNat - | T_True : has_type ETrue TyBool - | T_False : has_type EFalse TyBool - | T_Plus : forall e1 e2, - has_type e1 TyNat -> has_type e2 TyNat -> - has_type (EPlus e1 e2) TyNat - | T_Eq : forall e1 e2, - has_type e1 TyNat -> has_type e2 TyNat -> - has_type (EEq e1 e2) TyBool. - -(** Evaluation *) -Inductive eval : expr -> value -> Prop := - | E_Const : forall n, eval (EConst n) (VNat n) - | E_True : eval ETrue (VBool true) - | E_False : eval EFalse (VBool false) - | E_Plus : forall e1 e2 n1 n2, - eval e1 (VNat n1) -> eval e2 (VNat n2) -> - eval (EPlus e1 e2) (VNat (n1 + n2)) - | E_Eq : forall e1 e2 n1 n2, - eval e1 (VNat n1) -> eval e2 (VNat n2) -> - eval (EEq e1 e2) (VBool (Nat.eqb n1 n2)). - -(** Value typing *) -Definition value_has_type (v : value) (t : ty) : Prop := - match v, t with - | VNat _, TyNat => True - | VBool _, TyBool => True - | _, _ => False - end. - -(** Type soundness: well-typed expressions evaluate to well-typed values *) -Theorem type_soundness : forall e t v, - has_type e t -> eval e v -> value_has_type v t. -Proof. - intros e t v Htype Heval. - induction Htype; inversion Heval; subst; simpl; auto. -Qed. diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr b/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr deleted file mode 100644 index b94a5dbfd..000000000 --- a/czech-file-knife/verification/proofs/idris2/ABI/Compliance.idr +++ /dev/null @@ -1,41 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- ABI Proof: C ABI compliance --- Proves that struct layouts are C ABI compliant. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module ABI.Compliance - -import ABI.Layout -import ABI.Platform - -%default total - -||| Evidence that every field in a layout is correctly aligned. -public export -data AllFieldsAligned : List StructField -> Type where - AFANil : AllFieldsAligned [] - AFACons : FieldAligned f -> AllFieldsAligned fs -> AllFieldsAligned (f :: fs) - -||| Evidence that every field is within the struct bounds. -public export -data AllFieldsInBounds : (size : Nat) -> List StructField -> Type where - AFBNil : AllFieldsInBounds size [] - AFBCons : FieldInBounds size f -> AllFieldsInBounds size fs -> AllFieldsInBounds size (f :: fs) - -||| A struct layout is C ABI compliant when: -||| 1. All fields are aligned to their natural alignment -||| 2. All fields are within bounds of the struct size -||| 3. The struct size is a multiple of the struct alignment -public export -record CABICompliant (layout : StructLayout) where - constructor MkCompliant - fieldsAligned : AllFieldsAligned (layoutFields layout) - fieldsInBounds : AllFieldsInBounds (layoutSize layout) (layoutFields layout) - sizeAligned : modNatNZ (layoutSize layout) (layoutAlignment layout) SIsNonZero = 0 - -||| An empty struct is trivially compliant (size=1, alignment=1). -export -emptyStructCompliant : CABICompliant (MkLayout "empty" [] 1 1) -emptyStructCompliant = MkCompliant AFANil AFBNil Refl diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr b/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr deleted file mode 100644 index 1e550dd9e..000000000 --- a/czech-file-knife/verification/proofs/idris2/ABI/Foreign.idr +++ /dev/null @@ -1,53 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- ABI Proof: FFI function return type proofs --- Proves that all FFI functions return expected types. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module ABI.Foreign - -%default total - -||| Result type for FFI operations. -||| All FFI functions must return through this type. -public export -data FFIResult : Type -> Type where - FFISuccess : (value : a) -> FFIResult a - FFIError : (code : Int) -> (msg : String) -> FFIResult a - -||| Proof that FFIResult is a functor (map preserves structure). -export -mapFFIResult : (a -> b) -> FFIResult a -> FFIResult b -mapFFIResult f (FFISuccess value) = FFISuccess (f value) -mapFFIResult f (FFIError code msg) = FFIError code msg - -||| Proof that mapping identity preserves the result. -export -mapIdPreserves : (r : FFIResult a) -> mapFFIResult Prelude.id r = r -mapIdPreserves (FFISuccess value) = Refl -mapIdPreserves (FFIError code msg) = Refl - -||| An FFI function specification: name, argument types, return type. -public export -record FFISpec where - constructor MkFFISpec - ffiName : String - ffiReturnType : Type - -||| Proof that an FFI spec has a specific return type. -||| Use this to verify at compile time that FFI functions return the -||| types we expect across the C ABI boundary. -public export -FFIReturns : FFISpec -> Type -> Type -FFIReturns spec ty = ffiReturnType spec = ty - -||| C calling convention marker. -||| Proofs about calling convention compatibility. -public export -data CallingConv = CDecl | StdCall | FastCall - -||| All hyperpolymath FFI uses CDecl. -public export -defaultCallingConv : CallingConv -defaultCallingConv = CDecl diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr b/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr deleted file mode 100644 index 9040a5e9a..000000000 --- a/czech-file-knife/verification/proofs/idris2/ABI/Layout.idr +++ /dev/null @@ -1,63 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- ABI Proof: Memory layout correctness --- Proves struct size, alignment, and padding properties. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module ABI.Layout - -%default total - -||| Witness that a type has a known size in bytes at compile time. -public export -interface HasSize (ty : Type) where - sizeOf : Nat - -||| Witness that a type has a known alignment in bytes. -public export -interface HasAlignment (ty : Type) where - alignOf : Nat - -||| Calculate padding needed to reach the next aligned offset. -||| paddingFor offset alignment = bytes to add so (offset + padding) `mod` alignment == 0 -public export -paddingFor : (offset : Nat) -> (alignment : Nat) -> {auto 0 ok : NonZero alignment} -> Nat -paddingFor offset alignment = let r = modNatNZ offset alignment ok - in case r of - Z => Z - (S _) => minus alignment r - -||| Proof that an offset with zero remainder needs zero padding. -export -alignedNeedsPadding : (n : Nat) -> (a : Nat) -> {auto 0 ok : NonZero a} -> - modNatNZ n a ok = 0 -> paddingFor n a = 0 -alignedNeedsPadding n a prf = rewrite prf in Refl - -||| A field within a struct, carrying its offset and size. -public export -record StructField where - constructor MkField - fieldName : String - fieldOffset : Nat - fieldSize : Nat - fieldAlignment : Nat - -||| Proof that a field is correctly aligned within a struct. -public export -FieldAligned : StructField -> Type -FieldAligned f = modNatNZ (fieldOffset f) (fieldAlignment f) SIsNonZero = 0 - -||| Proof that a field does not overflow past a given struct size. -public export -FieldInBounds : (structSize : Nat) -> StructField -> Type -FieldInBounds sz f = LTE (fieldOffset f + fieldSize f) sz - -||| A struct layout is a list of fields with a total size. -public export -record StructLayout where - constructor MkLayout - layoutName : String - layoutFields : List StructField - layoutSize : Nat - layoutAlignment : Nat diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr b/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr deleted file mode 100644 index a8d6b947a..000000000 --- a/czech-file-knife/verification/proofs/idris2/ABI/Platform.idr +++ /dev/null @@ -1,63 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- ABI Proof: Platform-specific type size proofs --- Proves that C type sizes are correct per platform. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module ABI.Platform - -%default total - -||| Supported target platforms for ABI verification. -public export -data Platform = Linux64 | LinuxARM64 | MacOS64 | MacOSARM64 - | Windows64 | FreeBSD64 | WASM32 - -||| Pointer size in bytes for each platform. -public export -ptrSize : Platform -> Nat -ptrSize WASM32 = 4 -ptrSize _ = 8 - -||| C `int` size in bytes. -public export -cIntSize : Platform -> Nat -cIntSize _ = 4 - -||| C `size_t` size in bytes (matches pointer size). -public export -cSizeT : Platform -> Nat -cSizeT = ptrSize - -||| Proof that size_t always equals pointer size on all platforms. -export -sizeTEqPtrSize : (p : Platform) -> cSizeT p = ptrSize p -sizeTEqPtrSize _ = Refl - -||| Proof that pointer size is always 4 or 8 bytes. -export -ptrSizeValid : (p : Platform) -> Either (ptrSize p = 4) (ptrSize p = 8) -ptrSizeValid WASM32 = Left Refl -ptrSizeValid Linux64 = Right Refl -ptrSizeValid LinuxARM64 = Right Refl -ptrSizeValid MacOS64 = Right Refl -ptrSizeValid MacOSARM64 = Right Refl -ptrSizeValid Windows64 = Right Refl -ptrSizeValid FreeBSD64 = Right Refl - -||| Proof that C int is always 4 bytes on all platforms. -export -cIntAlways4 : (p : Platform) -> cIntSize p = 4 -cIntAlways4 _ = Refl - -||| Proof that pointer size is always at least 4 bytes. -export -ptrSizeAtLeast4 : (p : Platform) -> LTE 4 (ptrSize p) -ptrSizeAtLeast4 WASM32 = lteRefl -ptrSizeAtLeast4 Linux64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) -ptrSizeAtLeast4 LinuxARM64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) -ptrSizeAtLeast4 MacOS64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) -ptrSizeAtLeast4 MacOSARM64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) -ptrSizeAtLeast4 Windows64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) -ptrSizeAtLeast4 FreeBSD64 = lteSuccRight (lteSuccRight (lteSuccRight (lteSuccRight lteRefl))) diff --git a/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr b/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr deleted file mode 100644 index 31b6c5f29..000000000 --- a/czech-file-knife/verification/proofs/idris2/ABI/Pointers.idr +++ /dev/null @@ -1,52 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- ABI Proof: Non-null pointer safety --- Template proof — customise for your project's pointer types. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module ABI.Pointers - -import Data.So - -%default total - -||| A pointer value that has been proven non-null. -||| The `So` constraint carries a compile-time witness that `ptr /= 0`. -public export -record SafePtr where - constructor MkSafePtr - ptr : Bits64 - {auto 0 nonNull : So (ptr /= 0)} - -||| Proof that SafePtr can never hold a null (zero) value. -||| This is enforced by the `So` constraint in the record. -export -safePtrNeverNull : (sp : SafePtr) -> So (sp.ptr /= 0) -safePtrNeverNull sp = sp.nonNull - -||| Wrap a raw pointer with a runtime null check. -||| Returns Nothing if the pointer is null. -export -checkPtr : (raw : Bits64) -> Maybe SafePtr -checkPtr 0 = Nothing -checkPtr raw = case choose (raw /= 0) of - Left prf => Just (MkSafePtr raw) - Right _ => Nothing - -||| Proof that checkPtr 0 always returns Nothing. -export -checkPtrZeroIsNothing : checkPtr 0 = Nothing -checkPtrZeroIsNothing = Refl - -||| An opaque handle backed by a non-null pointer. -||| Use this for FFI resource handles (file descriptors, sockets, etc.). -public export -record Handle (tag : String) where - constructor MkHandle - safePtr : SafePtr - -||| Proof that two handles with equal pointers are equal. -export -handlePtrEq : (h1, h2 : Handle tag) -> h1.safePtr.ptr = h2.safePtr.ptr -> h1 = h2 -handlePtrEq (MkHandle (MkSafePtr p)) (MkHandle (MkSafePtr p)) Refl = Refl diff --git a/czech-file-knife/verification/proofs/idris2/MANIFEST b/czech-file-knife/verification/proofs/idris2/MANIFEST deleted file mode 100644 index ae378fec4..000000000 --- a/czech-file-knife/verification/proofs/idris2/MANIFEST +++ /dev/null @@ -1,10 +0,0 @@ -# Idris2 proof manifest — read by scripts/check-proofs.sh idris2 -# Format: ||gated|quarantine| -# gated = must compile. quarantine = known-broken, must keep failing. -# Ground-truthed 2026-07-17 with idris2 0.7.0 (developer/tools/opt/pack). -verification/proofs/idris2|ABI/Foreign.idr|gated| the one shipped Idris2 module that compiles — real content, not a stub -verification/proofs/idris2|Types.idr|quarantine| Undefined name LTE (needs Data.Nat); Idris1-era template, has never compiled -verification/proofs/idris2|ABI/Layout.idr|quarantine| Undefined name NonZero (needs Data.Nat); then a genuine unification failure (S ?x vs f .fieldAlignment) -verification/proofs/idris2|ABI/Platform.idr|quarantine| Undefined name LTE (needs Data.Nat); then Undefined name lteRefl -verification/proofs/idris2|ABI/Pointers.idr|quarantine| .nonNull declared at quantity 0 (erased) yet projected into a value position, plus a unification failure — design decision, not a typo -verification/proofs/idris2|ABI/Compliance.idr|quarantine| depends on quarantined ABI.Layout / ABI.Platform diff --git a/czech-file-knife/verification/proofs/idris2/Types.idr b/czech-file-knife/verification/proofs/idris2/Types.idr deleted file mode 100644 index f631b74ea..000000000 --- a/czech-file-knife/verification/proofs/idris2/Types.idr +++ /dev/null @@ -1,40 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- --- Typing Proof: Core data type well-formedness --- Template — replace with your project's core types. --- All proofs MUST be constructive (no believe_me, no assert_total). - -module Types - -import Data.Nat - -%default total - -||| Example: A bounded natural number (0 to max). -||| Replace with your project's core types. -public export -record Bounded (max : Nat) where - constructor MkBounded - value : Nat - {auto inBounds : LTE value max} - -||| Proof that a Bounded value is always <= max. -export -boundedLeMax : (b : Bounded max) -> LTE b.value max -boundedLeMax b = b.inBounds - -||| Proof that zero is always a valid Bounded value. -export -zeroIsBounded : {max : Nat} -> Bounded (S max) -zeroIsBounded = MkBounded 0 - -||| Example: A non-empty list with a compile-time guarantee. -public export -data NonEmpty : List a -> Type where - IsNonEmpty : NonEmpty (x :: xs) - -||| Proof that cons always produces a non-empty list. -export -consIsNonEmpty : (x : a) -> (xs : List a) -> NonEmpty (x :: xs) -consIsNonEmpty _ _ = IsNonEmpty diff --git a/czech-file-knife/verification/proofs/lean4/ApiTypes.lean b/czech-file-knife/verification/proofs/lean4/ApiTypes.lean deleted file mode 100644 index f02f259c3..000000000 --- a/czech-file-knife/verification/proofs/lean4/ApiTypes.lean +++ /dev/null @@ -1,45 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) --- --- Typing Proof: Public API type safety --- Template — replace with your project's API types. --- Proves properties about exported function signatures. - --- Example: Result type used across API boundaries -inductive ApiResult (α : Type) where - | ok : α → ApiResult α - | error : Nat → String → ApiResult α - -namespace ApiResult - -- Proof: map preserves structure (functor law: map id = id) - def map (f : α → β) : ApiResult α → ApiResult β - | .ok v => .ok (f v) - | .error c m => .error c m - - theorem map_id : ∀ (r : ApiResult α), map id r = r := by - intro r - cases r with - | ok v => simp [map] - | error c m => simp [map] - - -- Proof: map composition (functor law: map (g ∘ f) = map g ∘ map f) - theorem map_comp (f : α → β) (g : β → γ) : - ∀ (r : ApiResult α), map (g ∘ f) r = map g (map f r) := by - intro r - cases r with - | ok v => simp [map, Function.comp] - | error c m => simp [map] -end ApiResult - --- Example: Bounded confidence value (0.0 to 1.0 modelled as Nat/1000) --- Replace with your project's numeric invariants -structure BoundedNat (max : Nat) where - val : Nat - le_max : val ≤ max - -theorem bounded_nat_le {max : Nat} (b : BoundedNat max) : b.val ≤ max := - b.le_max - --- Proof: zero is always bounded -def zeroBounded {max : Nat} (h : 0 < max) : BoundedNat max := - ⟨0, Nat.zero_le max⟩ diff --git a/czech-file-knife/verification/proofs/lean4/MANIFEST b/czech-file-knife/verification/proofs/lean4/MANIFEST deleted file mode 100644 index 0f3adc273..000000000 --- a/czech-file-knife/verification/proofs/lean4/MANIFEST +++ /dev/null @@ -1,4 +0,0 @@ -# Lean4 proof manifest — read by scripts/check-proofs.sh lean4 -# Format: ||gated|quarantine| -# Ground-truthed 2026-07-17 with Lean leanprover/lean4:v4.15.0 (via elan). -verification/proofs/lean4|ApiTypes.lean|quarantine| ApiTypes.lean:44 `Nat.zero_le max` application type mismatch — `max` used where Nat is expected; has never compiled diff --git a/czech-file-knife/verification/proofs/lean4/lean-toolchain b/czech-file-knife/verification/proofs/lean4/lean-toolchain deleted file mode 100644 index d0eb99ff6..000000000 --- a/czech-file-knife/verification/proofs/lean4/lean-toolchain +++ /dev/null @@ -1 +0,0 @@ -leanprover/lean4:v4.15.0 diff --git a/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla b/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla deleted file mode 100644 index f34849477..000000000 --- a/czech-file-knife/verification/proofs/tlaplus/StateMachine.tla +++ /dev/null @@ -1,91 +0,0 @@ ---------------------------- MODULE StateMachine ---------------------------- -(* SPDX-License-Identifier: MPL-2.0 *) -(* Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) *) -(* *) -(* TLA+ Specification Template: State Machine *) -(* Replace with your project's distributed protocol or state machine. *) -(* Use TLC model checker to verify properties. *) -(* *) -(* Example: A simple request pipeline with safety properties. *) -(* Replace States, Init, Next with your project's actual states. *) -(***************************************************************************) - -EXTENDS Naturals, Sequences, FiniteSets - -CONSTANTS - MaxRequests \* Upper bound on concurrent requests (for model checking) - -VARIABLES - state, \* Current pipeline state - processed, \* Number of processed requests - queue \* Request queue - -vars == <> - -\* Pipeline states — replace with your project's states -States == {"idle", "scanning", "routing", "dispatching", "done", "failed"} - -\* Valid transitions — replace with your project's transition rules -ValidTransition(from, to) == - \/ from = "idle" /\ to = "scanning" - \/ from = "scanning" /\ to = "routing" - \/ from = "scanning" /\ to = "failed" - \/ from = "routing" /\ to = "dispatching" - \/ from = "routing" /\ to = "failed" - \/ from = "dispatching" /\ to = "done" - \/ from = "dispatching" /\ to = "failed" - \/ from = "done" /\ to = "idle" - \/ from = "failed" /\ to = "idle" - -\* Initial state -Init == - /\ state = "idle" - /\ processed = 0 - /\ queue = <<>> - -\* Transition action -Transition(newState) == - /\ ValidTransition(state, newState) - /\ state' = newState - /\ IF newState = "done" - THEN processed' = processed + 1 - ELSE processed' = processed - /\ UNCHANGED queue - -\* Enqueue a request (only when idle or scanning) -Enqueue == - /\ state \in {"idle", "scanning"} - /\ Len(queue) < MaxRequests - /\ queue' = Append(queue, "request") - /\ UNCHANGED <> - -\* Next-state relation -Next == - \/ \E s \in States : Transition(s) - \/ Enqueue - -\* Fairness: the system must eventually process -Spec == Init /\ [][Next]_vars /\ WF_vars(Next) - -\* ---- SAFETY PROPERTIES ---- - -\* State is always valid -TypeInvariant == state \in States - -\* Processed count never decreases (monotonicity) -ProcessedMonotonic == processed >= 0 - -\* Queue never exceeds max -QueueBounded == Len(queue) <= MaxRequests - -\* No impossible transitions (e.g., idle -> done) -NoSkipStates == - [][state' # state => - ValidTransition(state, state')]_state - -\* ---- LIVENESS PROPERTIES ---- - -\* Every request eventually completes or fails -EventualCompletion == <>(state = "done" \/ state = "failed") - -============================================================================ diff --git a/czech-file-knife/verification/safety_case/README.adoc b/czech-file-knife/verification/safety_case/README.adoc deleted file mode 100644 index 2d74c688e..000000000 --- a/czech-file-knife/verification/safety_case/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Safety case Unit diff --git a/czech-file-knife/verification/simulations/README.adoc b/czech-file-knife/verification/simulations/README.adoc deleted file mode 100644 index 772749d97..000000000 --- a/czech-file-knife/verification/simulations/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Simulations Unit diff --git a/czech-file-knife/verification/tests/README.adoc b/czech-file-knife/verification/tests/README.adoc deleted file mode 100644 index f49aadf3c..000000000 --- a/czech-file-knife/verification/tests/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Tests Unit diff --git a/czech-file-knife/verification/traceability/README.adoc b/czech-file-knife/verification/traceability/README.adoc deleted file mode 100644 index 05e6dcc28..000000000 --- a/czech-file-knife/verification/traceability/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Traceability Unit diff --git a/czech-file-knife/www/.well-known/ai.txt b/czech-file-knife/www/.well-known/ai.txt deleted file mode 100644 index 96e122700..000000000 --- a/czech-file-knife/www/.well-known/ai.txt +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# ai.txt - AI interaction policy -# See: https://site.spawning.ai/spawning-ai-txt - -User-Agent: * -Disallow-Training: yes -Disallow-Summarization: no -Disallow-Generation: yes - -# This project's code is licensed under MPL-2.0. -# AI agents may read and analyze this code for assisting contributors. -# AI agents must NOT use this code for model training without explicit consent. -# -# For AI agent integration instructions, see: -# _chora.deed — the repo deed: universal AI entry point (allocation + ply tree) -# AI.a2ml (Claude-specific instructions) -# .machine_readable/ (structured project state) diff --git a/czech-file-knife/www/.well-known/aibdp.json b/czech-file-knife/www/.well-known/aibdp.json deleted file mode 100644 index 2fe3b2444..000000000 --- a/czech-file-knife/www/.well-known/aibdp.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "aibdp_version": "0.2", - "status": "experimental \u2014 declaration-only; observing, never enforcing (see www/policies/ai-use.adoc)", - "canonical_uri": "https://github.com/hyperpolymath/czech-file-knife/blob/main/www/.well-known/aibdp.json", - "contact": "mailto:j.d.a.jewell@open.ac.uk", - "policy_uri": "https://github.com/hyperpolymath/czech-file-knife/blob/main/www/policies/ai-use.adoc", - "policies": { - "indexing": { - "status": "allowed", - "scope": "all", - "rationale": "The site is published to be found; indexing public material crosses no boundary." - }, - "summarization": { - "status": "allowed", - "scope": "all", - "rationale": "Mirrors ai.txt Disallow-Summarization: no \u2014 describing published material serves readers." - }, - "question_answering": { - "status": "allowed", - "scope": "all", - "rationale": "Mirrors ai.txt: agents may read and analyse to assist contributors." - }, - "embedding": { - "status": "conditional", - "scope": "all", - "conditions": [ - "Embeddings may serve retrieval over the public material with attribution preserved.", - "Embedding corpora must not be redistributed or used as training input without explicit consent." - ], - "rationale": "Retrieval aids discovery; corpus redistribution carries the licences (MPL-2.0 code, CC-BY-SA-4.0 docs)." - }, - "training": { - "status": "disallowed", - "scope": "all", - "rationale": "Mirrors ai.txt Disallow-Training: yes \u2014 model training on this corpus requires explicit consent, which this declaration does not give." - }, - "fine_tuning": { - "status": "disallowed", - "scope": "all", - "rationale": "Fine-tuning is training with a narrower objective; the ai.txt stance applies unchanged." - }, - "commercial_training": { - "status": "disallowed", - "scope": "all", - "rationale": "As training, and additionally: contact the maintainer before any commercial incorporation \u2014 consent is explicit or it is absent.", - "alternatives": "Cite the repository and its documentation instead of training on it." - }, - "generation": { - "status": "disallowed", - "scope": "all", - "rationale": "Mirrors ai.txt Disallow-Generation: yes \u2014 generated text must not be presented as this project's own statements." - } - }, - "enforcement": { - "mechanism": "none", - "note": "Declaration-only. HTTP 430 (draft-jewell-http-430-consent-required-00, EXPERIMENTAL) is the mechanism the draft defines for origins that can emit it; this template deliberately does not: minting or installing never enables 430. Enforcement would require a separate, explicit, versioned, provenance-bearing and independently testable profile choice, which this bundle does not ship.", - "contact_before_litigation": true, - "preferred_resolution": "Correspondence and correction via the contact above." - }, - "metadata": { - "created": "2026-09-28", - "last_modified": "2026-09-28", - "author": "Jonathan D.A. Jewell", - "project": "Czech File Knife", - "repository": "https://github.com/hyperpolymath/czech-file-knife", - "related_standards": [ - "draft-jewell-aibdp-00 (experimental, not a Datatracker submission)", - "draft-jewell-http-430-consent-required-00 (experimental, not a Datatracker submission)", - "AIPREF (mapped alternative representation where they overlap)", - "RFC 8615", - "RFC 9110", - "RFC 9309" - ] - }, - "philosophy": { - "statement": "Without refusal, permission is meaningless.", - "note": "Purpose headers are unverified assertions; User-Agent inference is heuristic; unknown purpose remains unknown." - } -} \ No newline at end of file diff --git a/czech-file-knife/www/.well-known/aibdp.json.license b/czech-file-knife/www/.well-known/aibdp.json.license deleted file mode 100644 index 75837903d..000000000 --- a/czech-file-knife/www/.well-known/aibdp.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -SPDX-License-Identifier: CC-BY-SA-4.0 diff --git a/czech-file-knife/www/.well-known/humans.txt b/czech-file-knife/www/.well-known/humans.txt deleted file mode 100644 index 9eaa949d4..000000000 --- a/czech-file-knife/www/.well-known/humans.txt +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# humanstxt.org - -/* TEAM */ -Maintainer: Jonathan D.A. Jewell (hyperpolymath) -Contact: j.d.a.jewell@open.ac.uk -From: United Kingdom - -/* SITE */ -Last update: 2026-09-28 -Standards: RSR (Rhodium Standard Repository) -License: MPL-2.0 (code) / CC-BY-SA-4.0 (docs) -Components: Idris2 ABI, Zig FFI -Tools: just, Podman, Guix diff --git a/czech-file-knife/www/.well-known/security.txt b/czech-file-knife/www/.well-known/security.txt deleted file mode 100644 index 15b9a4485..000000000 --- a/czech-file-knife/www/.well-known/security.txt +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# RFC 9116 - security.txt -# https://securitytxt.org/ - -# No Encryption: field. RFC 9116 §2.5.4 requires it to point at an actual key; -# the estate signs with SSH (gpg.format=ssh) and publishes no PGP key, so the -# field had nothing to reference and shipped the literal token instead — which -# also made this file unparseable. Report via the Policy: URL below. - -Contact: mailto:j.d.a.jewell@open.ac.uk -Expires: 2026-12-31T23:59:59.000Z -Preferred-Languages: en -Canonical: https://github.com/hyperpolymath/czech-file-knife/.well-known/security.txt -Policy: https://github.com/hyperpolymath/czech-file-knife/blob/main/.github/SECURITY.md diff --git a/czech-file-knife/www/README.adoc b/czech-file-knife/www/README.adoc deleted file mode 100644 index a431f894d..000000000 --- a/czech-file-knife/www/README.adoc +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `www/` — Site-Operations Bundle -:toc: left -:icons: font - -Canonical source tree for everything a project publishes or operates about a -public web presence: protocol metadata, site policy, security headers, -web-server integration, authoritative DNS, encrypted-DNS/privacy discovery, -TLS policy, validation and runbooks. - -This directory is a *source bundle*, **not** a document root. Never point a -web server at `www/` wholesale — see <<_publication_boundary>>. - -== Layout - -[cols="1,3", options="header"] -|=== -| Path | Role - -| `.well-known/` -| Explicitly public protocol metadata (RFC 8615): `security.txt` (RFC 9116), - `humans.txt`, `ai.txt`, and the experimental `aibdp.json` declaration. - Canonical location — the former repository-root `.well-known/` was migrated - here (see `scripts/migrate-wellknown-to-www.sh`). - -| `public/` -| Ordinary publishable HTML/assets. The site's document root content. - -| `policies/` -| Human-readable policy *sources* (AsciiDoc): privacy, AI use, acceptable - use. Rendered into `public/policies/` at deploy time — see - `runbooks/deploy.adoc`. - -| `errors/` -| Static 4xx/5xx bodies. No server identification, no stack details. - -| `security_headers/` -| CSP/HSTS/etc. source templates. Included by server configuration; - never served as content. - -| `webservers/` -| Caddy, nginx and Apache integration examples implementing the - publication boundary. - -| `dns/bind9/` -| Authoritative-only BIND 9 starting hand. No recursion, no automatically - started daemon, reserved example zones only. - -| `dns/records/` -| Zone and resource-record templates (example.invalid, RFC 3849/RFC 5737 - documentation ranges), DNSSEC and CAA guidance. - -| `dns/privacy/` -| DoT/DoQ/DoH/ODoH/ECH discovery guidance with the roles correctly - distinguished and capability detection documented. - -| `tls/` -| Certificate and rotation policy. **Never** private keys, issued - certificate secrets, TSIG secrets, journals, caches, PID files, logs or - runtime databases — in this tree or in git. - -| `profiles/` -| Explicit opt-in composition (baseline-site, consent-aware-web, - authoritative-dns, privacy-enhanced, full-expert). Nothing is enabled - by minting; no profile hides an enablement. - -| `schemas/` -| Validators and machine-readable declarations for public/generated - artefacts, including the publishable-paths boundary declaration and the - vendored AIBDP 0.2 schema. - -| `tests/` -| Local probes with planted controls: publication boundary, BIND safety, - profile enablement, AIBDP shape, well-known content, root migration. - Run them all: `bash www/tests/run-all.sh`. - -| `runbooks/` -| Deploy, rollback, certificate rotation, DNS change and incident - procedures. -|=== - -[#_publication_boundary] -== Publication boundary (security-critical) - -A supported deployment: - -. serves `www/public/` as ordinary content; -. explicitly exposes `www/.well-known/` at the URL path `/.well-known/`; -. keeps `dns/`, `tls/`, `security_headers/`, `webservers/`, `profiles/`, - `schemas/`, `tests/` and `runbooks/` — and the `policies/` *sources* — - outside the public document surface; -. fails validation if a generated deployment would publish operational - configuration (`tests/check-publication-boundary.sh`, enforced in CI and - wired into the mint smoke test). - -The canonical machine-readable statement of the boundary is -`schemas/publishable-paths.txt`. - -== Experimental material - -`aibdp.json` implements AIBDP 0.2 (`draft-jewell-aibdp-00`, experimental, -not a Datatracker submission; a mapped alternative representation of AIPREF -where they overlap). The `consent-aware-web` profile is -*declaration/observe-only by default*: minting or installing this template -never emits HTTP 430. Enforcement would require a separate, explicit, -versioned, provenance-bearing and independently testable profile choice — -none exists in this bundle. - -Purpose headers are unverified assertions; User-Agent inference is -heuristic; unknown purpose remains unknown. - -== Provenance - -Bundle shape and constraints: `czech-file-knife` issue #53. AIBDP schema -vendored from `metadatastician/consent-aware-web` (MPL-2.0) — see -`schemas/aibdp-schema-v0.2.json.license`. diff --git a/czech-file-knife/www/dns/bind9/README.adoc b/czech-file-knife/www/dns/bind9/README.adoc deleted file mode 100644 index 165ac8d27..000000000 --- a/czech-file-knife/www/dns/bind9/README.adoc +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `dns/bind9/` — Authoritative-Only Starting Hand - -A safe starting hand for the *authoritative* DNS role only. The posture, in -one line: **answers for its zones, does nothing else.** - -== What the example guarantees - -* `recursion no` + `allow-recursion { none; }` — not a resolver, so not an - open resolver: no amplification via queries, no cache-poisoning surface - for clients. -* `allow-query { none; }` globally; each zone opens itself explicitly — - deny by default. -* `allow-transfer { none; }` — no zone walks; name secondaries explicitly - per zone when you have them. -* `listen-on` bound to explicit addresses — never `any`. -* `version "none"` — no banner disclosure. -* `rate-limit` — response rate limiting against floods. -* Reserved example zones only (`.invalid`, documentation IP ranges). -* No keys, no TSIG secrets, no journals, no caches, no PID files — runtime - state lives outside the repository, always. - -== Bring-up is MANUAL, by an operator - -The bundle never starts a daemon, and minting a repository never starts -one. To exercise this configuration deliberately: - -[source,bash] ----- -# syntax + zone validation first — both must pass: -named-checkconf dns/bind9/named.conf.example -named-checkzone example.invalid dns/records/example.invalid.zone - -# foreground bring-up on a test host, with paths adjusted to that host: -sudo named -c /etc/bind/named.conf -g -u bind ----- - -Productionisation (systemd unit, working-directory ownership, AppArmor -profile, log rotation) is an ops decision recorded in your deployment docs — -not something a repository template should silently provide. - -== Roles are separated on purpose - -Authoritative, recursive/stub, ODoH-relay and ODoH-target are different -jobs with different threat models; mixing them in one daemon is how open -resolvers happen. See `../privacy/ENCRYPTED-DNS.adoc` for the role map and -why this bundle ships exactly one of them. - -== Safety check - -`www/tests/check-bind-safety.sh` asserts the posture above on every -`named.conf*` in this directory, rejects key/secret/journal material -anywhere under `www/dns/`, runs `named-checkconf`/`named-checkzone` when -the binaries are available, and proves itself against the planted -open-recursion control (`www/tests/controls/named.conf.open-recursion.control`). diff --git a/czech-file-knife/www/dns/bind9/named.conf.example b/czech-file-knife/www/dns/bind9/named.conf.example deleted file mode 100644 index 880cedbdc..000000000 --- a/czech-file-knife/www/dns/bind9/named.conf.example +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// -// named.conf.example — authoritative-only BIND 9 starting hand (issue #53, -// profile: authoritative-dns). Reserved names only: RFC 2606 .invalid, -// RFC 5737 192.0.2.0/24, RFC 3849 2001:db8::/32. -// -// HARD RULES for this bundle: -// * never started automatically — see README.adoc for manual bring-up; -// * no keys, no TSIG secrets, no live zones, no journals/caches/PIDs in git; -// * no recursion, no open resolver: this server answers for its zones only; -// * recursive/stub, ODoH-relay and ODoH-target roles are SEPARATE profiles -// and are not shipped here (see ../privacy/ENCRYPTED-DNS.adoc). - -options { - // Working and runtime paths live OUTSIDE the repository. - directory "/var/cache/bind"; - pid-file "/run/named/named.pid"; - - // ── Authoritative-only posture ──────────────────────────────────────── - recursion no; // do not resolve for clients, ever - allow-recursion { none; }; // belt and braces with recursion no - allow-query { none; }; // global default: deny; zones open themselves - allow-transfer { none; }; // no zone transfers unless a zone names secondaries - - // ── Minimal exposure ────────────────────────────────────────────────── - // Bind the explicit service addresses; do NOT use 'any'. - listen-on port 53 { 192.0.2.1; }; - listen-on-v6 port 53 { 2001:db8::1; }; - - // Authoritative servers answer from zone data; they do not validate as - // resolvers. Version disclosure off. - dnssec-validation no; - version "none"; - - // Response rate limiting: blunts amplification and flood abuse. - rate-limit { - responses-per-second 10; - errors-per-second 5; - }; -}; - -// ── DNSSEC (guidance; enable deliberately) ───────────────────────────────── -// Sign with an inline dnssec-policy. Key material is generated by named into -// the working directory (or an HSM/KMS in production) and NEVER committed: -// no KSK/ZSK, no .private/.key files, no signing journals in the repository. -// -// dnssec-policy "rsr-standard" { -// keys { -// ksk lifetime P1Y algorithm ecdsa-p256-sha256; -// zsk lifetime P30D algorithm ecdsa-p256-sha256; -// }; -// publish-safety P1H; -// retire-safety P1H; -// signatures-refresh P7D; -// }; -// -// Then add to the zone: dnssec-policy "rsr-standard"; - -zone "example.invalid" { - type primary; - file "zones/example.invalid.zone"; // deploy copies dns/records/*.zone here - allow-query { any; }; // public authoritative answers for THIS zone - allow-transfer { none; }; // add explicit secondary IPs when you have them -}; - -// Reverse zone for the documentation range (template symmetry; adjust or drop). -zone "2.0.192.in-addr.arpa" { - type primary; - file "zones/2.0.192.in-addr.arpa.zone"; - allow-query { any; }; - allow-transfer { none; }; -}; diff --git a/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc b/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc deleted file mode 100644 index 2d9dd9e64..000000000 --- a/czech-file-knife/www/dns/privacy/ENCRYPTED-DNS.adoc +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Encrypted DNS and Privacy Discovery — Guidance, Not Promise - -This bundle ships the *authoritative* role only. This document exists so -nobody confuses the roles, overclaims obliviousness, or promises universal -support for protocols that require the other side to cooperate. - -== The role map - -[cols="1,2,2", options="header"] -|=== -| Role | Job | In this bundle? - -| Authoritative server -| Answers for zones it is primary/secondary for (BIND example in - `../bind9/`). -| *Yes* — the only role shipped. - -| Recursive / stub resolver -| Resolves on behalf of clients; the role that DoT/DoQ/DoH *clients* talk - to. -| No — separate profile, separate threat model. - -| DoH/DoT/DoQ server -| Serves recursive answers over an encrypted transport. -| No. - -| ODoH relay -| Forwards oblivious HTTP requests; knows client, not query. -| No. - -| ODoH target -| Answers oblivious HTTP requests; knows query, not client. -| No. -|=== - -== Protocol facts (so the docs can't drift into marketing) - -[cols="1,1,2", options="header"] -|=== -| Protocol | Transport | Notes - -| DoT (RFC 7858) -| TLS over TCP/853 -| Direct: the resolver sees the client. Not oblivious. - -| DoQ (RFC 9250) -| QUIC over UDP/853 -| Direct: same visibility as DoT. Not oblivious. - -| DoH (RFC 8484) -| HTTP(S) over TCP/443 -| Direct: resolver sees client; indistinguishable from ordinary HTTPS to - observers. Not oblivious. - -| ODoH (RFC 9230) -| Oblivious HTTP (RFC 9420): relay + target -| Relay sees client, target sees query; neither sees both. This is the - *only* oblivious mode in this list. - -| ECH (RFC 9460 discovery via HTTPS RR) -| TLS extension -| Hides the SNI/origin name from the network. Requires client + server + - HTTPS RR support; document *capability detection*, never assume it. -|=== - -*There is no "ODoT" and no "ODoQ".* Obliviousness comes from OHTTP wrapping -(ODoH); direct DoT and DoQ modes remain **non-oblivious** and must be -described that way wherever this estate documents them. Where HTTP/3 is -appropriate, ODoH over OHTTP/HTTP3 is the standard path — do not invent -oblivious variants of other transports. - -== Capability detection (document, don't promise) - -Before claiming any encrypted-DNS feature for a host, probe it: - -[source,bash] ----- -# ECH + DoH advertisement via the HTTPS RR (RFC 9460): -dig +short TYPE65 example.invalid # or: dig +https -# DoH reachability of a resolver (RFC 8484 well-known path): -curl -sS -H 'accept: application/dns-json' \ - 'https://resolver.example.invalid/dns-query?name=example.invalid' -# DoT reachability (TCP/853 handshake): -kdig +tls-ca -t example.invalid @resolver.example.invalid # or openssl s_client -connect host:853 ----- - -A capability that is not detected is not supported; publish what the probes -returned, dated, rather than the protocol list you wish were true. - -== What minting does - -Nothing. No daemon starts, no resolver is configured, no declaration about -encrypted DNS is published. The `privacy-enhanced` profile -(`../../profiles/`) selects *this guidance* plus the TLS policy and the ECH -readiness checklist — and even it enables no service by itself. diff --git a/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone b/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone deleted file mode 100644 index a8f24bf16..000000000 --- a/czech-file-knife/www/dns/records/2.0.192.in-addr.arpa.zone +++ /dev/null @@ -1,14 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; -; Reverse zone for 192.0.2.0/24 (RFC 5737 documentation range) — template -; symmetry with the forward zone. Bump the serial on every edit. -$TTL 3600 -$ORIGIN 2.0.192.in-addr.arpa. -@ IN SOA ns1.example.invalid. hostmaster.example.invalid. ( - 2026091701 3600 900 604800 300 ) - IN NS ns1.example.invalid. - IN NS ns2.example.invalid. - -1 IN PTR ns1.example.invalid. -2 IN PTR ns2.example.invalid. -10 IN PTR example.invalid. diff --git a/czech-file-knife/www/dns/records/README.adoc b/czech-file-knife/www/dns/records/README.adoc deleted file mode 100644 index 5b6fe1f14..000000000 --- a/czech-file-knife/www/dns/records/README.adoc +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `dns/records/` — Zone and Resource-Record Templates - -Templates for the authoritative-only profile. Everything here uses reserved -names and documentation address ranges: `.invalid` (RFC 2606), -`192.0.2.0/24` (RFC 5737), `2001:db8::/32` (RFC 3849). Nothing in this -directory may contain a live zone, a live journal, or key material. - -== Files - -[cols="1,2", options="header"] -|=== -| File | Purpose - -| `example.invalid.zone` -| Forward zone: SOA/NS glue, A/AAAA, CNAME, CAA (RFC 8659), SPF `-all`, - MTA-STS/TLSRPT declarations for a mail-less domain. - -| `2.0.192.in-addr.arpa.zone` -| Matching reverse zone for the documentation range. -|=== - -== Discipline - -Serials:: `YYYYMMDDnn`, bumped on *every* edit — secondaries and signers key -off it (`runbooks/dns-change.adoc`). -CAA:: Set `issue`/`issuewild` to your actual CA(s); `0 issue ";"` forbids all -issuance. Keep `iodef` pointed at the security contact. -DNSSEC:: Sign via the inline `dnssec-policy` sketched in -`../bind9/named.conf.example`. Keys are generated by named into its working -directory (or an HSM/KMS in production): *never* commit KSK/ZSK material, -`.private`/`.key` files, or `.jnl` journals. The safety check -(`www/tests/check-bind-safety.sh`) rejects them. -Validation:: `named-checkzone example.invalid example.invalid.zone` must -pass before any deployment; the bundle's safety check runs it when the -binary is available. -== What is NOT here - -Recursive/stub resolver configuration, ODoH relay/target configuration, and -DoT/DoQ/DoH *server* configuration are separate roles with separate threat -models — see `../privacy/ENCRYPTED-DNS.adoc`. This bundle ships the -authoritative role only, and minting never starts a daemon. diff --git a/czech-file-knife/www/dns/records/example.invalid.zone b/czech-file-knife/www/dns/records/example.invalid.zone deleted file mode 100644 index 023d1ac4a..000000000 --- a/czech-file-knife/www/dns/records/example.invalid.zone +++ /dev/null @@ -1,38 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; -; example.invalid.zone — forward-zone template (RFC 2606 reserved name). -; Addresses are RFC 5737 / RFC 3849 documentation ranges. Substitute your -; real apex, hosts and CA before deployment; bump the serial on EVERY edit -; (runbooks/dns-change.adoc). -$TTL 3600 -$ORIGIN example.invalid. -@ IN SOA ns1.example.invalid. hostmaster.example.invalid. ( - 2026091701 ; serial YYYYMMDDnn — bump per change - 3600 ; refresh - 900 ; retry - 604800 ; expire - 300 ) ; minimum (negative-cache TTL) - - IN NS ns1.example.invalid. - IN NS ns2.example.invalid. - -ns1 IN A 192.0.2.1 -ns1 IN AAAA 2001:db8::1 -ns2 IN A 192.0.2.2 -ns2 IN AAAA 2001:db8::2 - -@ IN A 192.0.2.10 -@ IN AAAA 2001:db8::10 -www IN CNAME example.invalid. - -; CAA (RFC 8659): which CAs may issue for this name. Placeholder CA — set to -; your actual issuer(s), or 'issue ";"' to forbid issuance entirely. -@ IN CAA 0 issue "ca.example.invalid" -@ IN CAA 0 issuewild "ca.example.invalid" -@ IN CAA 0 iodef "mailto:j.d.a.jewell@open.ac.uk" - -; This zone sends no mail: hard-fail SPF, and declare MTA-STS/TLSRPT so -; senders to a spoofed address get a correct rejection signal (RFC 8461/8460). -@ IN TXT "v=spf1 -all" -_mta-sts IN TXT "v=STSv1; id=20260917000000Z;" -_smtp._tls IN TXT "v=TLSRPTv1; rua=mailto:j.d.a.jewell@open.ac.uk" diff --git a/czech-file-knife/www/errors/403.html b/czech-file-knife/www/errors/403.html deleted file mode 100644 index a8f4b0b52..000000000 --- a/czech-file-knife/www/errors/403.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 403 Forbidden - - -

403 — Forbidden

-

The server understood the request and refuses to authorise it. No further detail is disclosed by design.

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/errors/404.html b/czech-file-knife/www/errors/404.html deleted file mode 100644 index d28eef1b9..000000000 --- a/czech-file-knife/www/errors/404.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 404 Not Found - - -

404 — Not Found

-

Nothing is published at this path. If you followed a link from this site, the page may have moved.

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/errors/429.html b/czech-file-knife/www/errors/429.html deleted file mode 100644 index 8f3025b42..000000000 --- a/czech-file-knife/www/errors/429.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 429 Too Many Requests - - -

429 — Too Many Requests

-

Rate limiting is in effect. Back off and retry later; automated clients should honour Retry-After when present.

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/errors/500.html b/czech-file-knife/www/errors/500.html deleted file mode 100644 index 9633e0e66..000000000 --- a/czech-file-knife/www/errors/500.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 500 Internal Server Error - - -

500 — Internal Server Error

-

The request could not be completed. Operators are paged via the runbook; no internals are disclosed here.

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/errors/502.html b/czech-file-knife/www/errors/502.html deleted file mode 100644 index a040d5cd3..000000000 --- a/czech-file-knife/www/errors/502.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 502 Bad Gateway - - -

502 — Bad Gateway

-

An upstream component returned an invalid response. Retry shortly; persistent failures are an incident (see runbooks/incident.adoc in the source bundle).

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/errors/503.html b/czech-file-knife/www/errors/503.html deleted file mode 100644 index 7d008cbd8..000000000 --- a/czech-file-knife/www/errors/503.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - 503 Service Unavailable - - -

503 — Service Unavailable

-

The service is temporarily unavailable, usually for planned maintenance or overload protection. Retry later.

-

Security reports for this site: /.well-known/security.txt.

- - diff --git a/czech-file-knife/www/policies/acceptable-use.adoc b/czech-file-knife/www/policies/acceptable-use.adoc deleted file mode 100644 index 224bf7eba..000000000 --- a/czech-file-knife/www/policies/acceptable-use.adoc +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Acceptable Use — Czech File Knife - -Rendered into `public/policies/acceptable-use.html` at deploy time; this -AsciiDoc file is the source of record. - -== Using this site - -This service is provided for reading published project material. When using -it, do not: - -* attempt to access non-public or operational material (DNS configuration, - TLS material, headers sources, profiles, schemas, tests or runbooks are - deliberately outside the published surface and attempts to reach them are - logged); -* probe, scan or fuzz the service beyond ordinary browsing without prior - written permission (coordinated disclosure is welcome via - link:/.well-known/security.txt[security.txt]); -* exceed rate limits or interfere with the availability of the service for - others; -* republish substantial portions of the site in violation of the project - licences (MPL-2.0 code, CC-BY-SA-4.0 documentation). - -== Enforcement - -Abuse is handled proportionally: rate limiting first, then temporary or -permanent blocking at the edge. Decisions are recorded in the operations -log and can be appealed to the maintainer contact in -link:/.well-known/humans.txt[humans.txt]. diff --git a/czech-file-knife/www/policies/ai-use.adoc b/czech-file-knife/www/policies/ai-use.adoc deleted file mode 100644 index e31f3117b..000000000 --- a/czech-file-knife/www/policies/ai-use.adoc +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= AI Use Policy — Czech File Knife - -Rendered into `public/policies/ai-use.html` at deploy time; this AsciiDoc -file is the source of record. - -== Machine-readable declarations - -This site publishes two machine-readable declarations. They are -*declarations, not enforcement*: neither, by itself, causes this origin to -refuse any request. - -link:/.well-known/ai.txt[ai.txt]:: Plain-text AI interaction policy -(User-Agent scoped training / summarisation / generation stances). -link:/.well-known/aibdp.json[aibdp.json]:: **Experimental.** An AIBDP 0.2 -declaration (`draft-jewell-aibdp-00`, not a Datatracker submission; a -mapped alternative representation of AIPREF where they overlap). It is -published *observe/declaration-only*. HTTP 430 (Consent Required, -`draft-jewell-http-430-consent-required-00`) is **not** emitted by this -site or by any repository minted from this template; enforcement would -require a separate, explicit, versioned and independently testable profile -choice that this bundle does not contain. - -== Interpretation limits - -Purpose headers carried by AI clients are *unverified assertions*. Inference -from User-Agent strings is *heuristic*. Where a purpose is unknown, it -remains unknown — it is not treated as consent. - -== Human terms - -The prose stance for this project's corpus is the one recorded in `ai.txt` -and the repository licences (MPL-2.0 code, CC-BY-SA-4.0 documentation): -reading and analysis to assist contributors is permitted; model training on -this corpus without explicit consent is not. diff --git a/czech-file-knife/www/policies/privacy.adoc b/czech-file-knife/www/policies/privacy.adoc deleted file mode 100644 index 0a677d463..000000000 --- a/czech-file-knife/www/policies/privacy.adoc +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Privacy Policy — Czech File Knife - -Rendered into `public/policies/privacy.html` at deploy time (see -`www/runbooks/deploy.adoc`); this AsciiDoc file is the source of record. - -== Data this site processes - -This is a static site served from published content. It does not require -accounts and does not run server-side application logic beyond ordinary -request handling. - -Access logs:: Requests are logged by the web server (IP address, timestamp, -path, user agent) for security and operations. Logs are retained no longer -than 90 days and are never sold or shared beyond legal obligation. -Cookies:: None are set by the site itself. No advertising, tracking or -third-party analytics are embedded. -Contact data:: Anything you send to the addresses in -link:/.well-known/security.txt[security.txt] or the maintainer contact in -link:/.well-known/humans.txt[humans.txt] is used solely to respond to you. - -== Your rights - -Under UK GDPR / EU GDPR you may request access to, correction of, or erasure -of personal data this site holds about you. Contact the maintainer via the -addresses above; there is no separate data-protection officer for a static -project site. - -== Changes - -Material changes to this policy are recorded in the repository's -`CHANGELOG.adoc` with a dated entry. diff --git a/czech-file-knife/www/profiles/README.adoc b/czech-file-knife/www/profiles/README.adoc deleted file mode 100644 index a8b97aa3a..000000000 --- a/czech-file-knife/www/profiles/README.adoc +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `profiles/` — Explicit Opt-In Composition - -Profiles are how a repository selects parts of this bundle. Selection is -always explicit; **minting a repository enables nothing** — no daemon -starts, no experimental declaration is published, no HTTP 430 enforcement -exists to enable. - -[cols="1,1,2", options="header"] -|=== -| Profile | Status | Adds - -| `baseline-site` -| stable -| Headers, `.well-known` trio, safe errors, publishable content, boundary - validation, deploy/rollback runbooks. - -| `consent-aware-web` -| experimental -| AIBDP 0.2 declaration + AI-use policy page. *Declaration/observe-only:* - `enforcement_http_430 = false` is a hard default; enforcement would - require a separate explicit versioned profile that this bundle does not - ship. - -| `authoritative-dns` -| stable -| BIND 9 authoritative-only hand, zone/RR templates, DNSSEC/CAA guidance, - safety checks, DNS change runbook. `auto_start_daemon = false`. - -| `privacy-enhanced` -| experimental -| Encrypted-DNS/ECH guidance under a *capability-detection-required* rule, - TLS policy, certificate rotation runbook. Claims no ODoT/ODoQ (they do - not exist); ODoH is the only oblivious mode. - -| `full-expert` -| stable-composition -| `includes` the four above — explicitly listed, independently removable. - Composition never escalates flags: 430 stays false, daemons stay off. -|=== - -== Format - -Deliberately a flat, greppable TOML subset (single-line arrays, scalar -flags) so `www/tests/check-profiles.sh` — and the estate propagation -mechanism — can validate profiles without a TOML runtime. Keys the checks -enforce: - -* every profile declares `profile`, `version`, `status`; -* `consent-aware-web` and `full-expert` must carry - `enforcement_http_430 = false`; -* `authoritative-dns` and `full-expert` must carry - `auto_start_daemon = false`; -* `full-expert.includes` must name exactly the other four profiles; -* every path in `components` must exist in the bundle; -* `requires`/`includes` must resolve to sibling profile files. - -The planted controls (`www/tests/controls/profile-enforce-430.control.toml`, -`profile-hidden-start.control.toml`) violate the flag rules and must be -rejected — proof the defaults cannot silently flip. - -== Applying a profile - -Selection means: keep the listed `components` active in your deployment -pipeline (stage them, run their checks, follow their runbooks) and delete or -ignore the rest. Repositories without a website may retain the whole bundle -as inactive template material or decline the capability outright; nothing -here forces a deployment (issue #53, migration rule 5). diff --git a/czech-file-knife/www/profiles/authoritative-dns.toml b/czech-file-knife/www/profiles/authoritative-dns.toml deleted file mode 100644 index 63e1d08fa..000000000 --- a/czech-file-knife/www/profiles/authoritative-dns.toml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# authoritative-dns — issue #53 initial profile. -# BIND 9 authoritative configuration, zone/record templates, DNSSEC/CAA -# guidance and the safety checks. NO open recursion and NO automatically -# started daemon: bring-up is a manual operator act (dns/bind9/README.adoc). -# Recursive/stub, ODoH-relay and ODoH-target roles are separate profiles -# and are not shipped here. -profile = "authoritative-dns" -version = "0.1.0" -status = "stable" -requires = [] -auto_start_daemon = false -recursion = "disabled" -components = ["www/dns/bind9/", "www/dns/records/", "www/tests/check-bind-safety.sh", "www/tests/controls/named.conf.open-recursion.control", "www/runbooks/dns-change.adoc"] diff --git a/czech-file-knife/www/profiles/baseline-site.toml b/czech-file-knife/www/profiles/baseline-site.toml deleted file mode 100644 index 9024a1e4d..000000000 --- a/czech-file-knife/www/profiles/baseline-site.toml +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# baseline-site — issue #53 initial profile. -# Security headers, the .well-known trio, safe error bodies, publishable -# content and the boundary validation. No service is started by selecting -# this profile; it composes SOURCE material plus the deploy runbook. -profile = "baseline-site" -version = "0.1.0" -status = "stable" -requires = [] -components = ["www/security_headers/csp.conf", "www/.well-known/security.txt", "www/.well-known/humans.txt", "www/.well-known/ai.txt", "www/public/", "www/errors/", "www/policies/privacy.adoc", "www/policies/acceptable-use.adoc", "www/schemas/publishable-paths.txt", "www/webservers/", "www/tests/", "www/runbooks/deploy.adoc", "www/runbooks/rollback.adoc"] diff --git a/czech-file-knife/www/profiles/consent-aware-web.toml b/czech-file-knife/www/profiles/consent-aware-web.toml deleted file mode 100644 index b4a6743fc..000000000 --- a/czech-file-knife/www/profiles/consent-aware-web.toml +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# consent-aware-web — issue #53 initial profile. EXPERIMENTAL material: -# AIBDP 0.2 implements draft-jewell-aibdp-00 (not a Datatracker submission; -# a mapped alternative representation of AIPREF where they overlap). -# -# Declaration/observe-only BY DEFAULT. Selecting this profile publishes the -# declaration and the human policy page; it enforces nothing. HTTP 430 -# (draft-jewell-http-430-consent-required-00) is NEVER emitted by minting, -# installing or selecting this profile — enforcement would require a -# separate, explicit, versioned, provenance-bearing and independently -# testable profile, which this bundle deliberately does not ship. -profile = "consent-aware-web" -version = "0.1.0" -status = "experimental" -requires = ["baseline-site"] -mode = "declaration-only" -enforcement_http_430 = false -components = ["www/.well-known/aibdp.json", "www/schemas/aibdp-schema-v0.2.json", "www/policies/ai-use.adoc"] diff --git a/czech-file-knife/www/profiles/full-expert.toml b/czech-file-knife/www/profiles/full-expert.toml deleted file mode 100644 index d752e08f1..000000000 --- a/czech-file-knife/www/profiles/full-expert.toml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# full-expert — issue #53 initial profile. Composes the other four through -# EXPLICIT selection only: no hidden enablement. Composition adds -# components; it NEVER escalates flags — enforcement_http_430 stays false -# and auto_start_daemon stays false here exactly as in the profiles being -# composed. Disabling any included profile is a first-class operation -# (delete it from `includes`); nothing else changes. -profile = "full-expert" -version = "0.1.0" -status = "stable-composition" -includes = ["baseline-site", "consent-aware-web", "authoritative-dns", "privacy-enhanced"] -hidden_enablement = "forbidden" -enforcement_http_430 = false -auto_start_daemon = false -components = [] diff --git a/czech-file-knife/www/profiles/privacy-enhanced.toml b/czech-file-knife/www/profiles/privacy-enhanced.toml deleted file mode 100644 index 6073e2100..000000000 --- a/czech-file-knife/www/profiles/privacy-enhanced.toml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# privacy-enhanced — issue #53 initial profile. -# ECH and encrypted-DNS discovery/configuration guidance WHERE ACTUALLY -# SUPPORTED: capability_detection_required means every claim must be backed -# by a dated probe result (dns/privacy/ENCRYPTED-DNS.adoc), never by the -# protocol wish-list. Direct DoT/DoQ remain non-oblivious; ODoH (OHTTP) is -# the only oblivious mode — there is no ODoT/ODoQ and none is claimed. -profile = "privacy-enhanced" -version = "0.1.0" -status = "experimental" -requires = ["baseline-site"] -capability_detection_required = true -claims_oblivious_dot_or_doq = false -components = ["www/dns/privacy/", "www/tls/POLICY.adoc", "www/runbooks/cert-rotation.adoc"] diff --git a/czech-file-knife/www/public/index.html b/czech-file-knife/www/public/index.html deleted file mode 100644 index 1a1d7178d..000000000 --- a/czech-file-knife/www/public/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - Czech File Knife - - - - -
-

Czech File Knife

-

The Swiss File Knife of the hybrid machine: one reversible, space-aware interface over local, network and cloud storage.

-
-
-

This site is served from the www/public/ category of the - repository's site-operations bundle. Operational material (DNS, TLS, - headers, profiles, tests, runbooks) is deliberately not part of the - published surface.

- -
-
-

© 2026 Jonathan D.A. Jewell. Code: MPL-2.0. Docs: CC-BY-SA-4.0.

-
- - diff --git a/czech-file-knife/www/public/styles/site.css b/czech-file-knife/www/public/styles/site.css deleted file mode 100644 index 5f187e488..000000000 --- a/czech-file-knife/www/public/styles/site.css +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: CC-BY-SA-4.0 */ -/* Minimal, dependency-free site styling. No external fonts or assets. */ -:root { color-scheme: light dark; } -body { font-family: system-ui, sans-serif; margin: 0 auto; max-width: 42rem; - padding: 1.5rem; line-height: 1.55; } -header h1 { margin-bottom: .25rem; } -nav ul { padding-left: 1.2rem; } -footer { margin-top: 3rem; border-top: 1px solid currentColor; padding-top: .75rem; - font-size: .85rem; opacity: .8; } -code { font-family: ui-monospace, monospace; } diff --git a/czech-file-knife/www/runbooks/cert-rotation.adoc b/czech-file-knife/www/runbooks/cert-rotation.adoc deleted file mode 100644 index f791f2eff..000000000 --- a/czech-file-knife/www/runbooks/cert-rotation.adoc +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Runbook: Certificate Rotation (Normal and Emergency) - -Keys never touch the repository (see `../tls/POLICY.adoc`). Everything here -happens on the serving host and in the ACME client's spool. - -== Normal rotation (ACME, automated) - -. Confirm automation is alive: `certbot certificates` (or Caddy's - `caddy list-certs` / data-dir listing) shows the live certificate with - > 30 days remaining. -. Renewal is timer-driven; force a dry run monthly: - `certbot renew --dry-run`. -. After any renewal: reload the server, then verify from outside: -+ -[source,bash] ----- -openssl s_client -connect site.example.invalid:443 -servername site.example.invalid /dev/null | openssl x509 -noout -dates -issuer ----- - -== Emergency replacement (compromise or mis-issuance) - -. *Stop the bleeding*: if the private key is compromised, treat every - session since the last rotation as decryptable. -. *Issue first, revoke second* (avoid downtime): obtain a replacement via - ACME (`certbot certonly --force-renewal -d site.example.invalid ...`), - install, reload, verify from outside as above. -. *Revoke* the compromised certificate with the CA (`certbot revoke - --cert-path ...`); note the CRL/OCSP propagation delay. -. *CT consequence*: both the compromised and replacement certificates are - public in CT logs forever — the record cannot be scrubbed, which is why - rotation speed matters more than discretion. -. *Rotate anything derived*: OCSP stapling caches, pinned fingerprints in - clients/monitoring, and any TSIG/DNSSEC material touched by the same - host compromise (`runbooks/dns-change.adoc`, estate secret-scanner - procedure for history purge). -. *Record*: dated entry in `CHANGELOG.adoc` + incident notes (what was - exposed, for how long, what was rotated). - -== Expiry monitoring - -Alert at 21 days remaining on every served name. An expired certificate is -a self-inflicted outage and, for `security.txt`-contactable projects, an -availability incident worth a postmortem. diff --git a/czech-file-knife/www/runbooks/deploy.adoc b/czech-file-knife/www/runbooks/deploy.adoc deleted file mode 100644 index e9b40d127..000000000 --- a/czech-file-knife/www/runbooks/deploy.adoc +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Runbook: Deploy the Site from `www/` - -Purpose: turn the source bundle into a *staged* document root that satisfies -the publication boundary, validate it, then publish atomically. Never point -a web server at `www/` itself. - -== Procedure - -. *Stage* (on the deploy host, from a checkout of the repository): -+ -[source,bash] ----- -STAGE=/srv/staging/site.example.invalid-$(date +%Y%m%d%H%M%S) -mkdir -p "$STAGE/.well-known" "$STAGE/errors" "$STAGE/policies" -cp -r www/public/. "$STAGE/" -cp -r www/.well-known/. "$STAGE/.well-known/" -cp -r www/errors/. "$STAGE/errors/" -for f in www/policies/*.adoc; do - asciidoctor -o "$STAGE/policies/$(basename "$f" .adoc).html" "$f" -done ----- -+ -Minted repositories already have their `{{TOKEN}}` placeholders substituted; -if deploying from an unminted template, substitute them first -(`.machine_readable/ai/PLACEHOLDERS.adoc`). - -. *Validate the boundary* (must exit 0): -+ -[source,bash] ----- -bash www/tests/check-publication-boundary.sh --stage "$STAGE" ----- - -. *Publish atomically* (generational swap keeps rollback trivial): -+ -[source,bash] ----- -ln -sfn "$STAGE" /srv/site.example.invalid # servers point at the symlink -# then reload: systemctl reload nginx | caddy reload | systemctl reload apache2 ----- - -. *Smoke-check*: `curl -sS https://site.example.invalid/` returns the index; -`/.well-known/security.txt` resolves; `/dns/`, `/tls/`, `/tests/` return 404. - -== Stop conditions - -* Step 2 non-zero: **do not publish.** The stage contains operational - material; find it (the validator names every violation) and rebuild the - stage. -* `asciidoctor` missing or failing: publish without `policies/` rather than - publishing the `.adoc` sources — they are source material, not content. - -== What never enters a stage - -`dns/`, `tls/`, `security_headers/`, `webservers/`, `profiles/`, `schemas/`, -`tests/`, `runbooks/`, the `policies/` *sources*, any `*.key`, `*.pem`, -`*.zone`, `named.conf*`, TSIG material, journals, caches, PID files, logs, -`*.control` planted-control files. The validator enforces exactly this list. diff --git a/czech-file-knife/www/runbooks/dns-change.adoc b/czech-file-knife/www/runbooks/dns-change.adoc deleted file mode 100644 index 300aebc4d..000000000 --- a/czech-file-knife/www/runbooks/dns-change.adoc +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Runbook: Authoritative Zone Change - -For the `authoritative-dns` profile. Every change follows the same loop: -validate offline, deploy, verify from outside, keep the rollback artefact. - -== Procedure - -. *Edit the zone source* under `www/dns/records/` (never the deployed - copy), and **bump the serial** (`YYYYMMDDnn`). - -. *Validate before anything else*: -+ -[source,bash] ----- -named-checkzone example.invalid www/dns/records/example.invalid.zone -bash www/tests/check-bind-safety.sh ----- - -. *Commit* the zone change (zone sources are repository content; signed - key material and journals are not, ever). - -. *Deploy to the authoritative host* and reload: -+ -[source,bash] ----- -sudo cp www/dns/records/*.zone /var/cache/bind/zones/ -sudo rndc reload example.invalid # or: rndc retransfer example.invalid ----- -+ -With an inline `dnssec-policy`, named re-signs automatically; watch -`rndc status` and the signer's key-timeline output rather than touching -keys by hand. - -. *Verify from OUTSIDE the host* (authoritative answer, correct serial, - RRSIGs present if signed, CAA as intended): -+ -[source,bash] ----- -dig @ns1.example.invalid example.invalid SOA +short -dig @ns1.example.invalid example.invalid CAA +short -dig @ns1.example.invalid example.invalid DNSKEY +short # if signed ----- - -. *TTL discipline*: wait at least the zone's negative-cache TTL (and, for - removals, the old record's TTL) before considering a change complete. - For DNSSEC algorithm/key changes, follow the publish/retire safety - intervals in the policy — never delete old signatures early. - -== Rollback - -Restore the previous zone source (`git revert` or checkout of the prior -commit), bump the serial *again* (never reuse or lower a serial), reload, -and re-verify from outside. The committed history is the rollback store — -another reason zone sources live in git and runtime artefacts do not. - -== Stop conditions - -* `named-checkzone` fails: do not deploy. -* Serial not bumped: secondaries will not pick the change up — fix before - reload. -* Any key, journal, or TSIG secret found in the repository: treat as an - incident (`runbooks/incident.adoc`), rotate, purge history per the - secret-scanner procedure. diff --git a/czech-file-knife/www/runbooks/rollback.adoc b/czech-file-knife/www/runbooks/rollback.adoc deleted file mode 100644 index 373dfbd99..000000000 --- a/czech-file-knife/www/runbooks/rollback.adoc +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Runbook: Roll Back a Site Deployment - -The deploy runbook publishes through a generational symlink -(`/srv/site.example.invalid -> /srv/staging/site.example.invalid-`), so -rollback is re-pointing the symlink at the previous generation. No rebuild, -no redeploy, no data loss. - -== Procedure - -. *Identify the previous good generation*: -+ -[source,bash] ----- -ls -1dt /srv/staging/site.example.invalid-* | sed -n 2p ----- - -. *Re-point and reload*: -+ -[source,bash] ----- -ln -sfn "$PREV" /srv/site.example.invalid -systemctl reload nginx # or: caddy reload / systemctl reload apache2 ----- - -. *Verify*: index, `/.well-known/security.txt`, and the 404 body all serve; -the failure that prompted the rollback is no longer observable. - -. *Preserve evidence*: do not delete the rejected generation until the -incident is closed — it is the artefact that failed validation or smoke -checks. - -. *Record*: one dated line in the repository `CHANGELOG.adoc` (deployed / -rolled-back generations and reason). - -== When rollback is NOT the answer - -* Compromised TLS material: follow `runbooks/cert-rotation.adoc` and the - incident runbook — rolling the site back does not rotate keys. -* Boundary violation discovered in a *live* docroot: take the operational - paths offline first (the server configs already deny them), then roll - back, then fix the stage pipeline that let them through. diff --git a/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc b/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc deleted file mode 100644 index 99f4e8257..000000000 --- a/czech-file-knife/www/runbooks/stage5-wellknown-sweep.adoc +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= Runbook: Sweep Root `.well-known/` into `www/` Across the Estate - -Purpose: propagate the canonical `www/.well-known/` arrangement (#53 stage 5, -tracked by #119) across the repositories that still carry a root -`.well-known/`, in batches, without hand-making 270 pull requests and without -losing a byte of divergent content. - -Read this before running anything. The sweep is *unattended but not -unsupervised*: it will not overwrite content, and it will not stay quiet about -anything it could not resolve. - -== Before you start - -. A token with `repo` scope (public-only estates need `public_repo`): -+ -[source,bash] ----- -export GITHUB_TOKEN=... ----- -+ -. A committer identity, because the sweep commits: -+ -[source,bash] ----- -git var GIT_COMMITTER_IDENT # must succeed; the driver refuses to start otherwise ----- -+ -. The repository list. `stage5-repos.txt` in the #119 tracker holds the - enumerated denominator (270 repositories as of 2026-09-17). - -== Step 1 — classify (read-only, one API call per repository) - -[source,bash] ----- -bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --classify-only ----- - -This writes `classification.csv` and touches nothing else. Each repository -lands in one of three classes: - -`sweep`:: Identifiably RSR-templated (a root allowlist is present under either -canonical spelling) and not served from the repository root. Safe to sweep. -+ -`review-pages`:: Pages or a CDN serves the repository root, or the name is a -Pages site. For these a root `.well-known/` may be a *serving requirement* -rather than legacy layout, and moving it out of the served root can break live -discovery. #119 names `hyperpolymath.github.io` explicitly and asks for these -to be classified before sweeping, so the driver does not sweep them unless you -pass `--include-review`. -+ -`review-other`:: Carries a root `.well-known/` but shows no RSR marker, so the -arrangement is not known to be template-derived. Held back for the same reason. - -Resolve the `review-*` rows by hand first. That is a decision, not a batch -operation. - -== Step 2 — dry-run a batch - -[source,bash] ----- -bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --batch 1 --dry-run ----- - -Reports what would change, runs the suite, commits nothing. The per-repository -reports land in the work directory as `reports/.tsv`, one row per file, -with the action taken: `moved`, `deduped`, `quarantined`, `conflict`, `left`. - -== Step 3 — sweep the batch for real - -[source,bash] ----- -bash scripts/sweep-wellknown.sh --repos-file stage5-repos.txt --batch 1 ----- - -Commits locally, on branch `chore/well-known-to-www` by default. It does *not* -push unless you pass `--push`; until you have reviewed a few batches, leave it -off and push by hand. - -Per repository the driver runs the four steps #119 asks for: classify, migrate, -run the suite (`www/tests/run-all.sh` where the repository carries the bundle), -commit. - -== How divergence is handled - -Content that exists at both the root and under `www/` and *differs* is never -overwritten. The root copy is moved aside to: - -[source,text] ----- -www/.legacy-well-known-/ ----- - -The `www/` copy is left untouched, both hashes are printed, and the file is -recorded in the repository's report. The batch continues — an unattended sweep -that halts on the first conflict is not a sweep — but the run *exits non-zero* -so the conflict cannot pass unnoticed. Suppress that with `--allow-conflicts` -only when you have already triaged the reports. - -Two historical notes worth knowing: - -* PR #106's commit message describes the quarantine directory as a *root-level* - `.legacy-well-known-YYYYMMDD/`. It is under `www/` instead, deliberately: - the root allowlist is checked bidirectionally and matches entries literally - with no glob support, so a dated root directory would report as root drift in - every repository the sweep touched. It is also not under `www/public/`, so - the publication boundary still holds. -* `--in-place` restores the pre-stage-5 contract (keep both copies where they - are, exit 1) for anyone who prefers to resolve divergence before the tree is - touched at all. - -Repositories WITHOUT a `www/` bundle are migrated but flagged *"no www/tests -bundle — run the RSR update mechanism first"*. Migration is only half the job -there: the canonical location has to exist before the suite can prove it. - -== Failure handling - -One repository failing never abandons the batch. Failures are recorded in -`sweep-results.csv` with the status `failed` and a path to the relevant log, -the run continues, and the final exit status reflects that something failed. - -The run exits non-zero when any repository failed, or when any content was -quarantined (unless `--allow-conflicts`). Read the summary table it prints: - -[source,text] ----- ---- results by status --- - 2 migrated - 1 quarantined - 1 failed ----- - -== Recovering - -* *A repository was swept and is wrong.* Nothing was pushed unless you asked - for it; the commit is local to the work directory clone. Delete the clone. -* *Content was quarantined.* Decide which copy is canonical, reconcile - `www/.well-known/`, then delete `www/.legacy-well-known-/`. The - file is committed, so nothing is lost before you decide. -* *The API rate limit is hit mid-run.* Re-run the same `--batch`; already-clean - repositories report `clean` and are skipped. - -== Verification - -The migrated tree must satisfy the bundle's own suite: - -[source,bash] ----- -bash www/tests/run-all.sh # 6 checks -bash www/tests/check-migration.sh # 9 scenarios, incl. all four divergence cases ----- - -CI drives the migrator in `--dry-run` on every push that touches -`.well-known/**` (`dot-wellknown-enforcement.yml`), so a repository still -carrying legacy layout reports the divergence as a warning long before the -sweep reaches it. diff --git a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json deleted file mode 100644 index ffacdec3d..000000000 --- a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json +++ /dev/null @@ -1,377 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://consent-aware-web.org/schemas/aibdp-v0.2.json", - "$comment": "Vendored unmodified from metadatastician/consent-aware-web schemas/aibdp-schema-v0.2.json (MPL-2.0) on 2026-09-17 \u2014 see the .license sidecar. AIBDP 0.2 implements draft-jewell-aibdp-00 (EXPERIMENTAL; not a Datatracker submission) and is a mapped alternative representation of AIPREF where they overlap.", - "title": "AI Boundary Declaration Protocol (AIBDP) Manifest Schema", - "description": "JSON Schema for validating AIBDP manifests hosted at /.well-known/aibdp.json", - "type": "object", - "required": [ - "aibdp_version", - "contact", - "policies" - ], - "properties": { - "aibdp_version": { - "type": "string", - "description": "Protocol version number", - "pattern": "^\\d+\\.\\d+$", - "examples": [ - "0.1", - "0.2", - "1.0" - ] - }, - "canonical_uri": { - "type": "string", - "format": "uri", - "description": "Authoritative location of this manifest for cross-domain policies" - }, - "contact": { - "type": "string", - "description": "Contact URI for policy inquiries (mailto:, https:, etc.)", - "pattern": "^(mailto:|https?:).+", - "examples": [ - "mailto:policy@example.org", - "https://example.org/contact" - ] - }, - "expires": { - "type": "string", - "format": "date-time", - "description": "ISO 8601 timestamp when manifest should be re-fetched" - }, - "policy_uri": { - "type": "string", - "format": "uri", - "description": "Link to human-readable policy document" - }, - "policies": { - "type": "object", - "description": "AI usage policy declarations", - "properties": { - "training": { - "$ref": "#/$defs/policy" - }, - "indexing": { - "$ref": "#/$defs/policy" - }, - "summarization": { - "$ref": "#/$defs/policy" - }, - "question_answering": { - "$ref": "#/$defs/policy" - }, - "generation": { - "$ref": "#/$defs/policy" - }, - "fine_tuning": { - "$ref": "#/$defs/policy" - }, - "embedding": { - "$ref": "#/$defs/policy" - }, - "commercial_training": { - "$ref": "#/$defs/policy" - } - }, - "additionalProperties": { - "$ref": "#/$defs/policy" - }, - "minProperties": 1 - }, - "scope": { - "type": "object", - "description": "Describes what AI systems this manifest addresses", - "properties": { - "applies_to": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Types of AI systems this policy covers" - } - } - }, - "special_provisions": { - "type": "object", - "description": "Special provisions for specific use cases", - "properties": { - "academic_research": { - "$ref": "#/$defs/special_provision" - }, - "educational_use": { - "$ref": "#/$defs/special_provision" - }, - "standards_development": { - "$ref": "#/$defs/special_provision" - } - }, - "additionalProperties": { - "$ref": "#/$defs/special_provision" - } - }, - "enforcement": { - "type": "object", - "description": "Enforcement mechanism information", - "properties": { - "mechanism": { - "type": "string", - "enum": [ - "http_430", - "legal", - "reputational", - "technical", - "none" - ], - "description": "Primary enforcement mechanism" - }, - "note": { - "type": "string" - }, - "contact_before_litigation": { - "type": "boolean", - "description": "Whether to contact before legal action" - }, - "preferred_resolution": { - "type": "string", - "description": "Preferred approach to resolving violations" - } - } - }, - "metadata": { - "type": "object", - "description": "Manifest metadata", - "properties": { - "created": { - "type": "string", - "format": "date", - "description": "Creation date (YYYY-MM-DD)" - }, - "last_modified": { - "type": "string", - "format": "date", - "description": "Last modification date" - }, - "author": { - "type": "string" - }, - "organization": { - "type": "string" - }, - "project": { - "type": "string" - }, - "repository": { - "type": "string", - "format": "uri" - }, - "related_standards": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "philosophy": { - "type": "object", - "description": "Philosophical framing and values", - "properties": { - "core_principle": { - "type": "string" - }, - "values": { - "type": "array", - "items": { - "type": "string" - } - }, - "quote": { - "type": "string" - } - } - }, - "signature": { - "type": "object", - "description": "COSE cryptographic signature for manifest verification", - "required": [ - "algorithm", - "value" - ], - "properties": { - "algorithm": { - "type": "string", - "enum": [ - "ES256", - "ES384", - "ES512", - "RS256", - "RS384", - "RS512", - "EdDSA" - ], - "description": "Signature algorithm (COSE)" - }, - "public_key_uri": { - "type": "string", - "format": "uri", - "description": "URI to public key (JWK format)" - }, - "value": { - "type": "string", - "description": "Base64-encoded COSE signature" - }, - "note": { - "type": "string" - } - } - } - }, - "additionalProperties": true, - "$defs": { - "policy": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "string", - "enum": [ - "allowed", - "refused", - "conditional", - "encouraged" - ], - "description": "Permission status for this AI usage mode" - }, - "conditions": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Requirements that must be met when status is 'conditional'" - }, - "scope": { - "oneOf": [ - { - "type": "string", - "const": "all" - }, - { - "type": "array", - "items": { - "type": "string" - }, - "description": "Path patterns this policy applies to" - } - ] - }, - "exceptions": { - "type": "array", - "items": { - "type": "object", - "required": [ - "path", - "status" - ], - "properties": { - "path": { - "type": "string", - "description": "Path pattern for exception" - }, - "status": { - "type": "string", - "enum": [ - "allowed", - "refused", - "conditional", - "encouraged" - ] - }, - "note": { - "type": "string" - }, - "conditions": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "rationale": { - "type": "string", - "description": "Human-readable explanation of policy" - }, - "alternatives": { - "type": "string", - "description": "Suggested alternative approaches" - }, - "purpose": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific purposes this policy encourages (when status is 'encouraged')" - }, - "note": { - "type": "string", - "description": "Additional context or clarification" - } - }, - "additionalProperties": false - }, - "special_provision": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "string", - "enum": [ - "unrestricted", - "encouraged", - "allowed", - "conditional", - "refused" - ] - }, - "note": { - "type": "string" - }, - "conditions": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "examples": [ - { - "aibdp_version": "0.2", - "contact": "mailto:policy@example.org", - "policies": { - "training": { - "status": "conditional", - "conditions": [ - "Attribution required", - "Non-commercial use only" - ] - }, - "indexing": { - "status": "allowed", - "scope": "all" - }, - "generation": { - "status": "refused", - "rationale": "Content should not be synthetically replicated" - } - } - } - ] -} \ No newline at end of file diff --git a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license b/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license deleted file mode 100644 index 8b66169a0..000000000 --- a/czech-file-knife/www/schemas/aibdp-schema-v0.2.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/schemas/publishable-paths.txt b/czech-file-knife/www/schemas/publishable-paths.txt deleted file mode 100644 index 4820261c3..000000000 --- a/czech-file-knife/www/schemas/publishable-paths.txt +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# publishable-paths.txt — machine-readable declaration of the publication -# boundary (issue #53). Exactly the bundle subtrees listed below (one per -# line; '#' comments ignored) may reach a deployed document root: -# -# www/public/ ordinary publishable content, served at / -# www/.well-known/ public protocol metadata, served at /.well-known/ -# -# Staging rules (runbooks/deploy.adoc): -# * www/errors/ bodies are staged INTO /errors/ — they publish as -# public content, not as a third publishable source category; -# * www/policies/*.adoc sources are rendered INTO /policies/*.html; -# * everything else — dns/, tls/, security_headers/, webservers/, -# profiles/, schemas/, tests/, runbooks/ — is OPERATIONAL material and -# must never appear in a deployed document root. -# -# Consumed by: www/tests/check-publication-boundary.sh (CI + mint smoke test). -www/public/ -www/.well-known/ diff --git a/czech-file-knife/www/security_headers/README.adoc b/czech-file-knife/www/security_headers/README.adoc deleted file mode 100644 index 625361317..000000000 --- a/czech-file-knife/www/security_headers/README.adoc +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `security_headers/` — Header Sources (Not Served) - -`csp.conf` is the *source of record* for the site's security headers. -This directory is operational material: it must never be inside a deployed -document root (see `www/schemas/publishable-paths.txt` and -`www/tests/check-publication-boundary.sh`). - -== How each server consumes it - -Caddy:: Values are transcribed into the `header { … }` block of -`www/webservers/caddy/Caddyfile.example`. -nginx:: Convert each `Name: value` line into -`add_header Name "value" always;` inside the server block (the example -already carries the set), or generate a snippet and `include` it. -Apache:: Convert each line into `Header always set Name "value"` with -`mod_headers` enabled (the example already carries the set). - -Whatever the mechanism, the *values* come from `csp.conf`; when you change -one, change it there and re-sync the server example you deploy. - -== Policy notes - -* `style-src 'self'` (no `'unsafe-inline'`): the bundle's HTML uses an - external stylesheet only. If you add inline styles, you weaken CSP — - prefer a stylesheet. -* HSTS includes `preload`; only deploy that once the host is genuinely - HTTPS-everywhere and you accept the multi-month un-preload cycle. -* `Cross-Origin-Resource-Policy: same-origin` is correct for a project - site; relax it deliberately (e.g. `cross-origin` on an assets host) - rather than by accident. diff --git a/czech-file-knife/www/security_headers/csp.conf b/czech-file-knife/www/security_headers/csp.conf deleted file mode 100644 index fc003621a..000000000 --- a/czech-file-knife/www/security_headers/csp.conf +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# csp.conf — canonical security-header set for sites deployed from this -# bundle (source of record; the webservers/ examples embed these values and -# must be kept in sync with this file — see README.adoc in this directory). -# -# Form: one `Header-Name: value` per line, suitable for nginx include -# snippets (add_header), Apache mod_headers (Header always set) and manual -# transcription into Caddy header blocks. -# -# Deliberate omissions: -# X-XSS-Protection — deprecated; modern guidance is to not send it (it is -# an attack surface in legacy browsers). CSP frame-ancestors + XFO cover -# the same ground. -# Expect-CT — obsolete; CT is enforced by browsers via policy. -Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests -Strict-Transport-Security: max-age=31536000; includeSubDomains; preload -X-Content-Type-Options: nosniff -X-Frame-Options: DENY -Referrer-Policy: strict-origin-when-cross-origin -Permissions-Policy: geolocation=(), camera=(), microphone=(), browsing-topics=() -Cross-Origin-Opener-Policy: same-origin -Cross-Origin-Resource-Policy: same-origin diff --git a/czech-file-knife/www/tests/check-aibdp.sh b/czech-file-knife/www/tests/check-aibdp.sh deleted file mode 100755 index 9b321aa3e..000000000 --- a/czech-file-knife/www/tests/check-aibdp.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-aibdp.sh — validate www/.well-known/aibdp.json (issue #53: -# "experimental files are labelled as such"; declaration-only by default). -# Requires jq; skips with a note when absent. - -set -uo pipefail -WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -FAIL=0 -bad() { echo "AIBDP FAIL: $*" >&2; FAIL=1; } -ok() { echo "ok: $*"; } - -if ! command -v jq >/dev/null 2>&1; then - echo "note: jq unavailable — AIBDP checks skipped" - exit 0 -fi - -validate_aibdp() { # $1 = aibdp json; returns 1 on any violation - local f="$1" v=0 st - jq empty "$f" 2>/dev/null || { echo " not valid JSON" >&2; return 1; } - [ "$(jq -r '.aibdp_version // empty' "$f")" = "0.2" ] \ - || { echo " aibdp_version must be 0.2" >&2; v=1; } - jq -e '.contact | test("^(mailto:|https?:)")' "$f" >/dev/null \ - || { echo " contact must be a mailto:/http(s) URI" >&2; v=1; } - # Experimental labelling (declaration-only provenance must be visible). - st="$(jq -r '.status // empty' "$f")" - case "$st" in *experimental*declaration-only*) : ;; - *) echo " status must label the file experimental AND declaration-only" >&2; v=1 ;; esac - # Policies vocabulary + rationale discipline. - jq -e '.policies | type == "object" and length >= 1' "$f" >/dev/null \ - || { echo " policies must be a non-empty object" >&2; v=1; } - while IFS=$'\t' read -r name status; do - case "$status" in allowed|conditional|disallowed) : ;; - *) echo " policies.$name.status not in {allowed,conditional,disallowed}: $status" >&2; v=1 ;; esac - jq -e --arg n "$name" '.policies[$n].rationale | type == "string" and length > 0' "$f" >/dev/null \ - || { echo " policies.$name missing rationale" >&2; v=1; } - if [ "$status" = "conditional" ]; then - jq -e --arg n "$name" '.policies[$n].conditions | type == "array" and length >= 1' "$f" >/dev/null \ - || { echo " policies.$name is conditional without conditions" >&2; v=1; } - fi - done < <(jq -r '.policies | to_entries[] | [.key, (.value.status // "")] | @tsv' "$f") - # Enforcement: absent or mechanism "none" — never http_430. - if jq -e 'has("enforcement")' "$f" >/dev/null; then - local mech; mech="$(jq -r '.enforcement.mechanism // "none"' "$f")" - [ "$mech" = "none" ] \ - || { echo " enforcement.mechanism is '$mech' — this bundle is declaration-only" >&2; v=1; } - fi - return "$v" -} - -consistency_with_ai_txt() { # declaration must not contradict ai.txt - local ai="$WWW/.well-known/ai.txt" f="$WWW/.well-known/aibdp.json" v=0 - [ -f "$ai" ] || return 0 - local want got - for pair in "Disallow-Training:training" "Disallow-Summarization:summarization" "Disallow-Generation:generation"; do - key="${pair%%:*}"; field="${pair##*:}" - want="$(grep -m1 "^$key:" "$ai" | awk '{print $2}')" - got="$(jq -r --arg n "$field" '.policies[$n].status // "absent"' "$f")" - case "$want" in - yes) [ "$got" = "disallowed" ] || { echo " ai.txt $key yes but aibdp policies.$field.status=$got" >&2; v=1; } ;; - no) [ "$got" = "allowed" ] || { echo " ai.txt $key no but aibdp policies.$field.status=$got" >&2; v=1; } ;; - esac - done - return "$v" -} - -MAIN="$WWW/.well-known/aibdp.json" -AI_FILE="$WWW/.well-known/ai.txt" -# The AIBDP declaration is OPTIONAL (issue #53). A repository that does not -# make it has no AIBDP claims to validate, and demanding the file regardless is -# what made this check fail in every repository the .well-known/ stage-5 -# migration reached: the migration moves ai.txt and security.txt, and aibdp.json -# was never part of it. Silence is not a violation — but ai.txt REFERRING to -# aibdp while the file is absent is a real contradiction, and still fails below. -if [ ! -f "$MAIN" ] && ! grep -qi 'aibdp' "$AI_FILE" 2>/dev/null; then - echo "SKIP: no AIBDP declaration here (www/.well-known/aibdp.json absent and" - echo "SKIP: ai.txt makes no aibdp claim) — there is nothing to validate" - exit 77 -fi -if [ ! -f "$MAIN" ]; then - bad "ai.txt references aibdp but www/.well-known/aibdp.json is missing" -elif validate_aibdp "$MAIN" 2>/dev/null; then - ok "aibdp.json valid, experimental + declaration-only labelled" -else - bad "aibdp.json invalid" - validate_aibdp "$MAIN" 2>&1 | sed 's/^/ /' >&2 || true -fi - -if consistency_with_ai_txt 2>/dev/null; then - ok "aibdp.json consistent with ai.txt stances" -else - bad "aibdp.json contradicts ai.txt" - consistency_with_ai_txt 2>&1 | sed 's/^/ /' >&2 || true -fi - -shopt -s nullglob -for c in "$WWW"/tests/controls/aibdp-*.control.json; do - if validate_aibdp "$c" 2>/dev/null; then - bad "planted control was NOT rejected: tests/controls/$(basename "$c")" - else - ok "planted control rejected: tests/controls/$(basename "$c")" - fi -done - -exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-bind-safety.sh b/czech-file-knife/www/tests/check-bind-safety.sh deleted file mode 100755 index 4eb51de21..000000000 --- a/czech-file-knife/www/tests/check-bind-safety.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-bind-safety.sh — authoritative-only posture proof (issue #53, -# acceptance: "BIND example passes syntax validation and a planted -# open-recursion control is rejected"). -# -# Asserts, for every named.conf* under www/dns/ (examples AND controls are -# scanned by posture_assert; controls must FAIL it): -# * recursion no; present, and no 'recursion yes' -# * no unrestricted allow-recursion -# * no allow-transfer { any; } -# * no listen-on ... { any; } -# * no inline key/secret blocks (TSIG or otherwise) -# * zone names restricted to reserved/documentation names -# * no key/journal/secret FILES anywhere under www/dns/ -# When bind9utils is available: real named-checkconf via a temp jail -# (directory/pid-file rewritten into it, zones copied in) and -# named-checkzone on every *.zone. - -set -uo pipefail -WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -DNS="$WWW/dns" -FAIL=0 -bad() { echo "BIND SAFETY FAIL: $*" >&2; FAIL=1; } -ok() { echo "ok: $*"; } - -posture_assert() { # $1 = named.conf file; returns 1 on any violation - local f="$1" v=0 z - grep -Eq '^[[:space:]]*recursion[[:space:]]+no[[:space:]]*;' "$f" \ - || { echo " no 'recursion no;' — resolver posture forbidden" >&2; v=1; } - grep -Eq '^[[:space:]]*recursion[[:space:]]+yes' "$f" \ - && { echo " 'recursion yes' present — OPEN RESOLVER" >&2; v=1; } - if grep -Eq '^[[:space:]]*allow-recursion' "$f"; then - grep -Eq '^[[:space:]]*allow-recursion[[:space:]]*\{[[:space:]]*none[[:space:]]*;' "$f" \ - || { echo " unrestricted allow-recursion" >&2; v=1; } - fi - grep -Eq 'allow-transfer[[:space:]]*\{[[:space:]]*any' "$f" \ - && { echo " allow-transfer { any; } — zone walk exposure" >&2; v=1; } - grep -Eq 'listen-on(-v6)?[[:space:]]*(port[[:space:]]+[0-9]+[[:space:]]*)?\{[[:space:]]*any' "$f" \ - && { echo " listen-on any — bind explicit addresses" >&2; v=1; } - grep -Eq '^[[:space:]]*key[[:space:]]+"' "$f" \ - && { echo " inline key block — secrets never live in the bundle" >&2; v=1; } - grep -Eoq 'secret[[:space:]]+"' "$f" \ - && { echo " inline secret material" >&2; v=1; } - # Zone names must be reserved/documentation names only. - while IFS= read -r z; do - case "$z" in - example.invalid|example.com|example.net|example.org|*.example.invalid|*.example|localhost|2.0.192.in-addr.arpa|*.2.0.192.in-addr.arpa|0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa) : ;; - *) echo " non-reserved zone name in template: $z" >&2; v=1 ;; - esac - done < <(sed -nE 's/^[[:space:]]*zone[[:space:]]+"([^"]+)".*$/\1/p' "$f") - return "$v" -} - -secret_file_scan() { # no key/journal/runtime material under www/dns/ - local f found=0 - while IFS= read -r f; do - case "${f##*/}" in - *.key|*.private|*.jnl|*.journal|*.rndc|*.jbk|tsig*|K*+*+*.key|K*+*+*.private) - echo " secret/runtime file under dns/: $f" >&2; found=1 ;; - esac - done < <(find "$DNS" -type f) - return "$found" -} - -# ── shipped examples must pass posture + real validators when available ───── -shopt -s nullglob -for conf in "$DNS"/bind9/named.conf*; do - case "${conf##*/}" in *.control) continue ;; esac - if posture_assert "$conf" 2>/dev/null; then - ok "posture: ${conf#"$WWW"/}" - else - bad "posture violated: ${conf#"$WWW"/}" - posture_assert "$conf" 2>&1 | sed 's/^/ /' >&2 || true - fi -done - -if secret_file_scan 2>/dev/null; then - ok "no key/journal/secret files under www/dns/" -else - bad "secret/runtime material found under www/dns/" -fi - -if command -v named-checkconf >/dev/null 2>&1; then - for conf in "$DNS"/bind9/named.conf*; do - case "${conf##*/}" in *.control) continue ;; esac - jail="$(mktemp -d)"; mkdir -p "$jail/zones" "$jail/run" - sed -e "s|directory \"/var/cache/bind\";|directory \"$jail\";|" \ - -e "s|pid-file \"/run/named/named.pid\";|pid-file \"$jail/run/named.pid\";|" \ - "$conf" > "$jail/named.conf" - cp "$DNS"/records/*.zone "$jail/zones/" 2>/dev/null || true - if named-checkconf "$jail/named.conf" >/dev/null 2>&1; then - ok "named-checkconf: ${conf#"$WWW"/}" - else - bad "named-checkconf rejected ${conf#"$WWW"/}" - fi - rm -rf "$jail" - done -else - echo "note: named-checkconf unavailable — posture grep checks only" -fi - -if command -v named-checkzone >/dev/null 2>&1; then - for zone in "$DNS"/records/*.zone; do - origin="$(basename "$zone" .zone)" - [ "$origin" = "2.0.192.in-addr.arpa" ] || origin="${origin%.zone}" - if named-checkzone "$origin" "$zone" >/dev/null 2>&1; then - ok "named-checkzone: records/$(basename "$zone")" - else - bad "named-checkzone rejected records/$(basename "$zone")" - fi - done -else - echo "note: named-checkzone unavailable — skipped" -fi - -# ── planted controls MUST be rejected ──────────────────────────────────────── -for ctl in "$WWW"/tests/controls/named.conf*.control; do - [ -f "$ctl" ] || continue - if posture_assert "$ctl" 2>/dev/null; then - bad "planted open-recursion control was NOT rejected: ${ctl#"$WWW"/}" - else - ok "planted control rejected: tests/controls/$(basename "$ctl")" - fi -done - -exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-migration.sh b/czech-file-knife/www/tests/check-migration.sh deleted file mode 100755 index 59c89d4de..000000000 --- a/czech-file-knife/www/tests/check-migration.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-migration.sh — prove scripts/migrate-wellknown-to-www.sh honours the -# behaviours stage 5 (#119) depends on (czech-file-knife#53 acceptance: -# "Migration handles identical, missing and divergent root/www copies without -# data loss"). Each scenario runs in a throwaway git repository. -# -# Scenarios 1-3 are the original four-scenario contract from #106. Scenarios -# 4-8 cover the stage 5 additions: the sweep is unattended, so a divergent -# copy must be quarantined rather than left to halt the batch — loudly, and -# with the old in-place contract still available under --in-place. - -set -uo pipefail - -REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" \ - || REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -MIGRATOR="$REPO_ROOT/scripts/migrate-wellknown-to-www.sh" -# This check tests the MIGRATOR, not the repository. The migrator performs the -# one-time root .well-known/ -> www/.well-known/ move (issue #119) and is -# template-side tooling: a repository that has already been migrated has -# nothing for it to do and no business carrying it. A repository without it has -# nothing to test, so this is a SKIP, not a failure. Shipping a check that -# fails in every repository it reaches is how a suite stops being read. -if [ ! -f "$MIGRATOR" ]; then - echo "SKIP: scripts/migrate-wellknown-to-www.sh absent here — this check tests" - echo "SKIP: the template-side migrator, which this repository does not carry" - exit 77 -fi - -fail=0 -ok() { echo "ok: $1"; } -bad() { echo "FAIL: $1" >&2; fail=1; } - -scenario() { # name -> fresh git repo path on stdout - local dir; dir="$(mktemp -d)" - git -C "$dir" init -q - git -C "$dir" config user.email t@example.invalid - git -C "$dir" config user.name t - echo "$dir" -} -commit_all() { git -C "$1" add -A >/dev/null && git -C "$1" commit -qm fixture; } - -# The quarantine directory is named for the UTC date, so a scenario must read -# the same clock the migrator will. -qdir() { echo "www/.legacy-well-known-$(date -u +%Y%m%d)"; } - -# ── 1. identical copies: root removed, www kept, exit 0 ───────────────────── -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:s@example.invalid\n' | tee "$d/.well-known/security.txt" > "$d/www/.well-known/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" >/dev/null); then - if [ ! -e "$d/.well-known/security.txt" ] && [ -f "$d/www/.well-known/security.txt" ]; then - ok "identical -> root removed, www kept" - else - bad "identical scenario left wrong tree" - fi -else - bad "identical scenario exited non-zero" -fi -rm -rf "$d" - -# ── 2. root-only: moved into www, exit 0 ───────────────────────────────────── -d="$(scenario)" -mkdir -p "$d/.well-known/groove" -printf '{"service_id":"x"}\n' > "$d/.well-known/groove/manifest.json" -printf 'User-Agent: *\n' > "$d/.well-known/ai.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" >/dev/null); then - if [ -f "$d/www/.well-known/groove/manifest.json" ] && [ -f "$d/www/.well-known/ai.txt" ] \ - && [ ! -d "$d/.well-known" ]; then - ok "root-only -> moved (nested dirs too)" - else - bad "root-only move incomplete" - fi -else - bad "root-only scenario exited non-zero" -fi -rm -rf "$d" - -# ── 3. www-only (no root): no-op, exit 0 ───────────────────────────────────── -d="$(scenario)" -mkdir -p "$d/www/.well-known" -printf 'Contact: mailto:s@example.invalid\n' > "$d/www/.well-known/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" >/dev/null) && [ -f "$d/www/.well-known/security.txt" ]; then - ok "www-only -> no-op" -else - bad "www-only scenario damaged tree or exited non-zero" -fi -rm -rf "$d" - -# ── 4. divergent, default: root copy QUARANTINED, www untouched, exit 0 ────── -# The sweep is unattended, so this must not halt: the batch continues and the -# report names every quarantined file. -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" >/dev/null 2>&1); then - Q="$d/$(qdir)" - if [ -f "$Q/security.txt" ] && grep -q old "$Q/security.txt" \ - && [ -f "$d/www/.well-known/security.txt" ] && grep -q new "$d/www/.well-known/security.txt" \ - && [ ! -e "$d/.well-known/security.txt" ]; then - ok "divergent -> root copy quarantined, www copy untouched, exit 0" - else - bad "divergent scenario lost, overwrote or misplaced content" - fi -else - bad "divergent scenario must exit 0 by default (batch sweeps depend on it)" -fi -rm -rf "$d" - -# ── 5. divergent, --in-place: BOTH preserved where they are, exit 1 ────────── -# The pre-stage-5 contract, kept for hand resolution. -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" --in-place >/dev/null 2>&1); then - bad "divergent --in-place must exit non-zero" -else - if [ -f "$d/.well-known/security.txt" ] && [ -f "$d/www/.well-known/security.txt" ] \ - && grep -q old "$d/.well-known/security.txt" && grep -q new "$d/www/.well-known/security.txt"; then - ok "divergent --in-place -> both preserved, non-zero exit" - else - bad "divergent --in-place lost or overwrote content" - fi -fi -rm -rf "$d" - -# ── 6. divergent, --strict: quarantined AND non-zero exit ──────────────────── -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" --strict >/dev/null 2>&1); then - bad "divergent --strict must exit non-zero" -else - if [ -f "$d/$(qdir)/security.txt" ] && [ ! -e "$d/.well-known/security.txt" ]; then - ok "divergent --strict -> quarantined and non-zero exit" - else - bad "divergent --strict did not quarantine" - fi -fi -rm -rf "$d" - -# ── 7. --dry-run: reports, changes nothing, exit 0 ─────────────────────────── -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -commit_all "$d" -before="$(cd "$d" && git status --porcelain | sort)" -if (cd "$d" && bash "$MIGRATOR" --dry-run >/dev/null 2>&1); then - after="$(cd "$d" && git status --porcelain | sort)" - if [ "$before" = "$after" ] && [ -f "$d/.well-known/security.txt" ] \ - && [ ! -d "$d/$(qdir)" ]; then - ok "--dry-run -> nothing written" - else - bad "--dry-run mutated the tree" - fi -else - bad "--dry-run must exit 0 even when content diverges" -fi -rm -rf "$d" - -# ── 8. quarantine never overwrites: a taken name gets the next free suffix ─── -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" "$d/$(qdir)" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -printf 'PRIOR QUARANTINE — must survive\n' > "$d/$(qdir)/security.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" >/dev/null 2>&1); then - if grep -q 'PRIOR QUARANTINE' "$d/$(qdir)/security.txt" \ - && [ -f "$d/$(qdir)/security.txt.1" ] && grep -q old "$d/$(qdir)/security.txt.1"; then - ok "quarantine collision -> prior file preserved, new one suffixed .1" - else - bad "quarantine collision overwrote an existing file" - fi -else - bad "quarantine collision scenario exited non-zero" -fi -rm -rf "$d" - -# ── 9. --report: machine-readable TSV for batch drivers ────────────────────── -d="$(scenario)" -mkdir -p "$d/.well-known" "$d/www/.well-known" -printf 'Contact: mailto:old@example.invalid\n' > "$d/.well-known/security.txt" # divergent -> quarantined -printf 'Contact: mailto:new@example.invalid\n' > "$d/www/.well-known/security.txt" -printf 'User-Agent: *\n' > "$d/.well-known/ai.txt" # root-only -> moved -printf 'User-Agent: *\n' > "$d/www/.well-known/humans.txt" # identical -> deduped -printf 'User-Agent: *\n' > "$d/.well-known/humans.txt" -commit_all "$d" -if (cd "$d" && bash "$MIGRATOR" --report migrate.tsv >/dev/null 2>&1); then - if [ -f "$d/migrate.tsv" ] \ - && grep -qP '^quarantined\tsecurity.txt\t' "$d/migrate.tsv" \ - && grep -qP '^moved\tai.txt\t' "$d/migrate.tsv" \ - && grep -qP '^deduped\thumans.txt\t' "$d/migrate.tsv"; then - ok "--report -> TSV records quarantined/moved/deduped per file" - else - bad "--report TSV missing or incomplete" - fi -else - bad "--report scenario exited non-zero" -fi -rm -rf "$d" - -exit "$fail" diff --git a/czech-file-knife/www/tests/check-profiles.sh b/czech-file-knife/www/tests/check-profiles.sh deleted file mode 100755 index 840232acd..000000000 --- a/czech-file-knife/www/tests/check-profiles.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-profiles.sh — explicit-composition proof (issue #53 acceptance: -# "Profiles compose explicitly and can be enabled/disabled independently"; -# "Consent-aware profile defaults to declaration/observe-only and cannot -# silently enable 430"). -# -# Any profile that STATES an enforcement/auto-start flag must state it as -# false; the consent and DNS profiles (and full-expert) MUST state them — -# absence is as much a violation as `true`, so a flag can never be -# smuggled in by deletion. - -set -uo pipefail -WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -REPO="$(cd "$WWW/.." && pwd)" -FAIL=0 -bad() { echo "PROFILE FAIL: $*" >&2; FAIL=1; } -ok() { echo "ok: $*"; } - -assert_profile() { # $1 = profile toml; returns 1 on any violation - local f="$1" v=0 prof c - grep -Eq '^profile = "' "$f" || { echo " missing 'profile' key" >&2; v=1; } - grep -Eq '^version = "' "$f" || { echo " missing 'version' key" >&2; v=1; } - grep -Eq '^status = "' "$f" || { echo " missing 'status' key" >&2; v=1; } - prof="$(sed -nE 's/^profile = "([^"]+)".*/\1/p' "$f" | head -1)" - - # Flags: stated -> must be false; required by role -> must be present. - if grep -q '^enforcement_http_430' "$f"; then - grep -Eq '^enforcement_http_430 = false$' "$f" \ - || { echo " enforcement_http_430 stated but not false — 430 cannot be silently enabled" >&2; v=1; } - fi - if grep -q '^auto_start_daemon' "$f"; then - grep -Eq '^auto_start_daemon = false$' "$f" \ - || { echo " auto_start_daemon stated but not false — no daemon may auto-start" >&2; v=1; } - fi - case "$prof" in - consent-aware-web|full-expert) - grep -Eq '^enforcement_http_430 = false$' "$f" \ - || { echo " $prof must carry enforcement_http_430 = false explicitly" >&2; v=1; } ;; - esac - case "$prof" in - authoritative-dns|full-expert) - grep -Eq '^auto_start_daemon = false$' "$f" \ - || { echo " $prof must carry auto_start_daemon = false explicitly" >&2; v=1; } ;; - esac - case "$prof" in - *rogue*|*control*) : ;; # planted controls need not satisfy role rules beyond the flags - esac - - # full-expert composes EXACTLY the four, explicitly. - if [ "$prof" = "full-expert" ]; then - local inc; inc="$(sed -nE 's/^includes = \[(.*)\].*/\1/p' "$f")" - for want in baseline-site consent-aware-web authoritative-dns privacy-enhanced; do - case "$inc" in *"$want"*) : ;; *) echo " full-expert.includes omits $want" >&2; v=1 ;; esac - done - fi - - # Every component path must exist in the bundle. - # Process substitution (not a pipe) so violations set v in THIS shell; - # `|| true` around grep so an empty array is not a pipefail "failure". - while IFS= read -r c; do - [ -n "$c" ] || continue - [ -e "$REPO/$c" ] || { echo " component path does not exist: $c" >&2; v=1; } - done < <(sed -nE 's/^components = \[(.*)\].*/\1/p' "$f" | { grep -o '"[^"]*"' || true; } | tr -d '"') - - # requires/includes must resolve to sibling profile files. - for key in requires includes; do - while IFS= read -r c; do - [ -n "$c" ] || continue - [ -f "$WWW/profiles/$c.toml" ] || { echo " $key references missing profile: $c" >&2; v=1; } - done < <(sed -nE "s/^$key = \[(.*)\].*/\1/p" "$f" | { grep -o '"[^"]*"' || true; } | tr -d '"') - done - return "$v" -} - -shopt -s nullglob -for p in "$WWW"/profiles/*.toml; do - if assert_profile "$p" 2>/dev/null; then - ok "profile valid: profiles/$(basename "$p")" - else - bad "profile invalid: profiles/$(basename "$p")" - assert_profile "$p" 2>&1 | sed 's/^/ /' >&2 || true - fi -done -for c in "$WWW"/tests/controls/profile-*.control.toml; do - if assert_profile "$c" 2>/dev/null; then - bad "planted control was NOT rejected: tests/controls/$(basename "$c")" - else - ok "planted control rejected: tests/controls/$(basename "$c")" - fi -done - -exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-publication-boundary.sh b/czech-file-knife/www/tests/check-publication-boundary.sh deleted file mode 100755 index 81da361b5..000000000 --- a/czech-file-knife/www/tests/check-publication-boundary.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# check-publication-boundary.sh — prove the publication boundary (issue #53). -# -# Modes: -# (no args) bundle invariants + planted-control self-test: -# A. schemas/publishable-paths.txt declares exactly -# www/public/ and www/.well-known/; -# B. every webservers/ example keeps its document root -# OUTSIDE the bundle and denies all eight operational -# categories; every planted *.control config violates -# B and must be rejected; -# C. the planted good stage passes the stage scan and -# the planted bad stage is rejected. -# --stage scan a staged deployment directory (as produced by -# runbooks/deploy.adoc) and exit non-zero on any -# operational material. This is the check the runbook -# and CI invoke before anything reaches a docroot. -# -# Operational categories (never publishable): dns tls security_headers -# webservers profiles schemas tests runbooks. Secret/runtime patterns are -# rejected anywhere in a stage. - -set -uo pipefail - -WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -FORBIDDEN_DIRS="dns tls security_headers webservers profiles schemas tests runbooks" -FAIL=0 -bad() { echo "BOUNDARY FAIL: $*" >&2; FAIL=1; } -ok() { echo "ok: $*"; } - -# ── stage scanner ──────────────────────────────────────────────────────────── -scan_stage() { # $1 = staged dir; returns 1 on any violation (messages to stderr) - local root="$1" v f base found=0 - [ -d "$root" ] || { echo "stage scan: $root is not a directory" >&2; return 1; } - for v in $FORBIDDEN_DIRS; do - if [ -e "$root/$v" ]; then - echo "stage scan: operational category present: $v/" >&2; found=1 - fi - done - while IFS= read -r f; do - base="${f##*/}" - case "$base" in - *.key|*.pem|*.p12|*.pfx|*.jks|*.rndc|*.jbk|*.control|\ - named.conf*|rndc.conf*|tsig*|*.zone|*.journal|*.pid|*.log) - echo "stage scan: operational/secret material present: ${f#"$root"/}" >&2 - found=1 ;; - esac - done < <(find "$root" -type f) - return "$found" -} - -# ── config scanner ─────────────────────────────────────────────────────────── -scan_config() { # $1 = server config; returns 1 if it could publish the bundle - local f="$1" r found=0 v - # Document-root directives must not point at the bundle or into it. - while IFS= read -r r; do - [ -n "$r" ] || continue - case "$r" in - */www|*/www/|*/www/*|www|www/) - echo "config scan: document root points into the www/ bundle: $r" >&2; found=1 ;; - *dns*|*tls*|*security_headers*|*webservers*|*profiles*|*schemas*|*tests*|*runbooks*) - echo "config scan: document root points at operational material: $r" >&2; found=1 ;; - esac - done < <(sed -nE 's/^[[:space:]]*root[[:space:]]+\*[[:space:]]*([^;[:space:]]+).*$/\1/p - s/^[[:space:]]*root[[:space:]]+([^;[:space:]]+).*$/\1/p - s/^[[:space:]]*DocumentRoot[[:space:]]+([^[:space:]]+).*$/\1/p' "$f") - # Every operational category must be named in the config (deny coverage)… - for v in $FORBIDDEN_DIRS; do - grep -q "$v" "$f" || { echo "config scan: no deny coverage for category '$v'" >&2; found=1; } - done - # …and at least one explicit deny verb must be present. - grep -qE 'respond @operational 404|return 404;|Require all denied' "$f" \ - || { echo "config scan: no explicit deny directive" >&2; found=1; } - return "$found" -} - -# ── --stage mode ───────────────────────────────────────────────────────────── -if [ "${1:-}" = "--stage" ]; then - [ -n "${2:-}" ] || { echo "usage: $0 --stage " >&2; exit 2; } - if scan_stage "$2"; then - echo "publication boundary: stage $2 is clean" - exit 0 - else - echo "publication boundary: stage $2 REJECTED" >&2 - exit 1 - fi -fi - -# The publication boundary only exists once a repository PUBLISHES something. -# The bundle's schemas/, webservers/, dns/ and tls/ trees are what a site is -# built from; a library that serves no document root has no boundary to police -# and no shipped config to audit. Sections A, B and C all test that tree, so -# the guard is one test for all three rather than a rewrite of each. -# -# This is the guard whose absence planted three failing checks into every -# swept repository measured on 2026-09-18. `--stage ` mode above is -# unaffected: it is invoked by the deploy runbook against a real stage. -if [ ! -f "$WWW/schemas/publishable-paths.txt" ] && [ ! -d "$WWW/webservers" ]; then - echo "SKIP: this repository publishes nothing (no www/schemas/ and no" - echo "SKIP: www/webservers/) — the publication boundary does not apply" - exit 77 -fi - -# ── A. declaration ─────────────────────────────────────────────────────────── -DECL="$WWW/schemas/publishable-paths.txt" -if [ ! -f "$DECL" ]; then - bad "schemas/publishable-paths.txt missing" -else - declared="$(grep -v '^#' "$DECL" | grep -v '^[[:space:]]*$' | sort)" - expected="$(printf 'www/.well-known/\nwww/public/')" - if [ "$declared" = "$expected" ]; then - ok "publishable-paths.txt declares exactly www/public/ + www/.well-known/" - else - bad "publishable-paths.txt must declare exactly the two publishable categories; found: $(echo "$declared" | tr '\n' ' ')" - fi -fi - -# ── B. shipped configs pass; planted control configs fail ─────────────────── -shopt -s nullglob -for cfg in "$WWW"/webservers/*/*.example; do - if scan_config "$cfg" 2>/dev/null; then - ok "config implements the boundary: ${cfg#"$WWW"/}" - else - bad "shipped config violates the boundary: ${cfg#"$WWW"/}" - scan_config "$cfg" 2>&1 | sed 's/^/ /' >&2 || true - fi -done -for ctl in "$WWW"/tests/controls/*.control; do - if scan_config "$ctl" 2>/dev/null; then - bad "planted control was NOT rejected (control is broken): ${ctl#"$WWW"/}" - else - ok "planted control rejected: ${ctl#"$WWW"/}" - fi -done - -# ── C. staged-tree controls ────────────────────────────────────────────────── -if scan_stage "$WWW/tests/controls/good-deploy" 2>/dev/null; then - ok "planted good stage accepted" -else - bad "planted good stage was rejected (control is broken)" -fi -if scan_stage "$WWW/tests/controls/bad-deploy" 2>/dev/null; then - bad "planted bad stage was NOT rejected (boundary is unenforced)" -else - ok "planted bad stage rejected" -fi - -exit "$FAIL" diff --git a/czech-file-knife/www/tests/check-wellknown.sh b/czech-file-knife/www/tests/check-wellknown.sh deleted file mode 100755 index 2f1e62f09..000000000 --- a/czech-file-knife/www/tests/check-wellknown.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: CC-BY-SA-4.0 -# -# check-wellknown.sh — validate the canonical www/.well-known/ contents. -# Tolerates unminted {{TOKEN}} placeholders (this suite runs in the template -# itself as well as in minted repos). - -set -uo pipefail -WWW="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -WK="$WWW/.well-known" -FAIL=0 -bad() { echo "WELLKNOWN FAIL: $*" >&2; FAIL=1; } -ok() { echo "ok: $*"; } - -for f in security.txt ai.txt humans.txt; do - [ -f "$WK/$f" ] && ok "$f present" || bad "$f missing from www/.well-known/" -done - -# RFC 9116: Contact and Expires are required fields. -if [ -f "$WK/security.txt" ]; then - grep -q '^Contact:' "$WK/security.txt" && ok "security.txt has Contact" || bad "security.txt missing Contact" - if grep -q '^Expires:' "$WK/security.txt"; then - EXP=$(grep '^Expires:' "$WK/security.txt" | head -1 | cut -d: -f2- | tr -d ' ') - case "$EXP" in - *'{{'*) ok "security.txt Expires carries a mint placeholder (pre-mint)" ;; - *) - if date -d "$EXP" >/dev/null 2>&1; then - DAYS=$(( ($(date -d "$EXP" +%s) - $(date +%s)) / 86400 )) - [ "$DAYS" -ge 0 ] && ok "security.txt Expires valid ($DAYS days)" || bad "security.txt EXPIRED" - else - bad "security.txt Expires is not a parseable timestamp: $EXP" - fi ;; - esac - else - bad "security.txt missing Expires" - fi -fi - -# ai.txt: the stance lines the estate's ai.txt convention requires. -if [ -f "$WK/ai.txt" ]; then - grep -q '^User-Agent:' "$WK/ai.txt" && ok "ai.txt has User-Agent" || bad "ai.txt missing User-Agent" - grep -q '^Disallow-Training:' "$WK/ai.txt" && ok "ai.txt has Disallow-Training" || bad "ai.txt missing Disallow-Training" -fi - -# humans.txt: humanstxt.org section markers. -if [ -f "$WK/humans.txt" ]; then - grep -q '/\* TEAM \*/' "$WK/humans.txt" && ok "humans.txt has TEAM" || bad "humans.txt missing TEAM section" - grep -q '/\* SITE \*/' "$WK/humans.txt" && ok "humans.txt has SITE" || bad "humans.txt missing SITE section" -fi - -# Migration window: a repository-root .well-known/ alongside the bundle is -# legacy; warn (do not fail) — scripts/migrate-wellknown-to-www.sh resolves it. -if [ -d "$WWW/../.well-known" ]; then - echo "WELLKNOWN WARN: legacy root .well-known/ still present — run scripts/migrate-wellknown-to-www.sh" >&2 -fi - -exit "$FAIL" diff --git a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json deleted file mode 100644 index c2512ee7b..000000000 --- a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "_comment": "PLANTED CONTROL — must be REJECTED by check-aibdp.sh: unknown policy status vocabulary.", - "aibdp_version": "0.2", - "status": "experimental — declaration-only", - "contact": "mailto:sec@example.invalid", - "policies": { "training": { "status": "yolo", "rationale": "planted control" } } -} diff --git a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license b/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license deleted file mode 100644 index 8b66169a0..000000000 --- a/czech-file-knife/www/tests/controls/aibdp-bad-status.control.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json deleted file mode 100644 index 01cfef240..000000000 --- a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "_comment": "PLANTED CONTROL — must be REJECTED by check-aibdp.sh: it claims http_430 enforcement, which no declaration in this bundle may make.", - "aibdp_version": "0.2", - "status": "experimental — declaration-only", - "contact": "mailto:sec@example.invalid", - "policies": { "training": { "status": "disallowed", "rationale": "planted control" } }, - "enforcement": { "mechanism": "http_430" } -} diff --git a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license b/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license deleted file mode 100644 index 8b66169a0..000000000 --- a/czech-file-knife/www/tests/controls/aibdp-enforce-430.control.json.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -SPDX-License-Identifier: MPL-2.0 diff --git a/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf b/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf deleted file mode 100644 index 8f235aa78..000000000 --- a/czech-file-knife/www/tests/controls/bad-deploy/dns/named.conf +++ /dev/null @@ -1 +0,0 @@ -options { recursion yes; }; diff --git a/czech-file-knife/www/tests/controls/bad-deploy/index.html b/czech-file-knife/www/tests/controls/bad-deploy/index.html deleted file mode 100644 index 7891dc753..000000000 --- a/czech-file-knife/www/tests/controls/bad-deploy/index.html +++ /dev/null @@ -1 +0,0 @@ -bad stage diff --git a/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh b/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh deleted file mode 100644 index 1bad81edb..000000000 --- a/czech-file-knife/www/tests/controls/bad-deploy/tests/run-all.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -planted diff --git a/czech-file-knife/www/tests/controls/caddy-serve-everything.control b/czech-file-knife/www/tests/controls/caddy-serve-everything.control deleted file mode 100644 index 145e40d49..000000000 --- a/czech-file-knife/www/tests/controls/caddy-serve-everything.control +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# PLANTED CONTROL — must be REJECTED by check-publication-boundary.sh. -# It points Caddy at the www/ bundle wholesale and denies nothing. -site.example.invalid { - root * /srv/repo/www - file_server -} diff --git a/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt b/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt deleted file mode 100644 index 6133ffa0d..000000000 --- a/czech-file-knife/www/tests/controls/good-deploy/.well-known/security.txt +++ /dev/null @@ -1,2 +0,0 @@ -Contact: mailto:sec@example.invalid -Expires: 2099-01-01T00:00:00.000Z diff --git a/czech-file-knife/www/tests/controls/good-deploy/errors/404.html b/czech-file-knife/www/tests/controls/good-deploy/errors/404.html deleted file mode 100644 index d71ff47e5..000000000 --- a/czech-file-knife/www/tests/controls/good-deploy/errors/404.html +++ /dev/null @@ -1 +0,0 @@ -404 diff --git a/czech-file-knife/www/tests/controls/good-deploy/index.html b/czech-file-knife/www/tests/controls/good-deploy/index.html deleted file mode 100644 index 89e95fc31..000000000 --- a/czech-file-knife/www/tests/controls/good-deploy/index.html +++ /dev/null @@ -1 +0,0 @@ -good stage diff --git a/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html b/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html deleted file mode 100644 index 4161a5847..000000000 --- a/czech-file-knife/www/tests/controls/good-deploy/policies/privacy.html +++ /dev/null @@ -1 +0,0 @@ -privacy diff --git a/czech-file-knife/www/tests/controls/named.conf.open-recursion.control b/czech-file-knife/www/tests/controls/named.conf.open-recursion.control deleted file mode 100644 index c32bccebd..000000000 --- a/czech-file-knife/www/tests/controls/named.conf.open-recursion.control +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// PLANTED CONTROL — must be REJECTED by check-bind-safety.sh. -// This is exactly what an open resolver looks like: recursion on, queries -// and transfers unrestricted, listening everywhere, TSIG secret inline, -// and a live (non-reserved) zone name. -options { - directory "/var/cache/bind"; - recursion yes; - allow-query { any; }; - allow-transfer { any; }; - listen-on port 53 { any; }; -}; -key "leak" { - algorithm hmac-sha256; - secret "cGxhbnRlZC1jb250cm9sLW5vdC1hLXJlYWwtc2VjcmV0"; -}; -zone "real-domain.example" { - type primary; - file "zones/real.zone"; -}; diff --git a/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml b/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml deleted file mode 100644 index 40a6d40ec..000000000 --- a/czech-file-knife/www/tests/controls/profile-enforce-430.control.toml +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# PLANTED CONTROL — must be REJECTED by check-profiles.sh: it tries to -# enable HTTP 430 enforcement, which no profile in this bundle may do. -profile = "consent-aware-web-rogue-control" -version = "0.0.1" -status = "planted-control" -enforcement_http_430 = true -components = [] diff --git a/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml b/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml deleted file mode 100644 index 779a653e7..000000000 --- a/czech-file-knife/www/tests/controls/profile-hidden-start.control.toml +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# PLANTED CONTROL — must be REJECTED by check-profiles.sh: it tries to -# auto-start the DNS daemon, which minting/selection must never do. -profile = "authoritative-dns-rogue-control" -version = "0.0.1" -status = "planted-control" -auto_start_daemon = true -components = [] diff --git a/czech-file-knife/www/tests/run-all.sh b/czech-file-knife/www/tests/run-all.sh deleted file mode 100755 index 5d2294205..000000000 --- a/czech-file-knife/www/tests/run-all.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# -# run-all.sh — execute every www/tests/check-*.sh probe in order. -# Exit non-zero on the first failing check (all checks are listed at the end). - -set -uo pipefail -here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Three outcomes, not two. A check whose INPUTS DO NOT APPLY to this repository -# (it publishes nothing, it declares no AIBDP, it has no migrator to test) must -# be able to say so. The alternative is what actually happened: the bundle was -# written for the template, and every repository it reached that was not the -# template got a red check it could do nothing about. A suite that fails where -# it does not apply teaches people to ignore it. -# -# SKIP is declared, never inferred. The check must exit 77 AND print a line -# beginning "SKIP:". Exit 77 alone is not enough — a crash can produce any -# status, and a crash reported as a skip would be a lie of exactly the kind -# this estate keeps finding in its own documentation. -fail=0 -ran=0 -skipped=0 -for check in "$here"/check-*.sh; do - [ -f "$check" ] || continue - ran=$((ran + 1)) - name="$(basename "$check")" - out="$(mktemp)" - if bash "$check" >"$out" 2>&1; then - rc=0 - else - rc=$? - fi - cat "$out" - if [ "$rc" -eq 0 ]; then - echo "PASS $name" - elif [ "$rc" -eq 77 ] && grep -q '^SKIP:' "$out"; then - echo "SKIP $name" - skipped=$((skipped + 1)) - else - echo "FAIL $name" >&2 - fail=$((fail + 1)) - fi - rm -f "$out" -done - -echo "www/tests: ran $ran check(s), $fail failure(s), $skipped skipped" -if [ "$skipped" -gt 0 ]; then - echo "www/tests: $skipped check(s) did not apply here — see the SKIP lines above." - echo "www/tests: this is not full coverage of the bundle." -fi -[ "$fail" -eq 0 ] diff --git a/czech-file-knife/www/tls/POLICY.adoc b/czech-file-knife/www/tls/POLICY.adoc deleted file mode 100644 index 3b85ff418..000000000 --- a/czech-file-knife/www/tls/POLICY.adoc +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= TLS Policy - -== Key custody — the only rule that matters - -Private keys, issued-certificate secrets, TSIG secrets, CSR material, -keystores (`.key`, `.pem`, `.p12`, `.pfx`, `.jks`), journals, caches, PID -files and logs **never** live in this repository or the bundle. ACME state -lives under the ACME client's own spool (`/var/lib/letsencrypt`, -Caddy's data dir). The stage scanner -(`www/tests/check-publication-boundary.sh --stage`) and the DNS safety -check reject these patterns anywhere they should not be, and the estate -secret scanner covers history. - -== Issuance - -. *ACME first.* Caddy obtains and renews automatically; nginx/Apache via - certbot (or the estate's chosen ACME tooling). DNS-01 challenges for - wildcard or non-public names — which is where the `authoritative-dns` - profile's zone discipline pays off. -. *Manual issuance* is a documented exception with a rotation date in the - calendar, not a default. - -== Parameters - -[cols="1,2", options="header"] -|=== -| Item | Policy - -| Protocols -| TLS 1.2 and 1.3 only. No SSLv3/TLS 1.0/1.1 anywhere. - -| Keys -| ECDSA P-256 preferred; RSA >= 3072 where a counterpart requires RSA. - -| Certificates -| 90-day ACME lifetime; renewal automated at <= 30 days remaining; - monitoring alert at 21 days (a missed renewal is an incident, not a - surprise). - -| HSTS -| `max-age=31536000; includeSubDomains` once HTTPS is stable; add `preload` - only accepting the multi-month un-preload cycle. - -| CT -| Expect all public certificates to appear in CT logs; monitor the name - (e.g. crt.sh-style watch) and treat unexpected certificates as incidents. - -| ECH -| Tracked as *readiness*, not promise: see - `www/dns/privacy/ENCRYPTED-DNS.adoc` for detection discipline (HTTPS RR / - TYPE65). Publish support only where a dated probe demonstrates it. -|=== - -== Rotation - -Normal rotation is automatic (ACME). Emergency replacement (compromise, -mis-issuance) follows `www/runbooks/cert-rotation.adoc`, including -revocation and the CT consequences of both. diff --git a/czech-file-knife/www/webservers/README.adoc b/czech-file-knife/www/webservers/README.adoc deleted file mode 100644 index 022275081..000000000 --- a/czech-file-knife/www/webservers/README.adoc +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= `webservers/` — Integration Examples - -Three starting-hand configurations implementing the publication boundary -for a staged document root (see `www/runbooks/deploy.adoc`): - -[cols="1,2", options="header"] -|=== -| File | Server - -| `caddy/Caddyfile.example` -| Caddy 2 — automatic HTTPS; header block carries the canonical set. - -| `nginx/site.conf.example` -| nginx — `server` block for inclusion from `sites-available`; TLS material - referenced from outside the repository. - -| `apache/vhost.conf.example` -| Apache httpd 2.4 — `VirtualHost` with `mod_headers`. -|=== - -All three: - -. point their document root at a **staged directory** produced by the deploy - runbook — never at the `www/` bundle itself; -. serve `/.well-known/` explicitly; -. deny the operational categories (`dns`, `tls`, `security_headers`, - `webservers`, `profiles`, `schemas`, `tests`, `runbooks`) as defence in - depth, even though a correct stage never contains them; -. map 4xx/5xx statuses onto the staged `errors/` bodies; -. embed the header values from `www/security_headers/csp.conf` (keep in - sync). - -Hostnames use `site.example.invalid` (RFC 2606 reserved). TLS certificate -paths are placeholders; see `www/tls/POLICY.adoc` — keys never live in the -repository. - -`www/tests/check-publication-boundary.sh` statically scans these examples: -a document root inside the bundle, or a missing operational deny, fails the -check. The planted control `www/tests/controls/caddy-serve-everything.control` -demonstrates the rejection. diff --git a/czech-file-knife/www/webservers/apache/vhost.conf.example b/czech-file-knife/www/webservers/apache/vhost.conf.example deleted file mode 100644 index 55555c7cb..000000000 --- a/czech-file-knife/www/webservers/apache/vhost.conf.example +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Apache httpd 2.4 example for the www/ site-operations bundle (issue #53). -# Requires: mod_headers, mod_ssl (cert paths are placeholders — see -# www/tls/POLICY.adoc; keys never live in the repository). -# DocumentRoot is the STAGED directory produced by www/runbooks/deploy.adoc — -# NEVER the www/ bundle itself. - - - ServerName site.example.invalid - - DocumentRoot /srv/site.example.invalid - - # SSLEngine on - # SSLCertificateFile /etc/letsencrypt/live/site.example.invalid/fullchain.pem - # SSLCertificateKeyFile /etc/letsencrypt/live/site.example.invalid/privkey.pem - # SSLProtocol -all +TLSv1.2 +TLSv1.3 - - - Require all granted - Options -Indexes - AllowOverride None - - - # Explicit publication of .well-known. - Alias /.well-known/ /srv/site.example.invalid/.well-known/ - - Require all granted - - - # Defence in depth: refuse operational categories (never staged, but a - # bad stage must not publish them). - - Require all denied - - - # Canonical header set — keep in sync with www/security_headers/csp.conf. - Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" - Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - Header always set X-Content-Type-Options "nosniff" - Header always set X-Frame-Options "DENY" - Header always set Referrer-Policy "strict-origin-when-cross-origin" - Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" - Header always set Cross-Origin-Opener-Policy "same-origin" - Header always set Cross-Origin-Resource-Policy "same-origin" - Header unset Server - Header unset X-Powered-By - - # Staged error bodies. - ErrorDocument 403 /errors/403.html - ErrorDocument 404 /errors/404.html - ErrorDocument 429 /errors/429.html - ErrorDocument 500 /errors/500.html - ErrorDocument 502 /errors/502.html - ErrorDocument 503 /errors/503.html - - - - ServerName site.example.invalid - Redirect permanent / https://site.example.invalid/ - diff --git a/czech-file-knife/www/webservers/caddy/Caddyfile.example b/czech-file-knife/www/webservers/caddy/Caddyfile.example deleted file mode 100644 index 005c28869..000000000 --- a/czech-file-knife/www/webservers/caddy/Caddyfile.example +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# Caddy 2 example for the www/ site-operations bundle (issue #53). -# The document root is the STAGED directory produced by www/runbooks/deploy.adoc -# (public/ content + .well-known/ + errors/ + rendered policies) — NEVER the -# www/ bundle itself. Hostname is RFC 2606 reserved; TLS is automatic once the -# name is real (see www/tls/POLICY.adoc). - -site.example.invalid { - root * /srv/site.example.invalid - encode zstd gzip - - # Canonical header set — keep in sync with www/security_headers/csp.conf. - header { - Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - X-Content-Type-Options "nosniff" - X-Frame-Options "DENY" - Referrer-Policy "strict-origin-when-cross-origin" - Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" - Cross-Origin-Opener-Policy "same-origin" - Cross-Origin-Resource-Policy "same-origin" - -Server - } - - # Defence in depth: operational categories are never staged, but if a bad - # stage ever slips through, refuse them explicitly. - @operational path /dns/* /tls/* /security_headers/* /webservers/* /profiles/* /schemas/* /tests/* /runbooks/* - respond @operational 404 - - # .well-known is ordinary staged content (dotfiles included by file_server); - # no special-casing needed beyond keeping it out of any dotfile blocking. - - handle_errors { - rewrite * /errors/{err.status_code}.html - file_server - } - - file_server -} diff --git a/czech-file-knife/www/webservers/nginx/site.conf.example b/czech-file-knife/www/webservers/nginx/site.conf.example deleted file mode 100644 index f9051d60d..000000000 --- a/czech-file-knife/www/webservers/nginx/site.conf.example +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# -# nginx example for the www/ site-operations bundle (issue #53). -# Place in /etc/nginx/sites-available/ and symlink to sites-enabled/. -# The root is the STAGED directory produced by www/runbooks/deploy.adoc — -# NEVER the www/ bundle itself. TLS material lives outside the repository -# (see www/tls/POLICY.adoc). -# -# Requirements: nginx >= 1.25.1 (for 'http2 on;'). Before `nginx -t` can -# pass, the two ssl_certificate lines MUST be uncommented and pointed at -# real material — 'listen ... ssl' without a certificate is a hard error. -# Verified with nginx 1.26.3 (`nginx -t`: syntax ok) against a throwaway -# self-signed certificate; this file intentionally ships the cert paths -# commented so no environment-specific paths are baked into the template. - -server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - server_name site.example.invalid; - - root /srv/site.example.invalid; - index index.html; - - # Certificate/key paths — provisioned by ACME or ops tooling, never git. - # ssl_certificate /etc/letsencrypt/live/site.example.invalid/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/site.example.invalid/privkey.pem; - # ssl_protocols TLSv1.2 TLSv1.3; - - # Canonical header set — keep in sync with www/security_headers/csp.conf. - add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; upgrade-insecure-requests" always; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-Frame-Options "DENY" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), browsing-topics=()" always; - add_header Cross-Origin-Opener-Policy "same-origin" always; - add_header Cross-Origin-Resource-Policy "same-origin" always; - - # Defence in depth: refuse operational categories (never staged, but a - # bad stage must not publish them). - location ~ ^/(dns|tls|security_headers|webservers|profiles|schemas|tests|runbooks)(/|$) { - return 404; - } - - # Explicit publication of .well-known (prefix match wins over the - # dotfile deny below via ^~). - location ^~ /.well-known/ { - try_files $uri =404; - } - - # Deny any other dotfile/dotdir that might slip into a stage. - location ~ /\. { - deny all; - } - - # Staged error bodies. - error_page 403 /errors/403.html; - error_page 404 /errors/404.html; - error_page 429 /errors/429.html; - error_page 500 /errors/500.html; - error_page 502 /errors/502.html; - error_page 503 /errors/503.html; - location /errors/ { - internal; - } -} - -server { - listen 80; - listen [::]:80; - server_name site.example.invalid; - return 301 https://$host$request_uri; -}