Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
obstruction receipts from counterfactual retention: refusal is a causal event
but not admission, and counterfactuals begin only after a rewrite is legally
admitted and then left unselected at the scheduler boundary.
- `warp-core` now attaches an `ObstructionReceipt` to capability grant intent
refusals. The receipt records causal refusal context and remains explicitly
`RewriteDisposition::Obstructed`, not an admission ticket, law witness, or
counterfactual candidate. It also carries the authority policy id/posture used
to classify the refusal when that policy context is present.
- `docs/design/transaction-optic-atomicity-model.md` defines Echo's doctrine for
atomic composite optics: one basis, one admission surface, transaction-local
execution, one committed delta, and receipt-emitting refusal or admission. It
Expand Down
11 changes: 6 additions & 5 deletions crates/warp-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,12 @@ pub use optic::{
pub use optic_artifact::{
AuthorityContext, AuthorityPolicy, AuthorityPolicyEvaluation, CapabilityGrantIntent,
CapabilityGrantIntentGate, CapabilityGrantIntentObstruction, CapabilityGrantIntentOutcome,
CapabilityGrantIntentPosture, OpticAdmissionRequirements, OpticAdmissionTicketPosture,
OpticApertureRequest, OpticArtifact, OpticArtifactHandle, OpticArtifactOperation,
OpticArtifactRegistrationError, OpticArtifactRegistry, OpticBasisRequest,
OpticCapabilityPresentation, OpticInvocation, OpticInvocationAdmissionOutcome,
OpticInvocationObstruction, OpticRegistrationDescriptor, PrincipalRef, RegisteredOpticArtifact,
CapabilityGrantIntentPosture, ObstructionReceipt, OpticAdmissionRequirements,
OpticAdmissionTicketPosture, OpticApertureRequest, OpticArtifact, OpticArtifactHandle,
OpticArtifactOperation, OpticArtifactRegistrationError, OpticArtifactRegistry,
OpticBasisRequest, OpticCapabilityPresentation, OpticInvocation,
OpticInvocationAdmissionOutcome, OpticInvocationObstruction, OpticRegistrationDescriptor,
PrincipalRef, RegisteredOpticArtifact, RewriteDisposition, OBSTRUCTION_RECEIPT_KIND,
OPTIC_ADMISSION_TICKET_POSTURE_KIND, OPTIC_ARTIFACT_HANDLE_KIND,
};
pub use playback::{CursorReceipt, TruthFrame, TruthSink};
Expand Down
175 changes: 174 additions & 1 deletion crates/warp-core/src/optic_artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub const OPTIC_ARTIFACT_HANDLE_KIND: &str = "optic-artifact-handle";
/// Echo-owned kind for a ticket-shaped pre-admission obstruction posture.
pub const OPTIC_ADMISSION_TICKET_POSTURE_KIND: &str = "optic-admission-ticket-posture";

/// Echo-owned kind for a causal refusal receipt.
pub const OBSTRUCTION_RECEIPT_KIND: &str = "obstruction-receipt";

const OPTIC_ARTIFACT_HANDLE_ID_PREFIX: &str = "optic-artifact-handle:";

/// Opaque Echo-owned runtime handle for a registered optic artifact.
Expand Down Expand Up @@ -147,6 +150,120 @@ pub struct PrincipalRef {
pub id: String,
}

/// Disposition for submitted rewrite material after Echo evaluates it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RewriteDisposition {
/// Echo selected and committed the rewrite.
Committed,
/// Echo admitted the rewrite as legal, but the scheduler did not select it.
LegalUnselectedCounterfactual,
/// Echo refused the intent before admission.
Obstructed,
}

