Skip to content

chore(vehicle): land the agent-coordination stack (#16947, #16975, #16949, #16986) - #17107

Closed
mrveiss wants to merge 32 commits into
mainfrom
vehicle-v090-2026-09-19-7b
Closed

mrveiss wants to merge 32 commits into
mainfrom
vehicle-v090-2026-09-19-7b

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Closes #16947
Closes #16975
Closes #16949
Closes #16986
Refs #16948

This vehicle carries the agent-coordination stack: five open PRs whose sessions are gone. They were claimed in the owner-directed pool split (2026-09-19) and consolidated to land as one PR.

Member Branch Head (contained) Issue Verdict
#16965 issue-16947-agent-presence ae03d7e31e #16947 agent presence delivered
#16978 issue-16975-session-tenant 89f9b96dff #16975 session tenant delivered
#17015 issue-16949-idle-notice af744a8dc7 #16949 idle notice delivered (the canonical #16949)
#16996 issue-16948-16949-peer-messaging 6070c535c8 #16948 peer messaging partial: Refs (see below). Its #16949 part is replaced by #17015
#17001 issue-16986-peer-routing 74ab39e95d #16986 peer routing delivered

Thinking Path

The five members stack on each other: presence ← tenant ← {peer messaging, idle notice}, with peer routing on main. Every tip was merged in order onto current main. Three things needed resolving:

  1. protocols/idle_notice.py, add/add. feat(agents): peer-to-peer messaging and idle notice (#16948, #16949) #16996's feat(agents): subscribe once to be told when a peer is next idle #16949 part and feat(agents): one-shot idle notice (#16949) #17015's version collided. feat(agents): one-shot idle notice (#16949) #17015's was kept, per the coordinator ruling that feat(agents): one-shot idle notice (#16949) #17015 is the canonical feat(agents): subscribe once to be told when a peer is next idle #16949 delivery. Review diffed the two: the only difference is get_event_loop() → get_running_loop().
  2. A duplicate registration. Both sides registered AUTOBOT_IDLE_NOTICE_TIMEOUT_SECONDS, and the text merge kept both identical blocks. One was removed, and ENV_VARS.md regenerated.
  3. protocols/agent_communication.py imports. agents: the peer channel never delivers to the recipient — a request lands in the sender's own inbox and the sender answers itself #16986 moved the channels into protocols/agent_channels.py, and feat(agents): live presence — one registry of named agents with busy/idle state, across all three kinds #16947 added AgentKind. The imports are the union, and send_message is agents: the peer channel never delivers to the recipient — a request lands in the sender's own inbox and the sender answers itself #16986's recipient routing. One import was folded to keep the file under its ceiling (now 706 in both mirrors).

Why #16948 is Refs, not Closes. #16948's receive side is wired: peer inboxes drain at the chat turn boundary (tool_dispatch_guards.enforce_peer_messages) and at the terminal command boundary (service._drain_peer_messages). Its send side is not. PeerInboxDirectory.send() has no production caller, so nothing a real agent does puts a message into a peer's inbox. #16996 itself said it stays Refs until #16986 lands and an end-to-end test through the real transport passes. #16986 lands here, but that wiring and test weren't written, so #16948 stays open for them.

Overlap with #17106: #16975's tenant capture and #17106's session owner both touch services/agent_terminal/session_manager.py. Only the create_session docstring conflicts, so whichever PR lands second takes it.

What Changed

Verification

Pre-push on d568726c59: [pre-push OK] pytest: all relevant tests pass for 13 colocated files:

  • api/agent_presence_test.py
  • chat_workflow/manager_is_processing_16947_test.py
  • chat_workflow/peer_messages_dispatch_16948_test.py
  • initialization/agent_presence_sync_test.py
  • initialization/lifespan_test.py
  • protocols/agent_channels_test.py
  • protocols/agent_presence_feeds_test.py
  • protocols/agent_presence_test.py
  • protocols/idle_notice_test.py
  • protocols/peer_inbox_test.py
  • services/agent_terminal/peer_messages_dispatch_16948_test.py
  • services/agent_terminal/running_command_task_busy_signal_16947_test.py
  • services/agent_terminal/session_manager_tenant_16975_test.py

Also: bandit exits 0 on every changed source file; flake8 is clean on all 37 changed Python files; the baseline mirrors match the measured line counts.

Code review of the union (sonnet, at d568726c59):

The member PRs are closed as carried, each citing its head above. Their branches are kept, so a deleted base can't auto-close anything.

Model Used

Claude Opus 5 (claude-opus-5)

🤖 Generated with Claude Code

…t kinds (#16947)

Discovery today is four unconnected registries (#6828), none of them a live
named "who is here and are they busy" view: AgentHealthRegistry tracks
AI-stack health not activity, DistributedAgentManager tracks distributed
task assignment, agent-terminal's SessionManager holds live sessions with
no shared identity shape, and AgentOrgNode's heartbeat state is a poll, not
live. Sessions had no registry at all.

- protocols/agent_kind.py: AgentKind (COMPANY_OS, AI_STACK, SESSION,
  EXTERNAL), split from agent_communication.py to avoid growing a file
  already at its size-ratchet ceiling, and to avoid a circular import with
  the new presence module.
- protocols/agent_communication.py: AgentIdentity gains kind/name/tenant_id
  (the shared identity model from #16946) -- additive, defaults preserve
  every existing caller's behavior exactly. Net zero growth against the
  805-line ratchet ceiling: three restating-the-obvious comments removed
  (two in the CLI demo block, one duplicate `# noqa: E402`) to offset the
  new fields.
- protocols/agent_presence.py: AgentPresenceRegistry, in-memory and
  TTL-bounded (AUTOBOT_AGENT_PRESENCE_TTL_SECONDS, default 90s) --
  (kind, tenant_id, name) -> busy/idle. report() rejects a different
  instance_id claiming a live name (squat protection, #16946 §2) but lets
  the same instance heartbeat freely, and lets a new instance reclaim a
  name once the old one goes stale. EXTERNAL is refused outright -- owner
  decision 3: external A2A peers get identity for attribution only, never
  discovery.
- protocols/agent_presence_feeds.py: pull adapters for each kind's own
  authoritative source (never a caller-declared blob, per #16946 §2) --
  Company OS from AgentOrgNode + latest heartbeat run status (mirrors
  llc/api/agents.py's own join), AI-stack from AgentHealthRegistry (busy
  honestly reported as unknown/idle -- DistributedAgentManager's
  active_tasks would refine this, left as a follow-up rather than guessed),
  sessions from agent-terminal's SessionManager via has_running_task().
- 19 new tests: collision rejection, same-instance heartbeat, stale-name
  reclaim, TTL expiry (entries actually pruned from the live table, not
  just filtered at read time), EXTERNAL refusal, deregister ownership, and
  each feed adapter against a fake of its own source.

Lands the identity type and the registry interface first, as instructed --
#16948 and #16949 (both blocked_by this) can now build against a real
interface. The AI-stack busy signal and any push-based (non-pull) feed
wiring are explicitly left as follow-ups, not silently dropped.

Refs #16947
#16947)

Review found tenant_id=None doing two jobs: shared infrastructure (correct
for AI-stack) and unknown (wrong for a Company OS node with no company_id,
and for every session unconditionally) -- an agent whose tenant couldn't be
determined was becoming visible to every tenant's query.

- New UNKNOWN_TENANT sentinel, distinct from None. Never returned by
  list_live() under any argument, including the sentinel itself passed
  directly -- fails closed rather than becoming a backdoor to unknowns.
- sync_company_os_presence: a null company_id is UNKNOWN_TENANT, not shared.
- sync_session_presence: UNKNOWN_TENANT unconditionally. Investigated the
  conversation_id -> owner -> org path asked for and it does not exist in
  code -- chat_history's session/conversation mixins (base.py, session.py,
  security.py) carry no user_id/org_id anywhere, and the owner username
  threaded through SessionManager.create_session is never persisted onto
  AgentTerminalSession for a later lookup to recover. Reporting this back
  rather than picking something else, per the review's own instruction.
  The real fix is threading tenant through at session-creation time, not
  deriving it after the fact from a field that cannot carry it.
- list_live(tenant_id=None) -- returns {tenant_id} union {shared}, never
  unknown. No unscoped "every tenant" query on purpose, per the review:
  a caller that needed one would be reimplementing the leak this replaces.
- Logged both rejection paths (collision, EXTERNAL) and the TTL env
  fallback -- were silent before.
- Module docstring now states report()/deregister() enforce no caller
  identity of their own; the authoritative-source guarantee holds only
  because the three feed adapters are the sole intended callers.
- 6 new tests: the negative control (tenant X doesn't see tenant Y), shared
  entries included in a tenant query, no-arg defaults to shared-only, an
  UNKNOWN_TENANT entry never surfaces under any query, a null-company_id
  node is unknown not shared, sessions are UNKNOWN_TENANT and never listed.

Refs #16947
A terminal session had no tenant, and none could be recovered afterwards --
presence (#16947) had to report every session as UNKNOWN_TENANT. The route
that looked like it might work does not exist: conversation_id is a log-
linkage id only, chat_history's session/conversation stores carry no
user/tenant field, and the owner username threaded through
SessionManager.create_session was never persisted for a later lookup to
recover.

- AgentTerminalSession gains an explicit tenant_id field.
- Threaded through SessionManager.create_session ->
  AgentTerminalService.create_session -> the one real call site with
  authenticated context: POST /api/agent-terminal/sessions, which already
  resolves `owner` from current_user.get("username") for the WebSocket
  ownership gate (#14989/#14960) -- tenant_id is current_user.get("org_id")
  alongside it, the JWT claim only. TerminalCreateSessionRequest has no
  org_id/tenant_id field, so a caller cannot supply one through this route.
- sync_session_presence now reads session.tenant_id, falling back to
  UNKNOWN_TENANT only when it's unset -- a pre-#16975 session, or one
  created via a path with no authenticated context (e.g. the chat-tool
  internal session creation in tools/terminal_tool.py, which passes neither
  owner nor tenant_id today and is left alone: no code touches those paths
  in this PR, so they correctly keep reporting UNKNOWN_TENANT rather than
  a guess).
- 6 new tests: SessionManager.create_session threads tenant_id onto the
  session (and leaves it unset without one); presence reports a session
  under its real tenant and it is invisible to a different tenant's query
  (the AC's own discoverability negative control); a session with no
  tenant stays UNKNOWN_TENANT and is never listed.
- service.py's ratchet lowered 962 -> 959 (landed below ceiling after
  trimming its own docstring to make room for the new parameter).

Refs #16975
Review found to_persist_dict() left tenant_id out, and get_session()'s
Redis-reload branch rebuilt AgentTerminalSession without it -- a session
stamped with its tenant at creation lost the stamp on any reload (a
restart, an eviction, another worker) and reappeared as UNKNOWN_TENANT.
Fails closed, so nothing leaked, but it broke AC1 across a reload.

- to_persist_dict() now includes tenant_id.
- get_session()'s Redis-reload branch restores it via
  session_data.get("tenant_id").
- The pending-approval rebuild path is left as UNKNOWN_TENANT deliberately
  -- it predates this PR and never guessing is correct there.
- New test drives the real persist -> drop from memory -> get_session
  round trip (not the two methods in isolation), then confirms the
  reloaded session is discoverable through the real presence registry in
  its own tenant only.

Refs #16975, #16978 review
…16947)

AC2 says busy/idle is live -- both stubs were not. has_running_task()
always False (running_command_task was declared but never assigned
anywhere outside models.py), and sync_ai_stack_presence hard-coded
busy=False and called it a follow-up.

- services/agent_terminal/service.py: new _run_tracked() helper wraps both
  PTY-execution call sites (_execute_auto_approved_command,
  _run_approved_command_body) so running_command_task is actually set for
  the command's real span and cleared in a finally, success or exception.
- chat_workflow/manager.py: ChatWorkflowManager gains _active_streams
  (incremented/decremented around process_message_stream's real try/
  finally, not a copy of it) and is_processing(). No persistent per-role
  AI-stack process exists, so this is "any stream in flight right now" for
  the whole registry, not attributed to one role -- documented as the
  honest simplification, not a guess.
- protocols/agent_presence_feeds.py: sync_ai_stack_presence takes an
  optional chat_workflow_manager and reports its is_processing() as busy
  for every role; omitting it reports idle, same as before.
- AUTOBOT_AGENT_PRESENCE_TTL_SECONDS registered in
  autobot_shared/env_registry_agent_runtime.py -- the code-quality red:
  it was documented by hand in ENV_VARS.md but never registered, so
  check_env_var_registry.py flagged it as read-but-unregistered.
  docs/developer/ENV_VARS.md regenerated from the registry (byte-identical
  to the hand-written version once regenerated).
- 7 new tests: is_processing() true only for a stream's real span, across
  overlapping streams, and clearing on exception; has_running_task() true
  only for a command's real span and clearing on exception.
- File-size ratchet: manager.py and service.py both landed below their
  ceiling (redundant-comment trims offsetting the new lines) and are
  lowered to match, not left with slack.

Refs #16947
#16947)

duplication-guard: sync_company_os_presence's docstring said it "mirrors
llc/api/agents.py::list_agents's own latest-run-per-agent join" -- it did,
literally, 15 lines of the exact same SQLAlchemy query pasted into both
files. jscpd correctly caught it (9 lines over the whole-tree pin; main
alone measures 11718, 5 under it, so this diff's own +14 was the entire
overage).

agent_org_nodes_with_latest_heartbeat(company_id) in llc/api/agents.py is
now the one definition; list_agents and sync_company_os_presence both
call it. Behavior-preserving -- same subquery, same outer joins, same
where clause; list_agents adds its own .order_by(AgentOrgNode.name) after.

Verified with the same jscpd invocation CI runs
(autobot-backend + autobot-frontend/src, -k 70 -l 8 --skip-comments):
11718 duplicated lines, exactly main's own clean baseline, no longer 11732.

Refs #16947
…esult

Merging issue-16947-agent-presence into this branch landed
services/agent_terminal/service.py at 956 lines, under both branches' own
959 ceiling -- the ratchet only turns down, so lowering it here rather
than leaving the merge with unclaimed slack.

Refs #16947, #16975
A peer agent can now reach another by its stable presence name
(protocols/peer_inbox.py) instead of only through a human: delivery is
scoped by tenant (never crosses tenants, and UNKNOWN_TENANT / EXTERNAL
identities are never addressable) and drained at each kind's real
per-turn boundary, never mid-task -- AI_STACK's
chat_workflow/tool_handler.py::_dispatch_tool_call and SESSION's
services/agent_terminal/service.py::execute_command, the same seams
mapped for #16947's busy/idle signal. A peer message carries no
approval of its own: a sensitive tool call it triggers still goes
through the existing human-approval gate unchanged.

protocols/idle_notice.py adds the other half of #16949: a one-shot
notice the moment a busy peer's presence transitions to idle, built on
the existing EventBus (events/bus.py) rather than a new transport, with
a bounded wait (AUTOBOT_IDLE_NOTICE_TIMEOUT_SECONDS) so a waiter is
told explicitly on expiry instead of blocking forever.
Code review on PR #16996 found that sync_ai_stack_presence (#16947)
reports every AgentHealthRegistry role -- "chat", "rag" and
"system_commands" -- as its own addressable AI_STACK presence entry,
but the only drain wired for #16948 is chat_workflow's "chat" seam.
Addressing "rag" or "system_commands" succeeded silently and then lost
the message forever, since nothing ever drains those inboxes: rag_agent.py
and system_command_agent.py run their own StandardizedAgent.process_request
flow, with no relationship to chat_workflow's dispatch seam.

PeerInboxDirectory.send() now refuses any AI_STACK name outside the
addressable set with the same RecipientNotAddressableError already used
for tenant/EXTERNAL refusals, so misaddressing fails loud instead of
silently. Filed #16997 (sub-issue of #16946) to map rag/system_commands'
own real turn boundary and lift the restriction once wired.
…aches every other agent, a forwarding hop gets its reply (#16986)
… channel, peer requests through tracking and claims (#16986)
#16965)

Code review found sync_company_os_presence/sync_ai_stack_presence/
sync_session_presence and get_presence_registry had zero production
callers -- correct adapters, never invoked, so the registry existed but
was never populated. This wires both ends:

- initialization/agent_presence_sync.py: a background task (extracted
  out of lifespan.py, which is grandfathered at a fixed line count and
  may not grow) that calls all three feed adapters on an interval
  (AUTOBOT_AGENT_PRESENCE_SYNC_INTERVAL_SECONDS, default 30s, comfortably
  under the registry's own 90s TTL). Started from lifespan.py's Phase 2,
  cancelled in lifespan_shutdown.py.
- api/agent_presence.py: GET /agents/presence reads list_live() back,
  scoped to the caller's own tenant from the verified JWT/session context
  (require_org_context), never a request parameter.
- llc/services/agent_presence_queries.py: agent_org_nodes_with_latest_heartbeat
  moved out of the router module llc/api/agents.py (a nit from the same
  review) plus a new distinct_company_ids_with_agents(), so the sync loop
  can iterate every tenant without protocols/ importing a FastAPI router.
… issue-16948-16949-peer-messaging-merge

# Conflicts:
#	autobot_shared/env_registry_agent_runtime.py
Independent review of PR #16996 verified idle_notice.py's own design
sound (exactly one notice on a busy-to-idle transition, a bounded
expiry, the real EventBus, correct {"type","payload"} unwrapping) and
recommended splitting it into its own PR so it can land while #16948
waits on #16986's agent_communication consolidation.

protocols/idle_notice.py: notify_agent_idle() publishes EVT_AGENT_IDLE
via events/bus.py (persist=NONE, so it reaches both the in-process
EventManager listener and the live/websocket channel) on exactly a
busy=True -> busy=False transition; wait_for_idle() resolves immediately
for an already-idle target, otherwise subscribes for one matching event
or AUTOBOT_IDLE_NOTICE_TIMEOUT_SECONDS (default 300s), raising
IdleWaitExpiredError on timeout and always unsubscribing.

Review nit fixed: line 90 now uses asyncio.get_running_loop(), matching
the same call already used at line 56, instead of the deprecated
get_event_loop().

protocols/agent_presence.py::report() calls notify_agent_idle() on the
transition, outside the lock (no re-entrancy, no blocking call while
held).
…isolation (#16947, #16965)

Second independent-review round on PR #16965's wiring commit:

- HIGH: AUTOBOT_AGENT_PRESENCE_SYNC_INTERVAL_SECONDS and
  AUTOBOT_AGENT_PRESENCE_TTL_SECONDS were read with plain env_float/
  os.getenv, unclamped. A 0 or negative interval makes asyncio.sleep
  return immediately, busy-spinning _sync_once()'s DB/Redis/health-
  registry reads every tick; a 0 or negative TTL makes every entry look
  stale the instant it is reported. Both now use env_float_clamped
  (min 1s, max 3600s), and both EnvVarSpecs gained a matching range=.
- MEDIUM: api/agent_presence_test.py's "can't pick another tenant via a
  parameter" test overrode require_org_context directly, so it never
  exercised the real chain and proved nothing about parameters.
  Rewritten to drive get_tenant_context/_check_org_membership for real
  through a fake DB session: a non-member is refused (403), a member
  requesting their own tenant succeeds, and a platform admin choosing
  another tenant succeeds -- the last one is a real, pre-existing,
  JWT-claim-gated exception this route was never meant to hide, so the
  route's docstring now names it explicitly instead of claiming "never
  a request parameter".
- LOW: one company's sync_company_os_presence failure no longer skips
  the rest of the pass (try/except-and-continue, logged with the
  company id); added a _loop()-level test proving a failed iteration is
  logged and the next iteration still reaches and populates the
  registry.
…, #16948)

# Conflicts:
#	repo_tests/python_file_size_ratchet_baseline.py
#	scripts/python_file_size_known_large.py
# Conflicts:
#	autobot-backend/protocols/idle_notice.py
# Conflicts:
#	autobot-backend/protocols/agent_communication.py
#	docs/developer/ENV_VARS.md
@mrveiss mrveiss added the land-next Landing set: CI capacity goes to these PRs first (owner direction 2026-09-18, #15397) label Sep 19, 2026
@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ef6c49cd-eb99-493a-90d9-b21bfb598d43

📥 Commits

Reviewing files that changed from the base of the PR and between 1b9f514 and 9adc1c1.

⛔ Files ignored due to path filters (3)
  • autobot-frontend/src/types/generated/api.ts is excluded by !**/generated/**
  • repo_tests/python_file_size_ratchet_baseline.py is excluded by !repo_tests/python_file_size_ratchet_baseline.py
  • scripts/python_file_size_known_large.py is excluded by !scripts/python_file_size_known_large.py
📒 Files selected for processing (40)
  • autobot-backend/agents/base_agent.py
  • autobot-backend/api/agent_presence.py
  • autobot-backend/api/agent_presence_test.py
  • autobot-backend/api/agent_terminal.py
  • autobot-backend/chat_workflow/manager.py
  • autobot-backend/chat_workflow/manager_is_processing_16947_test.py
  • autobot-backend/chat_workflow/peer_messages_dispatch_16948_test.py
  • autobot-backend/chat_workflow/tool_dispatch_guards.py
  • autobot-backend/chat_workflow/tool_handler.py
  • autobot-backend/initialization/agent_presence_sync.py
  • autobot-backend/initialization/agent_presence_sync_test.py
  • autobot-backend/initialization/lifespan.py
  • autobot-backend/initialization/lifespan_shutdown.py
  • autobot-backend/initialization/router_registry/core_routers.py
  • autobot-backend/llc/api/agents.py
  • autobot-backend/llc/services/agent_presence_queries.py
  • autobot-backend/protocols/agent_channels.py
  • autobot-backend/protocols/agent_channels_test.py
  • autobot-backend/protocols/agent_communication.py
  • autobot-backend/protocols/agent_kind.py
  • autobot-backend/protocols/agent_presence.py
  • autobot-backend/protocols/agent_presence_feeds.py
  • autobot-backend/protocols/agent_presence_feeds_test.py
  • autobot-backend/protocols/agent_presence_test.py
  • autobot-backend/protocols/idle_notice.py
  • autobot-backend/protocols/idle_notice_test.py
  • autobot-backend/protocols/peer_inbox.py
  • autobot-backend/protocols/peer_inbox_test.py
  • autobot-backend/services/agent_terminal/models.py
  • autobot-backend/services/agent_terminal/peer_messages_dispatch_16948_test.py
  • autobot-backend/services/agent_terminal/running_command_task_busy_signal_16947_test.py
  • autobot-backend/services/agent_terminal/service.py
  • autobot-backend/services/agent_terminal/session_manager.py
  • autobot-backend/services/agent_terminal/session_manager_tenant_16975_test.py
  • autobot_shared/env_registry_agent_runtime.py
  • changelog/unreleased/16947-agent-presence-registry.md
  • changelog/unreleased/16948-16949-peer-messaging.md
  • changelog/unreleased/16949-idle-notice.md
  • changelog/unreleased/16975-session-tenant-capture.md
  • docs/developer/ENV_VARS.md

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.

@github-actions

Copy link
Copy Markdown
Contributor

Notice: 29 open PRs — past the runaway threshold (25)

There is no PR queue limit, and this is not a request to defer this PR. Work proceeds one issue at a time without a cap on open PRs; review capacity is the constraint.

This notice only means the count is high enough to be worth a glance for a runaway — something opening PRs in a loop, or a merge pipeline that has stalled so nothing is draining.

Currently open:

If the queue is draining normally, ignore this. Otherwise:

  1. Check whether CI is dispatching at all — see the ci-dispatch-watchdog status on these PRs
  2. Merge the ones whose CI has finished and review has passed: gh pr merge <number> --squash --delete-branch
  3. Look for a loop opening near-identical PRs

Warn-only runaway detector — .github/workflows/pr-queue-gate.yml. It never blocks a merge.

Auto-regenerated (py3.14) to match the backend and/or SLM backend
schema so the required verify-generated-types gate(s) pass.
Triggered by auto-fix-generated-types.yml.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@mrveiss mrveiss removed the land-next Landing set: CI capacity goes to these PRs first (owner direction 2026-09-18, #15397) label Sep 19, 2026
@mrveiss

mrveiss commented Sep 19, 2026

Copy link
Copy Markdown
Owner Author

Carried by the rest consolidation train #17133 at this PR's current head. Kept open until #17133 lands and this PR's ACs are verified on main; then it closes as carried. Removed land-next: it lands through #17133.

mrveiss added a commit that referenced this pull request Sep 19, 2026
chore(vehicle): rest consolidation train — #17107, #17119, #17122, #17117, #17129 in one CI run (#17128)
@mrveiss

mrveiss commented Sep 19, 2026

Copy link
Copy Markdown
Owner Author

Carried and landed — closing as a member of the rest train #17133, which merged to main at cd4e3896c.

Evidence this PR's work is in main, not an assumption that it is:

  • git rev-list --count origin/main..9adc1c11e = 1, and git log --no-merges origin/main..9adc1c11e is empty — every non-merge commit on this branch is already an ancestor of main.
  • The one remaining commit is the auto-update bot's Merge branch 'main' into <branch>, created after the train carried this head. That is why the head SHA itself is not an ancestor.

No code is lost and nothing here needs re-doing. The issues this PR was opened against are closed by #17133's Closes lines, each of which still needs its acceptance criteria verified against merged main before it counts as delivered — that verification is in progress separately, and any criterion that does not hold will reopen its issue rather than be quietly ticked.

The branch is left in place for now; it will be removed only after a check that no other PR targets it as a base.

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