Skip to content

feat(api): consolidate interpretation-run create and collection adapters - #436

Draft
seonghobae wants to merge 3 commits into
mainfrom
feat/interpretation-run-collection-cli-gap-003a
Draft

feat(api): consolidate interpretation-run create and collection adapters#436
seonghobae wants to merge 3 commits into
mainfrom
feat/interpretation-run-collection-cli-gap-003a

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Consolidated interpretation-run adapter vehicle

This PR now folds predecessor #425 together with the already-folded #433 collection GET into one contextual-orchestrator-facing Analysis Run / interpretation-run application-adapter vehicle. Its current head contains #425 -> #433 -> collection CLI ancestry, so retargeting to protected main preserves all three increments while removing another open micro-PR. #425/#433 remain immutable review/history evidence; retrieval vehicles #439/#454 retain the same ancestry.

Preserved create behavior from #425: tepp-orchestrator-loopback, published tepp-interpretation-runs create, InterpretationRunRequest stdin, contextual-orchestrator-only consumer, typed POST exchange, metric-free hypothetical stdout with claim_status=hypothetical and scientific_authority=false, loopback/credential/consumer/metric fail-closed boundaries, and no direct model-provider/scientific authority.

Preserved collection behavior: GET /v1/interpretation-runs plus published list CLI, bounded exclusive cursor pagination, metric-free hypothetical identities, extra-path/idempotency-header refusal, and naruon/LineageWeave refusal.

This is one Analysis Run application/adapter landing vehicle, not a new bounded context. Per-operation ADR 0064/0069/0070 identifiers are implementation lineage pending #437 normalization, not independent architecture authority. Future compatible interpretation-run transport operations should fold into this vehicle or a coherent successor rather than mint one-route PRs.

Merge only after fresh exact-head required workflows, resolved conversations, and qualifying independent approval under live ruleset 18156473. No predecessor-head evidence transfer, self-approval, or bypass.

GAP-003A unique slice: tepp-interpretation-runs create mints typed
contextual-orchestrator POST /v1/interpretation-runs onto spawned
tepp-orchestrator-loopback TCP. Metric-free hypothetical JSON only.
Naruon and LineageWeave are refused. Persistence remains GAP-003B.
ADR 0064 unique vs main.
GET /v1/interpretation-runs lists accepted hypothetical runs on
tepp-orchestrator-loopback so operators do not guess idempotency keys.
Rows stay metric-free with claim_status=hypothetical. Naruon and
LineageWeave are refused. Empty body; idempotency-key is refused.
GAP-003A unique slice stacked on collection GET: tepp-interpretation-runs
list mints typed contextual-orchestrator GET /v1/interpretation-runs onto
spawned tepp-orchestrator-loopback TCP. Metric-free hypothetical identities
only. Empty stdin admitted. Naruon and LineageWeave refused. ADR 0070.
@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: 6b83fa10-d9bb-4b32-9d86-425da0f00d23

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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@seonghobae seonghobae changed the title feat(api): enumerate interpretation runs via loopback collection CLI feat(api): consolidate interpretation-run collection GET and CLI Sep 1, 2026
@seonghobae
seonghobae changed the base branch from feat/interpretation-run-collection-get-gap-003a to feat/interpretation-run-cli-gap-003a September 1, 2026 16:08
@seonghobae seonghobae changed the title feat(api): consolidate interpretation-run collection GET and CLI feat(api): consolidate interpretation-run create and collection adapters Sep 1, 2026
@seonghobae
seonghobae changed the base branch from feat/interpretation-run-cli-gap-003a to main September 1, 2026 16:09
@seonghobae
seonghobae marked this pull request as draft September 1, 2026 16:32

@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 11 new 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.

🔍 Duplicated transports already diverge

Create and list duplicate flag parsing, TCP setup, framing, and response parsing. Their size bounds already differ, making future contract fixes easy to apply inconsistently.

Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Rust verification remains external

