Merge origin/master into connection grant resolution

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-21 01:55:23 +00:00
commit f5cbb2ac57
66 files changed
+3676 -1602

No files matched your search

+30
View File
@@ -84,6 +84,19 @@ export {
type ResponsibleUserSource,
type OriginatingActor,
} from "./issue-attribution.js";
export {
ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX,
MARKDOWN_ATTACHMENT_CONTENT_TYPES,
MARKDOWN_REVIEW_DOCUMENT_MAX_BYTES,
artifactReviewDocumentKey,
getAttachmentArtifactWorkProductMetadata,
getMarkdownWorkProductAttachmentMetadata,
isArtifactReviewDocumentKey,
isMarkdownArtifactWorkProduct,
isMarkdownAttachmentContent,
workProductIdFromArtifactReviewDocumentKey,
type AttachmentArtifactWorkProductLike,
} from "./markdown-work-products.js";
export {
ISSUE_WRITE_DENIAL_CODES,
describeIssueWriteDenial,
@@ -2510,6 +2523,23 @@ export {
type FeatureTier,
type InstanceFeatureKey,
} from "./feature-catalog.js";
export {
HIDEABLE_GENERAL_SECTIONS,
HIDEABLE_INSTANCE_PAGES,
HIDEABLE_SETTING_KEYS,
SETTINGS_OPERATOR_MANAGED_ERROR_CODE,
UI_ONLY_GENERAL_SECTIONS,
experimentalSettingKey,
hidesExperimentalSetting,
hidesGeneralSection,
hidesInstancePage,
parseHiddenSettingsList,
type HideableExperimentalSetting,
type HideableGeneralSection,
type HideableInstancePage,
type HideableSettingKey,
type ParsedHiddenSettings,
} from "./settings-visibility.js";
// --- Runtime exposure (opt-in Tailscale HTTPS for managed branch runtimes) ---
// PAP-17049 plan, PAP-17050 threat-model verdict. Contract shared across DB,
@@ -0,0 +1,178 @@
import { describe, expect, it } from "vitest";
import {
ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX,
artifactReviewDocumentKey,
getAttachmentArtifactWorkProductMetadata,
getMarkdownWorkProductAttachmentMetadata,
isArtifactReviewDocumentKey,
isMarkdownArtifactWorkProduct,
isMarkdownAttachmentContent,
workProductIdFromArtifactReviewDocumentKey,
} from "./markdown-work-products.js";
const ATTACHMENT_ID = "00000000-0000-4000-8000-000000000001";
const WORK_PRODUCT_ID = "11111111-2222-4333-8444-555555555555";
function attachmentMetadata(overrides: Record<string, unknown> = {}) {
const contentPath = `/api/attachments/${ATTACHMENT_ID}/content`;
return {
attachmentId: ATTACHMENT_ID,
contentType: "text/markdown",
byteSize: 128,
contentPath,
openPath: contentPath,
downloadPath: `${contentPath}?download=1`,
originalFilename: "notes.md",
...overrides,
};
}
function workProduct(overrides: Record<string, unknown> = {}) {
return {
type: "artifact" as const,
provider: "paperclip",
metadata: attachmentMetadata(),
...overrides,
};
}
describe("isMarkdownAttachmentContent", () => {
it.each([
"text/markdown",
"text/x-markdown",
"application/markdown",
"application/x-markdown",
])("accepts %s regardless of filename", (contentType) => {
expect(isMarkdownAttachmentContent({ contentType, originalFilename: null })).toBe(true);
});
it("ignores MIME parameters and case", () => {
expect(
isMarkdownAttachmentContent({
contentType: "Text/Markdown; charset=utf-8",
originalFilename: null,
}),
).toBe(true);
});
it.each([
"text/plain",
"application/octet-stream",
"binary/octet-stream",
"application/x-binary",
])("accepts a .md filename when the content type is %s", (contentType) => {
expect(isMarkdownAttachmentContent({ contentType, originalFilename: "README.md" })).toBe(true);
});
it("accepts a .markdown filename with a generic content type", () => {
expect(
isMarkdownAttachmentContent({
contentType: "application/octet-stream",
originalFilename: "guide.markdown",
}),
).toBe(true);
});
it("rejects .mdx files", () => {
expect(
isMarkdownAttachmentContent({
contentType: "application/octet-stream",
originalFilename: "page.mdx",
}),
).toBe(false);
expect(
isMarkdownAttachmentContent({ contentType: "text/plain", originalFilename: "page.mdx" }),
).toBe(false);
});
it("rejects markdown filenames with non-generic content types", () => {
expect(
isMarkdownAttachmentContent({ contentType: "text/html", originalFilename: "notes.md" }),
).toBe(false);
});
it("rejects non-markdown filenames with generic content types", () => {
expect(
isMarkdownAttachmentContent({ contentType: "text/plain", originalFilename: "notes.txt" }),
).toBe(false);
});
it("rejects a missing content type", () => {
expect(isMarkdownAttachmentContent({ contentType: null, originalFilename: "notes.md" })).toBe(
false,
);
});
});
describe("getAttachmentArtifactWorkProductMetadata", () => {
it("returns canonical metadata for a valid paperclip artifact work product", () => {
const metadata = getAttachmentArtifactWorkProductMetadata(workProduct());
expect(metadata?.attachmentId).toBe(ATTACHMENT_ID);
});
it("returns null for non-artifact work products", () => {
expect(getAttachmentArtifactWorkProductMetadata(workProduct({ type: "document" }))).toBeNull();
});
it("returns null for non-paperclip providers", () => {
expect(getAttachmentArtifactWorkProductMetadata(workProduct({ provider: "github" }))).toBeNull();
});
it("returns null when metadata is missing or non-canonical", () => {
expect(getAttachmentArtifactWorkProductMetadata(workProduct({ metadata: null }))).toBeNull();
expect(
getAttachmentArtifactWorkProductMetadata(
workProduct({
metadata: attachmentMetadata({ openPath: "https://evil.example/content" }),
}),
),
).toBeNull();
});
});
describe("getMarkdownWorkProductAttachmentMetadata", () => {
it("returns metadata only when the attachment is markdown-eligible", () => {
expect(getMarkdownWorkProductAttachmentMetadata(workProduct())).not.toBeNull();
expect(
getMarkdownWorkProductAttachmentMetadata(
workProduct({
metadata: attachmentMetadata({ contentType: "application/pdf", originalFilename: "a.pdf" }),
}),
),
).toBeNull();
});
it("backs the boolean eligibility helper", () => {
expect(isMarkdownArtifactWorkProduct(workProduct())).toBe(true);
expect(
isMarkdownArtifactWorkProduct(
workProduct({
metadata: attachmentMetadata({ contentType: "image/png", originalFilename: "a.png" }),
}),
),
).toBe(false);
});
});
describe("artifact review document keys", () => {
it("builds a deterministic key that fits the 64-character document key limit", () => {
const key = artifactReviewDocumentKey(WORK_PRODUCT_ID);
expect(key).toBe(`artifact-review-${WORK_PRODUCT_ID}`);
expect(key.length).toBeLessThanOrEqual(64);
expect(key).toMatch(/^[a-z0-9][a-z0-9_-]*$/);
});
it("identifies proxy review-document keys", () => {
expect(isArtifactReviewDocumentKey(artifactReviewDocumentKey(WORK_PRODUCT_ID))).toBe(true);
expect(isArtifactReviewDocumentKey("plan")).toBe(false);
expect(isArtifactReviewDocumentKey("notes")).toBe(false);
expect(isArtifactReviewDocumentKey(ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX)).toBe(false);
});
it("round-trips the work product id", () => {
expect(
workProductIdFromArtifactReviewDocumentKey(artifactReviewDocumentKey(WORK_PRODUCT_ID)),
).toBe(WORK_PRODUCT_ID);
expect(workProductIdFromArtifactReviewDocumentKey("plan")).toBeNull();
});
});
@@ -0,0 +1,99 @@
import type {
AttachmentArtifactWorkProductMetadata,
IssueWorkProduct,
} from "./types/work-product.js";
import { attachmentArtifactWorkProductMetadataSchema } from "./validators/work-product.js";
/**
* Shared classification for bridging attachment-backed Markdown work products
* into the issue document review experience. Server materialization and UI
* presentation must agree on eligibility, so both sides use these helpers.
*/
export const MARKDOWN_ATTACHMENT_CONTENT_TYPES = [
"text/markdown",
"text/x-markdown",
"application/markdown",
"application/x-markdown",
] as const;
const MARKDOWN_ATTACHMENT_CONTENT_TYPE_SET = new Set<string>(MARKDOWN_ATTACHMENT_CONTENT_TYPES);
const MARKDOWN_FALLBACK_CONTENT_TYPES = new Set([
"text/plain",
"application/octet-stream",
"binary/octet-stream",
"application/x-binary",
]);
const MARKDOWN_FILENAME_EXTENSIONS = [".md", ".markdown"];
/**
* Maximum attachment size eligible for review-document materialization.
* Matches the issue-document body limit: a UTF-8 payload of at most this many
* bytes always fits the 524288-character body cap.
*/
export const MARKDOWN_REVIEW_DOCUMENT_MAX_BYTES = 512 * 1024;
export const ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX = "artifact-review-";
function normalizeAttachmentContentType(contentType: string | null | undefined): string {
return (contentType ?? "").toLowerCase().split(";")[0]?.trim() ?? "";
}
export function isMarkdownAttachmentContent(input: {
contentType: string | null | undefined;
originalFilename?: string | null;
}): boolean {
const contentType = normalizeAttachmentContentType(input.contentType);
if (MARKDOWN_ATTACHMENT_CONTENT_TYPE_SET.has(contentType)) return true;
const filename = (input.originalFilename ?? "").toLowerCase();
if (!MARKDOWN_FILENAME_EXTENSIONS.some((extension) => filename.endsWith(extension))) {
return false;
}
return MARKDOWN_FALLBACK_CONTENT_TYPES.has(contentType);
}
export type AttachmentArtifactWorkProductLike = Pick<
IssueWorkProduct,
"type" | "provider" | "metadata"
>;
export function getAttachmentArtifactWorkProductMetadata(
workProduct: AttachmentArtifactWorkProductLike,
): AttachmentArtifactWorkProductMetadata | null {
if (workProduct.type !== "artifact" || workProduct.provider !== "paperclip") return null;
if (!workProduct.metadata) return null;
const parsed = attachmentArtifactWorkProductMetadataSchema.safeParse(workProduct.metadata);
return parsed.success ? parsed.data : null;
}
export function getMarkdownWorkProductAttachmentMetadata(
workProduct: AttachmentArtifactWorkProductLike,
): AttachmentArtifactWorkProductMetadata | null {
const metadata = getAttachmentArtifactWorkProductMetadata(workProduct);
if (!metadata) return null;
return isMarkdownAttachmentContent(metadata) ? metadata : null;
}
export function isMarkdownArtifactWorkProduct(
workProduct: AttachmentArtifactWorkProductLike,
): boolean {
return getMarkdownWorkProductAttachmentMetadata(workProduct) !== null;
}
export function artifactReviewDocumentKey(workProductId: string): string {
return `${ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX}${workProductId}`;
}
export function isArtifactReviewDocumentKey(key: string): boolean {
return (
key.startsWith(ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX) &&
key.length > ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX.length
);
}
export function workProductIdFromArtifactReviewDocumentKey(key: string): string | null {
if (!isArtifactReviewDocumentKey(key)) return null;
return key.slice(ARTIFACT_REVIEW_DOCUMENT_KEY_PREFIX.length);
}
@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";
import { INSTANCE_FEATURE_KEYS } from "./feature-catalog.js";
import {
HIDEABLE_GENERAL_SECTIONS,
HIDEABLE_SETTING_KEYS,
UI_ONLY_GENERAL_SECTIONS,
experimentalSettingKey,
hidesExperimentalSetting,
hidesGeneralSection,
hidesInstancePage,
parseHiddenSettingsList,
} from "./settings-visibility.js";
import { instanceGeneralSettingsSchema } from "./validators/instance.js";
describe("hideable setting keys", () => {
it("derives one key per experimental flag and has no duplicates", () => {
const experimental = HIDEABLE_SETTING_KEYS.filter(
(key) => key.startsWith("instance.experimental."),
);
expect(experimental).toEqual(INSTANCE_FEATURE_KEYS.map(experimentalSettingKey));
expect(new Set(HIDEABLE_SETTING_KEYS).size).toBe(HIDEABLE_SETTING_KEYS.length);
});
it("maps field-backed general sections onto real general-settings fields", () => {
const generalFields = Object.keys(instanceGeneralSettingsSchema.shape);
const uiOnly = new Set<string>(UI_ONLY_GENERAL_SECTIONS);
for (const section of HIDEABLE_GENERAL_SECTIONS) {
if (uiOnly.has(section)) continue;
expect(generalFields).toContain(section.slice("instance.general.".length));
}
});
});
describe("parseHiddenSettingsList", () => {
it("returns nothing hidden for undefined or empty input", () => {
expect(parseHiddenSettingsList(undefined)).toEqual({ hidden: [], unknown: [] });
expect(parseHiddenSettingsList(" , ,")).toEqual({ hidden: [], unknown: [] });
});
it("splits known from unknown keys, trims, and deduplicates", () => {
const parsed = parseHiddenSettingsList(
"instance.plugins, instance.general.backupRetention ,instance.bogus,instance.plugins,instance.experimental.enableEnvironments",
);
expect(parsed.hidden).toEqual([
"instance.plugins",
"instance.general.backupRetention",
"instance.experimental.enableEnvironments",
]);
expect(parsed.unknown).toEqual(["instance.bogus"]);
});
});
describe("membership helpers", () => {
const hidden = new Set(
parseHiddenSettingsList(
"instance.plugins,instance.general.censorUsernameInLogs,instance.experimental.enableEnvironments",
).hidden,
);
it("answers page, section, and experimental membership", () => {
expect(hidesInstancePage(hidden, "instance.plugins")).toBe(true);
expect(hidesInstancePage(hidden, "instance.adapters")).toBe(false);
expect(hidesGeneralSection(hidden, "instance.general.censorUsernameInLogs")).toBe(true);
expect(hidesGeneralSection(hidden, "instance.general.backupRetention")).toBe(false);
expect(hidesExperimentalSetting(hidden, "enableEnvironments")).toBe(true);
expect(hidesExperimentalSetting(hidden, "enableIsolatedWorkspaces")).toBe(false);
});
it("treats a hidden Experimental page as hiding every toggle", () => {
const pageHidden = new Set(parseHiddenSettingsList("instance.experimental").hidden);
expect(hidesExperimentalSetting(pageHidden, "enableEnvironments")).toBe(true);
});
});
+136
View File
@@ -0,0 +1,136 @@
import { INSTANCE_FEATURE_KEYS, type InstanceFeatureKey } from "./feature-catalog.js";
/**
* Operator-configurable settings visibility.
*
* A hosting operator (a managed cloud, an internal shared server) can hide
* instance-settings surfaces that do not apply to their deployment by setting
* the `PAPERCLIP_HIDDEN_SETTINGS` environment variable to a comma-separated
* list of keys from this registry. Hiding a surface removes it from the UI
* (nav, routes, page sections). Surfaces backed by instance-level mutation
* routes are also floored with a 403 carrying
* `SETTINGS_OPERATOR_MANAGED_ERROR_CODE`: the Access, Plugins, and Adapters
* pages, every field-backed General section, and every experimental toggle
* (individually or via the whole Experimental page).
*
* Nothing is hidden by default: with the variable unset, UI and API behave
* exactly as before this mechanism existed.
*
* Unknown keys are ignored (with a server-side warning) rather than rejected,
* so an operator may roll one list across a fleet of mixed app versions: an
* image that predates a key simply keeps that surface visible instead of
* refusing to boot.
*/
/**
* Instance settings pages that can be hidden (nav entry + route). The General
* page is deliberately not hideable: it is the settings root and the redirect
* target for hidden pages. Individual General sections are hideable below.
*/
export const HIDEABLE_INSTANCE_PAGES = [
"instance.profile",
"instance.environments",
"instance.access",
"instance.heartbeats",
"instance.experimental",
"instance.plugins",
"instance.adapters",
] as const;
export type HideableInstancePage = (typeof HIDEABLE_INSTANCE_PAGES)[number];
/**
* Sections of Instance → General that can be hidden. Field-backed sections
* (their suffix names a general-settings field) also floor writes to that
* field; `deploymentStatus` and `signOut` are read-only UI with no field.
*/
export const HIDEABLE_GENERAL_SECTIONS = [
"instance.general.deploymentStatus",
"instance.general.censorUsernameInLogs",
"instance.general.keyboardShortcuts",
"instance.general.backupRetention",
"instance.general.feedbackDataSharingPreference",
"instance.general.signOut",
] as const;
export type HideableGeneralSection = (typeof HIDEABLE_GENERAL_SECTIONS)[number];
/** General sections that are informational UI only, with no settings field. */
export const UI_ONLY_GENERAL_SECTIONS = [
"instance.general.deploymentStatus",
"instance.general.signOut",
] as const satisfies readonly HideableGeneralSection[];
export type HideableExperimentalSetting = `instance.experimental.${InstanceFeatureKey}`;
/** The visibility key for an experimental toggle; every boolean flag is hideable. */
export function experimentalSettingKey(key: InstanceFeatureKey): HideableExperimentalSetting {
return `instance.experimental.${key}`;
}
export type HideableSettingKey =
| HideableInstancePage
| HideableGeneralSection
| HideableExperimentalSetting;
/** Every key `PAPERCLIP_HIDDEN_SETTINGS` accepts. */
export const HIDEABLE_SETTING_KEYS: readonly HideableSettingKey[] = [
...HIDEABLE_INSTANCE_PAGES,
...HIDEABLE_GENERAL_SECTIONS,
...INSTANCE_FEATURE_KEYS.map(experimentalSettingKey),
];
/** Stable 403 code for writes to operator-hidden settings. */
export const SETTINGS_OPERATOR_MANAGED_ERROR_CODE = "settings_operator_managed";
export interface ParsedHiddenSettings {
/** Recognized keys, deduplicated, in input order. */
hidden: HideableSettingKey[];
/** Unrecognized entries, for the caller to warn about. */
unknown: string[];
}
/** Parse a `PAPERCLIP_HIDDEN_SETTINGS`-style comma-separated list. */
export function parseHiddenSettingsList(raw: string | undefined): ParsedHiddenSettings {
const hidden: HideableSettingKey[] = [];
const unknown: string[] = [];
if (!raw) return { hidden, unknown };
const known = new Set<string>(HIDEABLE_SETTING_KEYS);
const seen = new Set<string>();
for (const part of raw.split(",")) {
const key = part.trim();
if (!key || seen.has(key)) continue;
seen.add(key);
if (known.has(key)) {
hidden.push(key as HideableSettingKey);
} else {
unknown.push(key);
}
}
return { hidden, unknown };
}
export function hidesInstancePage(
hidden: ReadonlySet<string>,
page: HideableInstancePage,
): boolean {
return hidden.has(page);
}
export function hidesGeneralSection(
hidden: ReadonlySet<string>,
section: HideableGeneralSection,
): boolean {
return hidden.has(section);
}
/**
* Whether a toggle is hidden, either individually or because the whole
* Experimental page is hidden.
*/
export function hidesExperimentalSetting(
hidden: ReadonlySet<string>,
key: InstanceFeatureKey,
): boolean {
return hidden.has("instance.experimental") || hidden.has(experimentalSettingKey(key));
}