mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
3371ce4b2a859e4c8d230239c4f17afa5fbc31a7
265
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3371ce4b2a |
fix(release): bake the workspace-team gate into stable and preview builds (#6468)
release-stable.yml and release-preview.yml never set OPEN_DESIGN_AMR_PROFILE or OD_VELA_WEB_URL. Neither has a default in tools-pack, so velaWebUrl reached the packaged config as undefined, workspaceTeamTransportEnv returned an empty record, and the four vela transports were never injected into the daemon spawn env. Both channels therefore shipped clients with Workspace Team silently dormant — the build succeeds, the app installs and starts, and the absence only surfaces once a package is in a user's hands. beta and prerelease already inject the pair from their amr_profile input, which is why prerelease validation looked correct: the artifact under test had the feature and the stable artifact promoted from it would not have. Stable and preview are production channels, so the pair is pinned instead of dispatched — there is no legitimate stable build aimed at the test backend. Naming prod explicitly is a no-op for AMR itself (the daemon already defaults to prod in integrations/vela-profile.ts); it exists to satisfy the profile half of the gate. A fork or PR build without the secret leaves the origin empty, so the transports stay dormant rather than half-enabled. |
||
|
|
8e3853f0bc |
ci: route UI P0 jobs onto nexu-runners-xlarge (#6466)
UI P0 (especially project-collab) regularly drives the large runner container working set to the 16Gi limit. Add a dedicated ui_p0 profile on nexu-runners-xlarge and keep lighter browser jobs on large. |
||
|
|
5f5a589cd8 |
feat(packaged): ship Workspace Team on production builds (#6459)
Production was deliberately held outside the workspace-team profile allowlist until its Vela backend could actually serve the feature. It can now: WORKSPACE_TEAM_ENABLED and the three billing-scope flags are true on prod amr-api, and the resource hub it needs for team file sharing is configured (S3-backed blob store instead of the chart's mock default, which would have failed every push with 'unsupported protocol scheme mock' while comments kept syncing through Postgres). - Add prod to WORKSPACE_TEAM_AMR_PROFILES. - Resolve OD_VELA_WEB_URL for prod from secrets.VELA_WEB_URL_PROD, so release builds bake the production console origin. - Drop the temporary amr_profile=test default on release/v0.18.0; release builds now ship prod, with manual dispatch still able to target test or feature-test. The origin half of the gate is unchanged and still load-bearing: a build without an injected origin stays dormant on every profile rather than pointing at an unknown backend. Tests updated to lock the new prod behaviour and to keep local/profile-less builds dormant. |
||
|
|
5bca980d38 |
ci(release): forward-port the four prerelease workflow fixes from release/v0.18.0 (#6455)
* ci(release): forward-port release-prerelease.yml prerelease fixes from release/v0.18.0 * ci(release): forward-port notify-release-feishu.yml prerelease fixes from release/v0.18.0 |
||
|
|
8082a8d0d6 |
ci: pin pnpm store path across runner fleets (#6414)
* ci: pin pnpm store path across runner fleets Nexu ARC resolved the store under _work while hosted/seed used the home store, so actions/cache version hashes diverged and every Nexu job missed. Force $HOME/.pnpm-store and add restore-keys prefix fallback. * test(e2e): lock stable pnpm store path and restore-keys Add regression assertions for the non-persistent home store pin and OS-prefix restore-keys so the cross-fleet cache contract cannot regress silently. Generated-By: looper 0.11.8 (runner=fixer, agent=grok-build) * ci: seed Linux pnpm cache from cache-maintenance Pinning the store path alone cannot hit until a main job saves under the new actions/cache version. Add a single ubuntu-24.04 seed job; the cache is repo-scoped so every Nexu size reuses it. |
||
|
+13 |
356c8c364f |
feat(workspace-team): team workspaces, shared resources, workspace-scoped billing, and the #5517 redesign (#6142)
* fix(web): scope plugin URL imports to workspace * test(web): cover billing interest remount lifecycle * feat(packaged): enable the workspace-team transport for test-profile builds The vela test backend now serves the workspace-team API, so a packaged build baked with the test AMR profile should drive team projects / collab / resource sharing there instead of leaving them dormant. Replace the single feature-test equality check with a profile -> vela web origin map so adding a backend is one entry, and keep every unlisted profile (prod above all) off. * test(daemon): cover billing catch-up after SSE reconnect * fix(collab): surface and start first materialization when opening a shared project QA P0: a brand-new member on a fresh install joins someone's workspace, opens a project from it, and sees no loading state and no files — content only appears on a SECOND open, much later. Two defects, both on the first `/collab/status` request that opening a shared project makes. 1. No pull is started. The daemon's self-materialization block was gated on `callerIsOwner`, so a non-owner member got the placeholder record registered and nothing else. The web's auto-pull is gated on `publishedVersion` advancing past its cursor, and a fresh daemon's first status response cannot carry a published head: `collab.publishedVersion()` reads an in-process map that has never been written, and the real hub head is fetched fire-and-forget into `headEnrichmentCache` for a LATER poll to consume. Materialization therefore arrived only whenever a proactive lane (hub push / reconnect catch-up / the 30s recovery floor) next fired — the "second open" the report describes. 2. No download state is shown. `useProjectCollab.downloadPending` — which picks DesignFilesPanel's "Syncing files from the team…" over its empty state and create-a-file CTAs — reads only `publishedVersion > cursor`, `contentTransferState`, and an in-flight pull. All three are blank on that first response, so the member was shown an empty project inviting them to start creating over content that was still on its way. The fix makes the placeholder stamp the load-bearing signal on both sides: - `materializePlaceholderOnOpen` (routes/collab-sync.ts) replaces the owner-only block: any viewer opening a shared project whose only local record is an unmaterialized placeholder starts its pull on that same request, through the same coalesced flow. The retracted-share heal in its catch stays owner-only — a member who hits the hub tombstone has merely lost access and must not unshare anyone's project. - `/collab/status` reports `awaitingFirstMaterialization`, a purely local, synchronous fact that needs no hub round-trip and is therefore available on the very first response. `od collab status` prints it too. - `localFilesAreNotTheContentYet` (useProjectCollab.ts) makes it always download-pending, bypassing the member-only `shouldAutoPull` gate: whether the local files are the content is a fact about the record, not about who may pull it, so the owner of an unmaterialized placeholder (the reinstall case, recvqzaDvUU6B3) stops seeing the same empty state too. Red evidence: all six new specs fail on origin/feat/workspace-team @ |
||
|
|
d87ba8e5b9 |
ci: route Linux validation to Nexu runners (#6407)
* ci: route UI P0 to Nexu runners * ci: route Linux validation to Nexu runners * ci: size Nexu runners by workload * ci: preserve economic runner bootstrap * test: budget scope process coverage * ci: run browser validation on large runners * ci: align runner mode bootstrap semantics * ci: cap Playwright workers on large runners * ci: match hosted Playwright concurrency * test(desktop): accept full retry after interrupted download * test(web): stabilize privacy choice click * ci: use preseeded Playwright on Nexu runners |
||
|
|
f1809692e8 | fix fork PR CI runner routing (#6401) | ||
|
|
6ce4344fe1 | ci: allow open-design-crew[bot] to apply backport labels (#6398) | ||
|
|
176fae3dc6 |
ci: constrain Actions cache writes (#6270)
* fix(tools-pack): carry workspace build key into tarballs * ci: constrain Actions cache writes |
||
|
|
809d45f27c |
feat(integrations): add the local Open Design Cloud flow for Codex (#6055)
* feat(daemon): expose Vela login through CLI and MCP * feat(mcp): add interactive local brief card * feat(packaged): bootstrap local MCP headlessly * feat(daemon): secure local BYOK credential profiles * feat(cli): manage secure BYOK profiles * feat(web): store BYOK keys in secure profiles * fix(runs): deduplicate cloud generation requests * feat(daemon): add end-to-end plugin observability * fix(pack): include native PTY runtime in desktop packages Lazy-load node-pty in the daemon so missing native binaries only disable Terminal and interactive Keychain operations instead of crashing startup. Ship and validate target prebuilds in macOS and Windows packages. Repair the macOS spawn helper before signing, and invalidate stale Windows packaging caches. Cover the packaged PTY path in macOS and Windows smoke tests. Require Vela CLI for every beta desktop build target. * fix(mcp): localize and stabilize plugin briefs Keep collect and confirm on one MCP Apps card, report intrinsic size changes, and remove the fragile widget-state fallback. Localize brief forms, summaries, status, and public errors from the current request or Host locale while preserving stable protocol ids and selections. Keep internal runtime identifiers out of user-facing MCP guidance. * fix(packaged): register live Studio URLs with daemon Register the packaged web sidecar loopback origin over namespace-scoped daemon IPC after dynamic binding. Invalidate install-info by the live web port and remove the second MCP URL cache so run and project responses use the current Studio address. Validate loopback origins and cover registration, rebind, and cache refresh. * fix(mcp): stabilize brief confirmation lifecycle Keep confirmed briefs locked after Host publication failures and retry only the cached follow-up message instead of confirming twice. Replace blank initialization with a compact loading state, accept immediate and delayed tool results, and bump the MCP Apps resource to v3 so Codex does not reuse the stale card. Add an executable JSDOM Host bridge harness covering double-submit, publication retry, delayed results, and repeated draft notifications. Validated with the full daemon suite, workspace typecheck, and guard. * fix(mcp): prevent brief app resize feedback loops Replace the self-observed intrinsic-height loop with deduplicated body measurements so Codex MCP app iframes do not repeatedly resize themselves and fail sandbox startup. Bump the brief resource URI to v4 to avoid cached v3 cards and cover the regression with the focused brief-app spec. Validated with daemon tests/build, workspace guard/typecheck, a verified beta DMG, and a direct packaged MCP resource smoke. * fix(mcp): use the final Open Design plugin identity Make open-design the only accepted external plugin ID across MCP tools, run attribution, artifact version origins, and telemetry. Rename the Brief MCP App resource to v5 and reject the unpublished old ID instead of aliasing it. Validated with 171 daemon tests, 5 web tests, daemon/web typechecks, and guard. * chore(pack): bump Vela CLI to 0.0.27 * chore(nix): refresh pnpm dependency hashes * fix(mcp): honor host and plugin request contracts Respect independently advertised MCP Apps capabilities and prefer the Codex-native follow-up bridge so confirmed briefs render as readable user turns instead of context-only messages. Bump the brief resource to v6 to invalidate stale cards.\n\nMark Vela login and account probes as open-world interactions, and require stable request IDs for attributed generation so lost responses cannot create a second logical run. * fix(cli): support keyless BYOK profiles Add an explicit --no-api-key save mode for local and self-hosted providers while keeping credential-bearing profiles stdin-only. Reject missing or conflicting credential modes and cover both paths in the CLI tests. * fix(pack): replace headless owner when desktop opens Advertise headless packaged runtimes as having no visible window, then shut them down before a normal desktop launch continues instead of treating SHOW as a successful focus. Validated with the focused packaged headless and launcher lifecycle tests (20 passed). * fix(pack): clean up failed headless startup Acquire the headless identity, sidecars, and IPC server through one failure-atomic lifecycle so later bootstrap errors close every previously acquired owner in reverse order. Cover MCP-install and web-identity publication failures; validated with 22 focused tests and the packaged typecheck. * fix(byok): remove raw provider request contract Expose only daemon-owned BYOK profile references from shared run types and the web transport, while retaining the daemon's fail-closed rejection for legacy or untyped raw credential payloads. Validated with 76 web provider tests plus web and contracts typechecks. * fix(daemon): preserve interrupted run idempotency Hydrate queued or running durable requests as DAEMON_RESTARTED failures before idempotency reuse, preserving the original request and workflow mappings instead of creating a second cloud operation. Share the restart transition with boot reconciliation and expose synthetic terminal events to reattaching clients. Validated with 48 focused tests and the daemon typecheck. * fix(byok): roll back failed credential metadata writes Snapshot secure-store values before mutation and compensate create, update, and delete operations when profile metadata cannot be persisted, preventing orphaned or lost credentials. Validated with nine focused credential-service tests and the daemon typecheck. * fix(web): reconcile selected BYOK profile metadata Treat the daemon's selected secure profile as authoritative for protocol, endpoint, model, and API version so CLI profile edits cannot leave the browser showing stale execution settings. Validated with 66 config tests and the web typecheck. * fix(byok): migrate legacy credentials before cleanup Import each supported legacy browser credential into a stable daemon-owned secure profile before removing plaintext copies. Keep the original browser record on secure-store or unsupported-provider failures and surface a Settings recovery path. Cover successful migration, duplicate projections, and injected secure-backend failure while preserving existing Bedrock downgrade behavior in memory. * fix(byok): secure Windows profiles with DPAPI Dispatch native Windows BYOK profiles to a CurrentUser DPAPI backend rooted under the resolved daemon data directory. Secrets enter PowerShell only through stdin, encrypted blobs stay separate from non-secret profile metadata, and API responses remain secret-free. Add platform dispatch coverage, a Windows-only real DPAPI round-trip test, and the packaged Windows smoke path for create, resolve, response-redaction, and delete behavior. * fix(web): preserve config writes during hydration * fix(byok): persist secure credentials across entry flows * test(byok): cover secure onboarding persistence --------- Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com> Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
cf3ca4fc3d | ci: close packaged leaf test coverage (#6102) | ||
|
|
fac10139c0 |
perf(ci): narrow zero-effect queue validation (#6100)
* perf(ci): narrow zero-effect queue validation * docs(ci): keep scope spec state-focused |
||
|
|
f9a6e18c11 |
feat(ci): promote the certain-exempt docs core to merge-queue trust (#6047)
* feat(ci): promote the certain-exempt docs core to merge-queue trust Split the exempt surface into a certain-tier core (docs/, apps/landing-page/, .vscode/, .idea/, .github/ISSUE_TEMPLATE/, LICENSE, CODEOWNERS) and the medium residue (global markdown regex, workflow exacts, nix). A merge-queue group confined to the core now drops to the unconditional floor lanes instead of running everything; replaying the last 398 first-parent merges puts that at 12.3% of queue traffic. The promotion is backed by a new floor-lane guard check, "certain-exempt surface consumption": no skippable-lane source may reference a certain-exempt path (dot-relative literals flagged everywhere; bare repo-relative literals flagged outside test fixtures). guard.ts gains --list-checks as the machine registry, and the rule-table invariant test now requires every certain rule's guard field to resolve to a live check. * docs(ci): record the scope confidence methodology in specs/current/ci.md The living framework for confidence-tier changes: medium-tier cheap loop vs certain-tier deliberate loop, the three-sentence promotion discipline, guard strength levels and floor placement, evidence recipes (replay, offline plan, queue trace extraction), tooling graduation criteria, and the open questions deliberately left undecided. Indexed from AGENTS.md and .github/AGENTS.md required reading. * fix(ci): narrow certain-exempt fixture handling Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): pin the trae-cli consumption exception to the daemon-lane wiring The review round established that apps/daemon/tests/runtimes/trae-cli.test.ts genuinely reads docs/agent-adapters.md. Its exemption from the consumption guard was justified by a wiring fact (the ci.yml daemon lane runs only project-watchers.test.ts) stated as prose, which would rot silently if the lane ever widened. Make the exception conditional on that exact ci.yml needle: when the daemon lane changes, the exemption disappears and the guard reports the read, forcing reclassification instead of trusting a stale rationale. * docs(ci): codify exception-precondition binding in the methodology Backfill the pattern the first review round produced: guard allowlist entries justified by local definitional facts may stay prose, but entries justified by remote mutable facts (lane wiring, gate topology) must be conditional on a checked precondition so the exception dies loudly with its premise. Records the trae-cli/daemon-lane needle as the worked example. * fix(ci): fold static paths in exempt consumption guard Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): require exclusive daemon test wiring Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): recognize daemon pnpm selector forms Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(ci): tokenize daemon workflow test commands Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
de2020fb88 |
docs: reconcile code-evolution drift (#6083)
* docs: reconcile code-evolution drift * fix(web): preserve generic-disconnect recovery retries Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(web): retry capped reattach probe after cleanup Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
a2ba3ee2ad |
feat(updater): manual cache clear, installer-reinstall floor, and crash-recovery lifecycle (#6032)
* feat(updater): manual cache clear + installer-reinstall floor on the installed outer version
Two disaster-recovery capabilities for the packaged updater:
- Reuse control.launcher.version.min properly: the gate now compares the
physically installed outer package version (read at check time from the
outer bundle's open-design-config.json via the launcher launch path;
OD_UPDATE_INSTALLED_VERSION overrides for tests), not the running payload
version. When the floor trips - or min is set but the outer version is
unreadable (conservative) - the updater selects the installer artifact and
offers a same-version installer reinstall when no newer release exists
(clamped to min <= candidate so the offer always converges). The status
snapshot carries a reinstall block {reason, installedVersion, minVersion,
url}; UpdateDialog/UpdaterPopup render reinstall copy with an optional
operator jump link. tools-release publish-metadata publishes the block from
RELEASE_LAUNCHER_VERSION_MIN(_URL) (per-channel repo vars with a _STABLE
fallback wired in all release workflows) and hard-fails when min exceeds
the release version; verify-metadata re-checks the published block and
summary-metadata surfaces the floor.
- Manual clear-cache action (od:update:clear-cache IPC, sidecar clear-cache
action, Settings > About row with a two-stage inline confirm): resets
one-shot update state (downloaded release, install freeze) back to idle,
purges releases/staging/downloads/.back, removes a stale launcher
attempt.json plus any non-confirmed desktop-handoff journal, and deletes
non-retained payload versions. Runtime active/lastSuccessful versions,
retained cleanup entries, install.json, and a confirmed handoff journal
are never touched; locked files defer through the existing cleanup.json
retry machinery.
Also: tools-pack --update-action now validates through sidecar-proto's
exported isDesktopUpdateAction so clear-cache reaches the ops surface;
tools-serve updater fixture gains control min/url knobs and CLI flags;
9 new i18n keys across all 19 locales; tools/pack/AGENTS.md documents the
gate semantics and clear-cache boundaries.
Validated locally per the updater matrix (desktop/web/tools-serve/tools-pack
suites, guard, typechecks) plus a mac release-beta closed loop: platform full
spec, same-version reinstall against a real outer/payload version split,
clear-cache recovery, and a real-feed payload update to 0.15.1-beta.4.
* test(updater): close Windows recovery acceptance
* test(updater): mirror Windows same-version reinstall recovery on the mac lane
Port the Windows runSameVersionUpdaterRecoveryAcceptance shape into the mac
full profile: after the payload cold-start acceptance, restart the packaged
app against a fixture publishing an installed-outer floor at the running
payload version and assert the same-version installer reinstall snapshot
(reason/installedVersion/minVersion/url), the popup reinstall learn-more
link, clear-cache back to idle with launcher pointers untouched, explicit
re-check recovery, and the installer open in dry-run mode — macOS has no
silent DMG install to execute, unlike the Windows NSIS transaction lane.
Extends the popup eval expression/validator with reinstallLinkVisible and
records an updaterRecovery block in the smoke summary. Full-profile test
timeout raised 240s -> 360s for the extra app restart.
Validated locally: release-smoke full profile green in 80s with the recovery
summary populated (reinstall block exact, cleared idle, recovered dmg,
dryRun install).
* refactor(release): centralize launcher version floor channel policy in one resolver
Channel policy for control.launcher.version.{min,url} is now managed as one
repo-vars pair per channel (RELEASE_LAUNCHER_VERSION_MIN[_URL]_<CHANNEL>).
Workflows pass the channel pair plus the STABLE pair through verbatim — the
|| fallback expressions are gone from YAML — and the shared resolver in
tools/release/src/storage/launcher-version-floor.ts owns resolution and
validation: pair-level stable fallback for non-stable channels (never mixing
one channel's min with another's url), version format, http(s) url, orphan
url rejection, and the floor <= releaseVersion guard. publish-metadata and
verify-metadata consume the same resolver, so verify re-derives the expected
block from the same channel policy instead of trusting a pre-resolved env.
release-workflows.test.ts pins the verbatim passthrough keys in all five
release lanes and rejects any reintroduced YAML fallback.
* feat(updater): let clear-cache rebuild a provably-owned corrupt update store
A corrupt store is one of the blocking scenarios the manual clear exists to
recover from, yet the action previously bailed on the very store errors it
should heal. clear-cache now rebuilds the update root when — and only when —
ownership is provable: the current-generation sentinel is the proof, and
everything else inside an owned root is updater cache by definition. Roots
without a sentinel, with a foreign-generation marker, or failing IO are never
touched; the original store error surfaces unchanged. Four specs pin the
boundary (corrupt metadata rebuilt, stray entries rebuilt, unowned root
refused with files intact, foreign-generation marker refused).
* fix(launcher): pre-arm delegated payload launches so pre-bookkeeping crashes roll back
A payload that spawned successfully but died before reaching its own launcher
bookkeeping left no attempt.json behind, so every later cold start re-selected
the same broken active pointer and re-delegated to the dead payload — a
permanent dead loop the installer fallback could never surface (found while
designing the crash-rollback acceptance).
The delegating parent now arms attempt.json BEFORE spawning the payload and
passes the pointer as --od-launcher-delegated-* argv. Selection gains an
active-delegated reason: an attempt matching active AND the delegated pointer
is the launch in progress, not a previous failure; only an exact active match
qualifies, so a stale pointer cannot defeat the rollback. Rollback
(last-successful) delegations deliberately carry no marker and never re-arm —
the attempt on disk IS the rollback evidence the child must re-derive.
Covered end to end: packaged outer delegation pre-arms (and skips pre-arm on
rollback), the updater's payload activation pre-arms and threads the pointer
through the deferred relaunch, and confirm clears the attempt for delegated
launches. launcher-proto 18, packaged 237, desktop 264 all green.
* fix(updater): scrub a stale payload install freeze after a rollback
A payload install freezes the updater with installResult.activeVersion
awaiting the relaunch. When that payload crashed and the launcher rolled
back, the app restarts on the OLD version with the freeze still persisted —
every later check became a frozen no-op, so a rolled-back install never saw
another update offer (the crash-rollback acceptance exposed this in its
self-heal phase). Cold-start restore now detects the broken promise —
installResult.activeVersion newer than the running version on a live process
— and drops the freeze and stale install result while keeping the verified
downloaded release actionable. The installer-artifact freeze (no
activeVersion) keeps its existing restore semantics. Red spec:
'clears a stale payload install freeze when the promised relaunch never
became the running version'.
* test(updater): full-lifecycle acceptance — silent startup, crash rollback, coverage map
Two new packaged acceptance tests on both platform lanes (mac validated
locally; win symmetric for the Windows box):
- 'applies a downloaded payload silently on the next cold start': enables the
daemon-owned allowSilentUpdates through the production GET+PUT
/api/app-config path, restarts, and asserts the first scheduler tick
installs, quits, and relaunches with converged pointers — no updater action
issued.
- 'rolls back a crashing payload and self-heals on the next good update':
repacks the real payload archive with a crash-before-bookkeeping stub
(mac: shebang script via ditto; win: a real executable — System32 where.exe
— via the installed app's bundled 7z, since a script cannot stand in for an
exe), drives the update through the popup, asserts the pre-armed attempt
survives the crash as rollback evidence, cold-starts into the rolled-back
base version (degraded steady state pinned: broken pointer stays active
with its attempt), then self-heals via a version-bumped healthy payload —
real recovery releases ship as version+1.
Spec hygiene: resetPackagedRuntimeState wipes launcher/runtime/daemon state
before each lifecycle test (uninstall keeps runtime data by design, and
leftover pointers silently delegate fresh installs onto stale payloads);
mac waitForHealthyDesktopVersion gains win's requireSettledLauncher opt-out
for rollback waits.
docs/testing/updater-lifecycle.md is the new lifecycle-to-test coverage map
(incl. deliberate manual-only nodes), linked from tools/pack/AGENTS.md and
the e2e coverage status doc.
* fix(updater): harden Windows full-lifecycle acceptance
* test(release): make channel-prepare hermetic against local git tags
prepare-preview derives its latest-stable floor from the clone's real
open-design-v* tags, so the channel-prepare tests were green on tagless CI
checkouts but permanently red on any developer clone once shipped stable
tags advanced past main's package version (0.15.1 vs v0.15.1/v0.16.0 today).
The spawned scripts now run with GIT_DIR pointing at a throwaway repo
holding exactly the tags each test declares, and a new negative case pins
the floor rejection itself (packaged version below a v99.0.0 stable tag).
* test(e2e): model delegated payload launch
Pass the updater's pre-armed payload pointer into the cross-runtime launcher simulation so it represents the spawned payload process instead of a later crash-recovery launch.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* test(web): wait for question form readiness
Wait for the asynchronously derived submit-disabled state before asserting it, removing the suite-order race observed by the Web workspace tests check.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(updater): preserve recovery state across locks and restarts
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
14fcc411cd |
feat(ci): confidence-tiered scope rule table with merge-queue shadow evaluation (#6029)
* chore(scripts): make root scripts/ test-free and purify the guard entrypoint
Root scripts/ is now source-only, enforced by a new "scripts test-free"
guard check (any *.test.{ts,tsx,js,jsx,mjs,cjs} under scripts/ fails
pnpm guard). The root guard script becomes a pure tsx ./scripts/guard.ts
invocation instead of chaining a hand-maintained node --test file list.
Script behavior-contract coverage follows the boundary e2e/AGENTS.md
already declares for e2e/tests/scripts/: the fork PR approval security
tests migrate there (converted to Vitest, 24 tests passing), and the
remaining script unit tests are deleted — five of them (batch-design-
system-test, check-design-system-package-quality, plugin-previews-diff,
plugin-previews-gc, migrate-to-plugins/example) were orphans no runner
ever executed; the guard-chained checker tests covered logic whose
repo-wide enforcement remains in guard.ts checks. Coverage with a real
test claim gets rebuilt in e2e/tests/scripts/ on demand.
* test(ci): pin characterization goldens for scopes.ts scope detection
Golden tests run scripts/scopes.ts as a subprocess exactly the way ci.yml
does, with gh stubbed through the existing OPEN_DESIGN_GH_NODE_SCRIPT seam
(hermetic stub materialized per run), and pin the complete 20-key output
plan for a matrix of changed-file sets across pull_request,
workflow_dispatch (hot/full), and merge_group events.
They deliberately pin current quirks (skills/*.md arming daemon+web without
the ui-critical fallback; e2e/ui/*.md arming ui_p0 with workspace validation
off; dispatch-hot skipping the workspace_validation re-derivation) so the
upcoming rule-table restructuring can prove behavior preservation
byte-for-byte.
* refactor(ci): restructure scopes.ts into a confidence-tiered rule table
Scope detection becomes data plus a pure evaluator: every changed file is
classified by an additive rule table (union of matched effects, minimum
confidence across matched rules), and an evaluation context brings a trust
threshold — files below it, or matching no rule, escalate fail-closed to
the full radius. Today's behavior is exactly the degenerate case of this
structure (all rules medium, certain set empty): pull_request/hot believes
everything, the merge queue believes nothing and stays full. The
characterization goldens pass unchanged, byte-for-byte.
The exempt surface becomes an explicit empty-radius rule instead of a
special case, so exemptions can be split, rated, and promoted per rule.
Promotion to "certain" requires a guard field naming the enforcing check
(enforced by a rule-table invariant test).
New offline entry points for rule iteration: `plan --context
pr|merge-queue|full --files ...` evaluates without any GitHub environment
and prints { plan, trace }; `rules` lists the table. Every evaluation now
returns a decision trace (per-rule hit counts, per-file escalations, and
an ifTrustAll shadow plan that shows what medium-confidence rules would
have allowed — the evidence stream for future certain-tier promotions);
github-output mode logs it and writes a step-summary table without
touching the frozen 20-key output contract.
The legacy dispatch-hot asymmetry (no workspace_validation re-derivation)
is preserved behind an explicit option and pinned by the goldens.
* feat(ci): evaluate merge-group union diffs at the certain threshold with shadow trace
The merge queue now resolves the queued group's union diff
(compare base_sha...head_sha from the merge_group event payload) and
evaluates it through the rule table at the "certain" trust threshold.
While the certain rule set is empty every file escalates and the queue
keeps running everything — the goldens pin this byte-for-byte — but the
evaluation's ifTrustAll shadow column now records, for every queued
group, what medium-confidence rules would have allowed: the evidence
stream that future certain-tier promotions will cite.
Queue lanes derive from the evaluated outputs (ci_mode stays reported as
"full", but lanes are no longer unconditionally forced), so promoting a
rule to "certain" later is a pure rule-table change with no further
structural work.
Fail-safe behavior is explicit and tested: resolution errors and empty
resolutions fall open to the full plan without failing the scopes job
(queue throughput must never depend on this evaluation), while
pull_request resolution failures keep failing the run as before.
Changed-file resolution no longer filters out removed files (manual hot
runs previously did): deleting a runtime source file must trigger the
same validation scopes as editing it. This is the only behavior change,
and it is strictly fail-closed.
* fix(ci): preserve guard contracts and fail closed on compare limits
Restore the eight guard behavior suites in the e2e script lane and make merge-group compare resolution treat GitHub's 300-file ceiling as incomplete.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(ci): enforce extension-agnostic script test boundary
Match every suffix after .test. and cover TypeScript, MTS, and non-test filenames in the e2e script suite.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(ci): route script contract tests to e2e
Ensure changes under e2e/tests/scripts arm E2E Vitest with the same daemon and web scope effects as their root script counterparts, and pin the behavior with a PR golden.\n\nGenerated-By: looper 0.11.0 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
702d6f3a0a | fix(ci): make static gate runner-independent (#6039) | ||
|
|
b4f2677618 |
fix release: notarize prerelease mac builds (#5906)
* fix release: notarize prerelease mac builds * fix(release): inject Apple notary env on prerelease mac retry steps The cache-miss retry steps for both mac_arm64 and mac_x64 prerelease builds pass --notarize but lacked the APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID env block present on the primary build steps. notarize.cjs fails closed when those vars are missing, so a recovery build could not produce a notarized candidate. Mirror the primary step env on both retry steps and assert the notary env appears twice per mac job in release-workflows.test.ts. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
20c6c0a6ed |
ci(e2e): shard the full UI functional pool into a merge-ready matrix (#5883)
* ci: benchmark deduplicated UI merge sentinel * ci: benchmark restoration-hosted sentinel * ci: benchmark distributed P0 sentinel * ci: benchmark split P0 sentinel * ci: serialize split sentinel benchmark * ci: finalize deduplicated UI merge topology * ci: prototype all-shades UI domain topology * ci: bound full UI runtime by shade * test(e2e): stabilize full UI suite * fix(web): unclip DS picker popover and propagate patched project metadata Two regressions surfaced by re-enabling the full UI pool: - The New Project design-system popover lost its portal and was clipped by short viewports and the modal body. Render it through createPortal with a viewport-aware anchor that flips upward when space below runs out. - After a working-dir pick, onProjectMetadataChange forwarded only the metadata onto a stale project copy, so ProjectView.currentProject's recency comparison let an older useProjectDetail snapshot shadow the accepted PATCH and the picker label never updated. The callback now carries the daemon's authoritative post-patch project (fresh updatedAt included), matching the design-system switch path, and ProjectView hands it straight to onProjectChange. * test(e2e): repair remaining stale full-suite UI contracts Second repair batch for the failures surfaced by full diagnostic run 29480362163, plus one CI-invisible case that was serially skipped behind the BYOK failure in the same file: - Point the working-dir metadata spec at a directory that exists on the runner instead of a hardcoded absolute path, and assert the label from the picked path (this spec is red without the ProjectView patched-project fix). - Navigate back to All project files before asserting the plugin-folder cards, since a finished run auto-opens the produced file tab. - Resolve the create-run response race in real-daemon-run's sendPrompt via a single waitForResponse alongside the click. - Refresh selectors and copy for entry topbar, home hero rail, chat todo autoscroll, project management, workspace keyboard, design files, manual edit, restoration, and DS picker clipping flows to match the current UI contracts while keeping each spec's oracles. * ci: distribute the full UI suite over a generic 8-shard matrix Replace the diagnostic four-domain P0/P1/P2 serial topology with a single all-shades Playwright invocation per shard (--shard=N/8, two fully parallel workers, 20-minute cap) and drop the scheduled trigger; full runs stay manual until the merge-queue wiring lands. Update the workflow topology guard accordingly. * ci: serialize full UI shards and widen the matrix to twelve The eight-shard two-worker experiment left six light shards healthy (260-477s test steps) but both shards holding runtime-heavy files degraded: repeated two-minute timeouts with retry recovery in one, and a consistent create-run response starvation failure in the other. This matches the earlier finding that runtime-heavy scenarios cannot share a runner under two fully parallel workers. Run each shard with a single worker and rebalance to twelve shards; fully-parallel stays on only for test-granularity sharding. * fix(web): preserve prompt-extracted plugin inputs across apply reconcile The active-plugin apply reconcile rebuilt the inputs from the bind-time optimistic snapshot, so edits the user typed during the apply roundtrip (already extracted into active.inputs by handlePromptChange) were silently reverted to defaults — the submitted pluginInputs then disagreed with the visible prompt. Merge the apply result's defaults into the current inputs inside the functional updater instead, mirroring the guard the prompt-text reconcile already had. * test(e2e): stabilize home gallery flows for all-shades execution Generic all-shades sharding executes tests in orders the shade-filtered domain topology never produced, exposing a home-surface remount window: the async projects fetch can mount or remount HomeTemplatesReveal after a test starts interacting, collapsing the gallery (inert) under the test's cursor. Stabilize the shared helpers instead of individual specs: - gotoEntryHome arms a projects-list response waiter before navigating and holds until it settles. - revealHomeTemplates requires the revealed state (or a true pass-through) to survive a settle window, retrying when the wrapper mounts late; a card's isVisible() is true inside the collapsed container, so openHomePluginDetails now always routes through the reveal path. - The browse-registry spec reveals before clicking. - The DS-picker options moved out of the new-project-modal subtree with the portal fix; query the page-level popover per the clipping spec's convention. - real-daemon-run's sendPrompt reports on timeout whether POST /api/runs was ever issued, so the CI-only BYOK failure can be split into swallowed-click vs unanswered-request on the next reproduction. * test(e2e): make BYOK availability hermetic and reveal remaining gallery clicks The BYOK missing-provider spec could only pass on machines with a real `opencode` binary: daemon detection maps byok-opencode's availability through the `opencode` agent's configured cli env, and the web composer refuses to POST /api/runs for an unavailable agent (the sendPrompt diagnostic confirmed "was never issued" on CI). Point the helper's agentCliEnv at the fake opencode runtime — the run still fails pre-spawn on the missing provider config, so the fake binary never executes. Also route the two remaining collapsed-container gallery clicks (the browse-registry starter journey and the detail-use force-click) through revealHomeTemplates; an inert container swallows force-clicks silently. * ci: probe a 24-shard full UI matrix against the four-minute wall target The first green 12-shard run put eleven shards at 141-354s test steps but concentrated the contiguous runtime-heavy chain in one 894s shard, setting a ~16-minute wall tail. With ~3568 total test-seconds and ~90s per-job overhead, 24 shards should land every job near four minutes. * ci: settle the full UI matrix on twelve shards The 24-shard probe stayed green but proved the runtime-heavy chain is a serial group (test.describe.configure serial in real-daemon-run), which Playwright keeps atomic within one shard: the tail stayed ~16 minutes while the extra twelve cold starts raised aggregate runner time from ~76 to ~102 minutes. Twelve shards dominate; lowering the wall below the serial-chain floor is a de-serialization follow-up, not a shard-count knob. * test(e2e): drop serial mode from shard-splittable files Playwright keeps a serial group atomic within one shard, so real-daemon-run's file-level serial made its ~16-minute chain the floor of the full UI matrix wall regardless of shard count. Every test in the three serial files already performs its full setup per test (config reset, route stubs, own project), so serial only added skip-after-failure and shard atomicity. Verified order independence by running each file's two standalone shard halves locally (fully parallel sharding, one worker): 21/21, 57/57, and 12/12 across the halves. * test(e2e): settle the projects fetch in real-daemon-run home navigation With serial mode gone, the plugin-authoring spec runs after arbitrary shard predecessors, so the daemon holds more projects when it navigates home; the late-resolving projects list re-renders the hero and left the shortcuts trigger unstable for the whole click timeout on CI. Port the projects-settle waiter already used by entry-chrome-flows into this file's gotoEntryHome. * test(e2e): name the plugins-load gate in the plugin authoring spec The home shortcuts trigger is disabled while the plugins list loads, and this spec's mid-flow reload re-fires that fetch after gotoEntryHome's settle has already passed — on CI the click then times out opaquely for 120 seconds. Arm a response waiter across the reload and assert the trigger's enabled state explicitly, so a slow or hung /api/plugins fails with the actual cause named. * test(e2e): enter plugin authoring through the plugins page create button The home shortcuts trigger sits disabled on CI runners while a home plugin apply hangs (plugins list resolves, the pending-apply gate never clears); three reproduction attempts confirmed the anomaly is CI-only and independent of this journey. The create button on the Plugins page drives the same queuePluginAuthoring flow, and this spec's oracle is the generated scaffold plus its action cards, so route the entry there and track the disabled-trigger anomaly separately. * ci: let configure-ci-parallelism drive full UI shard workers The single-worker override predates the repairs that explained the two-worker instability (BYOK agent availability and the reveal remount race, both test-side and since fixed), so it was pinning throughput on a stale diagnosis. Drop it and let the repo's nproc/2 convention apply — two workers on the four-core ui_hot profile — halving the per-shard test step if the runtime holds. * ci: probe three full UI workers against Blacksmith's actual vCPU capacity The ui_hot profile resolves to blacksmith-4vcpu runners that reportedly provide six actual vCPUs while nproc's cgroup view says four, so the nproc/2 convention derives two workers and idles capacity. Pin three workers for this probe and print the CPU topology alongside for verification. * Revert "ci: probe three full UI workers against Blacksmith's actual vCPU capacity" This reverts commit 63c8296ae0ead2c3dadfbfda41f6e068d58331ef. * ci: skip the critical fallback for pure packaged-leaf changes Playwright starts tools-dev web (daemon + web runtimes) and never launches the desktop, packaged, or tools-pack entrypoints, so PRs confined to apps/desktop, apps/packaged, or tools/pack were paying the two-job critical fallback (5.4-13.4 minutes of wall each) for coverage their package tests and preflight already provide — seven of the eight fallback PRs in the latest 100-merge sample were such false positives. Add a fail-closed ui_critical_validation_required scope: it clears only when every non-exempt changed file sits under one of those three leaf roots. tools-dev, any transitive package (including the undeclared metatool edge), scripts, runtime resources, unknown roots, and mixed leaf + runtime changes keep the fallback armed; root manifests keep enabling P0 with the existing critical/P0 mutual exclusion; merge-group full mode is unchanged. * test(e2e): align question-form specs with inline Studio discovery Rebasing onto main picked up #5496, which moved clarification forms inline into the chat flow and removed the legacy Questions workspace tab. Re-point the submit-persistence spec at the inline form, and fix the oracles that no other lane had verified since: the answered summary echoes the picked visual-style card title (not the option label), and the submitted answer message carries the card title plus its value id. * test(e2e): absorb post-rebase drift in BYOK gating and zh-CN hero copy Two more contracts only the full pool guards moved under the rebase: - The composer's BYOK gate silently swallows submits until the incremental /api/agents stream has delivered byok-opencode's availability, so a fast submit races the stream (the sendPrompt diagnostic reports the POST was never issued). Retry through that window; runErrorCard reads the last card, so swallowed attempts cannot shadow the provider-validation oracle. The gate erroring instead of waiting while agents are still loading is a product-side follow-up. - The home hero title copy changed in every locale; re-point the zh-CN P2 smoke at the current translation. * test(e2e): align gallery specs with the all-types default slice The Community gallery now defaults to an All category pill (#5759) instead of pre-selecting Deck: assert the new default state in the hero and category-tab specs, and have the facet-filter spec pick Deck explicitly before asserting the deck-only visibility set. * test(e2e): align the first-run reveal spec with the all-types default slice Same #5759 drift family as the entry-chrome-flows gallery specs, missed by the first sweep because it lives in home-hero-rail: the revealed gallery now shows the All pill selected instead of hiding it with live-artifact pre-selected. * docs(e2e): add UI test stability rules distilled from the full-pool work Capture the invariants a UI test must hold to stay green under the generic sharded full pool, learned from stabilizing that suite and two rebases: - order independence (no serial groups; setup is per-file, not universal), and that neither shade-filtered local runs nor merge-path P0 prove it; - retry-only passes are an order-leak/readiness signal, not flake; - settle async surfaces (best-effort waiter + a state-survives-a-window check) before interacting; - an enabled control is not a ready control when a streamed precondition (e.g. BYOK-OpenCode agent availability over the /api/agents SSE stream) gates it; - never force-click into inert/collapsed reveal containers; use actionability, not visibility, as the readiness signal; - hermetic agent availability via route mocks (with the terminal SSE `done` event) or fake CLI runtimes + agentCliEnv, never host binaries; - realign stale oracles against the product and confirm them in the lane that actually executes the case, since a merged PR does not verify P1/P2 or unregistered-file P0 oracles; - name failure causes so a CI-only reproduction carries its own evidence. Cross-linked from the top-of-file coverage-status pointer. * test(e2e): realign BYOK missing-provider spec to the #5745 client-side preflight main #5745 added a client-side BYOK preflight that blocks a missing-provider run before any POST and opens the execution Settings section, instead of letting the run reach the daemon and fail there. Rewrite the spec to the new "blocked before spawn" behavior: submitting opens the execution-mode dialog, no create-run request is issued, and no artifact is produced (persisted across reload). This drops the now-obsolete daemon-failed-run assertions and the SSE-availability retry loop, which the preflight makes unnecessary. * docs(e2e): explain why the functional config ignores visual specs Clarify that visual-*.test.ts are owned by playwright.visual.config.ts and its playwright_visual lane, so excluding them from the functional config is intentional scope (it keeps a bare test:ui or the full-pool shard run from executing visual specs without their visual config), not a disabled lane. * fix(web): portal the New Project brand-preview flyout with the popover The earlier DS-picker unclip portaled the popover list but left its companion brand-preview flyout on the old inline `position: absolute` against the trigger wrapper. Once the popover moved to a body portal, the flyout for a finalized `user:<id>` brand was still clipped by the modal body and no longer anchored to the popover. Portal the flyout too and place it beside the popover with the same viewport-aware logic (right of it when the 320px card fits, else flipped left, then clamped), mirroring the popover's up/down vertical anchor. Red spec: e2e/ui/new-project-ds-picker-clipping.test.ts "finalized-brand preview flyout is not clipped by the modal body" — red with the popover portaled but the flyout inline, green with this change. |
||
|
|
3447f60a34 |
fix packaged payload desktop handoff (#5678)
* fix packaged payload desktop handoff * fix beta mac full smoke fixture wiring * fix tools-pack mac payload cold start * test payload desktop update restart * fix Nix daemon launcher protocol build * fix(pack): harden Windows long-jump updates * fix(pack): harden mac long-jump acceptance * fix(handoff): defer armed journal writes until spawn and shutdown succeed Previously executeLegacyPayloadDesktopHandoff wrote the armed handoff journal, attempt, and runtime state before spawning the payload desktop and before the old desktop accepted shutdown. When spawn() threw or the shutdown request failed, the armed journal was stranded on disk, so the next cold start bailed out of prepareLegacyPayloadDesktopHandoff() with reason "already-armed" and the install stayed pinned to the old desktop generation indefinitely. Move the three writes to after both the payload child has spawned and the shutdown has been accepted. The old desktop is still alive while it acks the shutdown, and the payload waits for the old pid to exit before resuming, so the state still lands before the payload reads it. Add regression coverage for both the spawn-failed and shutdown-failed paths, asserting the journal stays "prepared" and a later cold start can retry. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) * fix(updater): await payload spawn acceptance * fix(packaged): advance confirmed handoff recovery --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
6b90486c97 |
docs: reconcile stale documentation with current code (#5829)
* fix(docs): align question guidance with form flow * docs: reconcile stale documentation with current code * fix(docs): remove retired template metadata from guides Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(e2e): stabilize visual workspace helper Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(docs): align localized template guide metadata Generated-By: looper 0.10.6 (runner=fixer, agent=codex) |
||
|
|
94d48f41f2 |
feat(plugins-home): order the template gallery by usage (OPEND-449) (#5106)
* feat(plugins-home): order the template gallery by usage (OPEND-449) Non-prototype facets (Slides, Image, Video, …) now lead by real usage — a trailing 28-day blend of distinct-users + runs from `run_finished`. The Prototype facet keeps its curated editorial order; the default mode-seeds (Web Prototype, Simple Deck) and no-preview / manually-sunk tiles drop to the bottom of their facet and the All view. Ordering lives in one shared comparator (`comparePluginGalleryOrder`) used by the grid and the Home rail; the All view keeps its visual-appeal order with sunk tiles pushed last. A weekly in-repo workflow rebuilds the popularity snapshot from PostHog and opens a self-PR; a committed snapshot ships the ordering immediately. * test(plugins-home): make Live Artifact grouping assertions order-insensitive The rail + Community-section tests hardcoded the curated Live Artifact preset order. Facet order is now usage/sink-driven (OPEND-449), so these grouping/ routing tests assert membership via a sorted comparison instead. --------- Co-authored-by: Scarlett Huang <scarletthuang@Scarletts-MacBook-Air.local> |
||
|
|
6e38058dcf |
feat(web): add agent-assisted home headline (#5699)
* feat(web): add time-aware home greeting * feat(web): add contextual greeting emoji * feat(web): use Agent-focused home headline * fix(web): refine agent-focused home copy * fix(web): keep German home copy informal --------- Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com> |
||
|
|
925db8d2af |
ci: move nix and docker off the merge validation path (#5682)
* ci: move nix and docker off the merge validation path Core ci.yml / Validate workspace / merge_group no longer schedule or require Nix flake check or Docker image builds. Packaging lives on two standalone workflows: nix.yml (flake check) and docker-image.yml (PR smoke build + tag/release publish). Update scopes, topology tests, and agent docs. * fix(ci): detect Docker publish from release inputs, not workflow_call event Reusable workflow callers keep their own github.event_name (usually workflow_dispatch), so matching event_name == workflow_call never fires and stable GHCR publish would silently smoke-build only. Gate publish on tag push or non-empty release_version / publish_latest inputs instead. * fix(ci): keep smoke-only Docker sha tags off the publish path Gate pr-/manual-sha metadata tags on publish != true so release-stable workflow_dispatch callers do not push extra manual-sha-* tags to GHCR. |
||
|
|
37515a4864 | fix docker ghcr public pull verification (#5016) | ||
|
|
d7151b2b60 |
feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads * fix(analytics): initialize attribution routes after HTTP deps * feat(analytics): bridge first-party browser opens * feat(analytics): bridge first-party anchor opens * fix(analytics): make attribution claims idempotent * fix(analytics): record pending attribution results * chore(analytics): bind landing attribution KV * fix(ci): remove landing preview wrangler config override * fix(landing): resolve download attribution function * fix(analytics): harden attribution token consumption * fix(analytics): preserve same-installation claim retries * fix(analytics): merge recovered attribution retries * fix(analytics): preserve attributed browser links * fix(analytics): clear attribution on privacy reset * fix(analytics): bridge stopped AMR console links * fix(ci): deploy attribution schema before Pages * fix(ci): run attribution migrations with Wrangler 4 --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: PerishCode <perishcode@gmail.com> |
||
|
|
d954bee805 | Extract telemetry worker from monorepo (#5565) | ||
|
|
3e09ebf282 |
feat(release): publish localized changelog metadata (#5520)
* feat(release): publish changelog metadata * fix(release): verify release notes from stored bytes Generated-By: looper 0.10.3 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
fc5b276f7d |
feat(ci): surface merge-queue needs-validation ejection as a PR comment (#5519)
A PR ejected from the merge queue by the needs-validation gate leaves no trace on the PR itself: the gate fails on the queue's transient ref, the PR's own checks stay green, and mergeQueueEntry silently goes null. The only way to learn why the PR fell out of the queue is to dig through merge_group workflow runs, which nobody watches (observed on #5329). Producer: the merge_group gate in ci.yml now emits a handoff/comment artifact targeting the labeled PR right before it blocks, and uploads it on the failure path. Consumer: comment.atom.yml accepts merge_group workflow_run events; since a merge_group run's head_sha is the queue's synthetic merge commit, artifacts from those runs are bound to their producing run by run_id, and the base-freshness skip is waived (PRs ahead in the queue move the base while the run completes). The live-PR head check still guards staleness. Topology coverage pins the new boundary: comment.atom consumes pull_request + merge_group; autofix/report stay pull_request-only. |
||
|
|
edb6292f1c |
feat: post-update what's new card on home with release feed highlights (#5071)
* add post-update what's new card on home with release feed highlights
After an update, the home surface shows a one-time bottom-right card with
the release highlights (title, copy, optional image) and a "See what's new"
link. Content ships in the release feed metadata.json as an optional
whatsNew block sourced from tools/release/whats-new/<baseVersion>.json;
versions without highlights fall back to generic copy linking the release
notes. The card shows once per version, keyed on a locally stored
last-seen version, and never on fresh installs.
- packages/contracts: WhatsNewContent/WhatsNewResponse DTOs + analytics props
- daemon: GET /api/whats-new (channel feed fetch, 1h cache, stale/null
degradation) + od whats-new CLI mirror
- web: WhatsNewPopup card (CSS Module, i18n chrome in 19 locales,
surface_view/ui_click tracking), lastSeen decision logic in lib/whats-new
- release tooling: publish-metadata forwards the optional whatsNew block and
fails loudly on malformed files; docs in tools/pack/AGENTS.md and
tools/release/whats-new/README.md
* fix: gate whats-new card on active home view, soften Escape, validate locale overrides
Address review feedback on the post-update what's-new card:
- EntryShell passes `active={view === 'home'}` into WhatsNewPopup, and the
component defers its fetch/show decision (and `page_name: 'home'`
analytics) until Home is the active entry view, so direct routes like
/projects no longer surface the card or emit home-page events.
- The document-level Escape listener now ignores already-handled events
(`defaultPrevented`) and hides the card without marking the version as
seen or firing the dismiss analytics event, so a stray Escape no longer
permanently spends the once-per-version card.
- publish-metadata validates the optional `locales` block at publish time
(plain-object shape, non-empty `title`/`body`, HTTPS `linkUrl`) so
malformed locale overrides fail the publish loudly instead of being
silently dropped by the daemon parser; README documents the contract.
Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)
* fix(release): validate whats-new URLs by parsing, not prefix check
Replace the startsWith("https://") checks in readWhatsNewBlock with a
shared assertHttpsUrl helper that trims, constructs new URL(...), and
requires protocol === "https:", mirroring the daemon readHttpsUrl parser
so values it would silently drop (e.g. "https://") fail the publish
loudly for imageUrl, linkUrl, and locale linkUrl.
Generated-By: looper 0.9.11 (runner=fixer, agent=claude-code)
* fix(web): stop whats-new card fetch race from swallowing the card on first Home load
The card's fetch effect armed its once-guard when the fetch STARTED, and the
effect teardown re-armed it — so React StrictMode's double-invoke (dev) or a
Home toggle mid-fetch cancelled the first fetch, the second effect run saw the
guard still set and bailed, and the resolved-but-cancelled fetch re-armed the
guard without ever showing the card. The card then never appeared until the
Home view toggled again.
Arm the guard only once a decision is actually REACHED (fetch resolved with a
payload); keep the `cancelled` flag purely as a state-update guard that no
longer re-arms the fetch. A superseded fetch drops and the surviving one
records the decision, so the card shows on first Home activation and a genuine
mid-fetch Home exit still retries on the next activation.
Red spec: apps/web/tests/components/WhatsNewPopup.test.tsx renders under
StrictMode and asserts the card appears — red on the previous effect, green now.
* feat: add silent updater preference
* fix: block posthog in beta dry runs
* refactor(whats-new): drive the home card from a dedicated R2 document
Source the post-update "what's new" card from a single hand-curated document
on a dedicated R2 bucket (https://whatsnew.open-design.ai/whats-new.json,
overridable via OD_WHATS_NEW_URL) instead of per-version release metadata.
Operators edit that one file after a release; there is no publish tooling.
- Show-once is keyed on the document's `id`, not the app version: the card
shows whenever the id differs from the last one the client recorded, first
sight on a fresh profile included. This removes the version gating that could
never tell a fresh install from an existing profile upgrading across the
feature boundary (the reviewer's blocking finding) -- content identity
settles it instead.
- Daemon service just fetches the document and parses {id, ...content}; no
channel feed or release-version matching. Graceful degrade to no card on any
failure is preserved.
- Drop the now-dead release publish tooling: tools/release/whats-new/*, the
whatsNew forwarding in publish-metadata.ts, and its tests. Ops flow documented
in docs/whats-new.md.
- Remove the generic no-content fallback card and its two i18n keys; the card
shows only when the document has content.
Contracts, `od whats-new` CLI, and analytics updated to the {version,id,content}
shape.
* style(whats-new): align the home card with the marketing design
Match the reference card: brand serif body copy (var(--serif), same face as
the home hero), body + CTA stacked in a left column with the illustration
stretching to the card's full height on the right so there is no dead space,
a subtle CTA, and a slightly wider, softer card. CSS-module + markup only;
no behavior change (test ids and flow are untouched).
* style(whats-new): drop the title row, float the close over the card
Per design feedback the "Design system sync" label row is removed so the serif
body copy starts at the top edge; the ✕ now floats top-right as a bordered chip
that stays legible over the illustration. The title still labels the dialog via
aria-label for screen readers. Test asserts on the body + dialog name instead.
* style(whats-new): version eyebrow + release title as the card copy
Per design feedback: the top row is the "Open Design x.y.z" version eyebrow
(left) with the close button (right); the release title renders as the main
serif line (1-2 lines) and the illustration stretches to the card height; the
long body is dropped from the card. CTA + release-notes action unchanged.
* fix(whats-new): gate the card to release channels so it can't intrude on CI
The R2 rearchitecture dropped the daemon's channel gate, so development/CI
builds fetched the live document and showed the card on Home. That broke the
P0 "settings dialog reachable" smoke (getByRole('dialog') matched both the
settings modal and the what's-new card, which also carries role="dialog") and
would have dirtied every Home visual baseline. Restore the gate: only
beta/prerelease/preview/stable fetch the document; other channels resolve to no
card and never touch the network. OD_WHATS_NEW_URL still opts any channel in
for e2e fixtures that exercise the card on purpose.
* fix(whats-new): use role="complementary" instead of "dialog" for the toast
The card is a non-modal, dismissible announcement — not a dialog. As
role="dialog" it collided with getByRole('dialog') in the P0 Settings smokes
(settings-dialog-reachable, AMR reopen, Local CLI Codex fallback), which
strict-mode-failed the moment the card rendered on Home. A complementary
landmark is the correct a11y role for supporting content and never matches a
dialog query, so those P0s pass regardless of whether the card is showing. The
daemon channel gate (
|
||
|
|
d5aeab77fc |
fix(ci): push the release-cut baked manifest as a ruleset bypass bot (#5358)
* fix(ci): push the release-cut baked manifest as a ruleset bypass bot bake-plugin-previews-release.yml renders every preview on a release/** cut and pushes the authoritative manifest straight onto the release branch. But release/** is guarded by the 'Protected branches (preview/*, release/v*)' ruleset whose pull_request rule rejects direct pushes — and the workflow pushed with PREVIEW_BAKE_TOKEN||github.token, i.e. github-actions[bot], which is NOT a bypass actor. So the writeback failed with 'GH013: Repository rule violations found for refs/heads/release/vX.Y.Z' (observed on release/v0.14.2), and the release branch's bundled data/plugin-previews/manifest.json was never refreshed — the packaged app then falls back to rendering each preview as a live iframe (pathological for the WebGL example templates). Mint an open-design-bot token (app id 3640364 — already a bypass actor on that ruleset, same App the metrics/blog/contributors workflows use) after the render and push with it via an explicit token URL. persist-credentials: false on checkout so the leftover github.token extraheader can't override the inline bot token on push (same failure mode fixed in #5357). No ruleset change required — the bypass actor already exists; the workflow just wasn't using it. * test(ci): lock the release-cut bake's ruleset-bypass push contract Per .github/AGENTS.md, cross-workflow auth changes must update topology coverage in e2e/tests/packaged-smoke-workflow.test.ts rather than rely on YAML review. Add a companion assertion (alongside the existing bake-plugin-previews.yml one) that locks bake-plugin-previews-release.yml's three co-dependent auth invariants: persist-credentials: false on checkout, the open-design-bot token mint (BOT_APP_CLIENT_ID/BOT_APP_PRIVATE_KEY — the App that is a ruleset bypass actor, not RELEASE_BOT_APP_ID), and the explicit x-access-token:${BOT_TOKEN} push. A refactor dropping any one silently reintroduces the GH013 rejection that stranded release/v0.14.2's manifest. |
||
|
|
6cf1221cde |
fix(ci): push the rolling plugin-preview manifest as the release-bot App (#5357)
The post-merge bake force-pushes the chore/plugin-previews branch with the release-bot App token so the rolling PR's sync is attributed to app/open-design-release-bot and its CI runs automatically — bake-plugin-previews-automerge.yml then approves + enqueues it. But the Checkout step persisted the default GITHUB_TOKEN as an http.https://github.com/.extraheader Authorization header, which overrides the inline App token on push, so the force-push authenticated as github-actions[bot]. A github-actions[bot]-attributed pull_request run is held as action_required (GitHub's bot-recursion gate, 0 jobs, no REST approve for non-fork runs), so the rolling PR's CI never ran without a manual 'Approve and run'. With no green CI the automerge reactor (gated on workflow_run.conclusion == 'success') stayed skipped and the manifest PR sat BLOCKED — main's baked-preview manifest went stale and freshly-baked clips (e.g. the WebGL example templates) never surfaced in the gallery. Set persist-credentials: false so the App token is the sole push credential and the rolling PR is attributed to the App again. Nothing between checkout and the push needs the persisted credential. |
||
|
|
d41cdd9404 |
feat(release): post an immediate "branch cut" Feishu card on both release cuts (#5323)
Cutting a release branch (Tuesday minor via cut-release, Thursday patch via
cut-patch-release) pushes to release/**, which triggers notify-release-feishu to
build a full prerelease before it posts the download card — 20-40 minutes during
which the team has no signal the cut even happened.
Post an eager green "🌿 已切出 release 分支" card the moment the branch exists, from
the cut workflow itself, reusing feishu-notice.ts (node:crypto only, so the
existing setup-node suffices). The card names:
- the branch and whether it is a 大版本 (minor) or 小版本 (patch),
- the exact backport label (`backport release/vX.Y.Z`) so people know which
label to apply for backports,
- a note that the prerelease build is running and a download card will follow.
The download card from notify-release-feishu is unchanged; each cut now yields
an immediate ack card plus the eventual download card. The patch workflow's eager
card only fires on the happy (published) path, alongside the existing skip card.
Topology test asserts both workflows post the eager card after the branch push and
name the backport label.
|
||
|
|
dbc70663aa |
fix(release): let the Thursday patch cut survive an already-matching version (#5320)
The first scheduled cut-patch-release run (2026-07-09) failed at "Create branch + bump version + push" with "nothing to commit, working tree clean". The guard had passed (open-design-v0.14.0 published → happy path) and it computed patch v0.14.1, but main's apps/packaged/package.json was already 0.14.1: in steady state main leads stable by exactly one patch (finalize-release bumps main to X.Y.(Z+1) after X.Y.Z ships), and the Thursday patch equals that same X.Y.(Z+1). So `npm pkg set version=0.14.1` is an idempotent no-op, `git commit -am` finds an empty tree, and under `set -euo pipefail` the step dies before the push — no branch cut, no prerelease, no Feishu card. This collides every Thursday the version already matches, so it is structural, not a one-off. Commit the release marker with `--allow-empty` so the branch is always cut and pushed (which is what triggers notify-release-feishu); a real version bump still commits the change normally. Add a topology assertion that the release commit tolerates an empty tree. Verified locally: with a real `npm pkg set version=<same>` (idempotent, no diff) the old `git commit -am` fails exactly as CI did while `--allow-empty` succeeds with a marker commit; a differing version still commits the bump. |
||
|
|
b0e4f83e85 |
ci(release): Thursday patch-release cut, gated on the Tuesday minor shipping (#5294)
* ci(release): add Thursday patch-release cut gated on the Tuesday minor shipping cut-release cuts a minor every Tuesday (from main HEAD) and, via the release/** push, fires notify-release-feishu to build a prerelease and post a Feishu card. Add cut-patch-release: the same flow one weekday later (Thursday 09:00 Asia/Shanghai) with three deltas: - Bumps the PATCH of the highest release/vX.Y.Z branch (X.Y.(Z+1)), still cut from main HEAD like cut-release. - Gates on this week's minor having actually shipped: the line's minor base X.Y.0 must be a PUBLISHED stable GitHub Release (open-design-vX.Y.0, isDraft=false, isPrerelease=false — i.e. release-stable ran `gh release edit --draft=false --latest`, which rolls the tag back on failure). If it is not published, no branch is cut and nothing is built; instead a Feishu notice card announces the skip. - On the happy path it is otherwise identical to cut-release: create the branch, bump apps/packaged, App-token push (which triggers notify-release-feishu), and create the backport label. feishu-notice.ts is a lightweight sibling of feishu.ts for non-build events (here: the skip card). It reuses the same signed-webhook + retry transport and depends only on node:crypto, so it runs under the existing setup-node with no pnpm install. packaged-smoke-workflow.test.ts locks the guard: Thursday cron + patch bump, the published-stable predicate, and that every branch-cutting step is gated so the skip path posts the notice without cutting or building. * fix(release): gate the patch cut on the final version's minor, not the highest branch The guard target MINOR_BASE was derived from the highest release/v* branch before the manual workflow_dispatch `version=` override was applied. On the auto path V comes from that same branch, so it matched — but a manual `version=0.15.1` while the latest branch is release/v0.14.0 would check open-design-v0.14.0 (the wrong minor), letting the workflow cut release/v0.15.1 even when 0.15.0 has not shipped (and the inverse could wrongly block a valid manual patch on another line). Decide V first (validated override wins, else patch-bump the highest branch), then derive MINOR_BASE from V's own major/minor. Add topology assertions that MINOR_BASE comes from V and never from the highest branch, covering the manual dispatch path. |
||
|
|
088ea9041f |
ci(landing): post a Feishu card after production deploys (#5290)
`landing-page-production` promotes the live site (open-design.ai) but never announces it — the landing/QA group only learns the site changed by noticing. This adds a `notify` job that fires once the deploy settles (on success AND failure) and posts an interactive Feishu card, the production-deploy analog of notify-release-feishu. The card carries: status (green/red), the exact deployed commit + its HEAD subject, who promoted it, their "why promote now?" reason, a live "打开 open-design.ai" button, and the landing-relevant changelog since the previous successful production deploy (non-landing commits on main are filtered out as noise). Reuses FEISHU_LANDING_WEBHOOK with the release webhook as fallback, matching landing-page-daily-feishu. Card generation lives in .github/scripts/landing-page-deploy-feishu.ts with a `self-check` command (asserts copy, sanitization, and the failure/cold-start fallbacks) run as a workflow step before posting. |
||
|
|
390fcf88f3 |
fix(plugin-previews): keep Community gallery previews in sync with shipped plugins (#5262)
* fix(plugin-previews): validate baked preview metadata against shipped plugins The Home Community gallery renders whatever data/plugin-previews/manifest.json names, and nothing kept that metadata in sync with reality: #4815 deleted 19 example plugins but left all 19 manifest entries behind, #4040 shipped a card showing another plugin's imagery, a failed re-bake silently leaves the old clip serving outdated content, and durationMs recorded the intended walk time rather than the encoded file (23 entries carried holdMs > real duration, so the card's idle loop pointed past the end of the clip). - add scripts/check-plugin-preview-manifest.ts to pnpm guard: every manifest entry must name a shipped plugin, own its clip keys (<id>/<hash>/... or the legacy <id>.<hash>....), carry a coherent fingerprint, and never share a clip with another entry; prune the 19 orphaned entries it caught - bake validation (BAKE_VERSION 5): reject blank clips (whole-clip luma range via ffprobe signalstats), record durationMs from the encoded file, clamp holdMs to it, and write a bake-report.json naming skipped/stale/blank plugins instead of swallowing the failure - strict mode (--strict / PREVIEW_STRICT=1) fails the run when a bake left broken metadata behind; the pre-merge validation workflow now runs strict while the post-merge/nightly publishers stay lenient * fix(plugins): make the hyperframes example self-contained The example loaded cdn.tailwindcss.com (a render-blocking head script the page never used — every style is inline) plus Google Fonts. Inside the sandboxed gallery iframe or on a slow network the stalled script showed a long blank instead of the composition, and the CI bake raced the webfonts. Drop both: the page now renders from local system font stacks with zero external requests, so the live preview, the detail iframe, and the baked clip all show the same content. * fix(plugin-previews): address review — fail fast on probe errors, widen strict trigger paths - probeClipMs/clipLumaRange now throw instead of returning null: ffprobe is required validation infrastructure, and a swallowed probe error silently disabled exactly the checks this PR adds. A thrown error becomes an 'error …' skip, is recorded in bake-report.json under 'errors', and fails strict mode — while the routine skips every sweep has (non-html plugins 404ing the preview route) still never trip strict. - bake-plugin-previews-pr.yml now also triggers on plugins/community/**, .github/actions/bake-previews/**, and itself, so community-plugin preview changes and bake-recipe changes cannot merge without the strict pre-merge validation. * fix(plugin-previews): bake static pages as a held still instead of skipping forever The strict pre-merge bake on this PR caught five manifest entries that could NEVER refresh: example-dating-web, example-html-ppt-zhangzara-8-bit-orbit, example-frame-logo-outro, od-new-generation, example-orbit-notion. All five are (near-)static pages — the CDP screencast only delivers frames when something repaints, so they produce 1-4 frames and the old 'frames < 5' guard skipped them on every bake, nightly included, leaving their committed entries permanently stale (their old clips are the 103-210ms degenerate files in the manifest today). Encode <5-frame captures as a still instead: hold the last frame for the idle-loop span (half before the trailing repeated concat entry, half inherited by it, summing to ~HOLD_MS). Only a zero-frame capture is a real failure now. The blank-luma check still rejects stills of nothing. * fix(plugin-previews): never persist a manifest entry without a fingerprint When the preview fingerprint fetch failed, the loop still rendered and — on an otherwise successful bake — persisted an entry with hash: null and un-fingerprinted keys: metadata the content-hash reuse skip can never match again and the new manifest guard rejects. Treat a missing fingerprint as an infrastructure error instead: skip the plugin, record it under errors in bake-report.json (strict mode exits non-zero), and leave any committed entry untouched for the next sweep. --------- Co-authored-by: Tuola Ge <gexingli@refly.ai> |
||
|
|
e0d7a77230 |
fix(release): align stable notes public origin (#5037)
Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
6132da1d79 |
[codex] Restore stable release publish mode (#5031)
* fix(release): restore stable publish dispatch mode * test(e2e): cover stable publish dispatch mode --------- Co-authored-by: Siri-Ray <2667192167@qq.com> |
||
|
|
b2512e9a7e |
ci: block direct PRs into release branches except release automation (#4982)
Colleagues have been opening PRs straight into release/vX.Y.Z (e.g. into release/v0.13.0), bypassing main + the backport flow. Add a guard workflow: on a PR opened/reopened/edited with a release/** base, if the author is not a release-automation bot (open-design-release-bot, github-actions), post a comment explaining the backport flow (merge to main, add `backport release/vX.Y.Z`) and close the PR. Closing (not a failing check) is the only real gate here: release/* branches have no required status checks and no merge queue, so a red check would not block a merge. Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
6c424f4ad5 |
fix(release): populate github.commit in prerelease metadata so the changelog has a baseline (#4960)
The Feishu release card (notify-release-feishu.yml) builds its changelog as `git log <previous>..<current>`, reading <previous> from the published prerelease/latest/metadata.json `.github.commit`. That field is written by githubInfo(), which reads the RELEASE_COMMIT env. release-beta sets RELEASE_COMMIT on every build + publish job; release-prerelease set it on none, so every prerelease published an empty github.commit. Each prerelease then found no baseline and the card always rendered "首个 Prerelease 包,无上个版本可 对比" — the changelog was permanently empty, not just for a fresh release line. Add RELEASE_COMMIT to the four prerelease build jobs and the publish job, mirroring release-beta. Both are required: publish-metadata rejects a per-platform manifest whose github.commit disagrees with the metadata commit, so the build jobs (which write per-platform manifests) and the publish job (which writes the final metadata.json) must stamp the same built commit. Red spec in packaged-smoke-workflow.test.ts asserts each prerelease build + publish job carries RELEASE_COMMIT (red on main, green here). |
||
|
|
3fb3e3ca4a |
ci(release): finalize-release — bump main + drop backport label after a stable ships (#4936)
* ci(release): finalize-release — bump main + drop backport label after a stable ships * ci(release): arm merge queue + request core-maintainer review on the bump PR main enforces require_code_owner_review and the bump touches CODEOWNERS-owned release manifests, so a bot approval can't merge it hands-free (unlike backport-automerge / bake-plugin-previews-automerge, whose targets/diffs avoid code-owner review). Open the PR, arm the squash merge queue (gh pr merge --auto, proven on main by bake-plugin-previews-automerge), and request the @nexu-io/core-maintainers review that the code-owner rule needs. * ci(release): trigger finalize via workflow_run, not release:published release-stable.yml promotes its own draft to published with gh release edit using GITHUB_TOKEN, and a release event minted by GITHUB_TOKEN does not trigger other workflows — so a release:published trigger would never fire. React to release-stable completing instead (the backport-automerge / bake pattern), and gate on the open-design-vX.Y.Z release actually being published (draft=false) so dry-runs (metadata/prepublish) correctly no-op. * ci(release): resolve version from latest published release, not head_branch PerishCode review (blocking): release-stable supports building one ref while dispatched from another via its `ref` input, so workflow_run.head_branch can be the dispatch ref, not the built release branch — parsing it would miss-fire or read the wrong version. Read the version from the latest published release instead (release-stable marks it --latest), which is branch-independent. Drop the head_branch job-if guard; the draft/prerelease/X.Y.Z guards + bump idempotency keep dry-runs a clean no-op. * test(release): pin finalize-release workflow topology PerishCode review (blocking): .github/AGENTS.md asks cross-workflow behavior changes to update the topology tests, not rely on YAML review alone. Add a focused [P2] test in packaged-smoke-workflow.test.ts (alongside the backport / bake automerge coverage) that pins finalize-release's trusted-consumer contract: workflows: [release-stable] + success/dispatch gating, App-token use, version resolved via gh release view (latest/tag) with NO workflow_run.head_branch dependency, draft/prerelease/X.Y.Z + skip handling, forward-only synchronized bump, core-maintainer review request, and SHA-pinned merge-queue arming. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
110774e891 |
fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs (#4898)
* fix(ci): merge-queue needs-validation gate skips non-PR refs, not eject clean PRs The merge_group needs-validation gate enumerates queued PRs from three sources; source 2 scrapes a trailing `(#N)` from each commit subject. That `(#N)` can be an ISSUE reference an author put in the PR title, not a queued PR — e.g. #4833, titled "fix(plugins): ... (#4828)", where 4828 is the fixed issue. The gate then ran `gh pr view 4828`, which fails with "Could not resolve to a PullRequest"; as a plain assignment under `set -e` that ejected a clean, label-free, approved PR from the merge queue every time it entered — permanently unmergeable. Distinguish a definitive "not a PR" (skip — the number was a spurious reference) from a transient API failure (still fail closed → block), so real needs-validation PRs are still enforced while issue references no longer block clean merges. * fix(ci): resolve merge-group needs-validation per commit, not a flat union Addresses review: skipping a non-PR `(#N)` unconditionally could, in a batched merge group, drop the only candidate that would have checked a non-tip PR — so a non-tip PR still carrying `needs-validation` could slip through (#4736 hazard). Resolve each merge-group commit to at least one real PR from its own signals (subject `(#N)`, commits/<sha>/pulls, and — for the tip — the queue ref). A `(#N)` that does not resolve to a PR (e.g. an issue reference like #4833's "(#4828)") is ignored ONLY when its commit is still covered by another candidate that resolves to a real PR; a commit that resolves to no PR fails closed. Real needs-validation PRs are still blocked; clean PRs whose title references an issue are no longer ejected. Verified with a mocked gh harness: (A) tip "(#4828) (#4833)" → ignores 4828, checks 4833, clears; (B) batched non-tip whose only number is an issue ref + carries needs-validation → fails closed; (C) tip with needs-validation → blocks. --------- Co-authored-by: lefarcen <ontf116@gmail.com> |
||
|
|
211ec5df5a |
ci: make the Nix hash check advisory on PRs, enforce it only at merge time (#4896)
* ci: make the Nix hash check advisory on PRs, enforce it only at merge time A stale nix/pnpm-deps.nix hash is the routine, self-healing consequence of any pnpm-lock.yaml change: the nix_validation job already regenerates the hash and hands it to autofix.atom.yml, which pushes the refresh back to the PR branch. Failing the job on pull_request turned that transient, auto-fixed drift into a red check that flipped the PR to BLOCKED and read as a real CI failure to the review bot — the same deadlock the needs-validation gate already documents. The contributor then waited on the autofix round-trip every time the lockfile moved. Now nix_validation stays green on pull_request (the autofix still self-heals the hash in the background) and the hash is enforced as a hard gate only at merge time: merge_group and manual full runs fail closed, so a genuinely broken Nix build can never reach main. The Validate workspace gate ignores a red Nix job on PRs (both the blanket failure scan and the required-jobs check) and keeps requiring it everywhere else. * test(e2e): cover the Nix-advisory-on-PR Validate workspace gate Extract the two jq programs the `validate` job runs (the blanket failure scan and the required-jobs scan) straight out of ci.yml and feed representative `needs` JSON through the real `jq`, so the cross-workflow gate behavior is asserted rather than reviewed by eye: - pull_request + failed nix_validation -> gate passes (advisory), - merge_group / workflow_dispatch full + failed nix_validation -> gate fails closed, - pull_request + another failed required job -> gate still fails (the PR exception is scoped to nix_validation only). A future edit that makes Nix advisory on merge_group, or stops ignoring it on PRs, now fails this test instead of slipping through YAML review. |
||
|
|
cbf3882e7e |
[codex] Reset PR inactivity window on human activity (#4882)
* fix: reset PR inactivity window on human activity * fix: handle commit and failed CI blocker detection |
||
|
|
514ddba8ed |
ci: consolidate runner selection contract (#4803)
* ci: consolidate runner selection contract * test: stabilize connector settings privacy state |
||
|
|
8b9b169673 |
ci: add cost-sensitive runner tiering (#4764)
* test persistent contabo runner poc * test persistent runner web workspace poc * test persistent runner local cache setup * test persistent runner aggressive setup poc * test persistent runner web postinstall profile * test restore stable persistent runner poc * test keep persistent runner web tests default * test route persistent poc to zxiyun runner * test relax web vitest timeout on persistent poc * test harden web vitest cases for persistent runner * test add runner perf probe * test isolate runner perf probe dispatch * test pin runner perf probe node * test expand runner perf probe matrix * ci: route heavy jobs through runner modes * ci: keep web workspace tests on blacksmith * test: update web runner mode assertion * ci: route cost-sensitive runner tiers * ci: move preflight to hosted runner * ci: downshift blacksmith ui runners to 4vcpu * test: fix file viewer timeout after merge * ci: route e2e vitest to hosted default * ci: centralize runner profile selection * test: type runner profile assertions * ci: route e2e vitest to default blacksmith * ci: remove runner experiment probes * test: restore postinstall guard coverage Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex) * test: cover postinstall script contract * test: move postinstall guard to e2e --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
4c6f6549c6 | ci: add landing QA Feishu summary (#4763) | ||
|
|
a4dea37a22 | fix(ci): harden backport automerge provenance (#4761) |