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
11 changes: 2 additions & 9 deletions changelog.d/oidc-not-admitted.fixed.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
- A server with an OIDC resource identity no longer answers 401
`error="invalid_token"` to a valid access token whose subject the admission
file does not name. That answer told OAuth and MCP clients such as Claude to
sign in again, which issues a token for the same subject, so they looped or
reported a failed sign-in. The server now answers 403 with the new error
code `not_admitted` and no challenge. A valid token while the admission
snapshot is expired gets 503 `service_unavailable` with `Retry-After`,
instead of the same 401. A forged, expired or misaddressed token still gets
401 `invalid_token`, and the token's own checks still run first. See
- OIDC clients no longer loop on sign-in: an unadmitted identity returns 403
`not_admitted`, and an expired admission snapshot returns 503. See
[MCP][oidc-not-admitted-guide].

[oidc-not-admitted-guide]: ../docs/user/operations/mcp.md
5 changes: 5 additions & 0 deletions changelog.d/publication-error-commit.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Rust embedders must pass a graph commit ID to `manifest_publish_in_doubt`;
`ManifestError.publication_in_doubt` now holds `Option<Box<String>>` instead of
`bool`. Follow the [error API update][publication-error-api].

[publication-error-api]: ../docs/user/operations/storage-failures.md#rust-error-api
7 changes: 3 additions & 4 deletions changelog.d/server-otel-traces.added.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
- The server supports optional OTLP HTTP/protobuf tracing and structured JSON
logs with trace correlation. Request diagnostics use route templates and
exclude request payloads and credentials. Log verbosity does not disable
tracing or warning/error correlation. See [logs and traces][server-otel-guide].
- Optional OTLP tracing and correlated JSON logs omit payloads and credentials.
Tracing and warning/error correlation work at every log level. See
[logs and traces][server-otel-guide].

[server-otel-guide]: ../docs/user/operations/observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Server logs name an unknown publication's graph commit in a `publication`
field without exposing its error message. See
[storage failures][unknown-publication-log].

[unknown-publication-log]: ../docs/user/operations/storage-failures.md#logs
81 changes: 50 additions & 31 deletions crates/omnigraph-catalog/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,11 +1153,14 @@ fn publish_outcome_in_doubt(
graph_commit_id: &str,
cause: impl std::fmt::Display,
) -> OmniError {
OmniError::manifest_publish_in_doubt(format!(
"manifest publish outcome is in doubt: graph commit {graph_commit_id} may be durable but \
it could not be confirmed ({cause}); reopen the graph and look that commit up before \
retrying a non-idempotent write. original error: {original}"
))
OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!(
"manifest publish outcome is in doubt: graph commit {graph_commit_id} may be durable \
but it could not be confirmed ({cause}); reopen the graph and look that commit up \
before retrying a non-idempotent write. original error: {original}"
),
)
}

/// Where a publication attempt failed, relative to the store's
Expand Down Expand Up @@ -1297,19 +1300,25 @@ async fn create_candidate_tag(
// ambiguous one still can, and would address the candidate.
Err(LanceError::RefNotFound { .. }) => match classify_failed_request(&error) {
FailedRequest::Refused => Err(OmniError::storage(error).unpublished()),
FailedRequest::Ambiguous => Err(OmniError::manifest_publish_in_doubt(format!(
"the {} of {} '{graph_commit_id}' may still appear: its creation failed \
({error}) and the tag is not there yet",
FailedRequest::Ambiguous => Err(OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!(
"the {} of {} '{graph_commit_id}' may still appear: its creation failed \
({error}) and the tag is not there yet",
kind.tag_noun(),
kind.noun()
),
)),
},
Err(readback) => Err(OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!(
"the {} of {} '{graph_commit_id}' may exist: its creation failed ({error}) and \
the readback failed ({readback})",
kind.tag_noun(),
kind.noun()
))),
},
Err(readback) => Err(OmniError::manifest_publish_in_doubt(format!(
"the {} of {} '{graph_commit_id}' may exist: its creation failed ({error}) and the \
readback failed ({readback})",
kind.tag_noun(),
kind.noun()
))),
),
)),
}
}

