Skip to content

feat(cli)!: exit with what a failed command's outcome says - #1078

Merged
ragnorc merged 56 commits into
ModernRelay:mainfrom
ragnorc:feat/cli-exit-table
Oct 21, 2026
Merged

ragnorc merged 56 commits into
ModernRelay:mainfrom
ragnorc:feat/cli-exit-table

Conversation

@ragnorc

@ragnorc ragnorc commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1076 (error disposition), itself on #1074; review only the commits after #1076's head.

What & why

The CLI's exit follows from a failure's disposition, the same for every command: a command with no effects exits 1, one whose effects are unknown or partial 2, one refused for a condition the caller set 4, and one refused before starting with retry 75. A refused command line exits 64 (EX_USAGE) instead of Clap's 2.

  • Read commands report the same failure envelope as data writes, always with no effects, so a failed read prints its disposition too (JSON on stdout, the disposition line on stderr).
  • An embedded engine write's own error now carries the engine's completion evidence, as a server's does: a typed refusal before any request, outside a load that forks its branch, did not start. Embedded load conflicts and failed conditions therefore exit as served ones (1 and 4) instead of claiming unknown effects; any later error, such as one rendering a committed receipt, stays unknown. The rule is opt-in per call (EngineWrite::engine_write), so an unwrapped error stays conservative.
  • Embedded writes refuse a noncanonical branch name and read their data file before opening storage.

Managed commands, and the target and credential commands, keep their own exits for now.

Backing issue / RFC

Checklist

  • Change is focused (one logical change)
  • Tests: exit expectations across cli_data, cli_queries, cli_cluster, cli_cluster_e2e, cli_schema_config, parity_matrix (embedded and served now exit alike, including Blob preconditions at 4), system_remote (lost delivery exits 2), and the application unit tests
  • Public docs: troubleshooting exit table, CLI reference, Blob guide, query diagnostics; changelog.d/cli-exit-follows-outcome.breaking.md
  • Reviewed against docs/dev/invariants.md: the CLI never claims not_started for an error it cannot attribute to the engine write itself (invariant 11)

Local verification

  • cargo build -p omnigraph-server && RUST_MIN_STACK=16777216 cargo test -p omnigraph-cli -- --test-threads=6 — 221 lib + 327 integration pass on main with feat(cli)!: replace legacy routing with explicit targets #1090
  • RUST_MIN_STACK=16777216 cargo test -p omnigraph-server — all pass
  • clippy for both crates with -D warnings, cargo fmt --all --check, python3 scripts/check-docs.py, typos — clean

Notes for reviewers

Breaking for scripts that read exit 2 as a usage error or exit 1 as every failure. Usage errors other than Clap's (for example the addressing guard) still exit 1.

A request carrying omnigraph-expect-actor is refused with 412
actor_mismatch, after authentication and the contract check and before
graph lookup, body work or effects, when its credential resolves to
another actor. A match changes nothing, so the header only narrows a
request and never supplies an actor. Every protected router and /mcp
check it.

The CLI sends it for --as on served writes and cluster --server
commands, which no longer refuse --as. A mismatch is a not-started
refusal and exits 4, like a failed graph-commit precondition.
Every error body carries an outcome object (execution, effects, action)
that the server computes for the request it answered: not_started with
no effects when it refused before submitting a write, or refused the
write's one publication with a typed reason, which an engine gives only
before it publishes; unknown otherwise, including a typed refusal after
a load forked its branch. The next step a caller takes is retry,
refresh, recover or reconcile. A write whose completion is uncertain
answers outcome_unknown instead of internal. Errors that carried a
reason only in their details gain registry codes: precondition_failed,
recovery_required, external_blob_source and change_feed_gap.

CLI data-write failures print the same object as outcome, in place of
command_outcome, and take the server's when its request was the
command's only effectful one.
The exit follows from the failure's outcome: a command that did not
start exits 1, one whose effects are unknown 2, one refused for a
condition the caller set 4, and one refused before starting with retry
75. A refused command line exits 64 (EX_USAGE) instead of Clap's 2.

Read commands report the same failure envelope as data writes, always
not_started with no effects, so a failed read prints its outcome too.

An embedded engine write's own error now carries the engine's
completion evidence, as a server's does: a typed refusal before any
request, outside a load that forks its branch, did not start, so
embedded load conflicts and failed conditions exit as served ones
instead of claiming unknown effects. Any later error, such as one
rendering a committed receipt, stays unknown. Embedded writes refuse a
noncanonical branch name and read their data file before opening
storage.

Managed commands keep their own exits until the managed relay lands.
…es it

A server built before `omnigraph-expect-actor` speaks the same HTTP
contract and ignores the header, so `--as` on a served write would run
as the credential's actor. Every response now lists the assertions its
server enforces in `omnigraph-http-api-assertions`, and the CLI refuses
before sending when discovery does not list `expect-actor`.

A served `cluster plan` or `cluster apply` refused for another actor
now exits 4 as a data write does: its requests are observed, and a
refused assertion with nothing effectful sent before it is a condition,
with or without `--json`.
`main` returned an undelivered failure as an error, so the process
exited 1, the status that says nothing changed, after a write whose
receipt met a closed stdout. It now reports the error as before and
exits with the code the outcome chose.

`alias` is a read, as `query` is, so its failures report an outcome
and exit by it. Command classification is now an exhaustive match, so
a new command declares how its failure reads.
The cluster skill page still said `cluster apply --server` refuses
`--as`; it now names the assertion `cluster plan` and `cluster apply`
send and its exit 4. The release note moves to `breaking`, since a
matching `--as` now runs a write the CLI used to refuse. The server
guide says `/mcp` has no contract check, an empty assertion joins the
malformed-header test, and stale comments name the current behaviour.

ragnorc commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

A failed data command now exits with what its outcome says. Exit 1 means the command changed nothing. Exit 2 means the effects are unknown. Exit 4 means a condition the caller set did not hold. Exit 75 means the server refused before the start and a retry is allowed. Exit 64 means Clap refused the command line. Scripts that read exit 2 as a usage error, or exit 1 as every failure, must change.

This is the right contract for the commands kind() classifies. It removes the old report that a lost response was a generic failure. It adds liability in two places. The new public sentences describe every command, and the code classifies only some of them. load --from also drops the engine outcome and reports unknown effects for a refusal that published nothing.

I recommend changes before merge. Three findings block. Three are optional. The pull request is still open at b19f6c2f5ad5a76d10d14acf44ec6acaa91ec99c. No other review covers this head.

