Skip to content

Release: staging → main - #151

Merged
djl11 merged 35 commits into
mainfrom
staging
Aug 12, 2026
Merged

Release: staging → main#151
djl11 merged 35 commits into
mainfrom
staging

Conversation

@djl11

@djl11 djl11 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Release PR from staging to main.

Carries the OpenRouter spend-hardening and credential work alongside
in-flight test/CI fixes from other sessions.

Per AGENTS.md section 8 the image leads the manifest: this must merge, the
base image build must run, and the built image must be verified to contain
these changes, before unify-deploy is promoted.

🤖 Generated with Claude Code

djl11 and others added 26 commits August 11, 2026 17:24
…n pool

On a fresh roll the executive board-deck eval placed its tile correctly
but bound data_scope='team:<other-id>' because that team's description
advertised the referenced table. The guidance said team:<id> is for a
tile that 'must read a different team's data', and the model judged the
condition true from a description alone. State the default outright: a
data reference that names a table without naming a team resolves inside
the destination's own root; team:<id> is only for a home the user
explicitly names. Applied to the create_tile/update_tile docstrings and
both dashboards spec notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… is absent

Dev checkouts keep a repo-local .venv, but poetry-managed installs (CI)
store the virtualenv in poetry's cache, so the hardcoded
orchestra/.venv/bin/python path made every worker-cycle helper fail with
'Orchestra venv not found' on CI runners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-run guard

Typed provider-event definitions have no Tasks log mirror, so the
store-only lookup in _running_execution raised 'No task found' when
_ensure_not_active_task guarded an authored mutation on one. Resolve
through _resolve_task_for_mutation, which already falls back to the
typed Tasks API exactly like the mutation paths it protects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hing

The image build authenticated every clone with one personal access token
carrying write scope across every private repository its owner could reach.
It was passed as a build argument, so it was recorded verbatim in the
metadata of every layer that followed, and written into a global git config,
so it persisted in the image filesystem too.

Only two of the six repositories are private: branding and the iso-animation
submodule its canvas kit needs. Each is now read with its own repo-scoped,
read-only deploy key, mounted as a secret for the single command that needs
it. Both keys are offered on one ssh invocation, so github resolves whichever
is authorised for the repository being fetched and no host aliasing is
needed.

The other four clones — unisdk, unillm, magnitude and the job-watcher's own
unisdk — are public and now run unauthenticated, as does resolving their
SHAs. Nothing in the build carries a credential that can write anywhere.

UNIFY_KEY is untouched here: it is still baked into the image because that is
currently the only way the runtime receives it. Moving it to the cluster
secret is a separate change that has to be sequenced against the pods.
The cache-diff upload ran under always(), and consolidation is
outcome-blind, so a shard with one failing roll pushed that roll's full
trajectory into the shared store. Write-misses replays hits, which made
the recorded failure deterministic on every subsequent rerun until a
prompt change rotated its keys -- observed on the dashboards team-routing
re-record, which needed exactly such a rotation to recover. Gate the
upload on the shard passing: a discarded diff costs one re-roll, a
poisoned entry costs a key rotation. Failure logs still upload either
way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…'s residue

The seeded branch of the contact/transcript scenario fixtures recommits
the shared scenario contexts whenever the embeddings backfill did work —
which is nearly every session, since each rollback re-creates rows under
new ids and orphans their vectors. That recommit ran under only the
scenario seeding lock, while tests serialize on a different lock and roll
back at START, not at end. So a finished test's created rows (e.g. the
Acme Corp support contact from test_nameless_service_contact_preserves_
no_name) stay live until the next test's rollback, and any session whose
setup recommitted in that window baked them into the rollback target.
Every later test then faithfully restored the leaked rows: the mutation
scenario held 7 or 8 contacts depending on process interleaving, and
every LLM prompt embedding the contact count flipped between variants —
unresolvable cache thrash for read-only CI (12 of the residual
contact_manager failures in run 31538082830 trace to this and its
cascades).

