docs(acl): reconcile spec prose with the shipped implementation
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 7s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / build-macos (pull_request) Successful in 2m49s
Build App (Preview) / build-linux (pull_request) Successful in 5m16s
Build App (Preview) / build-windows (pull_request) Successful in 10m4s
Build App (Preview) / prune-previews (pull_request) Successful in 9s
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (pull_request) Successful in 7s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / build-macos (pull_request) Successful in 2m49s
Build App (Preview) / build-linux (pull_request) Successful in 5m16s
Build App (Preview) / build-windows (pull_request) Successful in 10m4s
Build App (Preview) / prune-previews (pull_request) Successful in 9s
Final-wave cleanups from the whole-branch review (final-review.md Minor 1-5): spec §4 now says selective pruning, not "deletes the directory every build"; spec §3.3 now describes the TypeScript-AST scan (fail-closed Vite-order resolution, namespace imports as member access only, the every-code-file boundary check) instead of the old regex/chunk description; the viewer spec's historical "every command is callable from every window" line gets a dated "closed by the AppManifest lockdown" note; the lib.rs doc comment on the_generated_app_manifest_matches_the_handler_list no longer claims independence from the shared parser it actually reuses; and the vitest command-name regex now allows digits, matching Rust's [a-z0-9_]+. Also adds a cargo test backstop (the_tauri_config_capability_check_runs_against_the_real_tree) that runs build.rs's tauri-config capability check against the real app/src-tauri tree on every `cargo test`, closing the gap where a new tauri.<platform>.conf.json on an already-built tree only gets checked by build.rs on a clean build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -972,12 +972,16 @@ mod tests {
|
||||
|
||||
/// `build.rs` derives the AppManifest from the handler list and this reads back what
|
||||
/// tauri-build actually embedded. `cargo test` runs the build script first, so
|
||||
/// `gen/schemas/acl-manifests.json` is fresh. Independent of the shared parser: if
|
||||
/// `registered_commands` ever lost half the list, `build.rs` would declare half a
|
||||
/// manifest and this would still compare it against… the same half. So the count is
|
||||
/// pinned too, from a source that is not the parser: the `#[tauri::command]` scan in
|
||||
/// `every_command_is_registered_exactly_once` guarantees definitions == registrations,
|
||||
/// and here the embedded set must match the number of registrations that scan found.
|
||||
/// `gen/schemas/acl-manifests.json` is fresh. This guards against the committed/generated
|
||||
/// artifact diverging from `generate_handler!` — a stale `acl-manifests.json`, or a
|
||||
/// tauri-build naming change — using the same `registered_commands` parser `build.rs` used
|
||||
/// to derive the manifest in the first place. It is *not* independent of a parser dropout on
|
||||
/// its own: if `registered_commands` lost half the list, `build.rs` would declare half a
|
||||
/// manifest and this would still compare it against the same half. That guarantee is
|
||||
/// transitive, not local — `every_command_is_registered_exactly_once` covers it, by
|
||||
/// cross-checking the parser's output against an independent `#[tauri::command]` scan, so a
|
||||
/// parser regression that silently dropped commands fails there rather than going unnoticed
|
||||
/// here.
|
||||
#[test]
|
||||
fn the_generated_app_manifest_matches_the_handler_list() {
|
||||
use std::collections::BTreeSet;
|
||||
@@ -1021,4 +1025,44 @@ mod tests {
|
||||
);
|
||||
assert!(app["default_permission"].is_null(), "no app `default` permission set");
|
||||
}
|
||||
|
||||
/// `build.rs`'s `check_tauri_config` (inline `app.security.capabilities`, a JSON5/TOML tauri
|
||||
/// config, `TAURI_CONFIG`) only runs inside the build script, so it only re-runs on a clean
|
||||
/// build or in CI — cargo's incremental build has no reason to notice a new
|
||||
/// `tauri.<platform>.conf.json` dropped into an already-built tree (CLAUDE.md, "Known
|
||||
/// limit"). This runs the same check, using the same `command_census` functions build.rs
|
||||
/// calls, directly against the real `app/src-tauri` directory on every `cargo test`, so that
|
||||
/// gap is closed locally too.
|
||||
#[test]
|
||||
fn the_tauri_config_capability_check_runs_against_the_real_tree() {
|
||||
let dir = env!("CARGO_MANIFEST_DIR");
|
||||
let mut problems = Vec::new();
|
||||
for entry in std::fs::read_dir(dir).expect("readable src-tauri/") {
|
||||
let path = entry.expect("readable entry in src-tauri/").path();
|
||||
let name = path
|
||||
.file_name()
|
||||
.expect("a directory entry has a file name")
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
match crate::command_census::tauri_config_file(&name) {
|
||||
None => {}
|
||||
Some(false) => problems.push(format!(
|
||||
"{name}: the census reads JSON tauri configs only; a JSON5/TOML config \
|
||||
could declare capabilities it cannot see"
|
||||
)),
|
||||
Some(true) => {
|
||||
let json =
|
||||
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
problems.extend(crate::command_census::tauri_config_problem(&name, &json));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(json) = std::env::var("TAURI_CONFIG") {
|
||||
problems.extend(crate::command_census::tauri_config_problem("TAURI_CONFIG", &json));
|
||||
}
|
||||
assert!(
|
||||
problems.is_empty(),
|
||||
"cargo test found what build.rs would refuse on a clean build: {problems:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,9 @@ function wrapperCommands(): Map<string, string> {
|
||||
for (const [name, calls] of callsByWrapper) {
|
||||
expect(calls, `${name} must call invoke exactly once`).toHaveLength(1);
|
||||
const command = stringLiteralText(calls[0].arguments[0]) ?? "<not a string literal>";
|
||||
expect(command, `${name} must invoke a string literal (a computed name cannot be audited)`).toMatch(/^[a-z_]+$/);
|
||||
expect(command, `${name} must invoke a string literal (a computed name cannot be audited)`).toMatch(
|
||||
/^[a-z0-9_]+$/,
|
||||
);
|
||||
map.set(name, command);
|
||||
}
|
||||
expect(map.size).toBeGreaterThan(100);
|
||||
|
||||
Reference in New Issue
Block a user