Blocking

1. Embedded load --from reports unknown effects when the engine published nothing

GraphClient::load calls compound() when from is set. open_write_session then sets writable_open(). The engine runs after both flags. classify keeps an embedded engine outcome only when dispatched == 0 and compound is false. With compound set, an EngineWriteError becomes execution: unknown, effects: unknown, action: reconcile, exit 2.

load_input_as calls enforce(Change) before load_as_inner forks. A policy denial returns there. branch_created stays false, so without_pre_effect_evidence does not run. The engine settled that nothing published. The CLI still exits 2.

The served path marks the write compound, then authorize_load_scope denies the same policy before the owned write. ApiError::outcome returns not_started when after_write is false. The CLI exits 1.

parity_load covers the other case. Invalid NDJSON after the fork exits 2 on both arms, and the branch exists. That case is right. It does not cover a refusal before the fork.

Do not fix this by trusting engine_error_output as it stands. That function calls into_step_output. into_step_output forces compound false and sets after_write only when completion is uncertain. A payload failure after a real fork would then look not_started. Project the embedded compound error with the same after_write and compound inputs the request path uses. Forbidden stays not_started. A post-fork conflict stays unknown.

GQT cannot express a process exit code. Add a CLI regression in parity_matrix or cli_data. Policy denies Change. Run load --from with one NDJSON line into a missing branch, and again into a branch that already exists. Expect exit 1 and not_started on the embedded arm and the served arm.

I drove classify at this head with the flags load --from sets, and with writable_open alone. The error was EngineWriteError(OmniError::Policy("change denied")).

  • With compound: exit 2, {"execution":"unknown","effects":"unknown","action":"reconcile"}
  • Without compound: exit 1, {"execution":"not_started","effects":"none","action":"refresh"}

Command: cargo test -p omnigraph-cli --lib probe_load_from_policy_refusal_exit -- --test-threads=1. The probe was temporary and is not in the tree. It calls classify. It does not spawn load.

2. Docs this change owns tell two stories for exit 4

docs/user/cli/reference.md now says a verified conditional mismatch exits 4, embedded or served. The next section, Conditional mutations, still says a mismatch exits 4 against a server and 1 on an embedded --store run.

The skill still says the old pair. skills/omnigraph/SKILL.md says a served mutate exits 4 and a direct --store mutate exits 1. skills/omnigraph/references/changes.md says the same. skills/omnigraph/references/remote-ops.md still calls exit 1 the generic failure exit. For a classified data command, exit 2 is the unknown-effects exit. The CLI embeds this skill. An agent that follows it will treat an embedded precondition failure as exit 1.

#1080 and #1081 still carry those three sentences. Correct them here, or the stacked pull requests will keep the contradiction.

3. The published exit meanings cover commands the code does not classify

kind() returns None for init, upgrade, optimize, rebuild-full-text-indexes, repair, cleanup, schema upgrade-system-columns, cluster, policy, and the managed family. Those failures skip classify. upgrade returns exit 1 from finish_report when the report is unsuccessful. That includes recovery_required after a published attempt. The new legend reads exit 1 as "the command changed nothing".

The changelog says a failed command whose effects are unknown exits 2, and a refused command line exits 64. planes::guard_addressing is a bail! inside the observed command. A data command refused for a bad scope flag exits 1 and prints an outcome. The pull request notes already say that usage errors other than Clap's still exit 1. The troubleshooting row and the changelog do not.

Narrow those sentences to Clap and to the commands kind() classifies, or classify the effectful commands and the addressing guard. Managed exits can stay deferred. Say that in the same sentences.

Optional

  • exit_for is not a function of the outcome fields alone. Exit 4 needs the side flag conditional. not_started with refresh is also a normal refusal, and that refusal exits 1. The next caller-set condition adds another arm. Put that condition on the outcome so the exit has one source.
  • The doc comment on run_from still says Clap keeps its exit status. The function maps every non-zero Clap status to 64.
  • Failure::classify uses downcast_ref and then downcast().expect for EngineWriteError. downcast_ref walks the source chain. downcast takes only the outer error. A later wrap_err on that path panics inside the failure reporter. No current call wraps this error.

Where the code and draft RFC #1017 differ

RFC head 7c72c7e7ed2b42fb0e5b3f5140d8235444ddcf33, section "Outcomes and refusals".

  • The RFC says each exit has one meaning on every plane. This table applies where kind() returns Some. Maintenance, cluster, init, upgrade, the addressing guard, and managed commands keep other meanings. Managed is an explicit deferral. The public table does not name the others.
  • The RFC says usage is 64. Here that is Clap only.
  • The RFC says the exit follows from the outcome. Exit 4 uses a flag beside the outcome. The outcome object cannot tell a precondition from another not_started / refresh refusal.
  • The troubleshooting text says a refusal is unknown only after a load forked its branch, and that embedded and served commands exit alike. The code sets compound() when from is Some, before the fork. The two transports then disagree for a pre-fork policy denial.
  • The skill and the Conditional mutations paragraph still document embedded precondition exit 1. The RFC table says that condition is exit 4.
  • Phase 1 of the RFC includes the exit table, and its test plan includes managed refusals at 1. This pull request defers managed commands on purpose. The public text has to match that deferral. I am not asking this pull request to take the managed table.

Stack and siblings

Order: #1074 (2fff68e8) → #1076 (55888983) → #1078 (b19f6c2f) → #1080 (f7fe98c7) → #1081 (dec6a52e). #1073 (f025bce5) is the access route that #1081 builds on. #1075 and #1077 are the local servers. They do not own this exit table.

#1080 and #1081 edit app.rs, client.rs, command_outcome.rs, lib.rs, main.rs, blobs.md, reference.md, troubleshooting.md, and the skill. A change here means they rebase. #1059 also edits parity_matrix.rs and docs/user/cli/reference.md. #1012 and draft #675 touch branch reporting and CLI targeting. They do not define this exit table. Server outcome rules, the expect-actor header, and the access route belong to #1076, #1074, and #1073.

Liability

The change replaces two overloaded exits with exits that say whether a classified command may have changed the graph. That is the smaller long-term surface for data reads and data writes. Five more commands of this kind each add another CommandKind arm, and another side flag, unless the exit comes from one outcome. The unique delta is 19 files, +636/−298. The full pull request is larger because it carries #1074 and #1076. Line count is not the liability. The liability is a global sentence that the code honors only for part of the CLI, and a compound flag set earlier than the fork it is meant to remember.

Validation