Take the scenario's test lock inside the seeding lock (same order
everywhere, no inversion) and roll every context back to its stored
commit before the embeddings check, so a recommit can only ever capture
the committed seed state plus vectors. In a fresh CI database every
commit is pristine by induction; long-lived local databases with
historically tainted commits reset via --overwrite-scenarios.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
poetry env info --executable exits nonzero inside the CI test session
even though the install-step-created cache env exists, and an inherited
VIRTUAL_ENV makes poetry silently adopt the unify test venv where
Orchestra is not installed. Route the fallback through 'poetry run
python' with VIRTUAL_ENV stripped — the same mechanism orchestra's
scripts/local.sh uses to boot the server in the same job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A deploy key authorises exactly one repository, and github binds the ssh
session to the first key that authenticates -- so offering several on one
connection lets whichever answered first decide what the rest of the clone
can reach. Bind each repository to its own host alias and identity instead.

The canvas stage also needs an ssh client: the base node image ships git but
not openssh, so an ssh transport fails there with 'ssh: not found'.
The system-source clones read two private repositories over ssh, using a
read-only deploy key each. The image ships git but not openssh, so the
transport failed with 'ssh: not found' before any key was consulted.
Dev machines bootstrap Orchestra with a repo-local .venv, but CI's
poetry install hid the virtualenv in poetry's cache, so test helpers
that exec Orchestra's interpreter had no stable path and fell back to
invoking poetry at test time — which silently resolves an activated
foreign virtualenv and fails outright inside CI's tmux test sessions.

Install with POETRY_VIRTUALENVS_IN_PROJECT=true in every workflow that
sets Orchestra up, cache the created .venv alongside poetry's package
cache, and collapse the helper to the single invariant path
<orchestra>/.venv/bin/python with a loud error when it is missing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend storage order varies between sessions for identically-seeded data
(rollback re-inserts rows under fresh log-event ids), so unsorted federated
reads returned the same contacts in session-dependent order. That order is
embedded verbatim in LLM-visible tool results, which makes read-only cache
replay miss on prompts that differ only by row order. Sorting server-side
makes the docstring's creation-order contract actually hold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
In interrupt mode a tool completion (or steering event) arriving while the
LLM is thinking cancels the in-flight step and re-issues the turn with the
updated context. The cancelled step's request is therefore never recorded,
so a cached read-only replay - where the lookup resolves instantly, long
before the real backend tools finish - deterministically issues that exact
unrecorded request and died on the miss.

Treat a typed CacheMissError with tools still in flight the way the live
run treated the never-answered call: discard the step and fall back to the
tool-wait block, which grants a fresh turn once the superseding event
lands. With nothing in flight the miss stays fatal, so read-only CI still
fails closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inistic

