use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
struct Fixture {
root: PathBuf,
}
impl Fixture {
fn new(label: &str, schema: &str) -> Self {
let ordinal = NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"noxid-wo40-{label}-{}-{ordinal}",
std::process::id()
));
fs::create_dir_all(root.join("server/api")).expect("create endpoint directory");
fs::create_dir_all(root.join("server/utils")).expect("create server utils directory");
fs::write(root.join("Noxid.toml"), "[app]\ntitle = \"WO-40\"\n").expect("write manifest");
fs::write(root.join("package.json"), "{\"type\":\"module\"}\n")
.expect("write package metadata");
fs::write(
root.join("server/api/probe.get.nox"),
"endpoint Probe { result: String }\n",
)
.expect("write endpoint");
fs::write(
root.join("server/host.ts"),
"import { progress } from './utils/schema.js';\nvoid progress;\nexport const endpoints = Object.freeze({ 'endpoint:Probe@1': async () => 'ok' });\n",
)
.expect("write host");
fs::write(root.join("server/utils/schema.ts"), schema).expect("write schema");
Self { root }
}
fn build(&self) -> Output {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.args(["build", ".", "--out-dir", "dist"])
.current_dir(&self.root)
.output()
.expect("execute noxid build")
}
fn write(&self, relative: &str, source: &str) {
let path = self.root.join(relative);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create fixture parent");
}
fs::write(path, source).expect("write fixture source");
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn output_text(output: &Output) -> String {
format!(
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
fn files_with_unsafe_calls(root: &Path, relative: &Path, found: &mut BTreeSet<String>) {
let path = root.join(relative);
if path.is_dir() {
for entry in fs::read_dir(&path).expect("read source directory") {
let entry = entry.expect("read source entry");
let child = relative.join(entry.file_name());
files_with_unsafe_calls(root, &child, found);
}
} else if matches!(
path.extension().and_then(|value| value.to_str()),
Some("js" | "mjs")
) && fs::read_to_string(&path)
.expect("read JavaScript source")
.contains(".unsafe(")
{
found.insert(relative.to_string_lossy().replace('\\', "/"));
}
}
fn advisory_no_query_contract(security_manifest: &str, openapi: &str) -> Result<(), String> {
if !security_manifest.contains("\"policy\":\"scoped\"") {
return Ok(());
}
let normalized = openapi.to_ascii_lowercase();
for field in [
"sql",
"rawsql",
"raw_sql",
"querytext",
"query_text",
"statement",
] {
if normalized.contains(&format!("\"{field}\"")) {
return Err(format!(
"advisory[RAW_QUERY_CONTRACT]: typed agent surface exposes SQL-shaped field `{field}` while scoped data exists"
));
}
}
Ok(())
}
#[test]
fn adapter_enforces_declared_scopes_and_runtime_principal_authority() {
let repository = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let output = Command::new("node")
.args(["--test", "plugins/drizzle-orm/data-scopes.test.mjs"])
.current_dir(repository)
.output()
.expect("run adapter scope tests");
assert!(output.status.success(), "{}", output_text(&output));
}
#[test]
fn build_emits_canonical_declared_table_policies_and_guarantee() {
let fixture = Fixture::new(
"manifest",
r#"
export const publicConfig = unscopedTable(pgTable("public_config", {}));
export const progress = scopedTable(pgTable("progress", {}), "user_id");
"#,
);
let output = fixture.build();
assert!(output.status.success(), "{}", output_text(&output));
let manifest =
fs::read_to_string(fixture.root.join("dist/server/security.manifest.json")).unwrap();
assert!(manifest.contains("\"schemaVersion\":10"), "{manifest}");
assert!(
manifest.contains("Agents have no query surface"),
"{manifest}"
);
assert!(manifest.contains(
"\"dataPolicies\":[{\"table\":\"progress\",\"policy\":\"scoped\",\"principalColumn\":\"user_id\",\"principalType\":null},{\"table\":\"public_config\",\"policy\":\"unscoped\",\"principalColumn\":null,\"principalType\":null}]"
), "{manifest}");
}
#[test]
fn build_refuses_a_table_without_an_explicit_policy() {
let fixture = Fixture::new(
"undeclared",
"export const progress = pgTable(\"progress\", {});\n",
);
let output = fixture.build();
assert!(!output.status.success(), "bare table unexpectedly built");
let text = output_text(&output);
assert!(text.contains("error[DATA_POLICY_UNDECLARED]"), "{text}");
assert!(text.contains("scopedTable"), "{text}");
assert!(text.contains("unscopedTable"), "{text}");
}
#[test]
fn developer_server_entry_is_trusted_with_dynamic_driver_assembly() {
let fixture = Fixture::new(
"unsafe-sql",
"export const progress = scopedTable(pgTable(\"progress\", {}), \"user_id\");\n",
);
fixture.write(
"server/host.ts",
r#"const driver = await import("post" + "gres");
void driver;
export const endpoints = Object.freeze({ "endpoint:Probe@1": async () => "ok" });
"#,
);
let output = fixture.build();
assert!(output.status.success(), "{}", output_text(&output));
assert!(fixture.root.join("dist/server/handler.js").is_file());
}
#[test]
fn trusted_developer_server_entry_builds_with_inert_decoys() {
let fixture = Fixture::new(
"unsafe-sql-decoys",
"export const progress = scopedTable(pgTable(\"progress\", {}), \"user_id\");\n",
);
fixture.write(
"server/host.ts",
r#"// import("post" + "gres"); client.unsafe("SELECT secret");
const teaching = 'from"postgres" .unsafe( __installNoxidPrincipalAuthority "SELECT " + value';
void teaching;
export const endpoints = Object.freeze({ "endpoint:Probe@1": async () => "ok" });
"#,
);
let output = fixture.build();
assert!(output.status.success(), "{}", output_text(&output));
}
#[test]
fn unsafe_sql_allowlist_is_exactly_the_two_documented_ddl_owners() {
const ALLOWLIST: [(&str, &str); 2] = [
(
"tools/noxid-db.mjs",
"forward migration and PostgreSQL RLS DDL owned by noxid db",
),
(
"crates/codegen-server-js/src/lib.rs",
"idempotent _noxid_jobs bootstrap DDL owned by the generated queue runtime",
),
];
let repository = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let mut found = BTreeSet::new();
files_with_unsafe_calls(&repository, Path::new("tools"), &mut found);
files_with_unsafe_calls(&repository, Path::new("plugins"), &mut found);
let generated = Path::new("crates/codegen-server-js/src/lib.rs");
if fs::read_to_string(repository.join(generated))
.expect("read generated server runtime")
.contains(".unsafe(")
{
found.insert(generated.to_string_lossy().into_owned());
}
let allowed = ALLOWLIST
.iter()
.map(|(path, rationale)| {
assert!(!rationale.is_empty());
(*path).to_string()
})
.collect::<BTreeSet<_>>();
assert_eq!(
found, allowed,
"unsafe SQL ownership changed; review and keep the allowlist exact"
);
}
#[test]
fn advisory_manifest_lint_names_sql_shaped_agent_inputs() {
let fixture = Fixture::new(
"query-advisory",
"export const progress = scopedTable(pgTable(\"progress\", {}), \"user_id\");\n",
);
fixture.write(
"server/api/probe.get.nox",
"endpoint Probe { query { sql: String } result: String }\n",
);
let output = fixture.build();
assert!(output.status.success(), "{}", output_text(&output));
let security = fs::read_to_string(fixture.root.join("dist/server/security.manifest.json"))
.expect("read security manifest");
let openapi =
fs::read_to_string(fixture.root.join("dist/api.openapi.json")).expect("read OpenAPI");
let advisory = advisory_no_query_contract(&security, &openapi)
.expect_err("SQL-shaped free text should be named by the advisory lint");
assert!(advisory.contains("advisory[RAW_QUERY_CONTRACT]"));
assert!(advisory.contains("`sql`"));
let clean = Fixture::new(
"query-advisory-clean",
"export const progress = scopedTable(pgTable(\"progress\", {}), \"user_id\");\n",
);
let output = clean.build();
assert!(output.status.success(), "{}", output_text(&output));
advisory_no_query_contract(
&fs::read_to_string(clean.root.join("dist/server/security.manifest.json")).unwrap(),
&fs::read_to_string(clean.root.join("dist/api.openapi.json")).unwrap(),
)
.expect("closed typed endpoint has no raw-query advisory");
}
#[test]
fn generated_audit_surface_allowlists_identity_and_scope_metadata_only() {
let fixture = Fixture::new(
"audit-fields",
"export const progress = scopedTable(pgTable(\"progress\", {}), \"user_id\");\n",
);
let output = fixture.build();
assert!(output.status.success(), "{}", output_text(&output));
let handler =
fs::read_to_string(fixture.root.join("dist/server/handler.js")).expect("read handler");
for field in [
"agentSemanticId",
"actingPrincipal",
"dataTable",
"scopeColumn",
] {
assert!(handler.contains(field), "missing audit field {field}");
}
assert!(handler.contains("\"data.access\""));
assert!(!handler.contains("rowValue"));
assert!(!handler.contains("rowValues"));
}