/// Causal receipt for a refused intent.
///
/// Refusal is causal evidence, not an unrealized legal world. An
/// [`ObstructionReceipt`] is not an admission ticket, not a law witness, and not
/// a counterfactual candidate.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObstructionReceipt {
/// Stable discriminator for callers and wire adapters.
pub kind: String,
/// Intent id named by the refused intent.
pub intent_id: String,
/// Principal that proposed the refused intent.
pub proposed_by: PrincipalRef,
/// Subject named by the refused intent.
pub subject: PrincipalRef,
/// Artifact hash named by the refused intent.
pub artifact_hash: String,
/// Operation id named by the refused intent.
pub operation_id: String,
/// Requirements digest named by the refused intent.
pub requirements_digest: String,
/// Authority policy id supplied with refusal context, if any.
pub policy_id: Option<String>,
/// Obstruction-only policy evaluation posture.
pub policy_posture: String,
/// Structured obstruction kind encoded for receipt consumers.
pub obstruction_kind: String,
/// Rewrite disposition. Obstruction receipts must remain obstructed.
pub disposition: RewriteDisposition,
/// Deterministic receipt input bytes.
pub receipt_input_bytes: Vec<u8>,
/// BLAKE3 digest of `receipt_input_bytes`.
pub receipt_digest: [u8; 32],
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

impl ObstructionReceipt {
/// Creates a causal refusal receipt for a capability grant intent.
#[must_use]
pub fn for_capability_grant_intent(
intent: &CapabilityGrantIntent,
authority_context: &AuthorityContext,
obstruction: CapabilityGrantIntentObstruction,
) -> Self {
let obstruction_kind = obstruction.receipt_label().to_owned();
let policy_id = authority_context
.policy
.as_ref()
.map(|policy| policy.policy_id.clone());
let policy_posture = authority_context
.policy_evaluation
.receipt_label()
.to_owned();
let receipt_input_bytes = Self::capability_grant_intent_receipt_input(
intent,
policy_id.as_deref(),
&policy_posture,
&obstruction_kind,
);
let receipt_digest = *blake3::hash(&receipt_input_bytes).as_bytes();

Self {
kind: OBSTRUCTION_RECEIPT_KIND.to_owned(),
intent_id: intent.intent_id.clone(),
proposed_by: intent.proposed_by.clone(),
subject: intent.subject.clone(),
artifact_hash: intent.artifact_hash.clone(),
operation_id: intent.operation_id.clone(),
requirements_digest: intent.requirements_digest.clone(),
policy_id,
policy_posture,
obstruction_kind,
disposition: RewriteDisposition::Obstructed,
receipt_input_bytes,
receipt_digest,
}
}

fn capability_grant_intent_receipt_input(
intent: &CapabilityGrantIntent,
policy_id: Option<&str>,
policy_posture: &str,
obstruction_kind: &str,
) -> Vec<u8> {
let mut bytes = Vec::new();
push_receipt_field(&mut bytes, OBSTRUCTION_RECEIPT_KIND.as_bytes());
push_receipt_field(&mut bytes, b"rewrite-disposition.obstructed");
push_receipt_field(&mut bytes, intent.intent_id.as_bytes());
push_receipt_field(&mut bytes, intent.proposed_by.id.as_bytes());
push_receipt_field(&mut bytes, intent.subject.id.as_bytes());
push_receipt_field(&mut bytes, intent.artifact_hash.as_bytes());
push_receipt_field(&mut bytes, intent.operation_id.as_bytes());
push_receipt_field(&mut bytes, intent.requirements_digest.as_bytes());
push_receipt_field(&mut bytes, policy_id.unwrap_or_default().as_bytes());
push_receipt_field(&mut bytes, policy_posture.as_bytes());
push_receipt_field(&mut bytes, obstruction_kind.as_bytes());
push_receipt_field_list(&mut bytes, &intent.rights);
push_receipt_field(&mut bytes, &intent.scope_bytes);
push_optional_receipt_field(&mut bytes, intent.expiry_bytes.as_deref());
push_optional_receipt_field(&mut bytes, intent.delegation_basis_bytes.as_deref());
bytes
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// Authority policy selected for grant-intent evaluation.
///
/// No policy is implemented in this slice. The shape exists so Echo can name
Expand All @@ -172,6 +289,16 @@ pub enum AuthorityPolicyEvaluation {
Unsupported,
}

impl AuthorityPolicyEvaluation {
fn receipt_label(self) -> &'static str {
match self {
Self::InvalidDelegation => "authority-policy.invalid-delegation",
Self::ScopeEscalation => "authority-policy.scope-escalation",
Self::Unsupported => "authority-policy.unsupported",
}
}
}

/// Authority context supplied when proposing a capability grant intent.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityContext {
Expand Down Expand Up @@ -230,6 +357,21 @@ pub enum CapabilityGrantIntentObstruction {
UnsupportedAuthorityPolicy,
}

impl CapabilityGrantIntentObstruction {
fn receipt_label(self) -> &'static str {
match self {
Self::MissingIssuerAuthority => "capability-grant-intent.missing-issuer-authority",
Self::MalformedGrantIntent => "capability-grant-intent.malformed-grant-intent",
Self::InvalidDelegation => "capability-grant-intent.invalid-delegation",
Self::ScopeEscalation => "capability-grant-intent.scope-escalation",
Self::ReplayOrDuplicateIntent => "capability-grant-intent.replay-or-duplicate-intent",
Self::UnsupportedAuthorityPolicy => {
"capability-grant-intent.unsupported-authority-policy"
}
}
}
}

/// Obstructed posture for a submitted capability grant intent.
///
/// This is not an admitted grant receipt and does not make the grant authority.
Expand All @@ -248,6 +390,9 @@ pub struct CapabilityGrantIntentPosture {
pub subject: PrincipalRef,
/// Structured reason Echo obstructed before admitting the grant.
pub obstruction: CapabilityGrantIntentObstruction,
/// Causal refusal receipt. This is not an admission ticket, law witness, or
/// counterfactual candidate.
pub receipt: ObstructionReceipt,
}

/// Submission outcome for a capability grant intent skeleton.
Expand Down Expand Up @@ -383,7 +528,7 @@ impl CapabilityGrantIntentGate {
.insert(intent.intent_id.clone(), intent.clone());
}

Self::obstructed_grant_intent(&intent, obstruction)
Self::obstructed_grant_intent(&intent, &authority_context, obstruction)
}

/// Returns the number of submitted grant intents.
Expand Down Expand Up @@ -463,6 +608,7 @@ impl CapabilityGrantIntentGate {

fn obstructed_grant_intent(
intent: &CapabilityGrantIntent,
authority_context: &AuthorityContext,
obstruction: CapabilityGrantIntentObstruction,
) -> CapabilityGrantIntentOutcome {
CapabilityGrantIntentOutcome::Obstructed(CapabilityGrantIntentPosture {
Expand All @@ -471,10 +617,37 @@ impl CapabilityGrantIntentGate {
proposed_by: intent.proposed_by.clone(),
subject: intent.subject.clone(),
obstruction,
receipt: ObstructionReceipt::for_capability_grant_intent(
intent,
authority_context,
obstruction,
),
})
}
}

fn push_receipt_field(bytes: &mut Vec<u8>, field: &[u8]) {
bytes.extend_from_slice(&(field.len() as u64).to_be_bytes());
bytes.extend_from_slice(field);
}

fn push_receipt_field_list(bytes: &mut Vec<u8>, fields: &[String]) {
bytes.extend_from_slice(&(fields.len() as u64).to_be_bytes());
for field in fields {
push_receipt_field(bytes, field.as_bytes());
}
}

fn push_optional_receipt_field(bytes: &mut Vec<u8>, field: Option<&[u8]>) {
match field {
Some(field) => {
bytes.push(1);
push_receipt_field(bytes, field);
}
None => bytes.push(0),
}
}

/// Echo-owned runtime-local registry for Wesley-compiled optic artifacts.
#[derive(Clone, Debug, Default)]
pub struct OpticArtifactRegistry {
Expand Down
Loading
Loading