diff --git a/crates/aft/src/subc/manifest.rs b/crates/aft/src/subc/manifest.rs index 1530c6bd..4314a800 100644 --- a/crates/aft/src/subc/manifest.rs +++ b/crates/aft/src/subc/manifest.rs @@ -89,7 +89,7 @@ pub(super) fn is_subc_native_plumbing_tool(name: &str) -> bool { ) } -pub(super) fn command_lane(command: &str) -> Lane { +pub(super) fn command_lane_explicit(command: &str) -> Option { match command { "ping" | "version" @@ -108,12 +108,12 @@ pub(super) fn command_lane(command: &str) -> Lane { | "glob" | "grep" | "git_conflicts" - | "ast_search" => Lane::PureRead, + | "ast_search" => Some(Lane::PureRead), // Lazy reads mutate parser/terminal/url caches on a miss, but are still // classified onto the reader pool; install races are handled at the // individual cache sites. - "bash_status" | "outline" | "zoom" => Lane::PureRead, + "bash_status" | "outline" | "zoom" => Some(Lane::PureRead), "status" | "inspect" @@ -122,10 +122,12 @@ pub(super) fn command_lane(command: &str) -> Lane { | "lsp_hover" | "lsp_goto_definition" | "lsp_find_references" - | "lsp_prepare_rename" => Lane::SerialLspStatus, + | "lsp_prepare_rename" => Some(Lane::SerialLspStatus), "semantic_search" | "search" | "callgraph" | "callers" | "impact" | "call_tree" - | "trace_to" | "trace_to_symbol" | "trace_data" | "inspect_tier2_run" => Lane::HeavyInit, + | "trace_to" | "trace_to_symbol" | "trace_data" | "inspect_tier2_run" => { + Some(Lane::HeavyInit) + } "bash" | "bash_abort_inflight" @@ -141,30 +143,40 @@ pub(super) fn command_lane(command: &str) -> Lane { | "checkpoint" | "restore_checkpoint" | "write" + | "apply_patch" | "delete_file" + | "delete" | "move_file" + | "move" | "edit" | "edit_symbol" | "edit_match" | "batch" | "add_import" + | "import" | "remove_import" | "organize_imports" | "configure" + | "refactor" | "move_symbol" | "extract_function" | "inline_symbol" | "ast_replace" + | "safety" | "lsp_rename" | "list_filters" | "trust_filter_project" | "untrust_filter_project" - | "snapshot" => Lane::Mutating, + | "snapshot" => Some(Lane::Mutating), - _ => Lane::Mutating, + _ => None, } } +pub(super) fn command_lane(command: &str) -> Lane { + command_lane_explicit(command).unwrap_or(Lane::Mutating) +} + static SUBC_TOOL_SCHEMAS: LazyLock> = LazyLock::new(|| { serde_json::from_str(include_str!("../subc_tool_schemas.json")) .unwrap_or_else(|e| panic!("subc_tool_schemas.json: {e}")) @@ -270,9 +282,10 @@ pub(super) fn control_flags() -> Flags { #[cfg(test)] mod tests { use super::*; - use std::collections::HashMap; + use crate::subc_translate::supports_tool; + use std::collections::{HashMap, HashSet}; - const CORE_TOOLS: [&str; 21] = [ + const CORE_TOOLS: &[&str] = &[ "status", "bash", "read", @@ -296,6 +309,10 @@ mod tests { "safety", ]; + /// Tools listed here deliberately skip translation; adding one is a reviewed + /// decision because it weakens the registration guard's translation check. + const TRANSLATION_EXEMPT: &[&str] = &[]; + fn is_bare_placeholder_schema(schema: &Value) -> bool { schema == &json!({ "type": "object" }) } @@ -357,6 +374,58 @@ mod tests { ); } + #[test] + fn embedded_subc_tools_are_registered_across_all_rust_surfaces() { + let schema_names: HashSet<&str> = SUBC_TOOL_SCHEMAS.keys().map(String::as_str).collect(); + let core_names: HashSet<&str> = CORE_TOOLS.iter().copied().collect(); + assert_eq!( + CORE_TOOLS.len(), + schema_names.len(), + "CORE_TOOLS count must match embedded schema key count" + ); + assert_eq!( + core_names, schema_names, + "CORE_TOOLS must exactly match embedded schema keys" + ); + + let manifest = build_manifest(); + let tools = match manifest.provides.first() { + Some(ProviderRole::ToolProvider { tools, .. }) => tools, + _ => panic!("expected ToolProvider"), + }; + let manifest_names: HashSet<&str> = tools.iter().map(|tool| tool.name.as_str()).collect(); + + for name in schema_names { + assert!( + is_subc_agent_core_tool(name), + "tool {name:?} is missing from is_subc_agent_core_tool in crates/aft/src/subc/manifest.rs" + ); + assert!( + manifest_names.contains(name), + "tool {name:?} is missing from build_manifest in crates/aft/src/subc/manifest.rs" + ); + assert!( + command_lane_explicit(name).is_some(), + "tool {name:?} is missing an explicit command_lane arm in crates/aft/src/subc/manifest.rs" + ); + if !TRANSLATION_EXEMPT.contains(&name) { + assert!( + supports_tool(name), + "tool {name:?} is missing from supports_tool in crates/aft/src/subc_translate.rs" + ); + } + } + + // BARE_TOOL_ORDER is TypeScript-only; the embedded schema map is its + // generated Rust-side artifact, so the manifest count is the Rust + // denominator check for this derived guard. + assert_eq!( + SUBC_TOOL_SCHEMAS.len(), + tools.len(), + "registration guard denominator must match manifest tool count" + ); + } + #[test] fn build_manifest_classifies_execution_mode_by_observable_effect() { let manifest = build_manifest(); diff --git a/crates/aft/tests/integration/subc_bridge_test.rs b/crates/aft/tests/integration/subc_bridge_test.rs index 805137f2..58b84cef 100644 --- a/crates/aft/tests/integration/subc_bridge_test.rs +++ b/crates/aft/tests/integration/subc_bridge_test.rs @@ -6883,7 +6883,16 @@ async fn drive_module_hello_health_manifest_daemon(input: FakeDaemonInput) { Some(subc_protocol::manifest::ProviderRole::ToolProvider { tools, .. }) => tools, other => panic!("expected first provider role to be ToolProvider, got {other:?}"), }; - assert_eq!(tools.len(), 21, "expected 21 manifest tools"); + let schema_count = serde_json::from_str::>(include_str!( + "../../src/subc_tool_schemas.json" + )) + .expect("embedded subc tool schemas should be a JSON object") + .len(); + assert_eq!( + tools.len(), + schema_count, + "manifest tool count must match embedded schema key count" + ); for tool in tools { assert!( tool.description diff --git a/docs/v0.49-agent-prefix-capture.json b/docs/v0.49-agent-prefix-capture.json index 613c15db..867c2351 100644 --- a/docs/v0.49-agent-prefix-capture.json +++ b/docs/v0.49-agent-prefix-capture.json @@ -1,7 +1,7 @@ { "artifact_id": "ART-V049-S5-AGENT-PREFIX-CAPTURE-001", "artifact_version": "0.49.0", - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "capture_scope": "complete plugin-owned production agent-prefix input for every checked host profile; host-owned base prompt text is outside the plugin boundary", "captures": [ { @@ -18,7 +18,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_outline", @@ -201,7 +201,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_conflicts", @@ -1061,7 +1061,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_callgraph", @@ -2081,8 +2081,8 @@ "profile_id": "REG-V049-PI-MIN", "harness": "pi", "host_version": { - "value": "0.84.0", - "method": "pi --version" + "value": null, + "method": "pi --version (not available in capture environment)" }, "capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input", "production_cache_key": { @@ -2090,7 +2090,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_outline", @@ -2272,8 +2272,8 @@ "profile_id": "REG-V049-PI-REC", "harness": "pi", "host_version": { - "value": "0.84.0", - "method": "pi --version" + "value": null, + "method": "pi --version (not available in capture environment)" }, "capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input", "production_cache_key": { @@ -2281,7 +2281,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_conflicts", @@ -3240,8 +3240,8 @@ "profile_id": "REG-V049-PI-ALL", "harness": "pi", "host_version": { - "value": "0.84.0", - "method": "pi --version" + "value": null, + "method": "pi --version (not available in capture environment)" }, "capture_method": "production registerPiToolSurface registration output plus before_agent_start workflow hint input", "production_cache_key": { @@ -3249,7 +3249,7 @@ "exposed": false, "source": "host did not expose a production cache key" }, - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "prefix_input": { "registered_tool_names": [ "aft_callgraph", diff --git a/docs/v0.49-agent-surface-manifest.json b/docs/v0.49-agent-surface-manifest.json index d7297890..77f49f06 100644 --- a/docs/v0.49-agent-surface-manifest.json +++ b/docs/v0.49-agent-surface-manifest.json @@ -3,7 +3,7 @@ "artifact_id": "ART-V049-S5-AGENT-SURFACE-MANIFEST-001", "artifact_version": "0.49.0", "manifest_id": "MAN-V049-S5-AGENT-SURFACE-001", - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "source_inventory": "docs/v0.49-agent-surface-sources.json", "hash_rule": "Hash exact UTF-8 file bytes from the source commit; do not normalize newlines, reserialize JSON, or apply test-only normalization.", "artifacts": [ @@ -17,7 +17,7 @@ "REG-V049-OC-REC", "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 45394, "sha256": "c561a9a3f7604a2aedc57882c84d609a706c4959474a915c671bc16e177e8bea" @@ -32,7 +32,7 @@ "REG-V049-OC-REC", "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 17922, "sha256": "2d41d04ad7e0cb97d0b1064ab584e7daa8f1db5f7a8cb4d73732c0db1696e9df" @@ -45,7 +45,7 @@ "profiles": [ "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 7944, "sha256": "673c050c76f1ae592440c4574d07708b26a98ba0af5f2bace81c690be289f676" @@ -60,7 +60,7 @@ "REG-V049-OC-REC", "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 8228, "sha256": "83185ecfad76ac049bddb97a82b9c0265976067107f2cd5e13d69b6acc7352ca" @@ -74,7 +74,7 @@ "REG-V049-OC-REC", "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 6092, "sha256": "4dc0ba1675a425dcfe9fa08fb49bda9d3fedaa7044a25f9b6ac333ee79c3b46e" @@ -87,7 +87,7 @@ "profiles": [ "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 7766, "sha256": "1db4595041262d7e0a977154c2e1eca67b2d7fd1509428322aba6b7b58164f0e" @@ -102,7 +102,7 @@ "REG-V049-OC-REC", "REG-V049-OC-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 12504, "sha256": "213ef9b09bcc8854e00a4a3240791223ed0d338a33ee9667368075ab1b486954" @@ -117,7 +117,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 40749, "sha256": "3ad552ac8156f101d2314ba004ea9e5cb6725f11abe13c5f361b34d3cb24dfef" @@ -132,7 +132,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 21652, "sha256": "719b244afe0ac41e167db6ce1714f89fea6daa5d6cab79113b184b41bb1edaeb" @@ -145,7 +145,7 @@ "profiles": [ "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 8789, "sha256": "7f8764e69b85378cb820c3963116ab65296fc45310d2555d096ff5fdee87cee2" @@ -160,7 +160,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 9499, "sha256": "8261d91d898f1423ddb591d8c30d0285492c19f841d0f3721c05d2f0842ff5c2" @@ -174,7 +174,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 8182, "sha256": "b26a8f7761f99dfdd46b9f1b96115377d6102f0cbd4ecb3b4c1f2599c7a743fb" @@ -187,7 +187,7 @@ "profiles": [ "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 8760, "sha256": "47f557a3b8ead3a9c863e99218b6016959de85a4c8be48a2e2e5b65e4742c836" @@ -200,7 +200,7 @@ "profiles": [ "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 9967, "sha256": "416d5dcf384248e0727d9a04e23556d14b98a51dfd57a9ae2c350ebaf285a51a" @@ -215,7 +215,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 11887, "sha256": "5c67943dc898c2a4235c033c5947faa30d64c05de7dc75dee3f2e842dcf89b4a" @@ -233,7 +233,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 44313, "sha256": "b604c6f80c2527582b457a4d8538bd9c4cc8987a6fac449a60e21e8780faa131" @@ -248,7 +248,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 7494, "sha256": "9b4bffd31c6c9224d379a7e01521892825563266697acf3f9826b6bd3371788b" @@ -259,7 +259,7 @@ "kind": "generated subc manifest schema", "owner": "subc-schema-generation", "profiles": [], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 42099, "sha256": "561b2677b11de8c7005aa299b5b57b96e36dc0d74a6d46b2e8eff326fb7d8d4d" @@ -277,7 +277,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 5193, "sha256": "f6dd35f338014a2adbae1814d8cc8fc7adf05abdde9209d7b28c432ea620eaa9" @@ -295,10 +295,10 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 214468, - "sha256": "46275e5fc1084539c92ca4a239027a11da273f8e4ea4a0f1ac5baff5b9c7d9ee" + "sha256": "32828e24553b1935c5f7a4ae5b6e01b341c07808bb120a5a2a5b7b2e6e3c116e" }, { "id": "ART-V049-S5-AUDIT-IMPLEMENTATION-001", @@ -313,7 +313,7 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", "byte_length": 23451, "sha256": "c72cc1c1bc82575057552d62900174e57a31e50abf6e6d38b1c25679bcaaa872" @@ -331,10 +331,10 @@ "REG-V049-PI-REC", "REG-V049-PI-ALL" ], - "source_commit": "d842d702a6afa8395c810282591c062e17cff521", + "source_commit": "d34f8bd1b2825bb5e903d3a1de3601a33c677248", "encoding": "UTF-8", - "byte_length": 244929, - "sha256": "8e32fdd615c9066c922479edcf8aeb15b8e06c01dd5a10fa055686288bc976f6" + "byte_length": 245034, + "sha256": "fa8a9cf0629ecd09e9999db9ce88a54d7464e6330f8ba5e07b605ee58bf8311d" } ] } diff --git a/docs/v0.49-legacy-vocabulary-allowlist.json b/docs/v0.49-legacy-vocabulary-allowlist.json index 6039eee8..7a0dc5c1 100644 --- a/docs/v0.49-legacy-vocabulary-allowlist.json +++ b/docs/v0.49-legacy-vocabulary-allowlist.json @@ -197,8 +197,8 @@ }, { "path": "crates/aft/src/subc/manifest.rs", - "location": "line 338, column 53", - "line": 338, + "location": "line 355, column 53", + "line": 355, "column": 53, "token": "filePath", "class": "rust-compatibility", @@ -206,8 +206,8 @@ }, { "path": "crates/aft/src/subc/manifest.rs", - "location": "line 343, column 38", - "line": 343, + "location": "line 360, column 38", + "line": 360, "column": 38, "token": "filePath", "class": "rust-compatibility", @@ -215,8 +215,8 @@ }, { "path": "crates/aft/src/subc/manifest.rs", - "location": "line 344, column 57", - "line": 344, + "location": "line 361, column 57", + "line": 361, "column": 57, "token": "filePath", "class": "rust-compatibility", @@ -1307,8 +1307,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 7069, column 18", - "line": 7069, + "location": "line 7078, column 18", + "line": 7078, "column": 18, "token": "filePath", "class": "internal-compatibility", @@ -1316,8 +1316,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 7082, column 14", - "line": 7082, + "location": "line 7091, column 14", + "line": 7091, "column": 14, "token": "filePath", "class": "internal-compatibility", @@ -1325,8 +1325,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 7149, column 18", - "line": 7149, + "location": "line 7158, column 18", + "line": 7158, "column": 18, "token": "filePath", "class": "internal-compatibility", @@ -1334,8 +1334,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 7158, column 14", - "line": 7158, + "location": "line 7167, column 14", + "line": 7167, "column": 14, "token": "filePath", "class": "internal-compatibility", @@ -1343,8 +1343,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 7170, column 14", - "line": 7170, + "location": "line 7179, column 14", + "line": 7179, "column": 14, "token": "filePath", "class": "internal-compatibility", @@ -1352,8 +1352,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 9086, column 25", - "line": 9086, + "location": "line 9095, column 25", + "line": 9095, "column": 25, "token": "filePath", "class": "internal-compatibility", @@ -1361,8 +1361,8 @@ }, { "path": "crates/aft/tests/integration/subc_bridge_test.rs", - "location": "line 9120, column 18", - "line": 9120, + "location": "line 9129, column 18", + "line": 9129, "column": 18, "token": "filePath", "class": "internal-compatibility",