The environment lacks cargo, so the new tests could not run locally. Current-head CI must provide the repository’s required verification evidence.

Devin Review

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

Comment on lines +208 to +214
InterpretationRunCollectionItem::new(
accepted.interpretation_run_id(),
accepted.idempotency_key(),
accepted.orchestration_mode(),
accepted.claim_status(),
accepted.scientific_authority(),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Long run keys disable listing

After POST accepts a key over 128 bytes, InterpretationRunCollectionItem::new rejects every list request. One accepted run makes the collection unavailable.

Prompt for agents
InterpretationRunRequest and InterpretationRunAccepted admit idempotency keys up to the overall JSON size, while InterpretationRunCollectionItem rejects keys longer than INTERPRETATION_RUN_COLLECTION_CURSOR_MAX_LEN. The list handler converts every accepted run, so one previously accepted long key makes all collection GETs return 413. Align creation-time idempotency-key validation with the collection cursor bound, or redesign cursor encoding so every accepted run is listable without treating its raw key as a bounded cursor. Add a POST-then-GET regression test using a key longer than 128 bytes.
Devin Review

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

Comment on lines +256 to +258
let end = (start + limit).min(items.len());
let next_cursor = if end < items.len() {
Some(items[end - 1].idempotency_key.clone())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Zero page limits crash callers

With items and a zero limit, page_interpretation_run_collection_items subtracts one from zero. The public helper panics instead of returning a page.

Prompt for agents
The public page_interpretation_run_collection_items helper accepts an arbitrary usize limit but assumes it is nonzero when deriving next_cursor. A zero limit with a nonempty collection underflows at items[end - 1], and very large limits can also overflow start + limit. Make the helper validate the limit or change its API to return Result, and use overflow-safe end calculation. Update direct helper tests for zero and extreme limits while preserving validated service behavior.
Devin Review

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

.unwrap_or(usize::MAX);
let mut service = OrchestratorLiveService::bind(bind_addr)?;
println!("{}", service.local_addr()?);
(0..request_limit).for_each(|_| drop(service.serve_one()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Server failures exit successfully

drop(service.serve_one()) discards accept and response-write errors. A bounded server counts failed attempts and exits successfully without serving the requested traffic.

Suggested change
(0..request_limit).for_each(|_| drop(service.serve_one()));
for _ in 0..request_limit {
service.serve_one()?;
}
Devin Review

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

Comment on lines +30 to +50
fn run_create(args: &[String]) -> Result<(), OrchestratorLiveError> {
let body = read_interpretation_run_cli_stdin(io::stdin().is_terminal(), io::stdin())?;
let invocation = InterpretationRunCliInvocation::from_args(args, body)?;
let response = execute_interpretation_run_cli(&invocation)?;
let stdout = render_interpretation_run_cli_stdout(&invocation, &response)?;
println!("{stdout}");
if (200..300).contains(&response.status_code) {
Ok(())
} else {
Err(OrchestratorLiveError::InvalidWirePayload)
}
}

fn run_list(args: &[String]) -> Result<(), OrchestratorLiveError> {
let body =
read_interpretation_run_collection_cli_stdin(io::stdin().is_terminal(), io::stdin())?;
let invocation = InterpretationRunCollectionCliInvocation::from_args(args, body)?;
let response = execute_interpretation_run_collection_cli(&invocation)?;
let stdout = render_interpretation_run_collection_cli_stdout(&invocation, &response)?;
println!("{stdout}");
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.

🔍 CLI failures expose inconsistent output

Create prints server error bodies before failing, while list suppresses every non-200 body. Operators receive different diagnostics from one consolidated command.

Devin Review

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

Comment on lines +204 to +219
let items = self
.accepted_runs
.values()
.map(|(_, accepted)| {
InterpretationRunCollectionItem::new(
accepted.interpretation_run_id(),
accepted.idempotency_key(),
accepted.orchestration_mode(),
accepted.claim_status(),
accepted.scientific_authority(),
)
})
.collect::<Result<Vec<_>, _>>()?;
let (page, next_cursor) =
page_interpretation_run_collection_items(items, cursor.as_deref(), limit);
let collection = InterpretationRunCollection::new(page, next_cursor)?;

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: Cursor ordering is internally stable

Accepted entries are immutable and keys are unique. Sorting by key makes the exclusive cursor stable for the in-memory registry, including later insertions.

Devin Review

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

Comment on lines +126 to +143
pub fn new(
items: Vec<InterpretationRunCollectionItem>,
next_cursor: Option<String>,
) -> Result<Self, OrchestratorLiveError> {
if items.len() > INTERPRETATION_RUN_COLLECTION_MAX_LIMIT {
return Err(OrchestratorLiveError::LimitExceeded);
}
for item in &items {
item.validate()?;
}
if let Some(cursor) = next_cursor.as_deref() {
parse_interpretation_run_collection_page_cursor(Some(cursor))?;
}
Ok(Self {
contract_version: INTERPRETATION_RUN_CONTRACT_VERSION,
items,
next_cursor,
})

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 pages omit pagination invariants

InterpretationRunCollection::new admits unordered rows and unrelated next cursors. Only the CLI renderer enforces those invariants for exported collection pages.

Devin Review

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

Comment on lines +325 to +356
require_nonempty(origin)?;
if !origin.starts_with("https://") || origin.ends_with('/') {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
let rest = origin
.strip_prefix("https://")
.ok_or(OrchestratorLiveError::InvalidWirePayload)?;
if rest.contains('@') || rest.contains('?') || rest.contains('#') || rest.contains('\\') {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
if host_implies_table_access(rest) {
return Err(OrchestratorLiveError::InvalidWirePayload);
}
parse_interpretation_run_collection_page_cursor(page_cursor)?;
parse_interpretation_run_collection_page_limit(page_limit)?;
let mut headers = vec![
("content-type".into(), "application/json".into()),
(
"tepp-consumer".into(),
CONTEXTUAL_ORCHESTRATOR_CONSUMER_CODE.into(),
),
("tepp-contract-version".into(), "1".into()),
];
if let Some(cursor) = page_cursor {
headers.push(("tepp-page-cursor".into(), cursor.to_owned()));
}
if let Some(limit) = page_limit {
headers.push(("tepp-page-limit".into(), limit.to_owned()));
}
Ok(InterpretationRunCollectionHttpExchange {
method: "GET",
target_url: format!("{origin}{INTERPRETATION_RUN_PATH}"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Exchange builder admits malformed origins

The exported builder accepts empty authorities and origins containing paths. CLI rendering later rejects many, but direct callers receive malformed target URLs.

Devin Review

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

Comment on lines +533 to +537
let mut body = String::new();
stdin
.read_to_string(&mut body)
.map_err(|_| OrchestratorLiveError::InvalidWirePayload)?;
Ok(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.

🟨 Unbounded streams enable memory exhaustion

read_to_string and read_to_end buffer untrusted streams without limits. Oversized stdin or loopback responses can exhaust the create process's memory.

Devin Review

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

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Exact-head COMMENT on 460503b (draft). Unique occupied: GET /v1/interpretation-runs plus tepp-interpretation-runs (ADR 0069/0070). Collection rows stay metric-free identities with claim_status=hypothetical and scientific_authority=false. FORBIDDEN_COLLECTION_KEYS includes rmse/scientific_acceptance/tenant_workspace_id/causal_score/findings/evidence_text. Naruon and LineageWeave refused. NaruonLiveService stays POST-only.

Do not duplicate this collection surface. Do not un-draft. Predecessor Devin COMMENTED on this SHA is not independent APPROVE. Zero current-head APPROVE. Never self-approve. Persistence remains GAP-003B. Do not weaken fail-closed. No Buyer language.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant