Skip to content

feat(db): c7e_testflight_submissions table for the overlay's feedback recovery - #49

Open
kariy wants to merge 8 commits into
mainfrom
feedback/testflight-submissions
Open

kariy wants to merge 8 commits into
mainfrom
feedback/testflight-submissions

Conversation

@kariy

@kariy kariy commented Sep 10, 2026

Copy link
Copy Markdown
Member

Why

The Cartridge overlay's TestFlight recovery scan (cartridge-gg/agent#379) needs one query for stranded submissions and the parsed submission content to re-dispatch a triage without a GitHub read. The overlay's own migration mechanism needs a manual psql apply; a fork migration ships with the base image and api-rs applies it at startup (RUN_MIGRATIONS).

What

services/api-rs/crates/centaur-session-sqlx/migrations/0056_c7e_testflight_submissions.sql: one table, one index, one backfill.

  • c7e_testflight_submissions: one row per submission with the parsed inbox comment (tester text, screenshots, device, tester id), the inbox comment and Slack post provenance, and the triage state (status, attempts, reason, run_id, dispatch_id, claimed_at, first_failed_at, last_failed_at, verdict, issue_url, issue_urls) plus the scan's last_dispatched_at. Index on (status, updated_at desc).
  • Backfill from company_context_documents rows with source_type = 'testflight_feedback_state', ON CONFLICT DO NOTHING, so earlier failures are visible the moment the migration runs. Backfilled rows carry no tester text or screenshots; the overlay re-reads their inbox comment.

Fork-only, like 0055. No upstream table is touched. Every statement is idempotent (if not exists, on conflict do nothing).

Validation

The overlay's scripts/check-testflight-submissions-sql applies this file twice against a throwaway local PostgreSQL 17 with a stub company_context_documents, checks the backfilled rows, and prepares and executes every statement the overlay runs against the table. It passes. .github/scripts/check-migration-order.sh origin/main passes.

Rollout

After merge, publish-images builds sha-<short> images from main. Bump CENTAUR_BASE_IMAGE_SHA, CENTAUR_BASE_IMAGE_TAG, and CENTAUR_BASE_REF in cartridge-gg/agent and dispatch overlay-image.yml; api-rs applies 0056 at startup and the overlay's scan stops reporting no_table.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NCk4NGNcLa52UfFKAYevtH

