Skip to content

feat(api): retrieve stored export authorization by idempotency key - #471

Merged
seonghobae merged 1 commit into
feat/export-idempotency-lookup-cli-gap-003afrom
feat/export-idempotency-lookup-stored-request-get-gap-003a
Sep 1, 2026
Merged

feat(api): retrieve stored export authorization by idempotency key#471
seonghobae merged 1 commit into
feat/export-idempotency-lookup-cli-gap-003afrom
feat/export-idempotency-lookup-stored-request-get-gap-003a

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

GAP-003A unique slice: GET /v1/exports/by-idempotency/{idempotency_key}/request returns the stored naruon export-authorization request of the unique accepted export on AnalysisRunLiveService / tepp-loopback.

Lookup GET (#466) returns identity only. Stored-request GET (#459) requires export_id. Operators who hold a 200 authorization receipt or log key still needed two hops. This is the dual-identity analog of {export_id}/request.

Does not duplicate lookup GET/CLI (#465/#466), stored-request GET/CLI (#457/#459), GET-by-id (#411), retrieval CLI (#417), collection GET/CLI (#443/#444), export-authorize CLI (#410), analysis-run lookup (#380), interpretation-run lookup stored-request (#469/#470), or cancel lineages (closed). Stale snapshots that still prefer export lookup CLI on #465 are wrong — that CLI is already #466. Stale snapshots that still prefer temporal-context stored-request on #451 are wrong — already #464.

Test plan

  • cargo test -p tepp_api --offline --test export_idempotency_lookup_stored_request_http_contract --lib handler_covers_metric_free_export
  • cargo clippy -p tepp_api --all-targets --offline -- -D warnings
  • Independent current-head APPROVE from two humans (not Devin/CodeRabbit/author COMMENTED, not Copilot)

Devin Review

Publish GET /v1/exports/by-idempotency/{idempotency_key}/request so
operators who hold a 200 authorization receipt can recover the stored
create without a second hop through export_id stored-request.
Metric-free. Zero and ambiguous matches fail closed. LineageWeave
refused. NaruonLiveService stays POST-only.

@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 3 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.

🔍 Coverage gate lacks evidence

The new parser and recursive filter add untested branches. The submitted verification omits the repository’s mandatory 100% line and branch coverage gate.

Devin Review

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

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.

Preserved as a landing gate rather than claimed green. The folded #466 exact head now contains additional RED/GREEN security branches, so predecessor coverage evidence is invalid by construction. #466 requires fresh exact-head 100% owned line/branch coverage and hosted Rust/documentation/security gates before Ready/merge; queued/pending evidence is non-passing.

Comment on lines +46 to +47
/// The route is segmented before percent decoding, so an encoded `/` remains
/// data inside one opaque key rather than becoming an extra path segment.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Encoded-slash contract is ambiguous

decode_path_segment accepts %2F inside a key, while ADR 0099 says slash-containing keys fail closed. Clarify the contract or reject decoded slashes.

Devin Review

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

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.

Addressed on surviving #466. GREEN 45754fdb46d4a2c84db9e440f5eb0001c2f26d10 rejects both raw / and percent-decoded %2F in the key, and the client builder rejects slash-containing keys before activation. ADR 0099 was updated in 14ef78c26a54a9a9e2e431d50731f830f65097e6 so path-normalization semantics now match code.

Comment on lines +417 to +429
let consumer = require_headers(headers, self.bound_addr, false)?;
if consumer != NARUON_CONSUMER_CODE {
return Err(ApiError::InvalidWirePayload);
}
refuse_metrics_on_export_lookup_stored_request_payload(body)?;
let prefix = format!("{consumer}\u{1f}");
let mut matches: Vec<&StoredExport> = self
.authorized_exports
.iter()
.filter(|(replay_key, stored)| {
replay_key.starts_with(&prefix)
&& stored.retrieval.idempotency_key == idempotency_key
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟥 Idempotency keys bypass tenant access

A caller with an idempotency key can use lookup_export_stored_request_by_idempotency across all Naruon tenants. The response exposes the matching tenant and principal without caller binding.

Devin Review

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

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.

Verified as valid after the strict-ancestor fold into #466 and repaired there rather than leaving the finding as a blocker. RED 8f38c2708771ead5ca197b65b3c8973ac7aebfea requires the unscoped stored-request lookup to fail closed and not echo tenant/principal/artifact identity. GREEN 45754fdb46d4a2c84db9e440f5eb0001c2f26d10 quarantines the route: serialized tenant_workspace_id/principal_id are forbidden on this response boundary and the client builder returns AuthorizationDenied until an authenticated tenant+principal binding exists. The surviving landing vehicle is #466; do not reactivate by trusting caller-controlled scope headers.

@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: ec0141aa-b519-4de8-bce3-59459a706f88

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.

@seonghobae
seonghobae merged commit 29e87b8 into feat/export-idempotency-lookup-cli-gap-003a Sep 1, 2026
5 of 8 checks passed
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