Skip to content

Sync upstream main: paradigmxyz/centaur 1e23712a → product main - #55

Closed
darkmatter[bot] wants to merge 194 commits into
mainfrom
sync/upstream-main
Closed

Sync upstream main: paradigmxyz/centaur 1e23712a → product main#55
darkmatter[bot] wants to merge 194 commits into
mainfrom
sync/upstream-main

Conversation

@darkmatter

@darkmatter darkmatter Bot commented Aug 31, 2026

Copy link
Copy Markdown

Upstream sync candidate — raw upstream tip published by automation. Conflicts are expected; the repair stage will rebuild this same PR. Conflict markers are never committed.

  • ONLY A HUMAN MAY MERGE THIS PR. Automation will never merge it.
  • MERGE-COMMIT ANCESTRY REQUIRED: merge with a true merge commit (first parent = product main c9fadb1dec5439c80325060970a1093134a9e719, second parent = upstream tip 1e23712ae491a2aaee9923655d1b42ae1c124a73). Do NOT squash, do NOT rebase.

Current SHAs:

  • Upstream tip: 1e23712ae491a2aaee9923655d1b42ae1c124a73 (paradigmxyz/centaur main)
  • Product base: c9fadb1dec5439c80325060970a1093134a9e719 (darkmatter/centaur main)
  • Candidate head: 1e23712ae491a2aaee9923655d1b42ae1c124a73 (sync/upstream-main)

Commits introduced: 193

Matthew Slipper (mslipper) and others added 30 commits July 31, 2026 11:28
* fix(workflows): shut down Python host cleanly

* fix(workflows): accept large host messages
* feat: scope company context reader user sources

* fix: retain Google subject context authorization

* test: strengthen company context reader RLS guards
* fix(console): restore inline markdown links

* fix(console): parse markdown placeholder indexes

* fix(console): preserve restored markdown fragments

* test(console): generalize markdown link regression
* feat(console): promote principal identity fields

* refactor(console): normalize principal identifiers

* revert(console): remove normalized principal identifiers

* fix(console): infer principal kinds from foreign IDs

* fix(console): require scoped Slack DM identifiers

* refactor(console): keep principal identity out of labels

* refactor(console): simplify principal label aliases

* fix(console): validate principal identity fields

* fix(console): support Slack enterprise identity scopes

* test: cover principal identity reconciliation

* fix: preserve principal identity compatibility

* fix: narrow principal kind backfill

* fix: validate reconciled Slack identities

* refactor: centralize principal identity promotion

* fix: validate MCP Slack identities

* fix: show principal identity labels in console
* feat: add default role assignments

* fix: preserve default roles when settings omit them
* feat(console): promote console user identity fields

* fix(console): reference console users by database id

* fix(console): allow stale console user references

* refactor(console): centralize principal identity labels
* fix(chart): require console

* refactor(api-rs): always enable iron-proxy

* fix(chart): ignore removed enable values
Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
Selects the awscliv2 package matching the image's dpkg architecture
(x86_64/aarch64), following the same pattern already used for kubectl
and Nushell in this Dockerfile.
Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
* fix(docsend): encode browserbase session metadata

* refactor(docsend): use base64 session metadata

* refactor(docsend): require encoded session metadata

* refactor(docsend): simplify base64 decoding

---------

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
* feat(slackbotv2): decouple response metadata from Console

* feat(slackbotv2): split response metadata controls

* chore(chart): bump release to 0.1.110

* feat(slackbotv2): add response metadata modes

---------

Co-authored-by: Liam Horne <liam@lihorne.com>
_serialize_message builds a fixed dict from the Slack payload and drops
everything it doesn't name, including the reactions array. Every read path
funnels through it (get_channel_history_page, get_thread_replies_page, and
the search fallback fetch), so no caller of the serialized shape can see
reaction signal at all.

That's a real blind spot in channels where people answer by reacting rather
than replying. A message with 12 check-marks and no replies serializes to
reply_count 0, which reads as "nobody responded" when the opposite is true.
An agent working off that shape either reports the reply count as the roster
or has to admit it can't answer.

The data is already on the wire and needs no new OAuth scope. reactions:read
gates the reactions.* methods, while the reactions array on a
conversations.history payload rides along with channels:history, which this
tool already requires. feedback.py:372 has been reading them straight off the
raw responses this way for its thumbsup/thumbsdown signal. Only the
serializer was throwing them away.

Consistent across both read paths: the API server proxy hands back an
untyped serde_json::Value, so reactions survive it and the proxy and direct
fetches agree.

