Files
PaperClipAI/server/src/__tests__/instance-settings-cloud-defaults.test.ts
T
DottaandPaperclip 9077748894 Remove the Pipelines experiment and preserve its data (#16834)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Pipelines was an unused experiment with its own UI, API, CLI, and
automation.
> - Keeping it leaves unsupported product surfaces and extra task-detail
queries.
> - Removing the experiment must preserve data that users already saved.
> - This pull request removes the feature and keeps its database schema
and migration history.
> - Cases and Routines remain available as separate features.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Remove the unused Pipelines experiment across the application.

**Current behavior**

The experiment includes pipeline boards, stage settings, case
automation, review queues, learnings, permissions, and CLI commands.
Task detail also queries pipeline links.

**Proposed behavior**

Remove all these product surfaces and their implementation. Preserve
existing tables, records, and documents. Ignore the retired boolean in
older managed configurations so upgrades still start.

**Breaking changes**

Pipeline URLs, API endpoints, CLI commands, and `pipelines:write` are
removed. Task detail no longer includes the old `linkedCases` field. The
independent Cases API remains available. No database migration or
cleanup is included.

Related feature work: #7907, #9110, #10905, #11375, #13004, #14671,
#16133. Related issues: #9663, #10310, #10312. This removal supersedes
the feature; it does not implement those pending enhancements or delete
their saved data.

## What Changed

- Remove Pipeline UI pages, navigation, gates, clients, query keys,
helpers, and unused design tokens.
- Remove Pipeline REST routes, services, automation dispatch,
task-detail queries, and OpenAPI discovery.
- Remove the CLI commands, shared contracts, experimental flag, catalog
entry, and permission defaults.
- Remove feature-specific tests, tutorials, smoke scripts, maps, and
Storybook examples.
- Preserve database schema, migration history, and the reserved document
key used by old records.
- Keep Cases routes independent and cover obsolete stored and managed
settings.

## Verification

- Passed: `pnpm build` and `pnpm -r typecheck`.
- Passed: targeted settings, managed configuration, sidebar, settings
UI, routing, and feature-catalog tests. A first sidebar run caught a
shared Workspaces icon import; the import was restored and the sidebar
rerun passed.
- Passed: 40 tests covering Cases routes, OpenAPI discovery, retained
Pipeline table constraints, and schema snapshot drift.
- Passed: token gates, map validation, generated API metadata check,
Storybook catalog validation, and 28 map/catalog tests.
- Passed: isolated local browser verification of navigation and
Experimental settings with Pipelines absent; Cases and Routines still
open normally. Live GET/POST requests to retired Pipeline endpoints
return 404, and the retired setting is absent. CLI help has no Pipeline
commands.
- Passed: 22 access-service and agent-default-grant tests after
restoring unrelated permission assertions flagged in review.
- Local full run: `pnpm test:run` reported one failure in the unchanged
workspace-runtime port-allocation test. That test passed in an isolated
rerun. The long local run was stopped after full CI passed; it is not
reported as a complete local pass.
- Passed: all 56 checks on `def9181f3a4491d95fca1d38e9739f5b821aae3e` (2
conditional checks skipped), including build, typecheck, test shards,
and browser suites. Eleven interrupted CI jobs reported “The operation
was canceled”; their one-time retry passed. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/39255211006).
- Greptile: 5/5 on the final commit, with the only review finding fixed
and resolved.
- Sandbox socket restrictions affected the first build/API test
attempts. The same commands ran with local sockets available.

## Risks

- Existing Pipeline clients must stop using the removed endpoints and
commands.
- Existing data remains in the database and backups, but the removed UI
no longer displays it.
- Historical boolean `enablePipelines` values are ignored. They cannot
restore the feature.
- Cases and Routines must retain their separate behavior. Targeted route
tests cover this boundary.

## Model Used

OpenAI Codex, GPT-6, with repository inspection, editing, shell
execution, and browser verification. The exact deployed model ID and
context-window size are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-19 08:09:46 -05:00

