openclaw/test/scripts/plugin-release-git-lifecycle.test.ts
Peter Steinberger 744b327606
test(scripts,ui): remove low-value tests (batch d456) (#182417)
* test(scripts): remove redundant q0041 proof layers

Keep conditional module resolution once in bun-standalone-module-resolution.test.ts. Keep RPC payload precedence in gateway-frame-payload.test.ts and kitchen-sink-rpc-walk.test.ts, and bounded tail reads in e2e-text-file-utils.test.ts.

Replace workflow inventories with generic action pinning and preflight authority checks. Retain executable publication and bootstrap admission cases. Remove computer observation replay covered by computer-tool.observation.test.ts and corruption refusal replay covered by openclaw-state-db.test.ts; keep archive image operations, authorization persistence, and native refusal.

Reduce same-branch dependency retry and rejection variants. No production or helper changes. CI-only validation: plain oxlint, oxfmt, and git diff --check; no tests or builds run. The shard remains below the requested 40 percent line reduction.

* test(scripts): consolidate q0041 runtime proof layers

Remove repeated native package variants and worker owner-behavior replays; share archive, subprocess, manifest and gateway fixtures. Keep executable publication authority and lifecycle error paths, replacing the registry-deferral source literal with evaluated policy cases.

Remaining coverage: bun-standalone-module-resolution.test.ts retains transformed ESM lazy/transitive loading; tsdown-config.test.ts retains relocated worker Bash grammar. exec-authorization-plan.test.ts, exec-control-command-guard.test.ts, sessions/tools/write.test.ts, edit-formatting.test.ts and read.test.ts own the removed behavioral replays. desktop/websocket-runtime.test.ts owns the desktop mode matrix. build-and-run-mac.test.ts retains generic Bash 3 compatibility. security-review-script.test.ts and security-sensitive-guard-script.test.ts retain shared author authority and rejected cleanup writes.

CI-only: no tests/builds/coverage run. Plain oxlint, oxfmt and diff-check passed. Shard 5011 to 3872 lines (22.7% reduction); 40% target remains unmet because the remaining independent release, native and process-lifecycle boundaries have no equivalent keeper.

* test(scripts): deslop q0045 tests

Consolidate build-cache spy cleanup and remove copied build-step inventories and planner self-comparisons. Keep targeted declaration selection, compiler environment, ordering, admission, cache recovery, and artifact ownership assertions.

The prepared-roster resume cases now retain the established-family and exact published-state assertions, replacing the standalone family case and duplicate published detail row. No production or test-support changes. CI-only: formatting, plain oxlint, and diff checks; runtime and coverage proof deferred to hosted CI. Further structural deletion would drop distinct publication authority, process cleanup, or install contracts, so this shard intentionally falls short of the line target.

* test(scripts): consolidate q0045 branch and fixture replays

Replace repeated native-protocol runner wiring with one planner ordering invariant. Keep full-profile compiler-input invalidation and gatewayWatch runtime-stamp proof; remove the copied cache-call inventory.

Prepared-roster restore/readback covers digest and inventory validation. Retained-dispatch recovery owns the no-child outcome. Drop the unused large comparison fixture and same-path prior-attempt, followthrough, and parser variants.

CI-only: plain oxlint, oxfmt, and diff checks. No production or support changes. The 40 percent target remains unmet because the remaining cases cover distinct authority, cache recovery, process lifecycle, and install failures.

* test(scripts): remove q0045 redundant fixture layers

Construct Codex legacy state directly and seed only columns consumed by real SQLite readers. Keep subprocess pin, path, binding-generation and followthrough checks. Drop the copied release-output inventory; successful install validation still enforces the platform contract.

Move direct runCase primary/terminal cleanup diagnostics into the existing profile CLI scenarios, retaining HOME retention, bounded error evidence and no leader fallback. Shared group observation remains covered by managed-child-process.test.ts. Keep native descendant exit/timeout/parent-signal proof.

Share ClawHub HTTP overrides and remove duplicate downstream readback covered by verify-clawhub-published-artifact.test.ts: verifies exact artifact bytes without claiming the publication authentication. Build tests retain cache/publication/staging lifecycle checks; drop wiring inventories already covered by build-all-ownership.test.ts.

CI-only: oxfmt, plain oxlint and diff checks passed; no tests, coverage or builds run. No production/support/routing changes. This is a partial reduction: further whole-layer deletion was not substantiated without losing distinct behavior.

* test(scripts): deslop q0044 tests

Consolidate duplicate postinstall pruning checks and scalar dispatch parser replays; remove repeated build/export and command inventories while retaining executable safety paths.

Named remaining proof: the postinstall authoritative-inventory test retains QA sidecar and unlink checks; full-release-child-reuse.test.ts retains real workflow default integration; update-managed-service-handoff-native.test.ts retains native staging boundaries. Keep all six isolated tooling imports and the publication, migration, recovery and custody failure paths.

CI-only campaign: plain oxlint, oxfmt and diff-check passed. Tests and coverage intentionally not run. Shard LOC 5086 -> 4850 (4.6%); below target because remaining cases protect distinct boundaries.

* test(scripts): remove duplicate q0044 proof layers

Remove PR mode-preflight scenarios duplicated by pr-prepare-preflight.test.ts, generic Git failure/setup replays covered by ci-git-owner.test.ts, and repeated command/source inventories.

Keep release preflight execution in release-preflight.test.ts and mixed-child/admission restoration proof in full-release-validation-state.test.ts and full-release-child-reuse.test.ts. Keep package archive/custody behavior in package-update-activation-lifetime.test.ts and external-helper retirement in package-update-activation-recovery-command.test.ts. Staging identity and claim/lock owner tests retain the removed duplicate rejection paths.

Consolidate packaged-postinstall operator-state preservation into one fixture and CLI invocation, retaining cache/dependency sentinels, sibling symlinks, database bytes, lifecycle completion and published-source classification.

CI-only validation: oxfmt, plain oxlint (exit 0), and diff-check passed; no test/build/coverage execution. Additional 550 lines removed; original shard 5086 -> 4300 (-15.5%), below the requested 40% without dropping distinct contracts.

* test(scripts): consolidate q0044 owner invariants

Replace workflow checkout and job inventories with generic non-vacuous trust, target separation, immutable-artifact and dispatch-authority checks. Remove repeated import-only scaffolding; executable owners remain in release-tooling-identity, release-ci-summary, full-release-validation-state and full-release-candidate-reuse suites.

Remove local-build/CLI flag wiring inventories, generic comparison variants and embedded restart-sentinel CRUD already owned by restart-sentinel.test.ts. Retain local build-failure evidence and detached production-runtime/native/ACL proof. Reduce survivor runner observations to the ordering contract and trim unconsumed schema fixture fields.

CI-only: oxfmt, plain oxlint (exit 0) and diff-check passed; no tests/builds/coverage run. This pass removes 198 lines; original shard 5086 -> 4102 (-19.3%). Forty-percent target remains unmet under distinct-behavior retention.

* test(ui): deslop q0043 tests

Remove redundant E2E account/default/cancellation/masked-answer replays covered by ui/src/pages/profile/model-accounts.test.tsx (recorded outcomes, cancellation, retained default selection, and masked auth steps) and profile-page.test.tsx (identity-refresh single flight, redaction/retry, and refresh overlapping ChatGPT completion). Keep a small connection-context scenario and all authenticated avatar/CSP/revision browser proofs.

Desktop status retry/no-inventory proof remains in desktop-panel-session.test.ts and desktop-document-mode.e2e.test.ts; late status settlement remains in desktop-panel-presentation.test.ts (preserves Disconnect during source lookup across tab switches until Reconnect). Keep real RFB, fullscreen, sizing, clipboard, authority, and credential coverage.

Remove screenshot/video/JSON artifact-only scaffolding; share existing browser lifetime/assertion owners and local progress-response, PNG, and overlap fixtures. No production, support, or routing changes. CI-only: oxfmt, plain oxlint, and diff checks passed; no tests or coverage run. Lines 5088 to 4362 (14.3 percent), below the 40 percent target to preserve distinct browser behaviors.

* test(scripts): deslop q0046 tests

Consolidate duplicate artifact attempt restoration and raw success cases, onboarding assertions, and native app-host success variants. Keep distinct publication, cancellation, Keychain, and process-lifecycle paths.

Remaining owner proof: build-and-run-mac.test.ts covers system-Bash compatibility; ci-workflow-guards.test.ts covers exact native Swift invocation arguments; run-vitest real mjs dependency-order test covers missing dependency guidance.

CI-only campaign: oxfmt, plain oxlint, and diff-check passed. No tests or coverage run. Honest reduction is below the 40 percent target; Linux manifest and preparation suites retain distinct boundary coverage.

* test(scripts): prune duplicate q0046 tooling layers

Retire workflow invocation inventories and redundant process/archive replays; minimize native fixtures and consolidate duplicate Docker prestart setup. Preserve native interruption and indeterminate-output Keychain boundaries.

Retained owners: ci-workflow-guards.test.ts for Swift workflow argv; managed-child-process.test.ts for nested preparation cleanup and generic drainage; plugin-publication-artifact.test.ts for archive digest and ZIP validation; pinned-bun.test.ts, test-extension.test.ts, and run-vitest-progress.test.ts for shared runner policy and lifecycle.

CI-only: oxfmt, plain oxlint, and diff-check passed; no tests or coverage run. Shard is 4880 to 4344 lines, below the requested 40 percent target because remaining cases protect distinct publication, cancellation, offline, and credential lifecycle paths.

* test(scripts): consolidate q0046 runner fixtures

Remove the Gateway inventory replay already covered by test-projects.test.ts: bounds full-suite CI processes for Testbox. Keep watchdog branch representatives while test-projects-routing.test.ts owns watch-option aliases. Retained real-child cache and scheduling tests cover environment forwarding.

Replace app-host fixture source rewriting with explicit host-mode behavior and direct executable copying. Preserve native Keychain lifetime and uncertain-cleanup cases.

CI-only: formatting, plain oxlint, and diff-check passed; no test runs. Shard totals 4880 to 4303 lines; the 40 percent target remains unmet without dropping distinct owner-boundary proof.

* test(ui): consolidate q0043 e2e coverage

Cut repeated fixture, inventory and scenario layers: 5088 original lines to 3052 (40.02%). Keep browser geometry, keyboard upload, authenticated shared images, hover timing, sidebar lifecycle and real noVNC transport proof.

Remaining owner proof: session-menu.test.ts child actions and Tab focus; profile/profile-page.test.tsx protected hero and users.self refresh; identity-avatar-view.test.tsx revision, credential and fallback lifecycle; gateway-store.self-profile.test.ts event/upload revisions; portaled-hovercard.test.ts, portaled-hovercard.browser.test.ts and portaled-hovercard-focus.test.tsx focus and placement; session-hovercard.test.ts PR, attribution and refresh behavior; authenticated-avatar-route.test.ts cached 404 release.

Desktop owners: desktop-panel-sizing.test.ts credential resolution; desktop-panel.test.ts late-node credential lifetime; desktop-panel-session.test.ts source preservation and lookup retry; desktop-panel-presentation.test.ts launch retirement; desktop-panel-credentials.e2e.test.ts and desktop-document-mode.e2e.test.ts credential and routing boundaries.

Settings owners: shell-layout-traits-solid.test.tsx layout publication; cron/view.test.ts shared tabs; config/view.browser.test.tsx section switching; settings-sections-loading.test.ts copy ordering; control-ui-chunking.test.ts lazy routes; config-form-guidance.e2e.test.ts browser-local disclosure.

CI-only: plain oxlint, oxfmt and diff checks passed; no tests run. Only the five shard test files changed. The now-unused credentialsRequired export in shared desktop-panel.test-support.ts is outside this restricted edit scope.

* test(scripts): deslop q0047 tests

Remove readiness duplicates owned by desktop-readiness-proof.test.ts and iOS failure-ordering replay owned by ios-lifecycle-workflow.test.ts. Drop copied workflow inventories while retaining permission, provenance, smoke-before-upload, publication-authority, and auth-selection guards.

* test(scripts): remove redundant q0047 proof layers

Keep CI collection at ci-collect-checks.test.ts and ci-static-step.test.ts, with failing-stripe composition in ci-workflow-planning.test.ts. Keep scanner execution at ci-security-fast-workflow.test.ts, cache ownership at apple-build-cache.test.ts and ci-swift-cache-lifetime.test.ts, and simulator preparation failures at simslim.test.ts. Retain unique native lifecycle and publication gates. Replace the nested logger process with fixed structured input; logger serialization remains owned by src/logging/subsystem.test.ts and logger-plugin-jsonl.test.ts.

* test(scripts): deduplicate q0047 routing and inventories

Drop closeout legacy routing rows covered by package-acceptance-workflow.test.ts (checks failed-parent closeout without app evidence); retain one workflow checksum rejection and full CLI checksum cases. Remove copied Docker topology/tag inventories and redundant iOS summary assertions. Keep distinct publication authority, receipt replay, and lifecycle safety checks.

* test(ui): deslop q0049 tests

Remove assertion-free HTML Count instrumentation and background-work proof JSON bookkeeping; retain publication, sandbox, exact-stop, draft, reload and screenshot-safety assertions.

Drop pending MCP setup replays covered by mcp-app-view-lifecycle.test.ts: initializing view retention, synchronous reconnect retirement, binding changes and explicit restart. Keep board lease, reconnect settlement, layout retention and real frame interaction scenarios.

Drop operator inventory/install replay covered by pages/skills/skills-page.test.tsx (sidebar selection and scoped installation), lib/skills/index.test.ts (legacy install payload/current-scope refresh), pages/devices/view.devices.test.ts and capability-chips.test.ts (inventory/capability rendering). Keep read-only and omitted-method denial plus reviewer approval editing.

CI-only campaign: plain oxlint, oxfmt and diff check passed; no tests, coverage or typecheck run. Shard LOC 4822 -> 4356 (-9.7%); below 40% because remaining scenarios protect distinct browser/security/lifecycle paths.

* test(ui): consolidate q0049 e2e owner coverage

Reduce the shard from 4822 to 2893 lines (-40%). Retain real-Gateway HTML publication, delayed speech, independent child/process Stop and peer crash recovery, plus browser sandbox and authorization boundaries.

Remove history replays covered by input-history.test.ts (refreshes composer recall after leaving a round), chat-pane-history.test.ts (older-page loading, retries and exhaustion), chat-transcript-prepend-anchor.test.ts (measurement and DOM replacement), chat-transcript-touch-prepend.test.tsx, chat-transcript-scroll-ownership.test.tsx and chat-pane-scroll-cache.test.ts. Browser anchor geometry remains in chat-resize-anchor.e2e.test.ts. Remove only the deleted path from the CI owner inventory.

Keep pending-descriptor fencing in session-active-resources; discovery, dismissal, explicit targets and resource retirement remain in chat-pane-active-resources.test.ts and chat-state-resource-layout.test.ts. Browser inline/explicit routing remains in browser-route-handoff.e2e.test.ts and browser-tab-card tests. Board lease replay remains in lib/board/provider.test.ts and board-widget-cell-mcp-app.test.tsx; retained layout remains in chat-pane-sidebar-layout.test.tsx and chat-pane-board.test.tsx. Keep real frame sizing and Inbox focus here.

Approval save replay remains in devices-page.actions.test.tsx and lib/nodes/exec-approvals.test.ts; all administration denial cases stay. Replace the sandbox proxy self-test with narrow HTTP routes. Keep TTS visible-answer/cursor reconciliation and delegate internal snapshot ownership to chat-pane-snapshot-hydration.test.ts and session-snapshot-store.test.ts. Remove screenshot/reporting layers and reuse the existing CLI helper.

CI-only: oxfmt, plain oxlint and diff check passed. No test, coverage, build or typecheck runs. Shared history-pagination helper is intentionally untouched; its now-unused sampleHistoryAnchor/historyMessageCount exports need coordinator cleanup.

* test(parallels): deslop q0050 tests

Trim repeated source inventories while retaining executable smoke cases and safety guards. Windows background behavior remains covered by parallels-npm-update-smoke.test.ts (cleanup, completion controls, and log draining); POSIX background behavior remains covered by the retained runFakePosixBackground cases. Metadata retry behavior remains covered by the executable Windows prerequisite metadata test.

CI-only shard: no tests or coverage executed. The publication admission suite is unchanged because its independent release authority, artifact, worker-isolation, cancellation and byte-limit boundaries do not overlap. This conservative cut is below the requested 40 percent target.

* test(scripts): remove redundant q0050 test layers

Replace Parallels source inventories with minimal safety guards and generated-script contracts; retain shared-parser branches once and shrink host-IP fixtures. MinGit behavior stays in parallels-windows-git.test.ts, and Windows background cleanup/control stays in parallels-npm-update-smoke.test.ts.

Reduce publication npm bootstrap replays to worker success and rejection boundaries. The exact authority matrix remains in full-release-artifact-contract.test.ts: retains only supported plugin absence and rejects pending npm authority. Consolidate source projections into the Android and Windows matrices; retain qualification, isolation, termination, byte limits, action pinning and ignore-scripts guards.

CI-only: plain oxlint, oxfmt and diff-check passed; no tests or coverage run. Original shard 4762 lines to 4288 (10.0 percent reduction), below the requested 40 percent because remaining lifecycle and authority paths are distinct.

* test(scripts): deslop q0048 tests

Remove shared install-policy replays already covered by install-sh tests: linked checkout boundaries, transactional clone, immutable refs, checkout recovery, bounded curl and comma tarball identity. Reuse the existing npm config contract and retain CLI-specific installation, rollback and service guards.

Trim repeated build-admission selector variants covered by test-projects runtime prerequisites and vitest.pattern-file tests. Retain the real CLI build barrier and signal proof plus the distinct batch spawn-rejection path. Keep release publication/security cases, simplify platform fixtures and remove one same-branch retired-alpha input.

CI-only validation: oxfmt, plain oxlint and git diff --check passed. No tests, coverage, builds or Testbox runs. Test LOC 4410 -> 4069 (-7.7%); the 40% target is not reached because further safe layer deletions were not established.

* test(scripts): consolidate q0048 boundary coverage

Replace duplicate installer pnpm and npm retry harnesses with existing prefix-aware contracts. Keep CLI lifecycle and rollback behavior. Remove obsolete disk-backed YAML snapshot checks now that the verified release child uses in-memory module records; retain authority, byte integrity and environment isolation guards.

Keep one native runtime gate per success, failure and signal outcome. Selector spelling and native metadata cases remain covered by test-projects.test.ts, vitest.pattern-file.test.ts, vitest-report-owner.test.ts and vitest-worker-artifacts.batch.test.ts. Terminal failure and peer-drain replays remain covered by vitest-runner-outcome.test.ts; cache reuse by vitest-cache-slots.test.ts. Retain focused admission, unverified joins, preflight identity, Windows lease policy and UI build barriers here.

CI-only: oxfmt, plain oxlint and git diff --check pass. No tests, coverage, Testbox or build commands run. Original shard 4410 -> 3322 lines (-24.7%); this additional commit removes 747 lines. The 40% target remains unmet rather than deleting distinct retained boundaries.

* test(scripts): reduce q0048 fixture and duplicate proof layers

Reuse local installer fixtures and minimize release fixture inventories. Retain distinct release identity, YAML isolation, cancellation, cache lease, and runtime replacement contracts.

Remaining owner proof: vitest-runner-outcome.test.ts covers preparation and terminal lifecycle; run-vitest-bounded.test.ts covers native preparation; ci-run-node-test-shard.test.ts covers Gateway barriers; vitest-shard-timings.test.ts covers timing identity; test-projects.test.ts and test-extension.test.ts cover selection. install-sh.test.ts covers the shared curl download budget.

CI-only: plain oxlint, oxfmt, and git diff --check passed. No tests or coverage run. Original shard 4410 -> 2726 lines (38.19%); remaining distinct boundaries prevent an honest 40% cut.

* test(scripts): deslop q0051 tests

Remove repeated named-boundary parser cases: the whole-store syntax matrix and retained family scan cover the same detector branches. Preserve specialized SDK forwarding, create lifecycle, corpus traversal, export and identity checks.

Share archive process/extraction fixtures and table the three distinct symlink rejection paths without removing cases. CI-only: oxlint, oxfmt and diff checks; no tests or coverage executed. Safe reduction is below the requested 40 percent; retained tooling fixtures cover distinct lifecycle, release and security contracts.

* test(scripts): remove redundant q0051 proof layers

Keep generic wrapper closure checks instead of copied worker inventories. Owner coverage remains in template-registry.test.ts (reads absent templates without writer queueing and recovers on the first mutation), openclaw-state-lease-existing.test.ts (acquires, verifies, renews and releases a timer lease without migrating), and upgrade-survivor-cron-seed.test.ts (bootstraps before publishing migration specimens).

Remove registry self-checks from the worker fixture; keep real source/compiled execution, collection acquisition, IPC and shutdown. Reduce release worker cases to decision paths; consolidate parser acceptance fixtures and retain formatter proof at the failing command boundary. Same-run transport remains guarded by control-ui-locale-refresh.test.ts. CI-only static lint, formatting and diff checks; no tests run. The fixed shard still cannot safely meet 40 percent without removing distinct behavior.

* test(scripts): compact q0051 executable fixtures

Retain every archive rejection branch while sharing minimal member fixtures; producer extraction checks replace its copied tar listing. Reuse wrapper file discovery, locale input mapping and checked subprocess output, and release CLI invocation setup. Locator success retains the redundant legacy worker success proof.

CI-only: oxfmt, plain oxlint and diff checks passed; no tests run. Fixed-shard 40 percent target remains unmet because remaining fixtures exercise distinct release, security, lifecycle and historical bootstrap contracts.

* test(scripts): deslop q0053 tests

Consolidate Windows spawn-spec assertions without dropping default-shell or explicit non-shell cases. Use scoped mock disposal and remove duplicate EIO/expired-budget rows.

Remove successful retry/long-running probes: ordinary success remains covered by the containing-owner registration test; timed strict success and timer cleanup remain covered by applies the strict wall deadline only before output closure (deadline after close). Keep all distinct cleanup, authority, and recovery paths.

Trim repeated invalid SHA/PR spellings while retaining each parser guard. CI-only: oxlint, oxfmt, and diff checks pass; runtime/coverage deferred to hosted CI. The 40 percent LOC target was not safely attainable: 4970 to 4891 lines.

* test(scripts): consolidate q0053 overlapping proof

Remove duplicate captured-output and wall-deadline subprocess fixtures. managed-child-process.drain.test.ts owns actual pipe/child close, timeout cancellation, and cleanup budgets; managed-child-process.output.test.ts retains real pipe-drain and ownership proof. Setup failure remains in the retained actual-output-close matrix; graceful TERM remains in nested cleanup; strict POSIX signal-error retention remains in managed-child-process.tree.test.ts.

Keep extraction/dependency execution proof but drop borrowed CLI, attribution, bypass-import, and publisher smoke checks owned by verify-pr-hosted-gates, pr-worktree-provision, check-changelog-attributions, pr-crabbox-merge-bypass, and pr-crabbox-gate-plan tests. Retain one parser rejection per decision branch and one dirty source per wrapper component class.

No test runs under CI-only policy. Plain oxlint, oxfmt, and diff checks pass. Cumulative shard reduction: 4970 to 4588 lines; 40 percent target remains unmet because remaining fixtures protect distinct recovery, authority, and lifecycle contracts.

* test(scripts): remove q0053 duplicate lifecycle layers

Retire the nested-cleanup matrix and its fixture-only startup probes. The real bounded-launcher test retains shim/implementation custody; times out and kills managed command descendants retains forced tree extinction; shares listeners across parallel inherit commands retains output forwarding. prepare-extension-package-boundary-artifacts.test.ts retains joins stubborn prep descendants, lets aborted sibling descendants drain during kill grace, and preparation CLI joins detached groups.

Replace the escaped-output mode harness with its one unique real sibling-failure scenario, preserving both aggregate errors, parent extinction, surviving escape and retained ownership. managed-child-process.drain.test.ts owns bounded failure for held output and actual-close completion; managed-child-process.output.test.ts retains real descendant pipe drainage. Remove the superseded fixture parameters and fixture-only launch diagnostics with the retired harness.

CI-only: no test runs. Plain oxlint, oxfmt and diff checks pass. Original shard 4970 lines, now 4219; the 40 percent target remains unmet rather than deleting unique security/recovery coverage.

* test: retain distinct campaign proof and repair CI fixtures

* test: preserve worker probe proof and explicit workflow typing
2026-10-24 08:55:00 +00:00

428 lines
14 KiB
TypeScript

import { expect, it } from "vitest";
import { runCiGitStep } from "./ci-git-owner.test-support.js";
// Plugin publication jobs run on Ubuntu. Native Job Object and POSIX owner
// behavior stays in the shared ci-platform-checkout suites.
const posixIt = it.skipIf(process.platform === "win32");
const sha = "a".repeat(40);
const workflowSha = "b".repeat(40);
const releaseTag = "v2026.8.1";
const alphaBranch = "tideclaw/alpha/2026-08-30-1200Z";
const packageDir = "extensions/fixture";
const packageJson = '{"name":"@openclaw/fixture","version":"2026.8.33"}\n';
const monthlyBranch = "extended-stable/2026.8.33";
const mainMiss = { "merge-base --is-ancestor HEAD origin/main": { code: 1 } };
const releaseMiss = {
...mainMiss,
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 0, output: "" },
};
const protectedReleaseEnv = {
RELEASE_PUBLISH_RUN_ATTEMPT: "2",
RELEASE_PUBLISH_RUN_ID: "123",
RELEASE_TAG: releaseTag,
TARGET_SHA: sha,
WORKFLOW_REF: "refs/tags/release-publish/abcdef123456-2",
WORKFLOW_SHA: workflowSha,
};
const preflightEnv = {
PREFLIGHT_ONLY: "true",
NPM_DIST_TAG: "extended-stable",
PUBLISH_SCOPE: "all-publishable",
SOURCE_REF: sha,
};
type PluginMode = keyof typeof modes;
type RunOptions = Partial<Parameters<typeof runCiGitStep>[0]>;
function modeConfig(
registry: "npm" | "clawhub",
step: string,
env: Record<string, string> = {},
job = `preview_plugins_${registry}`,
) {
return { workflow: { file: `.github/workflows/plugin-${registry}-release.yml`, job, step }, env };
}
const modes = {
"clawhub-resolve": modeConfig("clawhub", "Resolve checked-out ref", {
RELEASE_TAG: "",
TARGET_REF: "",
}),
"clawhub-oidc": modeConfig("clawhub", "Validate OIDC source matches workflow ref", {
DRY_RUN: "false",
RELEASE_PUBLISH_RUN_ATTEMPT: "",
RELEASE_PUBLISH_RUN_ID: "",
RELEASE_TAG: "",
TARGET_SHA: sha,
WORKFLOW_REF: "refs/heads/main",
WORKFLOW_SHA: sha,
}),
"clawhub-trust": modeConfig("clawhub", "Validate ref is on a trusted publish branch", {
TRUSTED_PUBLISH_BRANCH: "main",
}),
"npm-resolve": modeConfig("npm", "Resolve checked-out ref"),
"npm-trust": modeConfig("npm", "Validate ref is on a trusted publish branch", {
RELEASE_CANDIDATE_BRANCH: "",
NPM_DIST_TAG: "default",
PREFLIGHT_ONLY: "false",
// Ancestry cases isolate Git policy; tag admission is asserted with it enabled.
REQUIRE_NPM_PUBLISH_ENVIRONMENT: "false",
PREPARED_ARTIFACT: "",
PUBLISH_SCOPE: "selected",
RELEASE_PLUGINS: "",
RELEASE_PUBLISH_RUN_ATTEMPT: "",
RELEASE_PUBLISH_RUN_ID: "",
SOURCE_REF: sha,
TRUSTED_PUBLISHER_PREFLIGHT: "false",
WORKFLOW_REF: "refs/heads/main",
WORKFLOW_SHA: workflowSha,
}),
"npm-preflight-read": modeConfig(
"npm",
"Read exact npm preflight source package",
{ EXTENSION_ID: "fixture", PACKAGE_DIR: packageDir, SOURCE_SHA: sha },
"verify_plugin_npm_preflight",
),
"npm-publish-read": modeConfig(
"npm",
"Read exact npm publication source package",
{ EXTENSION_ID: "fixture", PACKAGE_DIR: packageDir, TARGET_SHA: sha },
"publish_plugins_npm",
),
};
function pluginRun(signal: AbortSignal, mode: PluginMode, options: RunOptions = {}) {
const selected = modes[mode];
return runCiGitStep({
signal,
workflow: selected.workflow,
fetchResults: [],
...options,
env: { ...selected.env, ...options.env },
revisions: { HEAD: sha, ...options.revisions },
});
}
function gitCommands(report: Awaited<ReturnType<typeof pluginRun>>) {
return report.commands.filter(({ tool }) => tool === "git").map(({ args }) => args);
}
posixIt.for([
["clawhub-resolve", `sha=${sha}\n`],
["npm-resolve", `sha=${sha}\n`],
["clawhub-trust", ""],
["npm-trust", ""],
] as const)(
"%s succeeds and publishes its expected output",
{ timeout: 55_000 },
async ([mode, output], { signal }) => {
const report = await pluginRun(signal, mode);
expect(report.code, report.output).toBe(0);
expect(report.githubOutput).toBe(output);
},
);
posixIt.for(
(["npm-preflight-read", "npm-publish-read"] as const).flatMap((mode) =>
[0, 23].map((code) => ({ mode, code })),
),
)(
"$mode admits exact source bytes only after a successful show (exit $code)",
{ timeout: 55_000 },
async ({ mode, code }, { signal }) => {
const output = code ? "{partial" : packageJson;
const report = await pluginRun(signal, mode, {
commandResults: { [`show ${sha}:${packageDir}/package.json`]: { code, output } },
});
expect(report, report.output).toMatchObject({
code,
pluginSourcePackage: code ? "" : packageJson,
});
},
);
posixIt.for([1, 125, 143])(
"ClawHub resolves origin fallback after safely drained ordinary local probe failure %s",
{ timeout: 55_000 },
async (code, { signal }) => {
const report = await pluginRun(signal, "clawhub-resolve", {
env: { TARGET_REF: "release/fixture" },
commandResults: {
"rev-parse --verify --quiet release/fixture^{commit}": { code, output: "" },
},
revisions: { "origin/release/fixture^{commit}": sha },
});
expect(report.code, report.output).toBe(0);
expect(report.githubOutput).toBe(`sha=${sha}\n`);
},
);
posixIt(
"ClawHub release tags retain their second bounded fetch",
async ({ signal }) => {
const report = await pluginRun(signal, "clawhub-resolve", {
env: { RELEASE_TAG: releaseTag },
});
expect(report.code, report.output).toBe(0);
expect(report.fetches).toHaveLength(2);
},
55_000,
);
posixIt.for([0, 1, 125])(
"ClawHub protected tooling binds the exact peeled tag (lookup exit %s)",
{ timeout: 55_000 },
async (code, { signal }) => {
const report = await pluginRun(signal, "clawhub-oidc", {
env: protectedReleaseEnv,
...(code
? { commandResults: { [`rev-parse ${releaseTag}^{commit}`]: { code } } }
: { revisions: { [`${releaseTag}^{commit}`]: sha } }),
});
expect(report.code, report.output).toBe(code ? 1 : 0);
expect(report.output).toContain(
code
? "Plugin ClawHub OIDC publish target is not bound to protected tooling and the exact release tag."
: "Protected release tooling is publishing the exact immutable release tag target.",
);
},
);
posixIt.for(["clawhub-trust", "npm-trust"] as const)(
"%s accepts a release branch only after successful enumeration",
{ timeout: 55_000 },
async (mode, { signal }) => {
const ref = "refs/remotes/origin/release/2026.8.1";
const report = await pluginRun(signal, mode, {
commandResults: {
...mainMiss,
"for-each-ref --format=%(refname) refs/remotes/origin/release": {
code: 0,
output: `${ref}\n`,
},
[`merge-base --is-ancestor HEAD ${ref}`]: { code: 0 },
},
});
expect(report.code, report.output).toBe(0);
},
);
posixIt.for([false, true])(
"npm-trust rejects retired alpha ancestry (preflight=%s)",
{ timeout: 55_000 },
async (preflight, { signal }) => {
const report = await pluginRun(signal, "npm-trust", {
env: {
WORKFLOW_REF: `refs/heads/${alphaBranch}`,
...(preflight ? { PREFLIGHT_ONLY: "true", SOURCE_REF: sha } : {}),
},
...(preflight ? { revisions: { [`${sha}^{commit}`]: sha } } : {}),
commandResults: releaseMiss,
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain(
preflight
? "Plugin npm preflight target must be reachable from main or release/*."
: "Plugin npm publishes must target a commit reachable from main or release/*.",
);
if (preflight) {
expect(report.fetches).toHaveLength(1);
expect(report.output).not.toContain("matching Tideclaw alpha branch");
} else {
expect(report.fetches.some(({ args }) => args.join(" ").includes(alphaBranch))).toBe(false);
}
},
);
posixIt(
"clawhub-trust rejects a retired Tideclaw alpha branch before ancestry admission",
async ({ signal }) => {
const report = await pluginRun(signal, "clawhub-trust", {
env: { TRUSTED_PUBLISH_BRANCH: alphaBranch },
commandResults: {
...releaseMiss,
[`merge-base --is-ancestor HEAD refs/remotes/origin/${alphaBranch}`]: { code: 0 },
},
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain("Alpha releases are retired;");
expect(report.fetches).toEqual([]);
},
55_000,
);
posixIt.for([
["canonical branch", `refs/heads/${monthlyBranch}`, sha, 0],
["main branch", "refs/heads/main", sha, 0],
["moved canonical tip", "refs/heads/main", "c".repeat(40), 1],
["untrusted workflow branch", "refs/heads/topic", sha, 1],
["same-name main tag", "refs/tags/main", sha, 1],
] as const)(
"npm extended-stable preflight: %s",
{ timeout: 55_000 },
async ([_name, workflowRef, branchSha, code], { signal }) => {
const branch = monthlyBranch;
const report = await pluginRun(signal, "npm-trust", {
env: { ...preflightEnv, WORKFLOW_REF: workflowRef },
revisions: {
[`${sha}^{commit}`]: sha,
...(code === 0 ? { [`refs/heads/${branch}`]: sha } : {}),
[`refs/remotes/origin/${branch}`]: branchSha,
},
});
expect(report.code, report.output).toBe(code);
if (code !== 0) {
expect(report.output).toContain("Extended-stable plugin");
}
},
);
const candidateAdmissionCases: Array<{
name: string;
env?: RunOptions["env"];
commands?: RunOptions["commandResults"];
code?: number;
message?: string;
}> = [
{ name: "qualified protected tooling", code: 0 },
{
name: "wrong candidate month",
env: { RELEASE_CANDIDATE_BRANCH: "extended-stable/2026.7.33" },
message: "release_candidate_branch must be extended-stable/2026.8.33",
},
{
name: "mutable main tooling",
env: { WORKFLOW_REF: "refs/heads/main" },
message: "require a protected release-publish/<sha12>-<n> tooling tag",
},
{
name: "tooling outside main",
commands: { [`merge-base --is-ancestor ${workflowSha} origin/main`]: { code: 1 } },
message: "workflow revision is not reachable from current main",
},
{
name: "candidate outside monthly branch",
commands: {
[`merge-base --is-ancestor HEAD refs/remotes/origin/${monthlyBranch}`]: { code: 1 },
},
message: "target must be reachable from extended-stable/2026.8.33",
},
{
name: "tooling ancestry Git failure",
commands: { [`merge-base --is-ancestor ${workflowSha} origin/main`]: { code: 23 } },
code: 23,
},
{
name: "preflight candidate override",
env: { PREFLIGHT_ONLY: "true", REQUIRE_NPM_PUBLISH_ENVIRONMENT: "false" },
message: "preflight must not include release_candidate_branch",
},
{
name: "non-extended candidate override",
env: { NPM_DIST_TAG: "default" },
message: "release_candidate_branch is only valid for extended-stable publication",
},
];
posixIt.for(candidateAdmissionCases)(
"npm canonical candidate admission: $name",
{ timeout: 55_000 },
async ({ env, commands = {}, code = 1, message }, { signal }) => {
const report = await pluginRun(signal, "npm-trust", {
env: {
NPM_DIST_TAG: "extended-stable",
PUBLISH_SCOPE: "all-publishable",
RELEASE_CANDIDATE_BRANCH: "extended-stable/2026.8.33",
REQUIRE_NPM_PUBLISH_ENVIRONMENT: "true",
WORKFLOW_REF: `refs/tags/release-publish/${workflowSha.slice(0, 12)}-123`,
...env,
},
revisions: { [`${sha}^{commit}`]: sha },
commandResults: commands,
});
expect(report.code, report.output).toBe(code);
if (message) {
expect(report.output).toContain(message);
}
},
);
posixIt.for(
(["clawhub-trust", "npm-trust"] as const).flatMap((mode) =>
["merge-base", "for-each-ref"].map((operation) => ({ mode, operation })),
),
)(
"$mode treats $operation failure as terminal",
{ timeout: 55_000 },
async ({ mode, operation }, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults:
operation === "merge-base"
? { "merge-base --is-ancestor HEAD origin/main": { code: 23 } }
: {
...mainMiss,
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 23 },
},
});
expect(report.code, report.output).toBe(23);
expect(gitCommands(report).at(-1)?.[0]).toBe(operation);
},
);
function terminalCase(
mode: PluginMode,
operation: string,
options: Pick<RunOptions, "env" | "commandResults" | "revisions"> = {},
match = `^${operation} `,
) {
return { mode, operation, match, ...options };
}
// The shared ci-git-owner suite owns unhandled Git/setup failures. These sites
// catch GitFailure themselves, so prove owner-fatal failures cannot enter recovery.
const terminalCases = [
terminalCase(
"clawhub-resolve",
"rev-parse",
{
env: { TARGET_REF: "release/fixture" },
revisions: { "origin/release/fixture^{commit}": sha },
},
"^rev-parse --verify --quiet ",
),
terminalCase("clawhub-oidc", "rev-parse", {
env: {
...protectedReleaseEnv,
RELEASE_PUBLISH_RUN_ATTEMPT: "1",
RELEASE_PUBLISH_RUN_ID: "1",
WORKFLOW_REF: "refs/tags/release-publish/abcdef123456-1",
},
}),
terminalCase("clawhub-trust", "merge-base"),
terminalCase("npm-trust", "merge-base"),
];
posixIt.for(
terminalCases.flatMap((entry) =>
(["cleanup-failure", "cancel"] as const).map((failure) =>
Object.assign({}, entry, { failure }),
),
),
)(
"$mode $operation $failure fences every later Git/output/consumer boundary",
{ timeout: 55_000 },
async ({ commandResults, env, failure, match, mode, operation, revisions }, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults,
env,
revisions,
gitFault: { match, code: failure },
});
expect(report.code, report.output).toBe(failure === "cancel" ? 143 : 125);
expect(gitCommands(report).at(-1)?.[0]).toBe(operation);
expect(
gitCommands(report).filter((args) => new RegExp(match).test(args.join(" "))),
).toHaveLength(1);
expect(report.githubOutput).toBe("");
expect(report.commands.some(({ tool }) => ["node", "pnpm"].includes(tool))).toBe(false);
},
);