Expand All @@ -1326,11 +1335,14 @@ fn candidate_probe_failed_after_release(
if error.is_read_set_changed() || matches!(&error, OmniError::BranchNotFound { .. }) {
return error;
}
OmniError::manifest_publish_in_doubt(format!(
"{} '{graph_commit_id}' may remain addressable: its authority probe failed ({error}); \
deleting its tag cannot rule out a delayed create",
kind.noun()
))
OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!(
"{} '{graph_commit_id}' may remain addressable: its authority probe failed \
({error}); deleting its tag cannot rule out a delayed create",
kind.noun()
),
)
}

impl GraphNamespacePublisher {
Expand Down Expand Up @@ -1442,11 +1454,14 @@ impl GraphNamespacePublisher {
CandidateKind::Unpublished,
&graph_commit_id,
),
Err(release) => OmniError::manifest_publish_in_doubt(format!(
"unpublished commit '{graph_commit_id}' may remain addressable: its \
authority probe failed ({error}) and the tag release failed \
({release})"
)),
Err(release) => OmniError::manifest_publish_in_doubt(
&graph_commit_id,
format!(
"unpublished commit '{graph_commit_id}' may remain addressable: \
its authority probe failed ({error}) and the tag release \
failed ({release})"
),
),
},
);
}
Expand Down Expand Up @@ -2033,11 +2048,15 @@ impl GraphNamespacePublisher {
CandidateKind::Unattached,
&property.graph_commit_id,
),
Err(release) => OmniError::manifest_publish_in_doubt(format!(
"unattached branch '{}' may remain addressable: its authority revalidation \
refused it ({refusal}) and the tag release failed ({release})",
property.graph_commit_id
)),
Err(release) => OmniError::manifest_publish_in_doubt(
&property.graph_commit_id,
format!(
"unattached branch '{}' may remain addressable: its authority \
revalidation refused it ({refusal}) and the tag release failed \
({release})",
property.graph_commit_id
),
),
});
}
Ok(UnattachedOutcome {
Expand Down
17 changes: 16 additions & 1 deletion crates/omnigraph-catalog/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4785,6 +4785,7 @@ async fn unattached_child_checks_expected_versions_against_its_base() {
crate::unattached::release_unattached_tag_confirmed(
&main,
&unattached_tag_name(&root.graph_commit_id, &root.graph_commit_id),
&root.graph_commit_id,
)
.await
.unwrap();
Expand Down Expand Up @@ -15207,7 +15208,8 @@ async fn manifest_create_without_a_definite_answer_stays_in_doubt_issue_1067() {
}

/// A manifest create no re-issue settled may still land, and its manifest
/// names the attempt's transaction file. Lance reads the version back after
/// names the attempt's transaction file. The in-doubt error names the graph
/// commit that may land, which is the one that does. Lance reads the version back after
/// the last re-issue; the missing version must not count as proof of
/// absence there, or Lance deletes that file and the late manifest names a
/// file that is gone. Here no storage scope backs the protocol, as on an
Expand All @@ -15229,6 +15231,19 @@ async fn unresolved_manifest_create_keeps_its_transaction_file_issue_1067() {
before + 1,
"the late create landed"
);
// The error named, as its own field, the graph commit that landed.
assert_eq!(
error.publication_id(),
Some(
read_manifest_rows(&landed)
.await
.unwrap()
.head
.graph_commit_id
.as_str()
),
"{error:?}"
);
let transaction = landed
.manifest()
.transaction_file
Expand Down
43 changes: 30 additions & 13 deletions crates/omnigraph-catalog/src/unattached.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,13 @@ pub async fn release_unattached_tag(dataset: &Dataset, name: &str) -> Result<()>

/// Delete a reserved candidate tag and confirm its absence by a read: a
/// tag still listed, or a read that fails, leaves the release in doubt.
pub async fn release_unattached_tag_confirmed(dataset: &Dataset, name: &str) -> Result<()> {
pub async fn release_unattached_tag_confirmed(
dataset: &Dataset,
name: &str,
graph_commit_id: &str,
) -> Result<()> {
release_unattached_tag(dataset, name).await?;
confirm_tag_released(dataset, name).await
confirm_tag_released(dataset, name, graph_commit_id).await
}

/// Reads snapshots against one captured tag inventory, sharing ancestry and
Expand Down Expand Up @@ -891,10 +895,20 @@ impl ManifestCoordinator {
let main =
open_manifest_dataset_native_with_session(root_uri, None, control_session).await?;
for descendant in descendants {
release_unattached_tag_confirmed(&main, &descendant.tag_name()).await?;
release_unattached_tag_confirmed(
&main,
&descendant.tag_name(),
&descendant.property.graph_commit_id,
)
.await?;
fail(&UNATTACHED_DROP_POST_DESCENDANT_PRE_PARENT)?;
}
release_unattached_tag_confirmed(&main, &candidate.tag_name()).await
release_unattached_tag_confirmed(
&main,
&candidate.tag_name(),
&candidate.property.graph_commit_id,
)
.await
}
}

Expand All @@ -920,18 +934,21 @@ fn descend<'a>(
Ok(())
}

/// The deletion of the candidate tag `name`, confirmed by a read: a tag
/// still listed, or a read that fails, leaves the release in doubt.
async fn confirm_tag_released(main: &Dataset, name: &str) -> Result<()> {
/// The deletion of the candidate tag `name`, which addresses graph commit
/// `graph_commit_id`, confirmed by a read: a tag still listed, or a read that
/// fails, leaves the release in doubt.
async fn confirm_tag_released(main: &Dataset, name: &str, graph_commit_id: &str) -> Result<()> {
match main.tags().get(name).await {
Ok(_) => Err(OmniError::manifest_publish_in_doubt(format!(
"the candidate tag '{name}' is still listed after its deletion"
))),
Ok(_) => Err(OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!("the candidate tag '{name}' is still listed after its deletion"),
)),
Err(lance::Error::RefNotFound { .. }) => Ok(()),
Err(error) if error.is_not_found() => Ok(()),
Err(error) => Err(OmniError::manifest_publish_in_doubt(format!(
"the deletion of the candidate tag '{name}' could not be confirmed: {error}"
))),
Err(error) => Err(OmniError::manifest_publish_in_doubt(
graph_commit_id,
format!("the deletion of the candidate tag '{name}' could not be confirmed: {error}"),
)),
}
}

Expand Down
23 changes: 15 additions & 8 deletions crates/omnigraph-catalog/src/unpublished.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,16 +837,23 @@ impl ManifestCoordinator {
let name = candidate.tag_name();
release_unpublished_tag(&main, &name).await?;
match main.tags().get(&name).await {
Ok(_) => Err(OmniError::manifest_publish_in_doubt(format!(
"the tag of unpublished commit '{}' is still listed after its deletion",
candidate.property.graph_commit_id
))),
Ok(_) => Err(OmniError::manifest_publish_in_doubt(
&candidate.property.graph_commit_id,
format!(
"the tag of unpublished commit '{}' is still listed after its deletion",
candidate.property.graph_commit_id
),
)),
Err(lance::Error::RefNotFound { .. }) => Ok(()),
Err(error) if error.is_not_found() => Ok(()),
Err(error) => Err(OmniError::manifest_publish_in_doubt(format!(
"the deletion of the tag of unpublished commit '{}' could not be confirmed: {error}",
candidate.property.graph_commit_id
))),
Err(error) => Err(OmniError::manifest_publish_in_doubt(
&candidate.property.graph_commit_id,
format!(
"the deletion of the tag of unpublished commit '{}' could not be confirmed: \
{error}",
candidate.property.graph_commit_id
),
)),
}
}

Expand Down
Loading
Loading