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
108 changes: 103 additions & 5 deletions crates/warp-core/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,12 @@ pub struct IntentOutcomeReceipt {
pub receipt_entry_index: u32,
/// Scheduler rule that produced the receipt entry.
pub rule_id: Hash,
/// Retained receipt evidence posture for generated contract work.
///
/// This is empty for non-contract work. When contract evidence is present,
/// Echo reports the semantic receipt coordinate honestly even if no retained
/// descriptor has been written yet.
pub retained_evidence: Vec<crate::RetainedEvidencePosture>,
}

impl IntentOutcomeReceipt {
Expand All @@ -699,10 +705,35 @@ impl IntentOutcomeReceipt {
commit_hash: correlation.commit_hash,
receipt_entry_index,
rule_id,
retained_evidence: retained_contract_receipt_evidence(correlation),
}
}
}

fn contract_receipt_coordinate(
correlation: &ReceiptCorrelationRecord,
) -> Option<crate::RetainedEvidenceCoordinate> {
correlation.contract.as_ref().map(|contract| {
crate::RetainedEvidenceCoordinate::new(
contract.clone(),
crate::RetainedEvidenceRole::ContractReceipt,
correlation.tick_receipt_digest,
)
})
}

fn retained_contract_receipt_evidence(
correlation: &ReceiptCorrelationRecord,
) -> Vec<crate::RetainedEvidencePosture> {
contract_receipt_coordinate(correlation)
.map(|coordinate| {
vec![crate::RetainedEvidencePosture::missing_coordinate(
&coordinate,
)]
})
.unwrap_or_default()
}

/// App-facing outcome posture for a witnessed intent submission.
///
/// This is a read-only polling shape. It does not tick, dispatch handlers,
Expand Down Expand Up @@ -809,11 +840,10 @@ impl IntentOutcome {
tick_receipt_digest,
},
} => {
let mut obstruction =
crate::ContractObstruction::missing_retention(tick_receipt_digest);
if let Some(contract) = correlation.contract.as_ref() {
obstruction = obstruction.with_contract(contract.clone());
}
let obstruction = contract_receipt_coordinate(&correlation).map_or_else(
|| crate::ContractObstruction::missing_retention(tick_receipt_digest),
|coordinate| coordinate.missing_retention_obstruction(),
);
Self::Obstructed {
submission_id: correlation.submission_id,
obstruction,
Expand Down Expand Up @@ -4617,6 +4647,74 @@ mod tests {
}
other => panic!("expected obstructed outcome, got {other:?}"),
}

let contract = crate::ContractEvidenceIdentity {
package_id: crate::InstalledContractPackageId::from_bytes(hash(13)),
echo_abi_version: 1,
package_name: "test-package".to_owned(),
package_version: "0.1.0".to_owned(),
artifact_hash_hex: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
.to_owned(),
codec_id: "cbor-canon-v1".to_owned(),
registry_version: 1,
wesley_generator_version: "echo-wesley-gen/0.1.0".to_owned(),
helper_api_version: 1,
schema_sha256_hex: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
.to_owned(),
op_id: 7,
op_kind: crate::ContractOperationKind::Mutation,
};
let contract_correlation = ReceiptCorrelationRecord {
contract: Some(contract.clone()),
..correlation.clone()
};
let applied_with_contract =
IntentOutcome::from_observation(IntentOutcomeObservation::Decided {
correlation: Box::new(contract_correlation.clone()),
decision: IntentOutcomeDecision::Applied {
receipt_entry_index: 5,
rule_id: hash(14),
},
});
let IntentOutcome::Applied { receipt, .. } = applied_with_contract else {
panic!("expected applied contract outcome");
};
assert_eq!(receipt.contract, Some(contract.clone()));
match receipt.retained_evidence.as_slice() {
[crate::RetainedEvidencePosture::MissingCoordinate {
coordinate,
obstruction,
}] => {
assert_eq!(coordinate.contract, contract);
assert_eq!(
coordinate.role,
crate::RetainedEvidenceRole::ContractReceipt
);
assert_eq!(
coordinate.semantic_digest,
contract_correlation.tick_receipt_digest
);
assert_eq!(
obstruction.subject,
crate::ContractObstructionSubject::Retention {
retention_id: coordinate.coordinate_id()
}
);
}
other => panic!("expected retained receipt coordinate posture, got {other:?}"),
}

let obstructed_with_contract =
IntentOutcome::from_observation(IntentOutcomeObservation::Decided {
correlation: Box::new(contract_correlation.clone()),
decision: IntentOutcomeDecision::NoMatchingReceiptEntry {
tick_receipt_digest: contract_correlation.tick_receipt_digest,
},
});
let IntentOutcome::Obstructed { obstruction, .. } = obstructed_with_contract else {
panic!("expected obstructed contract outcome");
};
assert!(obstruction.contract.is_some());
}