Defaults to [] like reply_users so callers can index without a guard. Slack
caps the per-reaction users array, so count can exceed len(users); anyone
needing a complete reactor list on a heavily reacted message still wants
reactions.get and the reactions:read scope it requires. The comment says so
at the call site.

Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ebhooks (paradigmxyz#1248)

* feat(githubbot): produce durable workflow events from CI and review webhooks

Add a workflow-event producer module, independent of the owned-PR manager:
lifecycle webhooks are translated into curated durable events on api-rs
(POST /api/workflows/events) that workflows suspend on via
ctx.wait_for_event. Emission runs before any owned-PR gating, because
workflow waiters are not bot-owned PRs.

- ci-completed (<owner>/<repo>:<head_sha>, payload {failed, failing})
  fires once every check for the sha has settled. The settled evaluation
  reads GitHub's GraphQL statusCheckRollup — the same aggregate gh pr
  checks uses, covering check runs and commit statuses including EXPECTED
  — because fine-grained PATs get an ungrantable 403 on the REST
  check-runs list. An unreadable rollup is unknown, never settled, and a
  settled-green rollup is confirmed by one delayed re-read before
  emission: a push can read SUCCESS moments before the real suite
  registers.
- review-submitted (<owner>/<repo>:pr-<n>:<head_sha>:<reviewer>, payload
  {review_id, state}) fires on every submitted review. Author-scoped
  correlations give each reviewer an independent row, so a waiter keys on
  exactly the author it cares about with no emission-side config.

Durable events are immutable per correlation (first write wins), which
forces two curation rules documented in the module header: an event must
be semantically complete when emitted (the settled gate), and anything a
waiter filters on belongs in the correlation. Correlations are computable
from data the waiter already has and are lowercased so case drift between
a PR URL slug and repository.full_name can never miss.

The manager shares the settled evaluation so an owned PR is not evaluated
twice per CI event. Off by default (githubbot.workflowEvents /
GITHUBBOT_WORKFLOW_EVENTS); the api-rs URL and service token are already
wired for the session API.

* fix(githubbot): harden workflow event delivery

* refactor(githubbot): simplify workflow event code
…e in the ruby-dependencies group (paradigmxyz#1259)

chore(deps): bump solid_queue

Bumps the ruby-dependencies group in /services/console with 1 update: [solid_queue](https://github.com/rails/solid_queue).


Updates `solid_queue` from 1.5.0 to 1.6.0
- [Release notes](https://github.com/rails/solid_queue/releases)
- [Commits](rails/solid_queue@v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: solid_queue
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: add OpenAI workflow dependency

* fix: require OpenAI SDK 2.53
* fix(console): serialize Slack DM credential sync

* fix(console): checkpoint Slack DM conversations

* fix(console): skip rejected Slack DM conversations
* fix(console): retry Slack DM sync after rate limits

* fix(console): sleep for short Slack rate limits

* refactor(console): retry individual Slack requests

* fix(console): cap inline Slack rate-limit retries
…e in the ruby-dependencies group (paradigmxyz#1471)

chore(deps): bump solid_queue

Bumps the ruby-dependencies group in /services/console with 1 update: [solid_queue](https://github.com/rails/solid_queue).


Updates `solid_queue` from 1.6.0 to 1.7.0
- [Release notes](https://github.com/rails/solid_queue/releases)
- [Commits](rails/solid_queue@v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: solid_queue
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…z#1472)

Bumps the github-actions group with 3 updates: [depot/setup-action](https://github.com/depot/setup-action), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) and [mikefarah/yq](https://github.com/mikefarah/yq).


Updates `depot/setup-action` from 1.7.1 to 1.7.2
- [Release notes](https://github.com/depot/setup-action/releases)
- [Commits](depot/setup-action@15c09a5...91bc849)

Updates `docker/setup-buildx-action` from 4.2.0 to 4.3.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@bb05f3f...37fe631)

Updates `mikefarah/yq` from 4.53.3 to 4.53.6
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](mikefarah/yq@1b9b4ac...c14f446)

---
updated-dependencies:
- dependency-name: depot/setup-action
  dependency-version: 1.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: mikefarah/yq
  dependency-version: 4.53.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…P server (paradigmxyz#1451)

* feat(console): allow github_token credentials on the hosted GitHub MCP server

api.githubcopilot.com authenticates with the same token as the REST and Git
hosts, so seed it into new github_token secrets and accept it in validation.
Validation is now host-based instead of an exact rule-list match, so secrets
seeded before a host was added stay valid on later saves.

* test(console): update github_token expectations for the copilot MCP host

The profile now seeds three rules, so counts and host lists gain
api.githubcopilot.com. The conflict-rejection test targets a host outside
ALLOWED_HOSTS, since a bare github.com rule is valid under host-based
validation.
feat(slackbotv2): log webhook retry headers

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
* feat: replace NewsAPI.org tool with NewsAPI.ai

* fix: preserve existing NewsAPI.org tool

* refactor: rename NewsAPI.ai tool to Event Registry
* fix(console): allow Drive readonly credentials to sync

* feat(api-rs): support incremental Google Docs sync

* refactor(console): paginate Google Docs sync

* refactor(console): discard legacy Google Docs sync jobs

* fix(console): recover Google Docs sync cursors

* refactor(console): consolidate Google Docs sync jobs

* refactor(console): simplify Google Docs sync flow

* refactor(console): simplify Google Docs checkpoints

* refactor(console): chain initial Google Docs pages

* fix(console): keep Google Docs crawls progressing

* refactor(console): simplify initial Google Docs crawl

* fix(console): isolate Google Docs sync queue
* feat(console): add Google Docs sync kill switch

* chore(chart): bump version to 0.1.126

* refactor(console): guard Google Docs sync jobs at base

* refactor(console): guard Google Docs job entry points

* fix(console): default Google Docs sync to disabled
* fix: retry Google Docs network failures

* test: cover transient DNS failures

---------

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
* fix: truncate oversized Google Docs names

* refactor: enforce Google Docs name limit at API boundary

---------

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>
Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
…init resources (paradigmxyz#1502)

* feat(k8s): sandbox priority class, pause-proxy teardown, quota-ready init resources

- New SESSION_SANDBOX_PRIORITY_CLASS_NAME arg: sets priorityClassName on
  agent sandbox pods and their iron-proxy pods so a cluster operator can
  make sandboxes first-evicted/preempted and scope a ResourceQuota to them.
- pause() now deletes the sandbox's iron-proxy pod/configmap alongside
  scaling the sandbox to zero; resume() already recreates them from the
  principal recorded at create. Suspended sandboxes previously kept their
  proxy pod for the full reaper lifetime, exhausting kubelet pod slots
  (~880 of 1500 slots during the 2026-08-26 prd-centaur-na outage).
- tools-bootstrap init container now carries explicit requests/limits so
  quota-scoped namespaces admit sandbox pods.
- Chart: sandbox.priorityClassName + apiRs.priorityClassName values.

Amp-Thread-ID: https://ampcode.com/threads/T-01a03e96-a489-73b8-a5ef-888930d4b95d
Co-authored-by: Amp <amp@ampcode.com>

* chore(chart): bump version to 0.1.127

Amp-Thread-ID: https://ampcode.com/threads/T-01a03e96-a489-73b8-a5ef-888930d4b95d
Co-authored-by: Amp <amp@ampcode.com>

* refactor: group proxy pod scheduling knobs into ProxyPodScheduling

Fixes clippy too_many_arguments after adding priority_class_name.

Amp-Thread-ID: https://ampcode.com/threads/T-01a03e96-a489-73b8-a5ef-888930d4b95d
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
…igmxyz#1500)

* feat(chart,api-rs): allow a serviceAccountName on sandbox pods

Add sandbox.serviceAccountName, rendered as
SESSION_SANDBOX_SERVICE_ACCOUNT_NAME and applied to every sandbox pod
(session, warm, and workflow-host), so deployments can attach cloud
workload identity (e.g. EKS IRSA) instead of long-lived credentials.
Identity webhooks inject their own projected credentials keyed off the
pod's serviceAccountName, so automountServiceAccountToken stays false.
iron-proxy pods are deliberately unaffected. Unset preserves current
behavior (namespace default account, nothing rendered).

* chore(chart): bump version to 0.1.127

* chore: rerun CI

---------

Co-authored-by: Matthew Slipper <me@matthewslipper.com>
…digmxyz#1494)

* feat: bind the console user's principal on console thread turns

Console sessions register an anonymous per-thread principal, so a user's
connected OAuth credentials (e.g. GitHub) never reach console-driven turns:
reconciliation matches identity-scoped credentials to principals, and the
thread principal carries no user identity. The proxy falls back to shared
role-granted tokens and PRs open as the bot.

The console now provisions the authenticated user's console-user principal
on each execute and passes its foreign ID as requester_principal_foreign_id
in the execute metadata. api-rs resolves it fetch-only for console: thread
keys (a namespace only the console service may write) and binds it as the
turn's requester principal, so the proxy serves the RFC 0005 union: the
thread principal's grants plus the requester's always-available OAuth direct
grants. Shared threads stay safe — a reply binds the replier's principal,
never the creator's. The provisioner no longer rewrites an unchanged
principal on repeat calls.

The RFC 0005 availability gate is unchanged: a credential joins console
turns only when an admin marked its OAuth app always-available.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: tighten console requester comments

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: support console requesters on shared threads

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>
* fix: inject selected persona prompts into sandboxes

* refactor: mount persona prompts as sandbox files

* refactor: generalize sandbox file materialization

* refactor: remove obsolete persona prompt env names

* refactor: share sandbox agent home path
* feat(slack): acknowledge steering messages

* fix(slack): clear steering reaction before retry execution

* chore(chart): bump version to 0.1.129

* fix(slack): keep steering reactions until response

* docs(slack): note steering reaction cleanup race

* refactor(slack): simplify steering reaction tracking
Iron-proxy pods were created with restartPolicy=Never, so any container
crash left the pod permanently Failed while its sandbox kept running with
zero egress: Codex gets connection refused to api.openai.com, in-sandbox
tools fail, and the turn dies. Nothing repairs the proxy until the next
session execute calls assign_proxy_principal and recreates it, so every
in-flight turn on that sandbox is lost.

This went from latent to load-bearing on prd-centaur-na when
tempoxyz/prd-centaur-infra#669 added a 512Mi memory limit: iron-proxy
buffers bodies while proxying, large transfers burst past the limit in
seconds, and the resulting OOM kills produced 40+ mid-turn 'stream
disconnected before completion' failures since 2026-08-27.

OnFailure makes the kubelet restart the container in place: same pod IP,
the per-sandbox Service keeps routing, and the proxy re-syncs its
principal config from iron-control on startup, so an OOM becomes a
seconds-long blip instead of a dead session. pod_running() requires the
Ready condition, so ensure/assign paths still treat a crash-looping proxy
as unusable, and wait_until_proxy_running now bounds a proxy that never
comes up by ready_timeout instead of failing on first crash.

Amp-Thread-ID: https://ampcode.com/threads/T-01a048d9-a0bb-72f0-b6cd-4769da726fdc

Co-authored-by: Amp <amp@ampcode.com>
…z#1496)

* feat: bind the console user's principal on console thread turns

Console sessions register an anonymous per-thread principal, so a user's
connected OAuth credentials (e.g. GitHub) never reach console-driven turns:
reconciliation matches identity-scoped credentials to principals, and the
thread principal carries no user identity. The proxy falls back to shared
role-granted tokens and PRs open as the bot.

The console now provisions the authenticated user's console-user principal
on each execute and passes its foreign ID as requester_principal_foreign_id
in the execute metadata. api-rs resolves it fetch-only for console: thread
keys (a namespace only the console service may write) and binds it as the
turn's requester principal, so the proxy serves the RFC 0005 union: the
thread principal's grants plus the requester's always-available OAuth direct
grants. Shared threads stay safe — a reply binds the replier's principal,
never the creator's. The provisioner no longer rewrites an unchanged
principal on repeat calls.

The RFC 0005 availability gate is unchanged: a credential joins console
turns only when an admin marked its OAuth app always-available.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat: bind the comment author's principal on GitHub turns

GitHub-triggered turns ran with only the anonymous per-thread principal, so
a commenter's connected GitHub OAuth credential never applied: githubbot
already forwards the webhook-verified sender (`user_id`, the numeric GitHub
id, and `user_name`) in every execute's metadata, but api-rs resolved a
requester only for Slack.

api-rs now derives and upserts a per-user `github-user-<id>` principal for
`github:` thread keys, labeled `github_subject: <id>`, and binds it as the
turn's requester principal. Reconciliation gains GitHub as a subject-label
provider (`Principal::KINDS` learns `github_user`), so the credential owner
is matched by GitHub user id — the only workable anchor, since the consent
flow collects no email scope. The proxy then serves the RFC 0005 union:
thread principal's grants plus the requester's always-available OAuth direct
grants, so pushes and PRs authenticate as the commenter.

A commenter can only bind their own identity: the sender id is authentic
from the signature-verified webhook, turns only run for author associations
the deployment allowlisted, and the hoisted credential is always the
commenter's own.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: tighten console requester comments

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: plan-based requester resolution and review cleanup

Address review findings on the requester binding:

- Collapse register_requester's per-source branches into a typed
  RequesterPlan (FetchExisting for console-provisioned principals,
  UpsertDerived for api-rs-owned Slack/GitHub principals) produced by one
  dispatch function, so a new source is one arm instead of another branch
  in the registrar.
- The test stub now records request bodies, and the GitHub upsert test
  asserts the serialized kind/name/github_subject payload, not just the
  path.
- RFC 0005 §4 rewritten around the single precedence rule (provider-native
  subject first, owner-identity/email fallback) so it no longer contradicts
  the GitHub subject path; Security Considerations lists the webhook and
  console trust anchors.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: resolve GitHub requesters on work sessions

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>
Co-authored-by: Liam Horne <1933029+snario@users.noreply.github.com>
@darkmatter darkmatter Bot closed this Aug 31, 2026
@darkmatter darkmatter Bot reopened this Aug 31, 2026
@darkmatter darkmatter Bot closed this Aug 31, 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.