Reviewed head b19f6c2f5ad5a76d10d14acf44ec6acaa91ec99c. Base 0a62bb7b2aa5b99b012f413807c216277f7f9596. RFC head 7c72c7e7ed2b42fb0e5b3f5140d8235444ddcf33. Local cargo test with RUST_MIN_STACK=16777216. These are local results, not CI.

The undelivered-exit fix is b19f6c2f. Its parent is 71dc46fc. GQT cannot express a process exit. The owner is cli_schema_config. The pull request already contains an_undelivered_failure_keeps_its_exit. I copied that test onto a detached worktree at 71dc46fc and ran:

RUST_MIN_STACK=16777216 CARGO_TARGET_DIR=/workspace/target cargo test -p omnigraph-cli --test integration cli_schema_config::an_undelivered_failure_keeps_its_exit -- --exact --test-threads=1

Result: FAILED. mutate --store … --json with a closed stdout exited 1 (unix_wait_status(256)). The test expects 2. stderr contained could not deliver CLI diagnostic and outcome unknown / reconcile. main returned the error, so the process exit was 1. That exit means nothing changed. The same test at b19f6c2f passed in 0.11s. I removed the worktree and the copied test. Nothing was pushed.

Passed at the head, each filter --exact, 1 passed unless noted:

  • cli_schema_config::an_undelivered_failure_keeps_its_exit (0.11s)
  • cli_schema_config::clap_exit_status_survives_closed_output (0.05s)
  • cli_data::mutate_if_commit_lost_cas_preserves_details_embedded_issue_365 (0.21s)
  • cli_data::data_write_outcomes_and_retry_permission_issue_466_issue_976 (1.12s)
  • cli_data::blob_put_and_clear_write_receipts_and_refuse_bad_input_before_scope (0.46s)
  • cli_data::blob_stat_fails_loudly_for_null_missing_and_non_blob_cells (0.12s)
  • cli_data::cli_fails_for_missing_schema_or_data_file (0.16s)
  • cli_data::removed_cli_spellings_refuse_and_canonical_commands_remain_available (0.13s)
  • cli_queries::a_source_this_cli_cannot_parse_is_sent_to_the_server_verbatim (0.06s)
  • cli_queries::alias_setup_keeps_one_operator_config_across_params_file (0.14s)
  • cli_queries::alias_unknown_name_errors_listing_defined (0.01s)
  • cli_cluster::managed_organization_requires_explicit_mode_and_write_keys (0.05s)
  • cli_cluster::managed_mode_is_explicit_and_direct_commands_ignore_folder_context (0.63s)
  • cli_cluster_e2e::cluster_e2e_offline_deployment_has_root_only_receipts_and_explicit_unlock (0.60s)
  • cli_cluster_e2e::cluster_e2e_removed_v1_commands_are_not_executable (0.03s)
  • parity_matrix::parity_load (0.99s)
  • parity_matrix::parity_blob_put_and_clear_share_receipts_and_refusals (0.31s)
  • system_remote::remote_merge_delivery_loss_never_replays_committed_effect (0.54s)
  • system_remote::remote_commit_delivery_loss_never_replays_committed_effect (0.85s)
  • system_remote::remote_blob_write_delivery_loss_never_replays_committed_effect (0.77s)
  • lib app::tests: 13 passed (0.44s)
  • bin tests::reported_failure_does_not_wait_for_a_blocking_task: 1 passed

Limitations

I did not run the full CLI suite (the author reports 234 lib + 332 integration), the workspace test graph, clippy, fmt, the doc checks, S3, or Azure. I did not run GQT. Process exits are outside that format. I did not measure performance. This change does not touch Lance storage, indexes, or transactions, so I did not fetch the Lance manual. The load --from probe calls classify with the flags the load path sets. It does not execute a policy-backed load on both transports.

Inline comments that could not be posted

  • crates/omnigraph-cli/src/client.rs:1354 — compound() runs because from is set, before the fork. A Change denial then exits 2. The served path exits 1. Project with the request path's after_write and compound inputs. Add the CLI regression above.
  • crates/omnigraph-cli/src/command_outcome.rs:260 — embedded requires !compound, so every engine error after compound() is exit 2. The policy probe above shows exit 2 with the flag and exit 1 without it.
  • crates/omnigraph-cli/src/command_outcome.rs:388 — Optional. Exit 4 depends on conditional, not on the outcome fields.
  • changelog.d/cli-exit-follows-outcome.breaking.md:1 — The note covers more commands than kind() classifies, and it says every refused command line exits 64.
  • docs/user/operations/troubleshooting.md:100 — The exit 64 row does not mention the addressing guard, which still exits 1.
  • docs/user/operations/troubleshooting.md:123 — "Embedded and served commands exit alike" is false for a pre-fork load --from policy denial.
  • docs/user/cli/reference.md:115 — This line says exit 4 on both transports. The next section, and the skill, still say embedded exit 1.
  • crates/omnigraph-cli/src/app.rs:46 — Optional. The comment says Clap keeps its exit status. The code maps every non-zero Clap status to 64.

Write facts survive task boundaries. Whether a request had submitted an
owned write, and whether its load forked a branch, lived in a task-local,
so an MCP tool call, which runs its handler in an owned read task and
builds the error body in another, lost both: a load whose `from` forked a
new branch and then met a key conflict reported not_started with no
effects. ApiError now carries `compound` beside `owned_write`, an owned
read keeps the request's record in its own task, and an error leaving it
takes that record along; the outcome reads the error's own fields. The
outcome unit test gains cases run through an owned read, the MCP write
test a load that forks and then conflicts, and the HTTP load test its
twin.

A load is compound only when it forks. Both load routes marked it whenever
`from` was set, so a refused load onto an existing branch reported unknown
although its one publication never ran. The routes now mark it from the
branch listing their authorization already reads, and only on the owned
write's error. An earlier step's publication also outranks a refusal the
write met before it started. The HTTP load test covers a key conflict onto
an existing branch with `from`, which reports not_started.

Every error the server answers has a code. ApiError's code is no longer
optional, and the deployment status read that races an owner change
answers 503 `service_unavailable` with a retry outcome instead of no code.

Malformed input, unknown routes and unserved methods answer the error
body. Handlers took axum's Json, Query and Path, whose rejections are plain
text (422 for a JSON body of the wrong shape), and unrouted requests had
empty bodies. ApiJson, ApiQuery and ApiPath refuse with ApiError (400, or
the transport status for 413 and 415), the router's fallbacks answer
not_found and method_not_allowed, and a source guard refuses a handler
parameter of axum's own extractors. The three query structs whose
parameter location came from the extractor's name declare it, so their
optional parameters render as the other query parameters do.