kariy and others added 8 commits September 4, 2026 20:59
Retarget publish-images.yml at ghcr.io/cartridge-gg/centaur on
GitHub-hosted runners with docker buildx (this fork has no Depot
project), add the release/c7e-centaur-overlay* branch glob and
contrib/chart/** path trigger, and auto-dispatch the c7e overlay deploy
when canonical-branch images publish.

Add tag-release.yml: durable c7e-<upstream-base>-<counter> tags cut from
main, so deployed commits can never be orphaned by history rewrites.
Gate release-chart.yml's tag minting to the upstream repo so the fork
cannot mint colliding centaur-* tags.

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

Console: add a github_app grant to the broker-credential registry. The
console holds the App private key (client_secret, stored encrypted) and
mints short-lived installation tokens via an RS256 App JWT, rotating
them like any other brokered credential. An idempotent seed migration
provisions the credential from GITHUB_APP_ID / GITHUB_APP_INSTALLATION_ID /
GITHUB_APP_PRIVATE_KEY_B64 when set; the chart wires those env vars into
the console from the shared infra Secret.

api-rs: let a tool's pyproject declare where an HTTP secret's value
comes from (source = {type = "token_broker", credential_id = ...}) and
forward the table verbatim into the generated iron-proxy fragment:
tool_discovery parses it, centaur-perms mirrors it, iron-control accepts
typed sources at startup. Falls back to the {placeholder: <secret_ref>}
shape when no source table is given.

Together these let a tool (the deployment's github_auth_headers) ride
broker-rotated GitHub App tokens: raw tokens never enter sandboxes and
no out-of-band CronJob rotates a shared secret.

Pending upstream as paradigmxyz#793 + companion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 3ccdd79)
The tag-fetch step adds an upstream remote to the checkout, so gh
resolved the base repo to paradigmxyz/centaur and the release step
failed with "tag has not been pushed" (c7e-0.1.110-2 needed a manual
gh release create). GH_REPO pins resolution to this repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Chat SDK's event dispatch drops file events (file_shared,
file_change) before any callback fires, so nothing today can react to a
file being created or edited. Add a raw-body sidecar (the
channel_created / lateSlackFiles pattern) that, when enabled, creates a
durable workflow run via POST /api/workflows/runs for matching file
events.

Deployment-neutral and default-off: the target workflow name, event
types, channel allowlist, and files.info title-keyword filter all come
from env config (SLACKBOTV2_FILE_EVENT_*). The bot stays timer-free —
any settle/debounce behavior belongs in the target workflow, which can
sleep durably. Per-edit event storms collapse at spawn time via the
idempotency key slack-file-event:{workflow}:{file_id} (optionally
time-bucketed); the dispatch task never throws into the webhook path.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Allow the company-context reader role to see company-scoped memory notes when
public Slack context is enabled, while keeping other c7e_memory document types
and user-scoped notes hidden. The embeddings policy delegates to the document
policy, so the vector search lane follows without a separate change.

Rebase note (upstream dafff4c): renumbered the fork migration from 0053 to
0055 because upstream owns 0053 and 0054. Production databases that already
applied the fork migration as version 53 must reconcile/delete that
_sqlx_migrations row before api-rs boots, then apply upstream 0053/0054 and the
fork's 0055 in order; the policy DDL is idempotent.
…lete (#43)

Three gaps in the calendar surface, found while exercising the tool against
a real calendar.

No way to create a Meet. calendar_create_event built a plain event body and
called events().insert() without conferenceDataVersion, so an agent could
book the slot but not the meeting. Meet is minted through the conferencing
subresource: the body carries a createRequest with a fresh requestId and the
call must opt in with conferenceDataVersion=1, or Calendar drops
conferenceData and returns an event with no link. Create and update now take
`conference`, return `meet_link`, and re-fetch once when Calendar answers
before the mint settles. On update the flag only adds a conference to an
event that lacks one; leaving conferenceDataVersion at its 0 default
elsewhere is what keeps an existing Meet intact on an ordinary update.

Edits reached attendees as silence. calendar_update_event passed
sendUpdates="all" only when add_attendees was set, so moving an event,
renaming it, or changing where it happens notified nobody: attendees' copies
moved, their invite emails did not. Verified against a real event -- the
reschedule produced no mail at all. `notify` now defaults to notifying when
the event has attendees and the edit is material: when, where, what it is
called, who else is coming, or a Meet just added. Description-only edits stay
quiet on purpose, so the notices that land keep meaning something.

Materiality is deliberately narrow. The field tests are truthiness, matching
how the function applies them -- it writes a field only when the value is
truthy, so `location=""` edits nothing and must not send mail. Attendee
notification keys off whether the merge actually appended somebody, not
whether a list was passed, so re-inviting an existing guest stays quiet. That
merge also now records the emails it appends, so a repeated address in one
call is added once rather than duplicated.

No way to cancel. calendar_delete_event is new; the tool could create and
update events but never delete one. It notifies by default, since a
cancellation nobody hears about just leaves the meeting on every calendar.

Submitted upstream as paradigmxyz#1522; the calendar functions, CLI
commands, and tests here are byte-identical to that submission.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nvUztTAb5Kvkb9PhCnCbT
… recovery

One row per TestFlight submission the Cartridge overlay's feedback pipeline
has seen: the parsed inbox comment (tester text, screenshots, device),
where the inbox comment and the Slack post live, and the triage state the
overlay's recovery scan queries by (status, attempts, the failure reason and
its timestamps, the dispatch that last claimed it). The overlay writes the
row at ingest and on every triage state save; its recovery scan
(`c7e_dango_feedback_recovery_scan`, agent#379) reads it with one query
instead of walking progress records and state documents.

The per-submission state document in company_context_documents stays as
the claim's lock target and what the shared readers use. The migration
backfills a row per existing state document so earlier failures are
visible the moment api-rs starts with it.

Fork-only migration, like 0055. No upstream table changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCk4NGNcLa52UfFKAYevtH
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.

2 participants