* feat: open Linear-triggered PRs as the triggering user
Add 'linear' to the set of sources that carry a mapped GitHub login
(Slack, Linear, dashboard, schedule), so Linear-triggered runs resolve
the per-user OAuth token when the author_prs_as_user profile flag is
enabled. Previously only Slack and dashboard runs could open PRs as the
user; Linear runs always used the bot token.
The Linear webhook now resolves the GitHub login from the Linear email
via the same user-mapping store Slack uses, and passes it through the
run configurable and thread owner metadata.
Refs: 5003c953 (upstream #1683)
* fix(open-swe): restrict Linear token attribution to comment author only
Split actor_email (comment_author only, feeds github_login for token
attribution) from user_email (full fallback chain, for display/model).
This ensures a PR is never opened as a non-actor (creator/assignee).
Add test_resolve_github_token_linear_defaults_to_bot to lock the
security-critical default: Linear + mapped login + no opt-in → bot.
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
Co-authored-by: Adam Moussa <adam@seahavenind.com>
* feat: surface attributed PR creation failures
Port upstream #1659: adds PullRequestCreationGuardMiddleware that
blocks shell fallbacks (gh pr create, gh api /pulls, curl) when
open_pull_request fails, keeping failures visible. Also adds preflight
branch/repo visibility checks in open_pull_request with structured
failure payloads, and updates the prompt to forbid PR creation
fallbacks.
Refs: #134
* fix: fall back to core GitHub App scope when optional grants missing (#1701)
* fix: fall back to core GitHub App scope when optional grants missing
Proxy-token minting requested workflows:write and actions:read in the
permission set used for every sandbox. GitHub 422s a token request that
asks for a permission the installation hasn't granted, so any install
without workflows:write failed to mint a token and every run died in
before-agent setup with "GitHub App installation token is unavailable".
_resolve_proxy_token now walks a permission ladder (full -> +workflows ->
core) and returns the first scope that mints, recording the granted scope
so hourly proxy refreshes stay consistent. A missing optional grant now
degrades to the install-time core scope instead of failing the run;
workflow-file HITL pushes still require workflows:write and fail at push
time when it is absent.
* refactor: flatten proxy-token ladder loop with continue
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
(cherry picked from commit f53caff1aa24a7b29d851b267aa3bdfe62c1e935)
Sea Haven fork deviation: upstream #1701 folds workflows:write into the
standing BASE/RUNTIME scope. This fork deliberately keeps workflows:write
OUT of the standing permission ladder (RUNTIME = core + actions:read;
LADDER = (RUNTIME, CORE)) so the sandbox proxy token cannot push
.github/workflows/* during normal operation. workflows:write is minted only
transiently by WorkflowPushGuardMiddleware for an approved HITL push and
dropped on restore, preserving token scope as a backstop for the workflow-
push approval control. Security-reviewed (agentic fan-out + GPT-4.1 cross
review); the standing-scope-carries-workflows:write bypass was blocked.
* fix(open-swe): harden proxy-token restore and mint error handling
Two low-severity follow-ups from the security review of the #1701 port.
Restore the recorded baseline scope after a workflow-push elevation instead
of a hardcoded RUNTIME. An install granted workflows:write but not actions:read
resolves its standing token to core; hardcoding RUNTIME on restore requested the
ungranted actions:read, 422'd, and fired a false "SECURITY: failed to downscope"
error on every approved workflow push before the core fallback recovered. The
guard now captures the run's recorded scope before elevating (via the new
get_recorded_proxy_permissions) and restores exactly that, falling back to the
guaranteed core scope only when the baseline restore fails.
Classify installation-token mint failures. get_github_app_installation_token_
with_expiry now treats HTTP 422 (a permission the installation hasn't granted)
as the ladder's expected descend signal and keeps it at debug, while a non-422
failure (network/5xx/timeout) is surfaced at WARNING even when errors are
otherwise suppressed — so a transient blip no longer silently downscopes a whole
run under a debug-only trace. The reduced-scope warning no longer asserts a
missing grant as the sole cause.
* chore(triage): mark upstream #1701 landed on this branch
Ported via PR #181 as Option A (workflows:write kept out of the standing
proxy-token scope). Regenerated triage.md from triage.jsonl.
* fix: restructure PR creation to POST-first with diagnose-on-failure
Move preflight checks from an authoritative gate (before POST) to a
diagnostic run after POST failure. This avoids false-positive failures
when a just-pushed head branch is momentarily invisible to GitHub ref
endpoints, and eliminates 2-3 extra serial API round-trips on the happy
path.
Also drop unused _PR_CREATED_FALSE indirection and add a docstring to
pr_creation_guard acknowledging the fail-open detection design.
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev>
Co-authored-by: Adam Moussa <adam@seahavenind.com>
* fix: fall back to core GitHub App scope when optional grants missing (#1701)
* fix: fall back to core GitHub App scope when optional grants missing
Proxy-token minting requested workflows:write and actions:read in the
permission set used for every sandbox. GitHub 422s a token request that
asks for a permission the installation hasn't granted, so any install
without workflows:write failed to mint a token and every run died in
before-agent setup with "GitHub App installation token is unavailable".
_resolve_proxy_token now walks a permission ladder (full -> +workflows ->
core) and returns the first scope that mints, recording the granted scope
so hourly proxy refreshes stay consistent. A missing optional grant now
degrades to the install-time core scope instead of failing the run;
workflow-file HITL pushes still require workflows:write and fail at push
time when it is absent.
* refactor: flatten proxy-token ladder loop with continue
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
(cherry picked from commit f53caff1aa24a7b29d851b267aa3bdfe62c1e935)
Sea Haven fork deviation: upstream #1701 folds workflows:write into the
standing BASE/RUNTIME scope. This fork deliberately keeps workflows:write
OUT of the standing permission ladder (RUNTIME = core + actions:read;
LADDER = (RUNTIME, CORE)) so the sandbox proxy token cannot push
.github/workflows/* during normal operation. workflows:write is minted only
transiently by WorkflowPushGuardMiddleware for an approved HITL push and
dropped on restore, preserving token scope as a backstop for the workflow-
push approval control. Security-reviewed (agentic fan-out + GPT-4.1 cross
review); the standing-scope-carries-workflows:write bypass was blocked.
* fix(open-swe): harden proxy-token restore and mint error handling
Two low-severity follow-ups from the security review of the #1701 port.
Restore the recorded baseline scope after a workflow-push elevation instead
of a hardcoded RUNTIME. An install granted workflows:write but not actions:read
resolves its standing token to core; hardcoding RUNTIME on restore requested the
ungranted actions:read, 422'd, and fired a false "SECURITY: failed to downscope"
error on every approved workflow push before the core fallback recovered. The
guard now captures the run's recorded scope before elevating (via the new
get_recorded_proxy_permissions) and restores exactly that, falling back to the
guaranteed core scope only when the baseline restore fails.
Classify installation-token mint failures. get_github_app_installation_token_
with_expiry now treats HTTP 422 (a permission the installation hasn't granted)
as the ladder's expected descend signal and keeps it at debug, while a non-422
failure (network/5xx/timeout) is surfaced at WARNING even when errors are
otherwise suppressed — so a transient blip no longer silently downscopes a whole
run under a debug-only trace. The reduced-scope warning no longer asserts a
missing grant as the sole cause.
* chore(triage): mark upstream #1701 landed on this branch
Ported via PR #181 as Option A (workflows:write kept out of the standing
proxy-token scope). Regenerated triage.md from triage.jsonl.
---------
Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev>
Port upstream 304032fa: clarify that information-only answers should
check out relevant repos first for context, answer fully inline, and
only post a concise summary to Slack threads.
The fork already carried the shared-base Slack guidance; this adds the
missing TASK_EXECUTION_SECTION update and its test coverage.
Refs: #146
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
* feat(models): re-add Fable 5 with admin disable toggle (port of upstream #1677)
* refactor(models): convert re-added Fable 5 to Bedrock model IDs
* fix(open-swe): correct Fable copy to describe provider data sharing, not ZDR
The ported admin toggle description and code comments described Fable 5 as
incompatible with Zero Data Retention. That is backwards: Fable 5 requires
the account to opt into Bedrock provider_data_share — prompts/completions are
retained and shared with Anthropic (up to 30 days, incl. human review). The
old UI copy would lead an admin to believe the opposite of what enabling the
toggle does. Reword the toggle description and the gate_fable_model /
team_settings comments accordingly. Still off by default. Refs #171.
* feat(dashboard): surface thread sandbox ID in sidebar
Port of upstream #1689 (feb7ac98): expose the thread's sandbox_id on the
dashboard thread summary and surface it in the sidebar via a "Copy sandbox
ID" action.
- thread_api: add sandboxId to the thread summary, hiding the
"__creating__" in-flight sentinel.
- queries/types: thread the sandboxId field through AgentThread and the
optimistic thread.
- AgentsSidebar: replace the hover-only resolve/delete buttons and the
right-click context menu with one touch-friendly kebab (⋮) menu that
works on both pointer and touch, and add the Copy sandbox ID item.
- vite: disable the PWA service worker in dev (it precaches assets and
defeats HMR).
- tests: unit test for the summary field + e2e spec in the real-backend
harness.
Closes#140. Part of #134.
* chore(triage): mark feb7ac98 (#1689) landed
Ported to dev via feat/thread-sandbox-id-sidebar (#140).
dev already pins fireworks-ai>=1.2.0a88 (uv.lock resolves a88) via #158
(efeb6b12), which jumped a85 -> a88 and exceeds the a86 that upstream
#1667 (fbc6de85) bumps to. Porting it would downgrade the constraint, so
mark the row landed/superseded rather than picking it.
Closes#141. Part of #134.
* feat: port plan-review & workflow-approval UX (#135)
Port six upstream commits onto dev:
- c03a6be7 (already ported): keep plan guidance high-level
- 546042a4: add workflow approval UI with diff preview, approval URLs,
web review links, and polling for approval status during active runs
- 216cf181: remove workflow token elevation; approved pushes pass
through directly without proxy token rewriting
- 3dbc0282: preserve plan redirects after login by accepting relative
same-origin redirect_to values and rejecting blocked paths
- bb104d93: submit plan comments with cmd+enter
- 90cb6caa: terse Slack replies, shared content via save_plan outside
plan mode (PLAN_STATUS_SHARED), reject shared-content mutations
Refs: #135
* feat: port durable dispatch hardening and startup latency improvements
Port five upstream PRs onto dev:
- #1621 / #1658: durable dispatch with loopback webhook defense,
create_durable_run helper, _config_with_prepare_run_id, degradation
to None for relative/loopback completion webhook URLs
- #1696: run-level completion webhook deduplication (replace
claim-then-post with post-then-flag per run_id), DeferredErrorModel
for graph-factory resilience, ToolRetryMiddleware for task subagents,
TimeoutWrapupMiddleware for all three graphs
- #1697: lazy-load __init__.py for agent.middleware, agent.tools,
agent.dashboard (PEP 562); defer heavy imports (exa_py in web_search,
agent.webapp in request_pr_review, deepagents in sandbox.py); add
ttl_cache.py with stale-while-revalidate for tool loaders
Refs: #137
* fix: restore login page render and clear CI lint/format
The plan-review port removed the authRedirectUrl import from login.tsx
but left its call site, crashing the login page at runtime (blank page,
no 'Sign in to open-swe'). Pass the relative path straight to loginUrl,
matching the plan route and the backend relative-redirect handling.
Also drop an unused os import in the guard test and reformat
workflow_push_guard.py to satisfy ruff.
* feat: port model-fallback resilience from upstream (#1694, #1695)
- Add httpx.TransportError to the transient-exception set so
incomplete chunked reads on streamed responses trigger a
fallback instead of cancelling the run (#1694 / c9f6dd86).
- Rewrite fallback to alternate primary/fallback with exponential
backoff instead of a single failover, so the agent survives
multi-minute gateway outages spanning both providers (#1695 /
c9a9a7cd).
- Default backoff schedule (0, 5, 15, 30, 45) reaches past the
gateway's ~30s recovery window; jittered ±25%.
- On exhaustion, surface a terminal AIMessage explaining the
outage instead of crashing — progress is checkpointed so the
user can retrigger to continue.
- Preserve fork conventions: Bedrock ClientError retryability
check, sync wrap_model_call (using time.sleep instead of
asyncio.sleep), and existing access-error surfacing for
Anthropic, OpenAI, and Bedrock (botocore) provider errors.
- Update triage ledger (c9f6dd86, c9a9a7cd → landed) and
re-render triage.md.
Refs: #139
* fix: align workflow-push-guard tests with dev's transient-elevation impl
The dev merge auto-combined dev's elevation tests with the stale passthrough
tests inherited from the durable-dispatch branch; the passthrough tests
contradict dev's restored _run_with_workflow_token impl. Take dev's test file.
* fix: drop dead ttl_cache module; make fallback backoff jitter two-sided
ttl_cache.py was re-introduced via the dev merge but dev/#160 deliberately
removed it as dead code (no agent importer). Remove it to match dev.
Also make _jittered_delay symmetric (±25%) to match its docstring.
* chore(upstream-sync): triage 4 new upstream commits (#1708-#1713)
Synced ledger to upstream/main (71e3b818). New rows all deferred:
- #1708 add GPT-5.6 OpenAI models (FLAG-HUMAN: fork picker is Bedrock/Fireworks-only)
- #1709 stale admin model defaults after upgrades
- #1710 bump langchain-fireworks 1.4.4
- #1713 align reviewer eval with published findings
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
Co-authored-by: Adam Moussa <adam@seahavenind.com>
* feat: port plan-review & workflow-approval UX (#135)
Port six upstream commits onto dev:
- c03a6be7 (already ported): keep plan guidance high-level
- 546042a4: add workflow approval UI with diff preview, approval URLs,
web review links, and polling for approval status during active runs
- 216cf181: remove workflow token elevation; approved pushes pass
through directly without proxy token rewriting
- 3dbc0282: preserve plan redirects after login by accepting relative
same-origin redirect_to values and rejecting blocked paths
- bb104d93: submit plan comments with cmd+enter
- 90cb6caa: terse Slack replies, shared content via save_plan outside
plan mode (PLAN_STATUS_SHARED), reject shared-content mutations
Refs: #135
* feat: port durable dispatch hardening and startup latency improvements
Port five upstream PRs onto dev:
- #1621 / #1658: durable dispatch with loopback webhook defense,
create_durable_run helper, _config_with_prepare_run_id, degradation
to None for relative/loopback completion webhook URLs
- #1696: run-level completion webhook deduplication (replace
claim-then-post with post-then-flag per run_id), DeferredErrorModel
for graph-factory resilience, ToolRetryMiddleware for task subagents,
TimeoutWrapupMiddleware for all three graphs
- #1697: lazy-load __init__.py for agent.middleware, agent.tools,
agent.dashboard (PEP 562); defer heavy imports (exa_py in web_search,
agent.webapp in request_pr_review, deepagents in sandbox.py); add
ttl_cache.py with stale-while-revalidate for tool loaders
Refs: #137
* fix: restore login page render and clear CI lint/format
The plan-review port removed the authRedirectUrl import from login.tsx
but left its call site, crashing the login page at runtime (blank page,
no 'Sign in to open-swe'). Pass the relative path straight to loginUrl,
matching the plan route and the backend relative-redirect handling.
Also drop an unused os import in the guard test and reformat
workflow_push_guard.py to satisfy ruff.
* fix: restore RepairOrphaned middleware export and repoint model fake to deferred_model boundary
* fix: restore RepairOrphanedToolCallsMiddleware, fix E2E model-fake patch, drop dead ttl_cache
- Re-add RepairOrphanedToolCallsMiddleware to the lazy middleware __init__
(_MIDDLEWARE_MODULES, __all__, TYPE_CHECKING) so agent.reviewer can import it.
- Reroute E2E model patching to deferred_model.make_model so make_model_or_defer
(used by all three graph factories) returns the scripted fake instead of
building a real model with fake credentials.
- Drop unused agent/utils/ttl_cache.py — no agent module imports it.
- Fix import ordering in agent/reviewer.py and agent/analyzer.py (ruff I001).
- Format tests/test_dispatch.py.
* fix: claim-then-post run-level failure dedup; stop permanent suppression
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
Co-authored-by: Adam Moussa <adam@seahavenind.com>
* feat: port plan-review & workflow-approval UX (#135)
Port six upstream commits onto dev:
- c03a6be7 (already ported): keep plan guidance high-level
- 546042a4: add workflow approval UI with diff preview, approval URLs,
web review links, and polling for approval status during active runs
- 216cf181: remove workflow token elevation; approved pushes pass
through directly without proxy token rewriting
- 3dbc0282: preserve plan redirects after login by accepting relative
same-origin redirect_to values and rejecting blocked paths
- bb104d93: submit plan comments with cmd+enter
- 90cb6caa: terse Slack replies, shared content via save_plan outside
plan mode (PLAN_STATUS_SHARED), reject shared-content mutations
Refs: #135
* fix: restore login page render and clear CI lint/format
The plan-review port removed the authRedirectUrl import from login.tsx
but left its call site, crashing the login page at runtime (blank page,
no 'Sign in to open-swe'). Pass the relative path straight to loginUrl,
matching the plan route and the backend relative-redirect handling.
Also drop an unused os import in the guard test and reformat
workflow_push_guard.py to satisfy ruff.
* fix: carry workflows:write on the standing proxy token
Complete the half-ported upstream 216cf181 cascade. The port dropped
_run_with_workflow_token from the guard but missed the paired github_app
change, so an approved .github/workflows push ran with the base token
(no workflows:write) and GitHub 403'd it.
Add workflows:write to BASE_RUNTIME_PROXY_TOKEN_PERMISSIONS and delete the
now-orphaned WORKFLOW_RUNTIME_PROXY_TOKEN_PERMISSIONS constant; update the
github_app and proxy_auth tests to match. The HITL approval gate in
workflow_push_guard.py is unchanged — this only lets the standing token
push once a human approves.
* fix: restore transient workflow-token elevation (revert standing workflows:write)
The standing GitHub-App proxy token (BASE_RUNTIME_PROXY_TOKEN_PERMISSIONS) is
ALWAYS-ON, so carrying workflows:write on it made the fork's HITL workflow-push
guard the sole control over unapproved workflow pushes. The guard's git-push
parser has gaps (obfuscated-expansion push, `gh api` REST contents PUT,
fully-qualified cross-branch refspecs); with a permanently workflows-scoped
token those gaps become live unapproved-workflow-push exploits (1 critical, 2
high — security review BLOCK on #159).
Restore dev's transient-elevation model:
- Drop workflows:write from BASE_RUNTIME_PROXY_TOKEN_PERMISSIONS; re-add the
WORKFLOW_RUNTIME_PROXY_TOKEN_PERMISSIONS constant (base + workflows:write).
- Re-introduce _run_with_workflow_token in the guard: it mints the
workflows-scoped token via refresh_proxy_token around the approved,
guard-normalized fixed_command, then downscopes to RUNTIME then BASE in a
finally. Route the approval branch through it.
- Restore the dev token/elevation tests.
The standing token no longer carries workflows:write, so the three parser
bypasses hit GitHub 403 again; an approved push still succeeds because the
elevation grants workflows:write only around the normalized command. Keeps all
of #159's diff-preview / approval-URL / Slack-card guard additions.
* fix: reject protocol-relative path from sanitizeAuthRedirect (open redirect)
sanitizeAuthRedirect returned parsed.pathname+search+hash, which `new URL` can
resolve to a protocol-relative `//host` (e.g. input `/..//evil.com` normalizes
same-origin, passing the origin check, but yields a path starting with `//`).
ClientRedirect / login.tsx feed that path to window.location.replace, so it
navigates cross-origin — an open redirect. Reject any resolved path that is not
a single-leading-slash path (`^/[^/]`), falling back to the default. Adds
coverage for `/..//evil.com`, `/.//evil.com`, and `//evil.com`.
* fix: log SECURITY error when workflow-token downscope fails
The elevate->push->downscope finally block was silent on failure. If both
refresh_proxy_token calls fail, the sandbox retains workflows:write for the
rest of the run with no signal. Log a SECURITY error on the partial and full
downscope-failure paths so the retention is observable.
Addresses the GPT-4.1 cross-family review of the token-scope remediation.
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
Co-authored-by: Adam Moussa <adam@seahavenind.com>
* feat: port LangSmith LLM Gateway routing from upstream (#1671, #1673, #1674, #1678)
Ports four upstream commits that add opt-in LLM call routing through the
LangSmith Gateway, preserving fork conventions (Bedrock/Fireworks model IDs,
no-agent-attribution, bun toolchain).
- #1671 (e9dc6e01): opt-in gateway routing — new gateway.py, team-settings
toggle, admin UI section, wired into make_model for all graph entrypoints
- #1673 (702ef908): dedicated LANGSMITH_GATEWAY_API_KEY precedence over
platform LANGSMITH_API_KEY
- #1674 (5f7c2f46): fix Fireworks gateway base URL to /fireworks (bare host,
SDK appends /v1/chat/completions) + SanitizeFireworksMessagesMiddleware
- #1678 (73b7d1c0): fix OpenAI Responses reasoning replay —
SanitizeOpenAIResponsesMiddleware, store/include config for encrypted
reasoning content, reasoning_effort coercion for Chat Completions fallback
Refs #134
* fix: downgrade gateway not-routed log to debug, add Bedrock UI note, add sanitizer parity
- Downgrade logger.warning to logger.debug in gateway_overrides for
not-routed providers and missing API key (Bedrock is the default
provider in this fork, so these are expected steady states)
- Add Bedrock to the LLMGatewaySection route-toggle description so
admins know it is not routed through the gateway
- Add SanitizeOpenAIResponsesMiddleware to chat.py for parity with
server.py and reviewer.py
- Restore the Bedrock region comment in model.py that explains the
AWS_REGION / AWS_DEFAULT_REGION precedence
Refs #138
---------
Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
A sweep of the deferred backlog found two rows whose changes already shipped
in dev via #81 (the upstream-sync merge) — an empty cherry-pick confirms no net
change:
- #1618 (omit plan link in PR description when no plan exists) — was 'likely regression'
- #1646 (install missing deps before verification)
No code change; ledger accuracy only.
* docs(upstream-sync): add triage ledger + cherry-pick hook plan
Seeds the upstream triage ledger (52 diverged commits from langchain-ai/open-swe
categorized: landed/won't-merge/deferred/untriaged) and the design plan for a
git-hook mechanism to keep it in sync during cherry-picks.
* feat(upstream-sync): jsonl-backed triage ledger + generator CLI
triage.jsonl is now the source of truth (52 rows migrated from triage.md);
triage.md is generated with a do-not-edit banner. scripts/triage.py provides
migrate/generate/reconcile/lookup/check-reject/set; make triage-render/check/reconcile
added (triage-check is CI-safe staleness gate). Stdlib-only so git hooks can call it.
* feat(upstream-sync): cherry-pick triage git hooks + git cp wrapper
post-commit journals each -x pick to an untracked .git-local journal; prepare-commit-msg
hard-blocks known-reject picks (commit-msg is a secondary backstop — clean picks skip it on
git 2.50.1), overridable via git cp --force / SH_CHERRYPICK_ALLOW_REJECT=1 /
sh.cherrypick.blockRejects=false. git-cp is the pre-apply guard + auto-reconcile. pre-push is
a SHIM that re-execs the global Sea Haven security pre-push so core.hooksPath=.githooks does
not shadow it; install-hooks.sh verifies that shim FIRST and refuses if it is missing.
* docs(upstream-sync): correct hook plan + git cp runbook
Record the verified git 2.50.1 finding that clean cherry-picks skip commit-msg, so the block
lives in prepare-commit-msg; note the locked HARD-BLOCK-by-default reject policy. CHERRYPICK.md
now leads with make install-hooks + git cp and explains the generated-md ledger.
* chore(upstream-sync): mark #1651 landed (Bedrock family fix on gateway-routing)
* docs(upstream-sync): rewrite CHERRYPICK.md as a repo-specific runbook
* feat(upstream-sync): add triage.py sync + make triage-sync
Discovers commits on dev..upstream/main not yet in the ledger and appends them
as untriaged (PR # and subject parsed from each commit), then bumps _meta
'last synced' to the upstream tip and regenerates triage.md. Closes the
discovery side of the workflow: triage-sync to pull in new work, git cp to land it.
* docs(upstream-sync): move cherry-pick runbook to PR1 branch as cherry-pick-runbook.md