Embedded callers can render an engine failure as a served request with
the same facts would: engine_error_output_after takes whether a write was
submitted and whether an earlier step published, and
engine_read_error_output renders a read, which never answers
outcome_unknown.

The RustFS S3 test expected the 424 external Blob source refusal to carry
no code; it now asserts its code and outcome. The troubleshooting guide
and the changelog note state the outcome decision table the server
implements.
…efusals as the server does

The merge keeps this branch's command kinds and ModernRelay#1074's served cluster
`--as` exit: a cluster command is observed without being classified.

An embedded `load --from` marked its write compound whenever `from` was
set, so a refusal before its fork exited 2. It now marks it only when the
branch is missing, as the server's load does, and an embedded write's
own error goes through `engine_error_output_after`: a policy refusal
precedes every publication, the fork's included, and any other refusal
of a compound load follows the fork. A read's engine error keeps its own
code instead of `outcome_unknown`, and the failure reporter no longer
panics on a wrapped engine write error.

The crossversion journeys read a `--json` read's refusal from stdout,
where the failure now goes. The exit documentation says once that a
failed condition exits 4 embedded or served, names the data commands the
outcome table covers and the commands that keep their own exits, and says
that only the argument parser's refusals exit 64.
@ragnorc

ragnorc commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Addressed at 04a9da5. That commit merges the updated #1076 and contains these fixes.

Blocking

  1. load --from.
    • The embedded load marks its write compound only when the target branch is missing, using the same branch listing as the server's authorize_load_scope.
    • An embedded engine error now goes through engine_error_output_after from feat(server)!: say on every error body whether the request ran #1076. A policy refusal precedes every publication, the fork's included, so it is not_started. Any other refusal of a compound load follows the fork, so it is unknown.
    • Tests:
      • command_outcome::tests::an_embedded_compound_write_reports_what_the_engine_did covers your probe, with and without compound.
      • cli_data::a_policy_refused_served_load_reports_not_started covers the served arm, for both a missing branch and an existing one, and checks that no branch is left behind.
    • An end-to-end embedded case isn't possible today. A direct --store write to a cluster graph applies no cluster policy (direct access doesn't load server policy), so the engine policy refusal is pinned at classify.
  2. Exit 4. It now reads one way everywhere: reference.md (Conditional mutations), SKILL.md, references/changes.md and references/remote-ops.md. The last one also describes exits 1, 2, 4 and 75.
  3. Coverage.
    • troubleshooting.md and the changelog now say the exit table covers the classified data commands.
    • They also say maintenance, cluster, policy and managed commands keep their own exits for now.
    • Only the argument parser's refusals exit 64; an addressing refusal inside a command exits 1.

Optional: the run_from comment is fixed. The downcast().expect is gone, and a wrapped engine-write error now falls through instead of panicking. I left exit_for's side flag as it is.

Also fixed from CI:

  • A read's engine error was rendered as outcome_unknown. It now keeps its own code, via engine_read_error_output.
  • The crossversion journeys read a --json read's refusal from stdout.

ragnorc added a commit to ragnorc/omnigraph that referenced this pull request Oct 19, 2026
…target's default graph

The merge keeps this branch's typed-refusal branch in the failure
reporter beside ModernRelay#1078's projection of embedded engine errors.

`queries` and `policy` keep their explicit `--graph` filter and no longer
take a target's `default_graph`, as RFC 0059 specifies for root tools, so
their all-graph and ambiguity behaviour is the same whatever selected the
root.

ragnorc commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

A script cannot tell a command that changed nothing from a command that may have changed the graph when both exit 1. This change gives each classified data command an exit that follows its outcome.

Exit 0 is success. Exit 1 means the command did not start. Exit 2 means the effects are unknown. Exit 4 means a condition the caller set did not hold. Exit 75 means the server refused before the start and a retry is allowed. Exit 64 means the argument parser refused the command line.

kind selects those commands. Failure::classify keeps a server outcome when one request answers the command. An embedded write uses the engine error. A policy refusal is projected as not started, including a load --from fork, because policy runs before publication. The load marks the write compound only when its branch list does not contain the target. That is the same pre-check authorize_load_scope uses.

Scripts that read exit 2 as a usage error, or exit 1 as every failure, must change. Maintenance, cluster, policy and managed commands keep their current exits. The table is still only the part of draft RFC #1017 named "Outcomes and refusals". Merge this after that RFC is accepted, as the pull request says.

Reviewed head d5cda7d55f348b85131519c8971ecb130397c3ba. The parent is #1076 at 23027f54f4ede65d40e0a6997c10ecee16f62ad4. The unique delta is 23 files, +841/−315. d5cda7d5 adds only docs/user/operations/server.md from #1076's docs commit. The fixes are in 04a9da53.

Earlier findings

  1. Embedded load --from reported unknown effects when a policy refusal published nothing. Resolved. The load calls compound() only when the target is absent (client.rs). A policy error goes through engine_error_output_after(engine, false, false), so the outcome stays not_started / none / refresh and the exit stays 1. command_outcome::tests::an_embedded_compound_write_reports_what_the_engine_did locks that projection with compound true and false. cli_data::a_policy_refused_served_load_reports_not_started locks the served arm for a missing branch and for main, and checks that fresh is not left behind. A direct --store open does not install Cedar policy, so an embedded policy refusal cannot be driven through the process. The projection test is the right owner.

  2. Exit 4 was described two ways. Resolved. Conditional mutations, the skill, changes.md, remote-ops.md and blobs.md now say exit 4 for a failed precondition on both a server and a direct store.

  3. The published exit table covered commands kind does not classify. Resolved, with a small residual. troubleshooting.md and the changelog limit the table to classified data commands. They name maintenance, cluster, policy and managed commands as keeping their own exits. Exit 64 is the argument parser only. An addressing refusal inside a command still exits 1. Two residuals remain. The changelog and the troubleshooting list say "target commands", and kind() has no target arm at this head. feat(cli): name targets in the operator configuration #1080 adds that arm and already contains this commit. The changelog also says "Managed commands keep their own exits for now" twice. reference.md says a refused command line exits 64 and does not repeat the addressing exception that the troubleshooting table states.

The run_from comment now says a refused command line exits 64. Resolved. The downcast().expect on EngineWriteError is gone. A wrapped engine-write error falls through. Resolved.

Blocking

None.

