Skip to content
Merged
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
8 changes: 4 additions & 4 deletions crates/harness-runtime/src/facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,9 +1047,8 @@ mod tests {
Operation::SoftwareUpdate,
Operation::SoftwareRemove,
Operation::PatchInstructionRegion,
// Performed by the kernel but withheld everywhere until a released
// consumer accepts the name — declaring it earlier would make
// every older reader refuse the whole provider-info answer.
// A surface this build does not carry: SAMPLE declares no
// instruction region, so neither region operation is declared.
Operation::DetachInstructionRegion,
] {
assert!(
Expand All @@ -1060,13 +1059,14 @@ mod tests {
}

#[test]
fn an_instruction_surface_without_software_still_declares_the_patch() {
fn an_instruction_surface_without_software_still_declares_the_region_lifecycle() {
let named = Harness {
instruction_region: Some("AGENTS.md"),
..SAMPLE
};
let info = named.provider_info().unwrap();
assert!(info.declares(Operation::PatchInstructionRegion));
assert!(info.declares(Operation::DetachInstructionRegion));
assert!(!info.declares(Operation::SoftwareInstall));
assert!(!info.declares(Operation::Launch));
}
Expand Down
89 changes: 86 additions & 3 deletions crates/harness-runtime/src/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,12 +537,40 @@ fn status_of(
},
),
("backups", backup_status(pool, resolved, harness, scope)?),
(
"instruction_region",
instruction_attachment_status(harness, resolved),
),
] {
answer.insert(key.to_owned(), value);
}
Ok(serde_json::Value::Object(answer))
}

/// The managed instruction attachment as `status` reports it, or `null` for a
/// harness that declares no user-global instruction surface. Reads nothing
/// else: an unreadable file, and one whose markers are ambiguous, both report
/// `section_present: false` — there is no well-formed owned section either
/// way, and `present` is what keeps those two apart for the caller.
fn instruction_attachment_status(harness: &Harness, resolved: &Target) -> serde_json::Value {
let Some(relative) = harness.instruction_region else {
return serde_json::Value::Null;
};
let surface = resolved.root().join(relative);
let existing = crate::instruction_region::read_utf8(&surface).unwrap_or_else(|_| String::new());
let section = if crate::instruction_region::markers_well_formed(&existing) {
crate::instruction_region::extract(&existing)
} else {
None
};
serde_json::json!({
"path": relative,
"present": surface.exists(),
"section_present": section.is_some(),
"section_sha256": section.map(|held| setup_core::digest::of_bytes(held.as_bytes())),
})
}

/// Everything a clean managed target's own state says about how it got here.
///
/// Serialized from the record rather than listed again here. The contract's
Expand Down Expand Up @@ -4706,6 +4734,62 @@ mod tests {
);
}

#[test]
fn status_reports_the_instruction_attachment_or_its_absence() {
const BARE: Harness = Harness {
instruction_region: None,
..TEST
};
let target = seeded("status-attachment");
let bare = dispatch(&BARE, argv::parse(args("status", &target, &[])).unwrap()).unwrap();
assert!(
bare["instruction_region"].is_null(),
"a harness without the surface must answer null, not a state: {bare}"
);

// File absent: nothing to report beyond that.
fs::remove_file(target.join("AGENTS.md")).unwrap();
let missing = run(args("status", &target, &[]));
assert_eq!(missing["instruction_region"]["path"], "AGENTS.md");
assert_eq!(missing["instruction_region"]["present"], false);
assert_eq!(missing["instruction_region"]["section_present"], false);
assert!(missing["instruction_region"]["section_sha256"].is_null());

// File present, section absent.
let target = seeded("status-attachment-file");
let plain = run(args("status", &target, &[]));
assert_eq!(plain["instruction_region"]["present"], true);
assert_eq!(plain["instruction_region"]["section_present"], false);
assert!(plain["instruction_region"]["section_sha256"].is_null());

// Section present: the digest is the region's own, never target_digest.
plan_then_apply(
&target,
"patch_instruction_region",
&["--instruction-section", INSTRUCTION_SECTION],
);
let attached = run(args("status", &target, &[]));
assert_eq!(attached["instruction_region"]["section_present"], true);
let digest = attached["instruction_region"]["section_sha256"]
.as_str()
.unwrap();
assert_eq!(
digest,
setup_core::digest::of_bytes(INSTRUCTION_SECTION.as_bytes()),
"section_sha256 must be the region digest: {attached}"
);

// Ambiguous markers read as "no well-formed section", not a crash.
fs::write(
target.join("AGENTS.md"),
b":::end-ai-stp\n:::begin-ai-stp\n",
)
.unwrap();
let ambiguous = run(args("status", &target, &[]));
assert_eq!(ambiguous["instruction_region"]["present"], true);
assert_eq!(ambiguous["instruction_region"]["section_present"], false);
}