135 lines
5.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { INSTANCE_FEATURE_CATALOG } from "@paperclipai/shared";
import {
applyCloudCatalogDefaults,
applyExperimentalSettingsPatch,
applyManagedExperimentalOverlay,
normalizeExperimentalSettings,
stripCloudCatalogDefaultEchoes,
} from "../services/instance-settings.js";
import type { ManagedInstanceConfig } from "../services/managed-config.js";
function managedConfig(features: ManagedInstanceConfig["features"] = {}): ManagedInstanceConfig {
return {
v: 1,
mode: "cloud",
catalogVersion: "test",
features,
plugins: { autoInstall: [] },
environments: [],
};
}
describe("applyCloudCatalogDefaults", () => {
it("pins the catalog so this rule has something to guard", () => {
// The rule exists for flags that default on for self-hosted and off for
// Cloud. If that set ever empties, the helper is dead code and should go.
const guarded = Object.entries(INSTANCE_FEATURE_CATALOG)
.filter(([, entry]) => entry.selfHostedDefault === true && entry.cloudDefault === false)
.map(([key]) => key);
expect(guarded).toContain("enableNativeRunner");
});
it("leaves self-hosted instances on the schema default", () => {
const experimental = applyCloudCatalogDefaults(normalizeExperimentalSettings({}), {}, null);
expect(experimental.enableNativeRunner).toBe(true);
});
it("re-asserts the Cloud default when the tenant row and the overlay omit the flag", () => {
const experimental = applyCloudCatalogDefaults(
normalizeExperimentalSettings({}),
{},
managedConfig(),
);
expect(experimental.enableNativeRunner).toBe(false);
// Flags with matching defaults are untouched.
expect(experimental.enableStreamlinedUi).toBe(true);
});
it("keeps an explicit tenant value", () => {
const raw = { enableNativeRunner: true };
const experimental = applyCloudCatalogDefaults(
normalizeExperimentalSettings(raw),
raw,
managedConfig(),
);
expect(experimental.enableNativeRunner).toBe(true);
});
it("lets a managed feature value win through the overlay", () => {
const config = managedConfig({ enableNativeRunner: true });
const { experimental } = applyManagedExperimentalOverlay(
applyCloudCatalogDefaults(normalizeExperimentalSettings({}), {}, config),
config,
);
expect(experimental.enableNativeRunner).toBe(true);
});
it("does not touch flags whose Cloud default is the enabled one", () => {
// enableOwnerInstanceAdmin defaults off for self-hosted and on for Cloud.
// That direction is resolved elsewhere; this helper must not flip it.
const experimental = applyCloudCatalogDefaults(
normalizeExperimentalSettings({}),
{},
managedConfig(),
);
expect(experimental.enableOwnerInstanceAdmin).toBe(false);
});
});
describe("stripCloudCatalogDefaultEchoes", () => {
/** What `updateExperimental` would persist for a given row and patch. */
function persisted(rawStored: unknown, patch: Record<string, unknown>, config: ManagedInstanceConfig | null) {
return stripCloudCatalogDefaultEchoes(
rawStored,
patch,
applyExperimentalSettingsPatch(rawStored, patch),
config,
) as Record<string, unknown>;
}
/** What a later read of that persisted row shows. */
function readBack(stored: Record<string, unknown>, config: ManagedInstanceConfig | null) {
return applyManagedExperimentalOverlay(
applyCloudCatalogDefaults(normalizeExperimentalSettings(stored), stored, config),
config,
).experimental;
}
it("does not persist the self-hosted default on Cloud during an unrelated write", () => {
const config = managedConfig();
const stored = persisted({}, { enableStatusCards: true }, config);
expect(stored.enableStatusCards).toBe(true);
expect("enableNativeRunner" in stored).toBe(false);
// The Cloud default still applies on the next read.
expect(readBack(stored, config).enableNativeRunner).toBe(false);
});
it("treats a full-GET echo of the Cloud default as no choice", () => {
const config = managedConfig();
const stored = persisted({}, { enableNativeRunner: false, enableStatusCards: true }, config);
expect("enableNativeRunner" in stored).toBe(false);
expect(readBack(stored, config).enableNativeRunner).toBe(false);
});
it("persists an explicit Cloud opt-in", () => {
const config = managedConfig();
const stored = persisted({}, { enableNativeRunner: true }, config);
expect(stored.enableNativeRunner).toBe(true);
expect(readBack(stored, config).enableNativeRunner).toBe(true);
});
it("keeps a stored tenant value across unrelated writes", () => {
const config = managedConfig();
const stored = persisted({ enableNativeRunner: true }, { enableStatusCards: true }, config);
expect(stored.enableNativeRunner).toBe(true);
expect(readBack(stored, config).enableNativeRunner).toBe(true);
});
it("leaves the whole normalized object in place for self-hosted rows", () => {
const stored = persisted({}, { enableStatusCards: true }, null);
expect(stored.enableNativeRunner).toBe(true);
expect(stored).toEqual(applyExperimentalSettingsPatch({}, { enableStatusCards: true }));
});
});