Optional

  1. exit_for still uses a side flag. This was left open on purpose. Exit 4 is selected by conditional, not by the outcome fields. not_started with refresh is also exit 1. The outcome object cannot tell a Blob or graph-commit precondition from another refusal with the same three fields. The RFC says the exit follows from the outcome. The next caller condition needs another flag beside conditional. Inline target: crates/omnigraph-cli/src/command_outcome.rs around the exit_for conditional arm.

  2. The new "fork published" sentences are wider than the runs. troubleshooting.md says a policy refusal is not started, a refusal after the load --from fork is unknown, and embedded and served commands therefore exit alike. embedded_write_output says any other error of a compound write follows the fork that published. client.rs says the branch list makes the two report alike. Three runs disagree with that wording.

    • A bad payload on an existing branch, with --from main, exits 1 on the embedded path: bad_request, not_started, none, refresh. The fork does not run. That is the steady state this fix wants.
    • The same shape on a missing base exits 2: not_found, unknown, unknown, reconcile. The fork does not run. The base is absent.
    • A bad payload on a missing target exits 2, and parity_load shows the branch exists on both arms. That case matches the sentence.
      A served load of a bad payload on a branch the pre-check already sees is an owned write. feat(server)!: say on every error body whether the request ran #1076's outcome() does not treat bad_request as a refusal before the start, so that arm is unknown and the CLI exits 2. I did not execute that served arm in this pass. The conclusion is from run_json_load (owned_write, then in_compound_write(false)) and from ApiError::outcome. Leave that server rule on feat(server)!: say on every error body whether the request ran #1076. Do not move the embedded existing-branch failure back to exit 2 to make the sentence true. Narrow the sentence to the cases that match: a policy refusal is not started on both planes, and a payload refusal after a fork that did publish is unknown on both planes.
      The pre-check is also a second look, not the engine's branch_created. If the target disappears after the CLI list, the engine can fork and publish, then without_pre_effect_evidence strips BeforeEffect and does not mark the error uncertain. With compound false, into_step_output reports not_started and exit 1. The server names this window and still fails closed, because the error is an owned write. I did not run that race. The durable fix is for the engine error to keep the fact that the fork published, so classify does not need a second prediction. Setting compound() for every --from would hide the race and would again report unknown effects when the existing branch never forked.
      Inline targets: docs/user/operations/troubleshooting.md at the "exit alike" paragraph, and crates/omnigraph-cli/src/command_outcome.rs at the embedded_write_output comment.
  3. The policy test does not lock the call site. an_embedded_compound_write_reports_what_the_engine_did still passes if load calls compound() for every --from, because a policy error ignores compound. Please add an embedded CLI case next to parity_load: the target branch already exists, --from is set, the payload is bad, the exit is 1, the outcome is not_started / none / refresh, and no new branch appears. Do not assert that case in parity_load until the served arm agrees. GQT cannot see a process exit.

RFC #1017 at 7c72c7e7

The draft still says each exit has one meaning on every command, and that the exit follows from the outcome. This pull request applies the table only where kind() is Some. The public text now says so. These differences remain.

  • Usage is 64 in the RFC. Here that is Clap only. Addressing inside a command exits 1. The troubleshooting table says this.
  • Exit 4 does not follow from the outcome object. It follows from conditional. See optional finding 1.
  • The RFC exit 2 also covers a deployment or local server that stopped with an uncertain write, and it moves managed refusals to 1 and managed recovery_required to 2. This pull request leaves maintenance, cluster, policy and managed exits unchanged. That deferral is explicit. It is not a silent second table.
  • outcome.check is in the RFC example and is not in this change. It belongs with the server outcome work.
  • The troubleshooting claim that embedded and served commands exit alike is not in the RFC table. It is new text in this pull request, and optional finding 2 is where it is wider than the code.

#1074 and #1076 behavior is their review surface. This pass did not re-review ApiError::outcome.

Stack and neighbors

The order is #1074, then #1076, then this pull request, then #1080, then #1081. #1080's history already contains d5cda7d5, and it classifies target commands. #1073 is the access route #1081 needs. #1075 and #1077 are the local servers. #1083 is omnigraph serve. None of those replace this exit table. #1059 and #1012 are open and both touch CLI files this pull request also edits (client.rs, reference.md, parity and support tests). They will need a rebase. They do not define this table. #675 (RFC 0059) unifies targeting and does not define these exits. #1067 is the server-stop issue the RFC cites. It is not this exit table.

Liability

The change removes one overloaded exit for the classified commands. The cost that remains is small and local. Each new classified command adds one CommandKind arm. Exit 4 as a side flag means the next caller condition adds another flag, or the outcome object gains a way to name that condition. compound() is a prediction beside the engine's completion evidence. The policy special case is a third input. Five more commands do not need five more exit tables. They do need the prediction to stay equal to "an earlier step published". The line count is not that cost. Scoping the public table removes the previous liability, where every command appeared to share exits the code did not implement.

Validation

Local, at the reviewed head, with CARGO_TARGET_DIR=/workspace/target and RUST_MIN_STACK=16777216. Not CI.

  • cargo test -p omnigraph-cli --lib -- --test-threads=8 — 236 passed, 0 failed, 9 ignored.
  • cargo test -p omnigraph-cli --lib command_outcome:: -- --test-threads=1 — the two new tests passed (included again in the lib run above).
  • cargo test -p omnigraph-cli --test integration with CARGO_BIN_EXE_omnigraph-server set to the built server, exact names: cli_data::a_policy_refused_served_load_reports_not_started, cli_queries::a_query_without_its_parameter_list_reports_q002_in_every_output_format, cli_schema_config::an_undelivered_failure_keeps_its_exit, cli_schema_config::clap_exit_status_survives_closed_output, parity_matrix::parity_load, cli_data::data_write_outcomes_and_retry_permission_issue_466_issue_976. All passed.

Regression proof. GQT cannot express a process exit. The owner is the CLI lib test. The same two tests were copied temporarily onto b19f6c2f5ad5a76d10d14acf44ec6acaa91ec99c and run as cargo test -p omnigraph-cli --lib command_outcome::tests -- --test-threads=1. Both failed for the predicted reason. an_embedded_compound_write_reports_what_the_engine_did got exit 2 against 1 when compound was true. an_embedded_read_error_keeps_its_own_code got OutcomeUnknown. At d5cda7d5 both pass. The copy was removed and was not pushed.

An embedded CLI probe at this head, also removed and not pushed, showed the three load --from shapes above (exit 1, exit 2 with not_found, exit 2 after a real fork).