#[test]
Expand Down
13 changes: 4 additions & 9 deletions docs/WorkItems.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,13 @@ Current active signposts:
Progress bars from the current work stream:

```text
[#####-----] Echo/jedit retained-evidence release-gate batch [5/10 slices]
[#####-----] PR checkpoint batch [5/5 slices before recommended PR]
[##########] Echo/jedit retained-evidence release-gate batch [10/10 slices]
[##########] PR checkpoint batch [10/10 slices before next PR]
[##########] Echo WAL truth boundary and runtime ACK plumbing [95/95 slices]
```

Immediate unfinished slice in the current batch:

- Slice 6: Echo product-facing intent outcome API polish.
- Slice 7: jedit generated structural-history request path.
- Slice 8: generated package install path.
- Slice 9: jedit real mutation and query round trip.
- Slice 10: jedit non-happy path and release-gate report.
Current batch status: complete; open paired Echo and jedit PRs before starting
the next implementation batch.

## Known Cross-Repo And Storage Doctrine Gaps

Expand Down
9 changes: 7 additions & 2 deletions docs/design/product-facing-intent-outcome-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ surface returning `IntentOutcome`:

Applied and rejected outcomes carry `IntentOutcomeReceipt`, which names the
ticketed ingress id, admission ticket digest, tick receipt digest, commit hash,
worldline tick, entry index, and rule id. Obstructed outcomes use the
contract-host obstruction taxonomy.
worldline tick, entry index, rule id, installed contract evidence when present,
and retained receipt evidence posture where Echo can name a lawful contract
coordinate. Obstructed outcomes use the contract-host obstruction taxonomy and
use the same retained receipt coordinate for generated contract work.

## Invariants

Expand All @@ -40,6 +42,9 @@ contract-host obstruction taxonomy.
- Rejected is a lawful scheduler outcome, not a runtime fault.
- Obstructed means Echo cannot honestly interpret required evidence.
- Missing receipt evidence maps to `MissingRetention`.
- Contract-backed outcomes surface retained receipt posture honestly. A missing
retained descriptor is reported as `MissingCoordinate`; it is not an empty
success and not a fabricated retained byte reference.
- The surface exposes no tick, step, scheduler, or trusted runtime authority.

## Non-Goals
Expand Down
54 changes: 47 additions & 7 deletions docs/design/v0.1.0-jedit-next-ten-slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ remain generic; jedit owns product nouns and product-facing adapter language.
## Progress

```text
[#####-----] jedit release-gate next-ten plan [5/10 slices]
[#####-----] PR checkpoint batch [5/5 slices before recommended PR]
[##########] jedit release-gate next-ten plan [10/10 slices]
[##########] PR checkpoint batch [10/10 slices before next PR]
```

- [x] Slice 1: jedit release-gate doctrine cleanup.
- [x] Slice 2: jedit witness retained-evidence inventory.
- [x] Slice 3: Echo retained evidence ref surface check.
- [x] Slice 4: jedit adapter consumes Echo retained refs.
- [x] Slice 5: jedit replay witness shell.
- [ ] Slice 6: Echo product-facing intent outcome API polish.
- [ ] Slice 7: jedit generated structural-history request path.
- [ ] Slice 8: generated package install path.
- [ ] Slice 9: jedit real mutation and query round trip.
- [ ] Slice 10: jedit non-happy path and release-gate report.
- [x] Slice 6: Echo product-facing intent outcome API polish.
- [x] Slice 7: jedit generated structural-history request path.
- [x] Slice 8: generated package install path.
- [x] Slice 9: jedit real mutation and query round trip.
- [x] Slice 10: jedit non-happy path and release-gate report.

## Slice 1: jedit Release-Gate Doctrine Cleanup

Expand Down Expand Up @@ -238,6 +238,15 @@ Witness:
- trusted host runtime-control policy remains separate;
- jedit can consume the outcome surface.

Current implementation note: Echo's app-facing `IntentOutcomeReceipt` now
carries installed contract context plus retained receipt evidence posture for
generated contract outcomes. Until durable receipt retention exists, the
surface reports a `MissingCoordinate` for the contract receipt coordinate
instead of fabricating a retained byte reference. Missing contract receipt
evidence also obstructs against the same retained coordinate. The app facade
still exposes only submit/observe surfaces; tick and drain authority remain on
the trusted host.

## Slice 7: jedit Generated Structural-History Request Path

Repository: `jedit`; `wesley` only if generator support is missing.
Expand All @@ -262,6 +271,12 @@ Witness:
- no hard-coded stack-witness operation ids remain on the real Echo path;
- Echo core remains noun-free.

Current implementation note: jedit's product-session witness now includes a
generated structural-history `replaceTextRange` intent envelope built from
Wesley metadata. The lower hot-text executor is still the transitional adapter
behind the jedit-owned session port; the witness no longer presents the old
stack-witness fixture operation shape as the product intent.

## Slice 8: Generated Package Install Path

Repositories: `echo`, `jedit`; `wesley` only if artifact descriptors are
Expand All @@ -288,6 +303,15 @@ Witness:
- unsupported op/query test returns typed rejection;
- no jedit nouns enter Echo.

Current implementation note: jedit now exposes an explicit
structural-history package descriptor built from Wesley-generated
`replaceTextRange` metadata. The descriptor installs through the same generic
Echo contract-package port as the transitional hot-text runtime descriptor.
Package preflight now checks the descriptor's own required operations, so the
structural-history package can reject unsupported queries at the package
boundary without being forced to carry hot-text observer operations. The real
mutation/query round trip is still owed by slice 9.

## Slice 9: jedit Real Mutation And Query Round Trip

Repository: `jedit`
Expand All @@ -313,6 +337,15 @@ Witness:
- real witness proves non-trivial vars, trusted-host runtime control, outcome
observation, bounded reading, retained refs, and app/host authority split.

Current implementation note: the jedit powered-session witness now reports an
explicit round-trip record. The structural-history package owns the
`replaceTextRange` mutation and receipt coordinate, while the transitional
hot-text package still owns the bounded `textWindow` query observer and reading
coordinates. This keeps the release proof honest: the product mutation identity
is structural-history, the reading path remains bounded and evidence-bearing,
and the host lifecycle remains trusted-control-only. Slice 10 still owes the
non-happy-path release-gate report.

## Slice 10: jedit Non-Happy Path And Release-Gate Report

Repositories: primarily `jedit`, with Echo changes only if a generic
Expand All @@ -339,6 +372,13 @@ Witness:
- hidden retry is absent;
- failure is named precisely and remains replayable or explicitly obstructed.

Current implementation note: `scripts/jedit-echo-release-gate.mjs --json-report`
now emits the consolidated release-gate artifact. The report includes
installed/generated package metadata, app/host authority posture, trusted
lifecycle requests, happy-path round-trip evidence, retained refs, unsupported
mutation obstruction, hidden-retry posture, and local replay comparison. This
closes the current ten-slice PR checkpoint.

## Release Bar After Slice 10

After slice 10, the expected claim is:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ The remaining work is adapter/API polish above this core surface.
- Outcome observation can report unknown, pending, applied, rejected, and
obstructed states.
- Applied/rejected outcomes bind to the relevant tick receipt evidence.
- Contract-backed applied/rejected outcomes expose retained receipt evidence
posture where Echo can lawfully name the coordinate.
- Obstructed outcomes use the contract obstruction taxonomy.
- Missing contract receipt material is reported as missing retention for the
retained receipt coordinate, not as an empty success.
- The API exposes no trusted runtime control and no tick/step command.
- Duplicate submission behavior is documented and tested.

Expand Down
Loading