Skip to content

fix(validate): retired declarations leave the root slot - #405

Merged
schickling merged 2 commits into
mainfrom
schickling/2026-08-31-issue-402
Aug 31, 2026
Merged

fix(validate): retired declarations leave the root slot#405
schickling merged 2 commits into
mainfrom
schickling/2026-08-31-issue-402

Conversation

@schickling-assistant

@schickling-assistant schickling-assistant commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

st2 validate and st2 catalog graph on current main count declarations that use legacy retired #true as active roots. On the live dev3 catalog (intended root: cos) the new one-root invariant reports root-count: host dev3 must declare exactly one root, found 8cos plus seven root-shaped legacy-retired tombstones — and graph v2 exposes those tombstones with desiredState: null in declarations[]. The deployed pre-#399 st2 had no root-count check at all, which is why the same catalog validated clean.

Root cause: #399 introduced the invariant in two places — validate.rs (root-count) and catalog_graph.rs::admitted_topology — and both count every spec with supervisor: none, ignoring the folded desired state. The spec layer already folds both spellings (retired #true, desired-state "retired") into AgentDesiredState::Retired; only the invariant's predicate and the declaration view missed it.

Goal

Retired declarations never participate in the one-root invariant; the graph declarations view shows the fold; genuine topology faults still error.

Decisions

  • Retired-only exclusion, suspended roots still count (q1): retirement is permanent exit from the org chart; suspension is a pause that keeps the slot, so a host suspending its only root never false-positives. Both spellings fold through one shared predicate supervisor_chain::is_counted_root, so validate and admitted topology cannot drift apart again.
  • Zero counted roots stays an error: a headless host (active workers, retired root) and a tombstone-only host both report found 0. No live host has this shape today.
  • Declarations view folds the legacy spelling only (q2): retired #true"retired"; an absent lifecycle stays null, which keeps one meaning ("nothing declared" → running). PartialAgent remains a declared-fields view.
  • Six stale tests/validate.rs repairs ride in this PR (q3): Make catalog delivery facts authoritative #399 rewrote validate semantics without updating that target, and the target is not in any CI gate, so main ships with it red (verified: 6 failures on unmodified 67b18b7 via keyed-stash baseline). Repairs pin the Make catalog delivery facts authoritative #399 semantics; no src/ changes beyond the fix.

Verification

  • Reproduced first: 1 running root + 3 retired #true roots → found 4, complete: false, declarations desiredState: null (unfixed binary).
  • Edge matrix with the fixed binary: dev3-shape green (found 1, cos gets rootId, depth 0); suspended-only root green; tombstone-only and headless hosts → found 0; two running roots → found 2 (regression intact).
  • Live dev3 catalog (read-only shared lock): no root-count error; exactly one counted root (dev3.cos); 622 declaration entries now publish folded "retired".
  • cargo test --test validate --test catalog_graph: 50/50 + 6/6 green after fix + repairs (was 44-pass/6-fail on main). All CI-gated flake targets green (--workspace --lib --bins --test discovery/codex_hooks/hooks/run/driver_expansion).
  • VRS: docs/vrs/spec.md amended (counting fold + declarations fold); experiment record at docs/vrs/.experiments/2026-08-31-root-count-retirement-fold.md; axe vrs check --profile strict clean for all touched artifacts.

Complexity

One new predicate (is_counted_root) shared by both consumers — a net simplification. No new modules or dependencies.

Concerns

  • Retired agents on a healthy host now get their own chain facts (rootId = themselves, depth 0) instead of null: with the invariant satisfied, admitted_topology admits tombstones too. This is an honest observation of declared edges (they are not in the active tree); flagging in case a consumer prefers null for retired rows.
  • A catalog that retires a host's root and expects the host to keep validating will now see found 0 — intentional (headless org is a fault), per the decision above.

Friction & bottlenecks

  • tests/validate.rs (and catalog_graph) are not CI-gated, so Make catalog delivery facts authoritative #399 shipped with the target red. Logged here rather than silently absorbed; see Follow-ups.
  • Local cargo fmt --check disagrees with the committed style on 236 pre-existing locations (rustfmt version drift); fmt is explicitly not a gate (flake.nix). New lines follow committed style.

Follow-ups

References

Closes #402. RCA of the #399 introduction in aa8eb48.

Posted on behalf of @schickling
field value
agent_identity dev3.direct.omp.wk3aavmh
session dev3.wk3aavmh
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.0.11
agent_runtime OMP 18.0.11
tooling_profile dotfiles@1defc60

Root counting in validate's root-count invariant and the graph's admitted
topology counted every supervisor-less spec, so legacy `retired #true`
tombstones held a host's root slot and the live dev3 catalog failed with
"found 8". The folded desired-state model already maps both retirement
spellings to AgentDesiredState::Retired; both consumers now share one
predicate (supervisor_chain::is_counted_root) so a retired declaration
never counts, a suspended root still does, and zero counted roots
remains an error.

The graph declarations view folds legacy `retired #true` to
desiredState "retired"; an absent lifecycle stays null (lowers to
running).

Also repairs six tests/validate.rs expectations to the #399 semantics
(that target is not CI-gated, so the drift shipped unnoticed): a
dangling supervisor is now an error, driver/deliver conflicts refuse at
parse time, native drivers require delivery-readiness, and fixtures
satisfy the one-root invariant.

agent-identity: dev3.direct.omp.wk3aavmh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.0.11
agent-runtime: OMP 18.0.11
tooling-profile: dotfiles@1defc60
@schickling

Copy link
Copy Markdown
Contributor

@codex

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T16:06:31.268781Z ec355e3 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec355e32fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/supervisor_chain.rs
Review finding on #405 (Codex P1): with retired declarations excluded
from root counting, one active root plus a retired root still
supervising an active worker validated clean — root-count saw exactly
one counted root while the worker's chain terminated at the tombstone,
and the graph published complete: true with two disconnected trees.

An active agent's chain must now terminate at a counted root
(retired-root error); retired chains under a retired root stay legal.
Live dev3 catalog verified: zero new errors beyond the pre-existing
delivery-readiness ones (#406).

agent-identity: dev3.direct.omp.wk3aavmh
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.0.11
agent-runtime: OMP 18.0.11
tooling-profile: dotfiles@1defc60
@schickling
schickling merged commit c2f3fac into main Aug 31, 2026
2 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.

Graph v2 counts legacy-retired declarations as roots

2 participants