Skip to content

feat(api): cancel interpretation runs via loopback POST - #440

Closed
seonghobae wants to merge 1 commit into
feat/interpretation-run-retrieval-get-gap-003afrom
feat/interpretation-run-cancel-http-gap-003a
Closed

feat(api): cancel interpretation runs via loopback POST#440
seonghobae wants to merge 1 commit into
feat/interpretation-run-retrieval-get-gap-003afrom
feat/interpretation-run-cancel-http-gap-003a

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

GAP-003A unique slice stacked on #438: loopback POST /v1/interpretation-runs/{idempotency_key}/cancel removes one accepted hypothetical interpretation-run identity from OrchestratorLiveService / tepp-orchestrator-loopback.

Test plan

  • cargo test -p orchestrator_live
  • cargo clippy -p orchestrator_live --all-targets -- -D warnings
  • cargo doc -p orchestrator_live --no-deps
  • python3 scripts/validate_documentation.py
  • python3 scripts/check_docstrings.py
  • Independent exact-head review (not author, not Copilot, not Devin/CodeRabbit COMMENTED)

Devin Review

GAP-003A unique slice stacked on GET-by-id HTTP: loopback
POST /v1/interpretation-runs/{idempotency_key}/cancel drops one
accepted hypothetical identity from tepp-orchestrator-loopback.
Naruon and LineageWeave refused. ADR 0073.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: d5efcc6e-f58f-4c92-8498-aa5470e95e5f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Devin Review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Required verification evidence is incomplete

Repository rules require complete tests and 100% production line and branch coverage. The submitted plan lists focused tests but no complete-suite or coverage result.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +151 to +160
if encoded.is_empty() || encoded.contains('/') {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
let idempotency_key = decode_path_segment(encoded)?;
require_nonempty(&idempotency_key)?;
if idempotency_key.contains('/') || idempotency_key.contains('\0') {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
if idempotency_key.len() > INTERPRETATION_RUN_CANCEL_ID_MAX_LEN {
return Err(OrchestratorLiveError::LimitExceeded);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Accepted runs become uncancellable

Creation accepts slash-containing or over-128-byte keys, but interpretation_run_cancel_path_id rejects them. Those accepted runs cannot use the new cancellation endpoint.

Prompt for agents
Align interpretation-run idempotency-key validation across creation and cancellation. InterpretationRunRequest::validate currently accepts slash-containing and arbitrarily long keys, while interpretation_run_cancel_path_id and the cancel exchange reject slashes and keys over 128 bytes. Either constrain creation to the shared path-safe limit or define an encoding and length contract that lets every previously accepted key be cancelled. Add service-level tests that create and then cancel boundary and encoded keys.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +192 to +194
if interpretation_run_cancel_path_id(path).is_ok() {
return self.cancel_interpretation_run(path, &headers, body);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Oversized cancellations report wrong status

is_ok() discards the cancel parser's limit error. Oversized cancellation keys return 400 instead of the contract's 413 response.

Suggested change
if interpretation_run_cancel_path_id(path).is_ok() {
return self.cancel_interpretation_run(path, &headers, body);
}
if path.starts_with(&format!("{INTERPRETATION_RUN_PATH}/")) && path.ends_with("/cancel") {
return self.cancel_interpretation_run(path, &headers, body);
}
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +282 to +291
let idempotency_key = interpretation_run_cancel_path_id(path)?;
if !body.is_empty() {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
refuse_metrics_on_interpretation_run_cancel_payload(body)?;
refuse_retrieval_get_headers(headers)?;
let (_, accepted) = self
.accepted_runs
.remove(&idempotency_key)
.ok_or(OrchestratorLiveError::InvalidWirePayload)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Validation precedes cancellation mutation

Body, header, consumer, credential, and path checks finish before accepted_runs.remove. Rejected requests leave the accepted run unchanged.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +174 to +199
pub fn refuse_metrics_on_interpretation_run_cancel_payload(
payload: &str,
) -> Result<(), OrchestratorLiveError> {
if payload.trim().is_empty() {
return Ok(());
}
let value: serde_json::Value =
serde_json::from_str(payload).map_err(|_| OrchestratorLiveError::InvalidWirePayload)?;
let Some(object) = value.as_object() else {
return Err(OrchestratorLiveError::InvalidWirePayload);
};
if object
.get("schema_version")
.and_then(serde_json::Value::as_str)
== Some("tepp.scientific_acceptance.v1")
{
return Err(OrchestratorLiveError::InvalidWirePayload);
}
if FORBIDDEN_CANCEL_KEYS
.iter()
.any(|key| object.contains_key(*key))
{
return Err(OrchestratorLiveError::InvalidWirePayload);
}
Ok(())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Public metric filter has narrow scope

refuse_metrics_on_interpretation_run_cancel_payload checks top-level keys only and accepts arbitrary objects. The live endpoint rejects them earlier, but external callers can misread this broader contract.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Closing as research_lineage_only with replacement mapping to #174. This loopback POST is destructive but authenticates no principal/resource/purpose grant; contextual-orchestrator in a caller-supplied consumer header is routing identity, not authorization. Preserve identifier-limit, metric-refusal, cancellation-state, and review findings for the authenticated operations landing vehicle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant