Sync public tree to 4a860d7
This commit is contained in:
parent
1dad0df5c4
commit
4f5df7da20
4 changed files with 236 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"kind": "disasmer-filtered-public-tree",
|
||||
"source_commit": "13dc821c5a4ff2b093b777374ebf510197ab5500",
|
||||
"release_name": "dryrun-13dc821c5a4f",
|
||||
"source_commit": "4a860d72efe34b376c4b08762555bee3fe3105f9",
|
||||
"release_name": "dryrun-4a860d72efe3",
|
||||
"filtered_out": [
|
||||
"private/**",
|
||||
"experiments/**",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -10,8 +10,9 @@ use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
|
|||
use clap::{Args, Parser, Subcommand};
|
||||
use disasmer_core::{
|
||||
diagnose_environment_references, BrowserLoginFlow, BundleIdentityInputs, BundleMetadata,
|
||||
Capability, CliLoginFlow, Digest, EnvironmentReference, LimitKind, NodeCapabilities,
|
||||
ProjectModel, ResourceLimits, SelectedInput, SourceProviderKind, SourceProviderManifest,
|
||||
Capability, CliLoginFlow, Digest, EnvironmentBackend, EnvironmentReference, LimitKind,
|
||||
NodeCapabilities, ProjectModel, ResourceLimits, SelectedInput, SourceProviderKind,
|
||||
SourceProviderManifest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
|
@ -678,6 +679,7 @@ struct NodeAttachPlan {
|
|||
node: String,
|
||||
coordinator: Option<String>,
|
||||
capabilities: NodeCapabilities,
|
||||
detection: NodeAttachDetectionEvidence,
|
||||
grant_disclosures: Vec<CapabilityGrantDisclosure>,
|
||||
enrollment: Option<NodeEnrollmentPlan>,
|
||||
}
|
||||
|
|
@ -711,6 +713,32 @@ struct CapabilityGrantDisclosure {
|
|||
coordinator_policy_limited: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
struct NodeAttachDetectionEvidence {
|
||||
auto_detected: bool,
|
||||
os: disasmer_core::Os,
|
||||
arch: String,
|
||||
command_backend: String,
|
||||
command_backend_available: bool,
|
||||
container_backend: Option<String>,
|
||||
container_backend_reported: bool,
|
||||
container_backend_available: bool,
|
||||
source_provider_backends: Vec<SourceProviderBackendStatus>,
|
||||
manual_capability_overrides_allowed: bool,
|
||||
manual_capability_overrides: Vec<String>,
|
||||
recognized_capability_overrides: Vec<Capability>,
|
||||
unrecognized_capability_overrides: Vec<String>,
|
||||
os_arch_capabilities_require_manual_flags: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
struct SourceProviderBackendStatus {
|
||||
provider: String,
|
||||
detected: bool,
|
||||
available: bool,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
struct NodeEnrollmentPlan {
|
||||
grant: String,
|
||||
|
|
@ -2794,6 +2822,12 @@ fn human_report(value: &Value) -> String {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(detection) = value
|
||||
.pointer("/plan/detection")
|
||||
.or_else(|| value.get("detection"))
|
||||
{
|
||||
push_node_attach_detection(&mut lines, detection);
|
||||
}
|
||||
if let Some(disclosures) = value
|
||||
.get("grant_disclosures")
|
||||
.and_then(Value::as_array)
|
||||
|
|
@ -2825,6 +2859,64 @@ fn human_report(value: &Value) -> String {
|
|||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn push_node_attach_detection(lines: &mut Vec<String>, detection: &Value) {
|
||||
push_nested_string_field(lines, detection, "os", "node os");
|
||||
push_nested_string_field(lines, detection, "arch", "node arch");
|
||||
push_nested_string_field(lines, detection, "command_backend", "command backend");
|
||||
if let Some(backend) = detection.get("container_backend").and_then(Value::as_str) {
|
||||
let available = detection
|
||||
.get("container_backend_available")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
lines.push(format!(
|
||||
"container backend: {backend} ({})",
|
||||
if available { "available" } else { "reported" }
|
||||
));
|
||||
} else if detection
|
||||
.get("container_backend_reported")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
lines.push("container backend: reported".to_owned());
|
||||
}
|
||||
if let Some(providers) = detection
|
||||
.get("source_provider_backends")
|
||||
.and_then(Value::as_array)
|
||||
{
|
||||
let entries = providers
|
||||
.iter()
|
||||
.filter_map(|provider| {
|
||||
let name = provider.get("provider").and_then(Value::as_str)?;
|
||||
let state = if provider
|
||||
.get("available")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"available"
|
||||
} else {
|
||||
"detected"
|
||||
};
|
||||
Some(format!("{name}={state}"))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !entries.is_empty() {
|
||||
lines.push(format!("source providers: {}", entries.join(", ")));
|
||||
}
|
||||
}
|
||||
if let Some(overrides) = detection
|
||||
.get("manual_capability_overrides")
|
||||
.and_then(Value::as_array)
|
||||
{
|
||||
let overrides = overrides
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
if !overrides.is_empty() {
|
||||
lines.push(format!("capability overrides: {}", overrides.join(", ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_string_field(lines: &mut Vec<String>, value: &Value, key: &str, label: &str) {
|
||||
if let Some(text) = value.get(key).and_then(Value::as_str) {
|
||||
lines.push(format!("{label}: {text}"));
|
||||
|
|
@ -5748,11 +5840,18 @@ fn add_workflow_actor_fields(request: &mut Value, session: &CliSession, fallback
|
|||
|
||||
fn attach_plan(args: AttachArgs) -> NodeAttachPlan {
|
||||
let mut capabilities = NodeCapabilities::detect_current();
|
||||
let mut recognized_capability_overrides = Vec::new();
|
||||
let mut unrecognized_capability_overrides = Vec::new();
|
||||
for cap in &args.caps {
|
||||
if let Some(parsed) = parse_capability(&cap) {
|
||||
recognized_capability_overrides.push(parsed.clone());
|
||||
capabilities.capabilities.insert(parsed);
|
||||
} else {
|
||||
unrecognized_capability_overrides.push(cap.clone());
|
||||
}
|
||||
}
|
||||
recognized_capability_overrides.sort();
|
||||
recognized_capability_overrides.dedup();
|
||||
let node = args.node.unwrap_or_else(default_node_id);
|
||||
let public_key = args
|
||||
.public_key
|
||||
|
|
@ -5762,17 +5861,94 @@ fn attach_plan(args: AttachArgs) -> NodeAttachPlan {
|
|||
public_key_fingerprint: Digest::sha256(public_key),
|
||||
exchanges_short_lived_grant_for_long_lived_node_identity: true,
|
||||
});
|
||||
let detection = node_attach_detection_evidence(
|
||||
&capabilities,
|
||||
args.caps,
|
||||
recognized_capability_overrides,
|
||||
unrecognized_capability_overrides,
|
||||
);
|
||||
let grant_disclosures = capability_grant_disclosures(&capabilities);
|
||||
|
||||
NodeAttachPlan {
|
||||
node,
|
||||
coordinator: args.coordinator,
|
||||
capabilities,
|
||||
detection,
|
||||
grant_disclosures,
|
||||
enrollment,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_attach_detection_evidence(
|
||||
capabilities: &NodeCapabilities,
|
||||
manual_capability_overrides: Vec<String>,
|
||||
recognized_capability_overrides: Vec<Capability>,
|
||||
unrecognized_capability_overrides: Vec<String>,
|
||||
) -> NodeAttachDetectionEvidence {
|
||||
let command_backend_available = capabilities.capabilities.contains(&Capability::Command);
|
||||
let container_backend_reported = capabilities
|
||||
.environment_backends
|
||||
.contains(&EnvironmentBackend::Container);
|
||||
let container_backend = container_backend_reported.then(|| "rootless-podman".to_owned());
|
||||
let container_backend_available = container_backend_reported
|
||||
&& capabilities
|
||||
.capabilities
|
||||
.contains(&Capability::RootlessPodman)
|
||||
&& command_available("podman");
|
||||
|
||||
NodeAttachDetectionEvidence {
|
||||
auto_detected: true,
|
||||
os: capabilities.os.clone(),
|
||||
arch: capabilities.arch.clone(),
|
||||
command_backend: if command_backend_available {
|
||||
"native-command".to_owned()
|
||||
} else {
|
||||
"unavailable".to_owned()
|
||||
},
|
||||
command_backend_available,
|
||||
container_backend,
|
||||
container_backend_reported,
|
||||
container_backend_available,
|
||||
source_provider_backends: source_provider_backend_statuses(capabilities),
|
||||
manual_capability_overrides_allowed: true,
|
||||
manual_capability_overrides,
|
||||
recognized_capability_overrides,
|
||||
unrecognized_capability_overrides,
|
||||
os_arch_capabilities_require_manual_flags: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn source_provider_backend_statuses(
|
||||
capabilities: &NodeCapabilities,
|
||||
) -> Vec<SourceProviderBackendStatus> {
|
||||
let mut statuses = BTreeMap::new();
|
||||
for provider in &capabilities.source_providers {
|
||||
let available = match provider.as_str() {
|
||||
"filesystem" => capabilities
|
||||
.capabilities
|
||||
.contains(&Capability::SourceFilesystem),
|
||||
"git" => command_available("git"),
|
||||
_ => true,
|
||||
};
|
||||
statuses.insert(
|
||||
provider.clone(),
|
||||
SourceProviderBackendStatus {
|
||||
provider: provider.clone(),
|
||||
detected: true,
|
||||
available,
|
||||
reason: if available {
|
||||
"detected by local node capability probe".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"source provider `{provider}` was detected but its local helper is missing"
|
||||
)
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
statuses.into_values().collect()
|
||||
}
|
||||
|
||||
fn capability_grant_disclosures(capabilities: &NodeCapabilities) -> Vec<CapabilityGrantDisclosure> {
|
||||
let mut disclosures = Vec::new();
|
||||
let mut push = |capability: Capability, grant: &str, description: &str, risk: &str| {
|
||||
|
|
@ -6225,6 +6401,27 @@ mod tests {
|
|||
.capabilities
|
||||
.contains(&Capability::QuicDirect));
|
||||
assert!(!plan.capabilities.arch.is_empty());
|
||||
assert!(plan.detection.auto_detected);
|
||||
assert_eq!(plan.detection.os, plan.capabilities.os);
|
||||
assert_eq!(plan.detection.arch, plan.capabilities.arch);
|
||||
assert_eq!(plan.detection.command_backend, "native-command");
|
||||
assert!(plan.detection.command_backend_available);
|
||||
assert!(plan.detection.manual_capability_overrides_allowed);
|
||||
assert_eq!(
|
||||
plan.detection.manual_capability_overrides,
|
||||
vec!["quic-direct".to_owned()]
|
||||
);
|
||||
assert!(plan
|
||||
.detection
|
||||
.recognized_capability_overrides
|
||||
.contains(&Capability::QuicDirect));
|
||||
assert!(plan.detection.unrecognized_capability_overrides.is_empty());
|
||||
assert!(!plan.detection.os_arch_capabilities_require_manual_flags);
|
||||
assert!(plan
|
||||
.detection
|
||||
.source_provider_backends
|
||||
.iter()
|
||||
.any(|provider| provider.provider == "filesystem" && provider.detected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const cliFirstAcceptance = read("scripts/acceptance-cli-first.sh");
|
|||
const outputModeSmoke = read("scripts/cli-output-mode-smoke.js");
|
||||
const errorExitSmoke = read("scripts/cli-error-exit-smoke.js");
|
||||
const browserLoginFlowSmoke = read("scripts/cli-browser-login-flow-smoke.js");
|
||||
const nodeAttachSmoke = read("scripts/node-attach-smoke.js");
|
||||
|
||||
expect(criteria, "header", /^# Disasmer CLI-First MVP Acceptance Criteria/m);
|
||||
expect(
|
||||
|
|
@ -250,6 +251,7 @@ for (const [name, pattern] of [
|
|||
["CLI locality failure classifier", /fn classify_cli_error_message\(message: &str\)[\s\S]*message_mentions_locality_failure\(&message\)[\s\S]*return "connectivity"/],
|
||||
["CLI locality failure report helper", /fn task_locality_failure_from_reason\(reason: &Value\) -> Value[\s\S]*coordinator_bulk_relay_used[\s\S]*safe_next_actions/],
|
||||
["CLI locality failure human output", /fn push_task_locality_failures\(lines: &mut Vec<String>, tasks: &\[Value\]\)[\s\S]*locality \{task_name\}/],
|
||||
["node attach auto-detection coverage", /fn node_attach_detects_and_accepts_capability_overrides\(\)[\s\S]*detection\.auto_detected[\s\S]*command_backend[\s\S]*source_provider_backends/],
|
||||
["node attach grant disclosure coverage", /fn node_attach_discloses_dangerous_capability_grants\(\)/],
|
||||
["node enroll public API grant coverage", /fn node_enroll_reports_short_lived_public_api_grant\(\)/],
|
||||
["quota local status coverage", /fn quota_status_uses_project_config_and_generic_public_limits\(\)/],
|
||||
|
|
@ -280,6 +282,11 @@ expect(
|
|||
"browser login smoke rejects provider token persistence",
|
||||
/assert\.doesNotMatch\([\s\S]*access_token\|refresh_token\|id_token/
|
||||
);
|
||||
expect(
|
||||
nodeAttachSmoke,
|
||||
"node attach smoke verifies auto-detection evidence",
|
||||
/plan\.detection\.auto_detected[\s\S]*plan\.detection\.command_backend[\s\S]*source_provider_backends/
|
||||
);
|
||||
|
||||
expect(
|
||||
coordinator,
|
||||
|
|
@ -356,6 +363,16 @@ expect(
|
|||
"CLI sends agent workflow actor fields",
|
||||
/fn add_workflow_actor_fields[\s\S]*actor_agent[\s\S]*agent_public_key_fingerprint/
|
||||
);
|
||||
expect(
|
||||
cli,
|
||||
"CLI exposes node attach detection evidence",
|
||||
/struct NodeAttachDetectionEvidence[\s\S]*command_backend[\s\S]*container_backend[\s\S]*source_provider_backends[\s\S]*manual_capability_overrides[\s\S]*os_arch_capabilities_require_manual_flags/
|
||||
);
|
||||
expect(
|
||||
cli,
|
||||
"CLI renders node attach detection evidence",
|
||||
/fn push_node_attach_detection[\s\S]*command backend[\s\S]*container backend[\s\S]*source providers[\s\S]*capability overrides/
|
||||
);
|
||||
expect(
|
||||
cli,
|
||||
"CLI exposes node attach grant disclosures",
|
||||
|
|
|
|||
|
|
@ -212,6 +212,23 @@ function runAttachedNodeWork(addr) {
|
|||
assert.ok(report.plan.capabilities.arch.length > 0);
|
||||
assert.ok(report.plan.capabilities.source_providers.includes("filesystem"));
|
||||
assert.ok(report.plan.capabilities.capabilities.includes("QuicDirect"));
|
||||
assert.strictEqual(report.plan.detection.auto_detected, true);
|
||||
assert.strictEqual(report.plan.detection.arch, report.plan.capabilities.arch);
|
||||
assert.deepStrictEqual(report.plan.detection.manual_capability_overrides, ["quic-direct"]);
|
||||
assert(
|
||||
report.plan.detection.recognized_capability_overrides.includes("QuicDirect")
|
||||
);
|
||||
assert.strictEqual(
|
||||
report.plan.detection.os_arch_capabilities_require_manual_flags,
|
||||
false
|
||||
);
|
||||
assert.strictEqual(report.plan.detection.command_backend, "native-command");
|
||||
assert.strictEqual(report.plan.detection.command_backend_available, true);
|
||||
assert(
|
||||
report.plan.detection.source_provider_backends.some(
|
||||
(provider) => provider.provider === "filesystem" && provider.detected
|
||||
)
|
||||
);
|
||||
assert.strictEqual(report.boundary.cli_contacted_coordinator, true);
|
||||
assert.strictEqual(report.boundary.used_enrollment_exchange, true);
|
||||
assert.strictEqual(report.boundary.coordinator_session_requests, 3);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue