Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c03612c
Add Fedora live remix path for first Framework 12 evidence
devteapot Aug 23, 2026
79465d2
Harden the diskless Fedora live gate
devteapot Aug 24, 2026
c70f5ec
Validate private live rootfs state through sudo
devteapot Aug 24, 2026
f25b449
Fix live image operator description
devteapot Aug 24, 2026
478a8ed
Fix fragment-packed EROFS extraction
devteapot Aug 24, 2026
f6ad4e9
Preserve EROFS metadata through policy relabel
devteapot Aug 24, 2026
dd97da7
Split EROFS metadata and xattr audits
devteapot Aug 24, 2026
1a658a8
Ignore XFS internal ACL xattrs in rootfs audit
devteapot Aug 24, 2026
b618608
Fix privileged live rootfs staging boundaries
devteapot Aug 24, 2026
24dc85c
Apply SELinux policy while building live EROFS
devteapot Aug 24, 2026
0ac336c
Fix physical Linux session startup and logout
devteapot Aug 24, 2026
1398e94
Gate agent provider controls by platform
devteapot Aug 24, 2026
fb70478
Record focused Framework activation evidence
devteapot Aug 24, 2026
d9d783c
Route Linux touch input to native text fields
devteapot Aug 24, 2026
361cd7d
Record touch-focus live ISO bake evidence
devteapot Aug 24, 2026
803b3c2
Fix Linux touch scheduling and clean logout
devteapot Aug 25, 2026
659003a
Make live media a mutable development environment
devteapot Aug 25, 2026
f057d25
Fix development liveuser boot provisioning
devteapot Aug 25, 2026
160c065
Start development SSH after live provisioning
devteapot Aug 25, 2026
28cf8ff
Support development Wi-Fi autoconnect
devteapot Aug 25, 2026
7e4c4da
Record credentialed development ISO bake
devteapot Aug 25, 2026
418c230
Record Framework development live boot
devteapot Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,21 @@ model:
--evidence-dir artifacts/framework12-first-gate
```

The same offline install can be baked into a checksum-pinned Fedora Workstation
`development-live` environment so the Framework 12 loop does not touch its
internal disk. Its password-protected SSH service, optional development-only
Wi-Fi autoconnect profile, and mutable overlay allow changed SOS binaries to be
deployed with `tools/linux-live-deploy` without rebuilding the ISO. Embedded
Wi-Fi credentials make that private development ISO unsuitable for sharing or
release. The bake accepts only Fedora's flat
EROFS rootfs format and performs a privileged metadata-preserving copy,
policy-based SELinux relabel, and repack so Linux ownership, ACLs,
capabilities, and security metadata are preserved.
Development-live diagnostics always record `promotion_eligible=false`; only a
future immutable release image can own release promotion.
It is not an installed product. See
[`docs/linux-live-image.md`](docs/linux-live-image.md).

`./tools/install-linux-login-session uninstall` removes the installed SOS
session while preserving user state and the existing display-manager/default
boot configuration.
Expand All @@ -210,7 +225,8 @@ The direct-DRM acceptance command targets the disposable reference Debian VM:
See [`docs/linux-stable-host.md`](docs/linux-stable-host.md),
[`docs/linux-compositor.md`](docs/linux-compositor.md),
[`docs/linux-vm.md`](docs/linux-vm.md),
[`docs/linux-hardware-gate.md`](docs/linux-hardware-gate.md), and
[`docs/linux-hardware-gate.md`](docs/linux-hardware-gate.md),
[`docs/linux-live-image.md`](docs/linux-live-image.md), and
[`docs/sos-agent.md`](docs/sos-agent.md) for prerequisites and evidence limits.

### AOSP Cuttlefish
Expand Down Expand Up @@ -319,8 +335,9 @@ documented external evidence directories and are intentionally not tracked.
the physical stable-host and stateful-swap evidence.
- [`docs/linux-stable-host.md`](docs/linux-stable-host.md),
[`docs/linux-compositor.md`](docs/linux-compositor.md),
[`docs/linux-vm.md`](docs/linux-vm.md), and
[`docs/linux-hardware-gate.md`](docs/linux-hardware-gate.md) cover the Linux
[`docs/linux-vm.md`](docs/linux-vm.md),
[`docs/linux-hardware-gate.md`](docs/linux-hardware-gate.md), and
[`docs/linux-live-image.md`](docs/linux-live-image.md) cover the Linux
path from virtual acceptance through the first physical campaign.
- [`docs/aosp-cuttlefish.md`](docs/aosp-cuttlefish.md) covers the reproducible
Android 17 system spike.
Expand Down
24 changes: 23 additions & 1 deletion apps/experience/src/android/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::thread;
use std::time::{Duration, Instant};

use async_channel::Sender;
use experience_ir::{AgentConversation, ExperienceModel};
use experience_ir::{AgentConfigurationAction, AgentConversation, ExperienceModel};
#[cfg(not(feature = "core-native"))]
use gpui_mobile::android::jni::{activity, find_app_class, get_string, with_env};
#[cfg(not(feature = "core-native"))]
Expand Down Expand Up @@ -246,12 +246,34 @@ pub fn status() -> Result<AgentStatus, String> {

pub fn apply_status(conversation: &mut AgentConversation, status: &AgentStatus) {
conversation.available = status.provider == "fake" || status.configured;
conversation.configuration_actions = supported_configuration_actions();
if !conversation.busy {
conversation.activity = status.activity.clone();
}
conversation.error = reconciled_request_error(conversation.error.take(), false);
}

fn supported_configuration_actions() -> Vec<AgentConfigurationAction> {
#[cfg(feature = "core-native")]
{
vec![
AgentConfigurationAction::ConfigureOpenRouter,
AgentConfigurationAction::UseFake,
AgentConfigurationAction::ClearCredential,
]
}
#[cfg(not(feature = "core-native"))]
{
vec![
AgentConfigurationAction::ConfigureOpenAi,
AgentConfigurationAction::ConfigureOpenRouter,
AgentConfigurationAction::ConfigureCodex,
AgentConfigurationAction::UseFake,
AgentConfigurationAction::ClearCredential,
]
}
}

pub fn configure_openai() -> Result<(), String> {
call_bool("configureOpenAi")
}
Expand Down
137 changes: 127 additions & 10 deletions apps/experience/src/android/pointer_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ struct Surface {
order: u64,
}

#[derive(Clone)]
struct NativeInput {
id: String,
bounds: [f32; 4],
epoch: u64,
order: u64,
}

#[derive(Clone)]
struct Capture {
surface: Surface,
Expand All @@ -53,6 +61,7 @@ struct Router {
epoch: u64,
order: u64,
surfaces: HashMap<String, Surface>,
native_inputs: HashMap<String, NativeInput>,
captures: HashMap<u64, Capture>,
}

Expand Down Expand Up @@ -92,8 +101,9 @@ pub fn install() {
}

#[cfg(target_os = "linux")]
pub fn install() {
pub fn install() -> async_channel::Receiver<()> {
let start = *CLOCK.get_or_init(Instant::now);
let (wake_tx, wake_rx) = async_channel::bounded(1);
gpui_linux::set_raw_touch_callback(Some(Box::new(move |event| {
let phase = match event.phase {
gpui_linux::RawTouchPhase::Down => Phase::Down,
Expand All @@ -113,16 +123,20 @@ pub fn install() {
.unwrap_or_else(|| "unavailable".into()),
);
}
push_sample(Sample {
id: event.id.max(0) as u64,
phase,
x: event.x,
y: event.y,
pressure: event.pressure.unwrap_or(1.0).clamp(0.0, 1.0),
pointer_count: event.pointer_count.min(32),
event_time_nanos: start.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64,
});
push_sample_and_wake(
Sample {
id: event.id.max(0) as u64,
phase,
x: event.x,
y: event.y,
pressure: event.pressure.unwrap_or(1.0).clamp(0.0, 1.0),
pointer_count: event.pointer_count.min(32),
event_time_nanos: start.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64,
},
&wake_tx,
);
})));
wake_rx
}

pub fn begin_frame() {
Expand All @@ -135,6 +149,9 @@ pub fn begin_frame() {
router
.surfaces
.retain(|_, surface| epoch.saturating_sub(surface.epoch) <= 2);
router
.native_inputs
.retain(|_, input| epoch.saturating_sub(input.epoch) <= 2);
}

pub fn record_surface(id: &str, bounds: Bounds<Pixels>, interaction: &Interaction) {
Expand All @@ -161,6 +178,34 @@ pub fn record_surface(id: &str, bounds: Bounds<Pixels>, interaction: &Interactio
router.surfaces.insert(id.to_owned(), surface);
}

pub fn record_native_input(id: &str, bounds: Bounds<Pixels>) {
let mut router = ROUTER
.get_or_init(|| Mutex::new(Router::default()))
.lock()
.expect("pointer router lock");
router.order = router.order.wrapping_add(1).max(1);
let input = NativeInput {
id: id.to_owned(),
bounds: [
f32::from(bounds.origin.x),
f32::from(bounds.origin.y),
f32::from(bounds.size.width),
f32::from(bounds.size.height),
],
epoch: router.epoch,
order: router.order,
};
router.native_inputs.insert(id.to_owned(), input);
}

pub fn native_input_at(x: f32, y: f32) -> Option<String> {
let router = ROUTER
.get_or_init(|| Mutex::new(Router::default()))
.lock()
.expect("pointer router lock");
hit_native_input(&router, x, y).map(|input| input.id.clone())
}

pub fn take_samples() -> Vec<Sample> {
SAMPLES
.get_or_init(|| Mutex::new(VecDeque::new()))
Expand Down Expand Up @@ -318,6 +363,14 @@ fn hit_surface(router: &Router, x: f32, y: f32, event_time_nanos: u64) -> Option
})
}

fn hit_native_input(router: &Router, x: f32, y: f32) -> Option<&NativeInput> {
router
.native_inputs
.values()
.filter(|input| contains(input.bounds, x, y))
.max_by_key(|input| input.order)
}

fn contains(bounds: [f32; 4], x: f32, y: f32) -> bool {
x >= bounds[0] && y >= bounds[1] && x <= bounds[0] + bounds[2] && y <= bounds[1] + bounds[3]
}
Expand All @@ -342,6 +395,15 @@ fn push_sample(sample: Sample) {
samples.push_back(sample);
}

#[cfg(target_os = "linux")]
fn push_sample_and_wake(sample: Sample, wakes: &async_channel::Sender<()>) {
push_sample(sample);
// Raw Wayland touch bypasses GPUI's element input path. Mark the host
// dirty through its foreground task instead of waiting for an unrelated
// mouse/key event to trigger the next entity render.
let _ = wakes.try_send(());
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -353,6 +415,61 @@ mod tests {
assert_eq!(phase_name(Phase::Cancel), "cancel");
}

#[test]
fn native_input_hit_uses_the_topmost_recorded_bounds() {
let mut router = Router::default();
router.native_inputs.insert(
"note-draft".into(),
NativeInput {
id: "note-draft".into(),
bounds: [10.0, 10.0, 80.0, 40.0],
epoch: 1,
order: 1,
},
);
router.native_inputs.insert(
"agent-prompt".into(),
NativeInput {
id: "agent-prompt".into(),
bounds: [20.0, 20.0, 80.0, 40.0],
epoch: 1,
order: 2,
},
);

assert_eq!(
hit_native_input(&router, 25.0, 25.0).map(|input| input.id.as_str()),
Some("agent-prompt")
);
assert_eq!(
hit_native_input(&router, 15.0, 15.0).map(|input| input.id.as_str()),
Some("note-draft")
);
assert!(hit_native_input(&router, 200.0, 200.0).is_none());
}

#[cfg(target_os = "linux")]
#[test]
fn raw_touch_queues_one_coalesced_host_wake() {
take_samples();
let (wake_tx, wake_rx) = async_channel::bounded(1);
let sample = Sample {
id: 1,
phase: Phase::Down,
x: 4.0,
y: 8.0,
pressure: 1.0,
pointer_count: 1,
event_time_nanos: 10,
};
push_sample_and_wake(sample, &wake_tx);
push_sample_and_wake(sample, &wake_tx);

assert!(wake_rx.try_recv().is_ok());
assert!(wake_rx.try_recv().is_err());
assert_eq!(take_samples().len(), 2);
}

#[test]
fn surface_capture_routes_two_pointer_transform_until_release() {
begin_frame();
Expand Down
31 changes: 31 additions & 0 deletions apps/experience/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,37 @@ mod tests {
.unwrap();
assert!(contains_action(&stock_scene.root, "audio_volume_up"));
assert!(contains_agent_composer(&stock_scene.root));
for source in [
super::DEFAULT_EXPERIENCE,
super::TIMEFLOW_EXPERIENCE,
super::DAILY_FLOW_EXPERIENCE,
] {
let runtime = runtime_luau::LuauRuntime::compile(source).unwrap();
let model = providers_fake::snapshot();
let scene = runtime.render(&model, &runtime.initial_state()).unwrap();
for action in [
"agent_configure_openai",
"agent_configure_openrouter",
"agent_configure_codex",
"agent_use_fake",
"agent_clear_credential",
] {
assert!(!contains_action(&scene.root, action));
}

let mut configurable = model;
configurable.agent.configuration_actions = vec![
experience_ir::AgentConfigurationAction::ConfigureCodex,
experience_ir::AgentConfigurationAction::UseFake,
];
let scene = runtime
.render(&configurable, &runtime.initial_state())
.unwrap();
assert!(contains_action(&scene.root, "agent_configure_codex"));
assert!(contains_action(&scene.root, "agent_use_fake"));
assert!(!contains_action(&scene.root, "agent_configure_openai"));
assert!(!contains_action(&scene.root, "agent_configure_openrouter"));
}
let outcome = runtime
.update_with_effects(
&stock_model,
Expand Down
Loading