Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 40 additions & 0 deletions .github/workflows/windows-sandbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: windows-sandbox

on:
pull_request:
paths:
- 'crates/worker/awaken-sandbox-fs/**'
- 'crates/worker/awaken-sandbox-local/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- '.github/workflows/windows-sandbox.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
regression:
runs-on: windows-2025
timeout-minutes: 30
env:
CARGO_PROFILE_DEV_DEBUG: '0'
CARGO_PROFILE_TEST_DEBUG: '0'
CARGO_INCREMENTAL: '0'
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
with:
toolchain: 1.96.0
components: clippy,rustfmt
- name: Check filesystem formatting
run: cargo fmt -p awaken-sandbox-fs -- --check
- name: Check filesystem warnings
run: cargo clippy -p awaken-sandbox-fs --all-targets --locked -- -D warnings
- name: Exercise Windows filesystem failure and race paths
run: cargo test -p awaken-sandbox-fs --locked
- name: Exercise provider cleanup completion and retry
run: cargo test -p awaken-sandbox-local --lib --locked windows_disposal
57 changes: 57 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/bin/awaken-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,7 @@ mod runtime_session_store_tests {
fn session(id: &str) -> PersistedSession {
PersistedSession {
session_id: id.to_string(),
created_at_unix_ms: 1_700_000_000_000,
revision: Default::default(),
baseline: SessionBaselineState::Preparing(creation_intent()),
title: None,
Expand Down
39 changes: 39 additions & 0 deletions crates/contract/awaken-session-contract/src/event_batches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ use awaken_agent_contract::agent::content::ContentBlock;
use awaken_agent_contract::agent::run::Id as RunId;
use serde::{Deserialize, Serialize};

fn current_epoch_millis() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}

const SESSION_EVENT_BATCH_OPERATION_PREFIX: &str = "session-event-batch-v1:";
const SESSION_ROOT_REVISION_BATCH_PREFIX: &str = "session-root-revision-v1:";
pub const MAX_SESSION_INITIAL_EVENTS: usize = 50;
Expand Down Expand Up @@ -365,6 +372,10 @@ pub struct SessionEventEntry {
pub event: SessionEventCommand,
#[serde(default)]
pub processed: bool,
/// Wall-clock time at which this command acquired its immutable projection
/// anchor. Zero is reserved for historical rows that only stored the flag.
#[serde(default)]
pub processed_at_unix_ms: u64,
/// Absent for unprocessed and legacy commands. Such entries remain durable
/// provenance but are not listable until the existing effect owner supplies
/// one immutable commit coordinate.
Expand Down Expand Up @@ -447,6 +458,7 @@ impl SessionEventBatch {
}
entry.projection_anchor = Some(projection_anchor);
entry.processed = true;
entry.processed_at_unix_ms = current_epoch_millis();
Ok(true)
}

Expand All @@ -459,10 +471,12 @@ impl SessionEventBatch {
projection_anchor: Option<SessionEventProjectionAnchor>,
) -> usize {
let mut resolved = 0;
let processed_at_unix_ms = current_epoch_millis();
for entry in &mut self.events {
if !entry.processed {
entry.projection_anchor = projection_anchor;
entry.processed = true;
entry.processed_at_unix_ms = processed_at_unix_ms;
resolved += 1;
}
}
Expand Down Expand Up @@ -542,6 +556,7 @@ impl SessionEventBatch {
data_subject_id: data_subject_id.clone(),
},
processed: false,
processed_at_unix_ms: 0,
projection_anchor: None,
});
}
Expand All @@ -555,6 +570,7 @@ impl SessionEventBatch {
content,
},
processed: false,
processed_at_unix_ms: 0,
projection_anchor: None,
});
}
Expand Down Expand Up @@ -582,6 +598,7 @@ impl SessionEventBatch {
max_iterations,
},
processed: false,
processed_at_unix_ms: 0,
projection_anchor: None,
});
}
Expand All @@ -599,6 +616,7 @@ impl SessionEventBatch {
reply,
},
processed: false,
processed_at_unix_ms: 0,
projection_anchor: None,
});
}
Expand Down Expand Up @@ -627,6 +645,7 @@ impl SessionEventBatch {
interrupt,
},
processed: false,
processed_at_unix_ms: 0,
projection_anchor: None,
});
}
Expand Down Expand Up @@ -816,6 +835,26 @@ mod tests {
"R3/E4"
);
assert!(progress.events[0].processed, "R3/E4");
assert_ne!(
progress.events[0].processed_at_unix_ms, 0,
"R3/E4 records a durable real processing time"
);
let processed_at = progress.events[0].processed_at_unix_ms;
assert!(
!progress
.mark_processed(
&retained,
SessionEventProjectionAnchor {
source_commit_cursor: 9,
},
)
.unwrap(),
"R3/E4 exact replay is a no-op"
);
assert_eq!(
progress.events[0].processed_at_unix_ms, processed_at,
"R3/E4 exact replay preserves the original time"
);
assert_eq!(
progress.events[0]
.projection_anchor
Expand Down
3 changes: 3 additions & 0 deletions crates/contract/awaken-session-contract/src/session_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ mod mutation_tests {

PersistedSession {
session_id: id.into(),
created_at_unix_ms: 1_700_000_000_000,
revision,
baseline: crate::SessionBaselineState::Preparing(crate::SessionCreationIntent {
control: crate::ControlSessionCreationInputs {
Expand Down Expand Up @@ -187,6 +188,7 @@ mod mutation_tests {
let metadata = BTreeMap::from([("key".to_string(), "value".to_string())]);
let prepared = PersistedSession::frozen_with_budget(
"constructor",
1_700_000_000_000,
compiled.baseline,
Default::default(),
Default::default(),
Expand All @@ -196,6 +198,7 @@ mod mutation_tests {
Default::default(),
);
assert_eq!(prepared.session_id, "constructor", "C1/E1");
assert_eq!(prepared.created_at_unix_ms, 1_700_000_000_000, "C1/E1");
assert_eq!(prepared.revision, SessionRevision(0), "C1/E1");
assert_eq!(
prepared.execution,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use super::*;
#[serde(deny_unknown_fields)]
pub struct PersistedSession {
pub session_id: String,
/// Immutable wall-clock creation time owned by the durable Session root.
/// Zero is reserved for historical rows written before this field existed.
#[serde(default)]
pub created_at_unix_ms: u64,
/// The one optimistic-concurrency fence for baseline, Resource, MCP,
/// environment, execution, and disposition mutations. New, not-yet-inserted
/// values use 0.
Expand Down Expand Up @@ -102,6 +106,7 @@ impl PersistedSession {
#[allow(clippy::too_many_arguments)]
pub fn frozen_with_budget(
session_id: impl Into<String>,
created_at_unix_ms: u64,
baseline: crate::SessionBaseline,
resources: crate::SessionResourceState,
mcp: crate::SessionMcpAttachmentSet,
Expand All @@ -112,6 +117,7 @@ impl PersistedSession {
) -> Self {
Self {
session_id: session_id.into(),
created_at_unix_ms,
revision: SessionRevision::default(),
baseline: crate::SessionBaselineState::Frozen(baseline),
title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ fn frozen_application_session(
.expect("prepare Worker Session resources");
awaken_session_contract::PersistedSession {
session_id: session_id.into(),
created_at_unix_ms: 1_700_000_000_000,
revision: Default::default(),
baseline: awaken_session_contract::SessionBaselineState::Frozen(
awaken_session_contract::SessionBaseline::compile(
Expand Down
3 changes: 3 additions & 0 deletions crates/resources/awaken-skill-store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ impl FsSkillStore {
.truncate(false)
.open(root.join(".awaken-skill.lock"))?
.sync_all()?;
// Opening a directory as a File is not supported on Windows. The lock
// file is already synced above; retain the directory fsync on Unix.
#[cfg(unix)]
File::open(&root)?.sync_all()?;
let store = Self { root };
Ok(store)
Expand Down
1 change: 1 addition & 0 deletions crates/server/awaken-coordinator/src/application_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ mod tests {
}));
PersistedSession {
session_id: id.into(),
created_at_unix_ms: 1_700_000_000_000,
revision: Default::default(),
baseline,
title: None,
Expand Down
35 changes: 35 additions & 0 deletions crates/server/awaken-protocol-managed/src/state/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,41 @@ pub(crate) const DEFAULT_SCOPE: &str = "default";
/// clock port; the wire only needs a valid RFC 3339 value here.
pub(crate) const PROCESSED_AT: &str = "2026-01-01T00:00:00Z";

/// Current wall-clock time for newly-created public resource projections.
///
/// `PROCESSED_AT` is retained for deterministic event projection until that
/// projection receives its clock port. It must not be used as the creation
/// time shown to users.
pub(crate) fn now_unix_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}

pub(crate) fn session_created_at(epoch_millis: u64) -> String {
if epoch_millis == 0 {
PROCESSED_AT.to_string()
} else {
awaken_session_contract::epoch_millis_to_rfc3339(epoch_millis)
}
}

#[cfg(test)]
mod tests {
#[test]
fn current_projection_time_is_not_the_deterministic_placeholder() {
let timestamp = super::session_created_at(super::now_unix_ms());
assert_ne!(timestamp, super::PROCESSED_AT);
assert!(timestamp.ends_with('Z'));
}

#[test]
fn historical_session_without_creation_time_keeps_legacy_placeholder() {
assert_eq!(super::session_created_at(0), super::PROCESSED_AT);
}
}

/// The Managed Agents contract error for a `memory_store` add/remove on a running
/// session — memory stores bind at session creation only.
pub(crate) const MEMORY_CREATE_ONLY: &str = "memory stores can only be attached at session creation time; adding or removing one from a \
Expand Down
Loading