Not run: workspace cargo test, clippy, fmt, docs checks, typos, GQT, DST, S3, Azure, and the served existing-branch payload. No performance claim. This change does not touch Lance storage.

Recommendation: approve. No blocking findings.

A server that predates `omnigraph-expect-actor` ignores it, so `--as` on
a served write would run as the credential's actor. The earlier fence, a
response header listing the assertions a server enforces, was a second
compatibility record beside the contract version: two servers speaking
0.13 could disagree about the assertion, and every further assertion
would extend an enum on every response.

The contract moves to 0.14 instead, the one bump the access route,
`outcome` and the code registry also ride. A 0.13 server answers
discovery with its own contract, so the CLI refuses it before sending,
and it refuses a 0.14 request before reading the header. The assertions
header, its constant and the CLI's discovery check are gone, and so is
the enum on every OpenAPI response.

The guides, the developer pages, the skill, the version samples and the
fixtures name 0.14, and a breaking note says to upgrade the CLI, server,
Studio and HTTP integrations together. The server guide says a repeated,
padded or empty header is refused, and that `/mcp` compares it after
authentication; the cluster skill names the subcommands that take `--as`.
Brings in HTTP contract 0.14 and the removal of the assertions response
header, so this branch speaks the contract its outcome field rides.
…failed

A load names whether it forked its branch only from inside the engine:
the routes took that fact from the branch listing their authorization
reads, before the load lists the branches again and decides. A branch
deleted between the two, as one can be while an NDJSON body is still
arriving, was forked by the load, and a refusal of its rows then answered
`not_started` with no effects although the fork had published.

`CompletionEvidence::PartlyPublished` records that an earlier step of
the owning operation published and a later one failed. The load marks
every failure after its fork with it, the window between the fork and the
rows included. It outranks the definite `BeforeEffect` and `Unpublished`,
which no longer cover the operation, survives composition, and never
hides `Uncertain`. The server reads it as the compound fact and leaves
containment where no evidence leaves it, so a typed refusal keeps its
code with `unknown` and `refresh`. The routes' list-derived flag and
`in_compound_write` are gone, and `engine_error_output_after` drops its
`compound` argument: embedded callers get the fact from the error.