#[test]
fn replacement_spares_a_never_touch_path_inside_a_namespace_it_empties() {
const SPARES: Harness = Harness {
Expand Down Expand Up @@ -8529,10 +8613,9 @@ mod tests {
assert!(info.declares(Operation::SoftwareUpdate));
assert!(info.declares(Operation::SoftwareRemove));
assert!(info.declares(Operation::PatchInstructionRegion));
// Implemented, but withheld until a released consumer accepts the name.
assert!(
!info.declares(Operation::DetachInstructionRegion),
"declaring before a reader ships makes older consumers refuse the whole answer"
info.declares(Operation::DetachInstructionRegion),
"the detach went out with the consumer release that accepts the name"
);
}

Expand Down
21 changes: 14 additions & 7 deletions crates/provider-v3/src/vocabulary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,9 @@ pub enum Operation {
/// payload it is free to empty.
PatchInstructionRegion,
/// Remove only the marked instruction section, keeping every byte outside
/// the markers. Optional; takes no `--instruction-section`. Implemented in
/// the kernel but withheld from `provider-info` until a released consumer
/// accepts the name — declaring it first would refuse every older reader
/// the whole `provider-info` answer.
/// the markers. Optional; takes no `--instruction-section`. Declared only
/// once a released consumer accepts the name — an older reader refuses the
/// whole `provider-info` answer on an unknown operation.
DetachInstructionRegion,
}

Expand All @@ -163,6 +162,7 @@ impl Operation {
Self::SoftwareRemove,
Self::Launch,
Self::PatchInstructionRegion,
Self::DetachInstructionRegion,
];

/// The operations every provider must support.
Expand All @@ -187,17 +187,20 @@ impl Operation {
Self::SoftwareRemove,
];

/// [`CORE`] plus the instruction-region patch, without software or launch.
/// [`CORE`] plus the instruction-region lifecycle, without software or
/// launch.
pub const CORE_AND_INSTRUCTION: &'static [Self] = &[
Self::Backup,
Self::Install,
Self::Remove,
Self::Replace,
Self::Restore,
Self::PatchInstructionRegion,
Self::DetachInstructionRegion,
];

/// [`CORE_AND_SOFTWARE`] plus the instruction-region patch, without launch.
/// [`CORE_AND_SOFTWARE`] plus the instruction-region lifecycle, without
/// launch.
pub const CORE_AND_SOFTWARE_AND_INSTRUCTION: &'static [Self] = &[
Self::Backup,
Self::Install,
Expand All @@ -209,6 +212,7 @@ impl Operation {
Self::SoftwareUpdate,
Self::SoftwareRemove,
Self::PatchInstructionRegion,
Self::DetachInstructionRegion,
];

/// [`ALL`] without the instruction-region patch.
Expand Down Expand Up @@ -256,7 +260,10 @@ impl Operation {

/// Operations this kernel can perform, whether or not they may be declared
/// to the consumer yet. [`ALL`] is the declarable set; the difference is
/// exactly the members awaiting a released consumer that accepts the name.
/// exactly the members awaiting a released consumer that accepts the name,
/// and today that difference is empty. A new operation whose name an older
/// reader cannot parse joins this list first and [`ALL`] only when the
/// consumer shipping the name is released.
pub const IMPLEMENTED: &'static [Self] = &[
Self::Install,
Self::Replace,
Expand Down
4 changes: 2 additions & 2 deletions provider-kit/v3/KIT-IDENTITY.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"aggregate_digest": "sha256:e2a35eaf2e0f834913962d9a6337948eeb78290b4ad8c3af7674620215610b0e",
"aggregate_digest": "sha256:b8941c16214c49b2e527f75f71df97cdcb18a1923861391d4b0db03db7cbe4e2",
"files": [
"conformance-cases.json",
"manifest.json",
"provider-info.schema.json",
"status-response.schema.json"
],
"kit_version": "0.2.13",
"kit_version": "0.2.14",
"protocol_version": 3,
"schema": "ai-stp-provider-kit-identity/1"
}
6 changes: 3 additions & 3 deletions provider-kit/v3/SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fe04d03b15cfe8d5b61835eafd3ea788074684e2be69d419f9c22a37ea0461b7 conformance-cases.json
cfc55bb6f301589ded449c12ba0309e12238db7bbce3f5e63fc45e9731d4bee5 manifest.json
2f28fef790dc2685e9790f4ae3123b1c6d581c94f87d15b75823ea8d575036aa provider-info.schema.json
7e67760f24bbc42a1242dad7ad3b378eef7ae15023c6813a33d582dfd2232293 status-response.schema.json
4c32e4320d6838cbea0f852d2ac3dd7095559120acdb94c5a51b2e2a4c2bea1f manifest.json
aeac0a4226796bbc5825eaa61c8b6c90e3a7e5ba9dec7f8f7bcc05f075d87d61 provider-info.schema.json
6af570bbecce664b6270b401904f97e632fdbae941fbb8925a0379beba5394bc status-response.schema.json
13 changes: 12 additions & 1 deletion provider-kit/v3/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
],
"decision": "docs/adr/ADR-0061-capability-negotiated-provider-protocol-v3.md",
"generated_from": "apps/cli/src/ai_stp_cli/provider/protocol_v3.py",
"kit_version": "0.2.13",
"kit_version": "0.2.14",
"operation_network": {
"backup": [
{
Expand All @@ -51,6 +51,16 @@
"phase": "apply"
}
],
"detach_instruction_region": [
{
"network_requirement": "none",
"phase": "plan"
},
{
"network_requirement": "none",
"phase": "apply"
}
],
"install": [
{
"network_requirement": "none",
Expand Down Expand Up @@ -160,6 +170,7 @@
"launch"
],
"optional_operations": [
"detach_instruction_region",
"launch",
"patch_instruction_region",
"reset",
Expand Down
3 changes: 2 additions & 1 deletion provider-kit/v3/provider-info.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@
"software_update",
"software_remove",
"launch",
"patch_instruction_region"
"patch_instruction_region",
"detach_instruction_region"
],
"type": "string"
},
Expand Down
39 changes: 39 additions & 0 deletions provider-kit/v3/status-response.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,45 @@
"minLength": 1,
"type": "string"
},
"instruction_region": {
"oneOf": [
{
"additionalProperties": false,
"properties": {
"path": {
"minLength": 1,
"type": "string"
},
"present": {
"type": "boolean"
},
"section_present": {
"type": "boolean"
},
"section_sha256": {
"oneOf": [
{
"$ref": "#/$defs/digest"
},
{
"type": "null"
}
]
}
},
"required": [
"path",
"present",
"section_present",
"section_sha256"
],
"type": "object"
},
{
"type": "null"
}
]
},
"journal": {
"oneOf": [
{
Expand Down
Loading