- ask/update stop tests: anchor the stop on the first assistant turn via
  _wait_for_next_assistant_response_event instead of wall-clock sleeps. A
  stop racing the first LLM call cancels it mid-flight, which records
  nothing and therefore can never replay from cache (the whole-cache scan
  showed zero recordings of ask_stop's opening call).
- routing tests: replace time_ns-derived team ids with fixed constants in
  a dedicated 61M band, and clean leftover team rows before each test. The
  id is embedded verbatim in the accessible-teams prompt block, so a
  wall-clock id can never replay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AsyncToolLoopHandle.stop is async; calling it bare created an un-awaited
coroutine, so the outer loop was never stopped when the guard tripped —
result() returned the cached early response while the loop kept running
as a zombie, issuing further LLM calls with nondeterministic progression
(unreplayable from cache, and billed for nothing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RoutingScenario team ids are embedded verbatim in the accessible-teams
system-prompt block and echoed in destination tool-call arguments, so
random ids could never replay from the LLM cache under read-only mode.
Give each scenario a fixed pair in a dedicated 62M band and pre-clean
the team roots on setup. The guidance/function eval sentinels sat in
the user message with hex runs too short for cache-key scrubbing, so
fix those too.

Every other flagged time_ns/uuid team-id site was audited and left
random with a comment: those tests drive managers programmatically
(or, for the transcript ask evals, the id provably never enters LLM
input), so randomness isolates concurrent runs at no replay cost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Orchestra migrated off poetry: uv.lock is its lockfile and uv sync
creates the repo-local .venv the test helpers exec directly. Cache the
created .venv keyed on orchestra's SHA, drop the poetry install and its
in-project shim, and align the helper's guidance with the uv invariant.
Shared-rules bump removes the poetry branches from the formatting rule
and the committed hook template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DashboardManager and its tests are deleted; canvas is the one
visualization surface. The manager registry, actor surface, primitives
registry/scope, coordinator activity, offline-runner function mode and
settings all lose their dashboard wiring, and the actor prompts and
examples now teach canvas only. The visualization eval is rehomed to
tests/actor/state_managers/simulated/canvas and asserts no plan touches
a dashboards primitive. Context-registry and team-homing tests that used
Dashboards contexts as fixtures now use Canvas/Views, Canvas/Actions and
Workflows/Meta. test_scope's expected alias set also gains canvas and
ingestion — it was stale against scope.py on staging and failing there
before this branch.

Retiring function mode removes UNITY_OFFLINE_TASK_MODE from the runner
contract, and the pod entrypoint used that variable's presence as the
switch deciding a pod is an offline task-run pod — without a matching
change, every offline task pod would boot interactive. The gate now keys
on UNITY_OFFLINE_RUN_KEY, which the contract always sets. The dispatcher
and contract tests drop their assertions on the deleted variable
(offline_runner, offline_runner_contract and local_offline_dispatcher
suites all pass); .secrets.baseline moves only by line numbers.

Targeted suite: 569 tests. The only failures are 5 environment-only
llm_client ones (platform-default model read from the staging assistant;
nothing here touches llm_client) and 2 that already fail on staging
(ingestion primitives missing Parameters docstrings; the scoped
prompt-context test asserting the pre-discovery format). The
canvas_manager suite passes 124/124 and the rehomed visualization evals
route to canvas on fresh inference.
The render gate's harness page hardcoded actions: [] in its canvas/init
message, so every canvas with an ActionButton reviewed with a visible
"No action named ..." error and the critique flagged working controls as
missing — observed live while verifying the SmartLead reply review
canvas on staging, where the populated-state critique reported the
approve and reject buttons absent while the same view mounted them fine
under console. The harness now receives the same CanvasActionDescriptor
shape console hands the frame (name, label, input schema, confirmation,
destructive — never the dispatch target): create_view passes the
resolved actions, update_view passes the revision's actions or the
stored rows when the revision does not touch them, and preview rebuilds
descriptors from the stored Canvas/Actions rows. update_view also
validates actions before the review now, for the same reason bindings
resolve before it — the gate must judge the action set the revision will
actually publish. A harness contract test asserts descriptors are
offered and that no function_id or task_id ever crosses the boundary.
Picks up the auth rule whose data-endpoint example now names canvas
tokens instead of dashboards, and regenerates the aggregate.
Staging edited the dashboards ManagerSpec, the Dashboards scoped tables,
the dashboard tests and the dashboards prompt-supersession test in place;
all of those files are deleted on this branch, so each resolves to the
deletion. test_scope keeps the alias set without dashboards — staging
independently added canvas and ingestion to the same expected set, so the
two sides agree except for the retired alias. The global-agent-rules
submodule resolves to 9d5f956, a reconciling merge of the diverged
staging (canvas-tokens rule) and main (uv poetry-drop) branches created
locally in the rules repo; AGENTS.md is regenerated against it and
carries both sides. The secrets baseline keeps the newer timestamp.
… probe

Two latent bugs seen together in logs/pytest/2026-08-12T00-08-04:

- tests/conftest.py cached a single 2s Orchestra probe for the process
  lifetime. Under parallel_run, sessions probing a still-warming (or
  briefly overloaded) server cached False, pytest_sessionstart skipped
  unify.init(), and tests crashed later with "EVENT_BUS has not been
  initialised yet". The first check now retries for up to 30s when
  UNITY_TEST_SOCKET is set (parallel_run sessions) before concluding
  False; plain pytest without Orchestra still fails fast, and the final
  verdict stays cached so init and requires_orchestra skips agree.

- unify/events/manager_event_logging.py guarded incoming ManagerMethod
  publishes but not outgoing ones, so a dead bus turned a successful
  manager-method result (or the method's own exception) into a
  RuntimeError from the publish. All lifecycle publishes now go through
  _publish_lifecycle_event, which degrades to a warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The STT/TTS plugins each open a WebSocket to their provider and read the key
from the environment. Mounting those keys in the runtime leaves them readable by
in-process sandbox code, so hold them only in the sidecar and forward the socket:
read the caller's placeholder credential off the handshake, open the upstream
with the real one injected, relay every frame verbatim both ways (no buffering).
The credential sits in a different place per provider (Authorization header,
xi-api-key header, api_key query param).
Point the Deepgram/Cartesia/ElevenLabs plugins at the sidecar's
/voice/<provider> passthrough over loopback, with the pod's UNIFY_KEY as the
nonce, so the runtime no longer needs the raw voice keys in its environment. No
sidecar configured (self-host / local dev) falls back to the plugins' own
env-key behaviour.
The broker authorized Orchestra's metering gate with the provider-shaped
body model (bare 'openai/gpt-5.4-mini'), which the gate's @openrouter
marker never matches -- so every model outside the curated catalogue was
refused 402 on each call, and curated ones passed only by colliding with
the catalogue's suffix-stripped entries. In production this collapsed
every voice fast-brain turn to the 'One moment.' fallback and killed the
ManagersWorker and StorageCheck aux models, while terra/sol kept working.

Each route now derives the accounting spelling from its own identity --
chat/completions is OpenRouter traffic, so '<id>@openrouter'; the
Messages route is Anthropic, so '<id>@Anthropic' -- and uses it for
authorize and settle, while the provider still receives the body's bare
id verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tavily (search) and Recall (bots) are plain REST authenticated with an
Authorization header. Their keys are held only in the sidecar, so the pod's
clients point here with UNIFY_KEY as the nonce and this swaps in the real key
(Bearer for Tavily, Token for Recall) before forwarding. No spend to meter, so
it forwards and returns the provider's response unchanged.
Voice, Tavily and Recall all need the same answer -- where is the sidecar, and
is there one -- so derive it once from UNILLM_LLM_GATEWAY_URL (and the UNIFY_KEY
nonce) here rather than re-deriving it at each call site.
Point the Tavily and Recall clients at the sidecar's /proxy/<provider> with
UNIFY_KEY as the nonce, so the runtime no longer needs their keys; recall's
configured-check now keys off the relay secret that stays on the pod. Also fold
the voice helper onto the shared broker_origin resolver. Self-host falls back to
env keys throughout.
const EXEC_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;

app.post('/exec', auth, async (req: Request, res: Response) => {
app.post('/exec', requireExecEnabled, auth, async (req: Request, res: Response) => {
});

app.post('/exec/signal', auth, async (req: Request, res: Response) => {
app.post('/exec/signal', requireExecEnabled, auth, async (req: Request, res: Response) => {

@magic-marty magic-marty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving staging→main release: pod secret-isolation (voice+Tavily+Recall→sidecar, /exec disabled) plus the standing staging delta. Flow smoke is red for a pre-existing CI clone-auth issue, not this change; merging via admin override per owner direction (urgent).

@YushaArif99
YushaArif99 deployed to unity-testing August 12, 2026 14:57 — with GitHub Actions Active
@YushaArif99
YushaArif99 deployed to unity-testing August 12, 2026 16:17 — with GitHub Actions Active
@YushaArif99
YushaArif99 deployed to unity-testing August 12, 2026 16:17 — with GitHub Actions Active
@djl11
djl11 merged commit 870a720 into main Aug 12, 2026
37 of 50 checks passed

@magic-marty magic-marty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Release approval: broker metering fix verified on staging (llm_broker tests 19/19, orchestra gateway pins 13/13); promoting to restore non-curated OpenRouter models in prod.

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.

6 participants