A route test deletes the branch while the NDJSON body waits and expects
the fork reported; the core test pins the composition rules. The key
conflict and `graph_unavailable` descriptions say what they now mean, and
the breaking note names the plain query-parameter types in openapi.json.
Brings in HTTP contract 0.14 and the engine's mark of an earlier
publication. The embedded load no longer lists the branches to predict a
fork: the engine marks every failure after its fork with
`PartlyPublished`, so `embedded_write_output` reads that mark, and
`Evidence.compound` and `command_outcome::compound` are gone. A branch
deleted after a list and before the load's own check can no longer
report `not_started` after the fork published.
The outcome unit test passed even if `load` predicted a fork for every
`--from`, because a policy refusal ignores the prediction. A process
test now drives the call site. Onto a branch that exists, `--from` forks
nothing, and a refused payload exits 1 with `not_started`. Onto a
missing branch the fork publishes first, and the same refusal exits 2
with `unknown` while the branch stays. The troubleshooting guide's
"exit alike" sentence is narrowed to those cases: a policy refusal is
`not_started` on both planes, and a refusal after a fork that did
publish is `unknown` on both.
Main replaced the legacy routing flags with explicit targets (ModernRelay#1090). The
actor assertion sits on that structure: `GraphClient::from_target` and the
served cluster client send `--as` as `Omnigraph-If-Actor` on a server
target, where ModernRelay#1090 refused it, and only a managed target still refuses
`--as`. `accepts_actor` keeps deciding which commands take it. The HTTP
contract stays 0.14.
It brings main's explicit targets (ModernRelay#1090) and Lance 13. A target refusal
that ModernRelay#1090 reports with the command's evidence carries `disposition`, as
every other data-write failure does.
…ges within the cap

The served cluster entry now takes the actor `--as` asserts. The server, CLI reference and cluster guides fold the merged text back under 350 lines.
Brings the actor branch's cluster remote test fix and its page trims.
Main's branch action route took axum's `Path` and answered 405 with no body. It takes the typed extractor, and its 405 carries `method_not_allowed` and the disposition beside the `Allow` header.
Brings in main through ModernRelay#1076, including ModernRelay#1090's explicit targets. The
exit table keeps its classification: the removed use and profile
commands leave it, and the target and credential commands, which keep
their own failure reports, join the unclassified ones. ModernRelay#1078's tests
move to --target, ModernRelay#1090's usage-error tests expect exit 64, and the
diagnostics checks pass the destination line ModernRelay#1090 prints first.
ragnorc and others added 5 commits October 20, 2026 12:05
…e result

A review found two ways the exit table and the single-result stdout
contract disagreed. A classified command's target refusal still exited
2, which the table reserves for unknown or partial effects; it now exits
as its disposition says, 1 with action fix, while the target commands
keep their own exit. And a JSON failure after part of a streamed result,
such as a later page of diff or changes poll, was appended to stdout,
leaving two documents in one stream; it now goes to stderr.
…s guards

The router-wide method fallback replaced each protected route's default
fallback, layers included, so a method the route does not serve answered
405 before bearer authentication, the contract check, the actor
assertion and admission. Each protected router now installs the
fallback before its guards, through one helper that owns their order.
@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

This review covers bcf0d3d2d4a92b0741749557b48fbe0bbf06e7d8. The previous review covered e5b11dd6. Recommendation: approve.

Why

A script must tell a command that changed nothing from a command that may have changed the graph. The exit is that signal. Exit 2 means the effects are unknown or partial. A refusal that changed nothing must not use that exit.

What

The pull request's own commits since e5b11dd6 are bd3ee6a6 and 21cea205. bcf0d3d2 then merges #1076. That merge does not change the CLI files.

Two contracts move.

  • A classified data command's target refusal exits 1. The body says effects: none and action: fix. target, credential, and cluster keep exit 2.
  • A JSON or JSONL failure goes to stderr when any result byte has already reached stdout. Stdout keeps those bytes.

How

finish in crates/omnigraph-cli/src/app.rs sees TargetError. A classified command with no dispatched request and no open write classifies a string form of that refusal. The string has no error code, so classify would say refresh. The effects are none, so the exit from failure.exit is 1. The body then sets action to fix. A target command has no evidence, so the same arm still exits 2.

StartedOutput records a successful write. When that flag is set, finish writes the failure envelope to stderr for JSON and JSONL.

The merge puts each protected router's 405 fallback behind the shared guards. guarded in crates/omnigraph-server/src/lib.rs installs method_not_served before bearer auth, the contract check, and the actor assertion. A wrong method now gets 401, 400, or 412 before 405. The 405 body is still method_not_allowed with effects: none and action: fix. The CLI exit table does not change. A normal data command does not send a wrong method.

Earlier findings

  • Embedded load --from reported unknown effects when a policy refusal published nothing. Resolved. This delta does not reopen it.
  • Exit 4 was described two ways. Resolved.
  • The published exit table covered commands kind does not classify. Resolved. The table still names the classified commands. This delta says a target refusal on those commands exits 1.
  • exit_for still takes conditional beside the disposition. Open, by choice. The author left it for rfc: graph access and cross-process writes #1017. This delta does not add another caller condition.
  • The fork-prediction sentences were wider than the runs. Resolved.
  • The policy test did not lock the call site. Resolved.
  • The run_from comment and the downcast().expect panic. Resolved.
  • The feat(server)!: say on every error body whether the request ran #1076 merge defects from the last review (axum Path, and a 405 with no body). Resolved on this head. server_branch_action takes ApiPath. branch_method_not_allowed and method_not_served return ApiError::method_not_allowed.

Blocking

None.

Optional

docs/user/operations/troubleshooting.md says that when a streamed page has already reached stdout, the failure goes to stderr "so stdout holds one document." diff --json and changes poll --json write one open object (DiffJsonStream, ChangeFeedJsonStream). A later page error leaves that prefix on stdout. The failure document is on stderr. Say that stdout keeps the bytes already written. Do not say that it holds one complete document. Closing the object in the failure path would hide a short read.

Liability

The old target-refusal arm printed effects: none and exited 2. Exit 2 is the unknown-effects exit. That disagreement is gone. A script can trust exit 1 as "nothing changed" for these refusals.

The cost that remains is local. exit_for still takes the conditional flag. The target-refusal arm sets action: fix beside classify, because the string error has no code. One rule covers the class: a target refusal before effects asks the caller to fix the destination. Five more target codes do not need new arms.

guarded is one helper for the shared guard order. Five more protected routers call it. They do not each copy the guard list.

Tradeoff

Correctness comes first. The failure document leaves the result stream, so a parser does not read an error object as a second value on stdout. A mid-stream diff or changes poll stays a prefix. That is the honest result of a short read.

Validation

Reviewed head bcf0d3d2d4a92b0741749557b48fbe0bbf06e7d8. The CLI bytes match 21cea2052b868135b7527e196ac317574adf5883. git diff 21cea205 bcf0d3d2 --stat lists only crates/omnigraph-server/src/lib.rs and crates/omnigraph-server/tests/integration/auth_policy.rs. Worktree /tmp/pr1078 at 21cea205 for the CLI runs. Rust 1.97.1. CARGO_TARGET_DIR=/workspace/target. The temporary probe on 93fc09c3 was restored. Nothing was pushed. The pull request was open at bcf0d3d2 immediately before this comment. No other review covers this head.

GQT cannot see a process exit or which stream holds a JSON failure. The owner is the CLI test.

Before, at 93fc09c33aff891bb4313121cfa80ff9f51837f7, with the head assertions applied only in /tmp/pr1078-before and then removed:

cargo test --locked -p omnigraph-cli --lib app::tests::argument_refusal_help_and_success_return_without_terminating_the_caller -- --exact --test-threads=1

Failed. left: 2, right: 1. The body was code: target_required, effects: none, action: refresh.

cargo test --locked -p omnigraph-cli --lib app::tests::probe_a_json_failure_after_a_page_stays_off_stdout -- --exact --test-threads=1

Failed. Stdout was the page prefix {"blocks": [ plus the failure JSON (effects: none, action: refresh). The probe asked stdout to stay the prefix and stderr to hold the failure.

After, at 21cea205 (same CLI sources as this head):

cargo test --locked -p omnigraph-cli --lib app::tests::argument_refusal_help_and_success_return_without_terminating_the_caller -- --exact --test-threads=1 — 1 passed.

cargo test --locked -p omnigraph-cli --lib app::tests::a_failure_after_part_of_a_json_result_goes_to_stderr -- --exact --test-threads=1 — 1 passed.

cargo test --locked -p omnigraph-cli --test integration -- cli_cluster::managed_data_process_refuses_missing_graph_and_actor_override_before_keychain system_local::local_cli_keyed_credentials_authenticate_url_matched_server_issue_1022 --exact --test-threads=1

The cluster test passed: a data command's target refusal exits 1, and the cluster command exits 2.

system_local::local_cli_keyed_credentials_authenticate_url_matched_server_issue_1022 failed before the exit assertion. The server did not report a listen address. The test looks for omnigraph-server under the worktree target/, and this run set CARGO_TARGET_DIR=/workspace/target. That is a setup failure, not an exit-code result.

CI on 21cea205 succeeded, including Test Workspace, GQT, Lint, and Format. On bcf0d3d2, Dependency Guard has failed and Test Workspace was still running when this comment was written. The log for the deny failure was not available. The delta from 21cea205 does not change Cargo.lock. I did not run the new auth_policy test. I read it: an unauthenticated wrong method is 401, and an admitted one is 405 with effects: none and action: fix.

This change does not alter Lance dataset behavior. I did not re-fetch Lance format pages. No performance claim.

The failure logs main added read ApiError's status, which this branch
derives from its code.
…lication

unpublished() returned an error unchanged once it carried any evidence,
so a step marked after its operation recorded an earlier publication
lost its own proof. It now sets step_unpublished on that evidence.
The 412 row now says what to do about actor_mismatch, the changelog
names branch_start_refusal's two statuses, and the CLI reference says a
served blob put, blob clear, commit publish and commit drop send --as as
the actor assertion.
@ragnorc
ragnorc added this pull request to the merge queue Oct 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 21, 2026

ragnorc commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

This review covers e3f9813068d8355c79df363c95cfcef45d1b7a9f. The previous review covered bcf0d3d2. Recommendation: approve.

Why

A script must tell a command that changed nothing from a command that may have changed the graph. The exit is that signal. Exit 2 means the effects are unknown or an earlier step left effects. A step that proved it published nothing must not be described as an unknown publication.

What

The code since bcf0d3d2 is 268ee918 and 1f34ad57, merged at 1e67bcab. e3f98130 then merges main. That merge changes GOVERNANCE.md, LICENSE, README.md, SECURITY.md and changelog.d/tls-distrusted-intermediates.fixed.md. The CLI, core and server sources are the same bytes as 1e67bcab. Main's squash of #1076 (d111c403) is the pull request base. This merge contains that squash already. Both merges have an empty conflict diff.

Two facts move in the code.

  • unpublished() on an opaque failure that already carries PartlyPublished { step_unpublished: false } sets step_unpublished. The earlier publication stays. The step proof is kept.
  • The docs name both 412 actions, both statuses of branch_start_refusal, and that a served blob put, blob clear, commit publish and commit drop send --as as the actor assertion.

The pull request's own diff against d111c403 is the CLI exit table. That table is unchanged since bcf0d3d2.

How

OmniError::unpublished in crates/omnigraph-core/src/error.rs used to return the error unchanged once any evidence was present. A call after after_earlier_publication left step_unpublished false. The method now attaches PartlyPublished { step_unpublished: true } for that case. compose_evidence ORs the flag. Uncertainty, BeforeEffect, Unpublished and a flag that is already true stay as they are. A typed refusal is not opaque, so unpublished() still leaves it unchanged.

The server maps a true flag to effects: partial, action: refresh, and keeps the cause code (internal for this I/O failure). The same I/O error without the flag stays outcome_unknown with reconcile. The CLI exit for both is 2. Partial and unknown share that exit. The body is what changes. The caller reads the target and follows action.

The loader marks the inner failure unpublished and then applies after_earlier_publication. That order already kept the proof. This change makes the reverse order agree. map_err(OmniError::unpublished) is the call that can mark an error which already carries evidence. No current caller passes PartlyPublished into it. The method owns the mark, so a later caller cannot lose the proof by order.

branch_create_from_as(start, name, actor) is wrapped with .engine_write(). That matches merged #1016.

Earlier findings

  • Embedded load --from reported unknown effects when a policy refusal published nothing. Resolved. This delta does not reopen it.
  • Exit 4 was described two ways. Resolved.
  • The published exit table covered commands kind does not classify. Resolved.
  • exit_for still takes conditional beside the disposition. Open, by choice. The author left it for rfc: graph access and cross-process writes #1017. This delta adds no caller condition.
  • The fork-prediction sentences were wider than the runs. Resolved.
  • The policy test did not lock the call site. Resolved.
  • The run_from comment and the downcast().expect panic. Resolved.
  • The feat(server)!: say on every error body whether the request ran #1076 merge defects from the review of e5b11dd6 (axum Path, and a 405 with no body). Resolved.
  • troubleshooting.md says stdout holds one document after a short read. Open. The docs commit in this delta changes the 412 row and does not change that sentence.

Blocking

None.

Optional

  1. docs/user/operations/troubleshooting.md says that when a streamed page has reached stdout, the failure goes to stderr "so stdout holds one document." diff --json and changes poll --json write one open object. A later page error leaves that prefix on stdout. The failure document is on stderr. Say that stdout keeps the bytes already written.

  2. The containment logs call ApiError::status(). That method returns the cause code's first HTTP status. An owned write the server cannot settle answers 500 outcome_unknown. A DataFusion failure is mapped to bad_request, so status() is 400 and the response is 500. The warning in from_omni and the error in OperationRuntime::submit both record that 400. Log the status the response sends. This code is on main through feat(server)!: say on every error body whether the request ran #1076. It is not part of this pull request's diff against d111c403. A follow-up can fix it. It does not hold this pull request.

Liability

unpublished() no longer drops a step proof because of call order. The two orders record one fact: an earlier publication, and the step's own proof that it published nothing. A script that reads action can refresh that step. It does not have to reconcile a publication that did not happen. Exit 2 stays exit 2, because the earlier step did publish.

The cost that remains is local. exit_for still takes the conditional flag. compose_evidence still ignores a later BeforeEffect, which the existing test requires. unpublished() holds the late-proof rule in its own match, so before_effect does not gain the same behavior. One more mark of this kind belongs beside compose_evidence, with before_effect left out.

Tradeoff

Correctness comes first. The disposition matches the proof the engine recorded. The exit number for this case stays 2. The caller uses action to tell refresh from reconcile.

Validation

Reviewed head e3f9813068d8355c79df363c95cfcef45d1b7a9f. The error and server bytes match 1e67bcab783601b9d0ff179b3b4bd0b8296bf3bd. The fix parent is 40708ede7729a32949aa333feb4aaa2aba9ede9c. Rust 1.97.1. CARGO_TARGET_DIR=/workspace/target. The temporary assertions lived only in /tmp/pr1078-before and were removed. Nothing was pushed. The pull request was open at e3f98130 immediately before this comment. No other review covers this head. During the review the head moved from 1e67bcab to e3f98130. The tree diff is the five documentation files above.

GQT cannot see completion evidence or which stream holds a JSON failure. The owner is the core unit test. The server test projects that evidence into the error body.

Before, at 40708ede, with the head assertions applied only in /tmp/pr1078-before:

cargo test --locked -p omnigraph-core --lib error::tests::a_settled_step_after_an_earlier_publication_keeps_its_proof -- --exact --test-threads=1

Failed. Left: PartlyPublished { step_unpublished: false }. Right: step_unpublished: true.

cargo test --locked -p omnigraph-server --lib api_error_tests::an_embedded_engine_error_reports_what_a_served_request_would -- --exact --test-threads=1

Failed. Left: OutcomeUnknown. Right: Internal. The message was io: detached manifest put failed. A shared target directory first ran the head binary. The result above is from a clean rebuild of the parent sources.

After, at 1e67bcab (same error.rs and server sources as this head):

cargo test --locked -p omnigraph-core --lib error::tests::a_settled_step_after_an_earlier_publication_keeps_its_proof -- --exact --test-threads=1 — 1 passed.

cargo test --locked -p omnigraph-server --lib api_error_tests::an_embedded_engine_error_reports_what_a_served_request_would -- --exact --test-threads=1 — 1 passed.

CI on 1e67bcab succeeded, including Test Workspace, GQT, Lint, Format and Dependency Guard. On e3f98130, those checks had succeeded except Test Workspace, which was still running when this comment was written. The tree diff from 1e67bcab changes no Rust file. Cargo.lock already has rustls-webpki 0.103.16 and webpki-roots 1.0.10, which the new changelog note names.

This change does not alter Lance dataset behavior. I did not re-fetch Lance format pages. No performance claim.

Merged via the queue into ModernRelay:main with commit 269c618 Oct 21, 2026
32 checks passed
ragnorc added a commit to ragnorc/omnigraph that referenced this pull request Oct 21, 2026
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