feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,268 @@
import { createHash } from 'node:crypto';
import {
normalizePluginPackageLifecycleImpact,
type PluginPackageLifecycleImpact,
} from './pluginPackageLifecycle';
import type { SecuritySubject } from '../../security/security';
export const PLUGIN_PACKAGE_LIFECYCLE_PLAN_SCHEMA =
'qinglong/plugin-package-lifecycle-plan@v1' as const;
export const MAX_PLUGIN_PACKAGE_LIFECYCLE_PLAN_LIFETIME_MS = 15 * 60 * 1000;
export const MAX_PLUGIN_PACKAGE_LIFECYCLE_PLAN_BYTES = 96 * 1024;
export interface PluginPackageLifecyclePlan {
readonly schema: typeof PLUGIN_PACKAGE_LIFECYCLE_PLAN_SCHEMA;
readonly actionRef: string;
readonly impact: Readonly<PluginPackageLifecycleImpact>;
readonly requestedBy: Readonly<SecuritySubject>;
readonly plannedAtMs: number;
readonly expiresAtMs: number;
readonly planDigest: string;
}
export interface CreatePluginPackageLifecyclePlanInput {
readonly actionRef: string;
readonly impact: PluginPackageLifecycleImpact;
readonly requestedBy: SecuritySubject;
readonly plannedAtMs: number;
readonly expiresAtMs: number;
}
export interface CreatePluginPackageLifecyclePlanResult {
readonly status: 'created' | 'existing';
readonly plan: Readonly<PluginPackageLifecyclePlan>;
}
export interface PluginPackageLifecyclePlanRepository {
create(
plan: Readonly<PluginPackageLifecyclePlan>,
): Promise<Readonly<CreatePluginPackageLifecyclePlanResult>>;
findByActionRef(
actionRef: string,
): Promise<Readonly<PluginPackageLifecyclePlan> | null>;
}
export class InvalidPluginPackageLifecyclePlanError extends TypeError {
readonly code = 'PLUGIN_PACKAGE_LIFECYCLE_PLAN_INVALID';
constructor(message: string) {
super(`Plugin Package lifecycle plan is invalid: ${message}`);
this.name = 'InvalidPluginPackageLifecyclePlanError';
}
}
export class PluginPackageLifecyclePlanConflictError extends Error {
readonly code = 'PLUGIN_PACKAGE_LIFECYCLE_PLAN_CONFLICT';
constructor(message: string) {
super(
`Plugin Package lifecycle plan conflicts with durable state: ${message}`,
);
this.name = 'PluginPackageLifecyclePlanConflictError';
}
}
export class PluginPackageLifecyclePlanUnavailableError extends Error {
readonly code = 'PLUGIN_PACKAGE_LIFECYCLE_PLAN_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Plugin Package lifecycle plan is unavailable', options);
this.name = 'PluginPackageLifecyclePlanUnavailableError';
}
}
const ACTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
const SUBJECT_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
const PLAN_DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const PLAN_DIGEST_DOMAIN =
'qinglong/plugin-package-lifecycle-plan-digest@v1\0';
function invalid(message: string): never {
throw new InvalidPluginPackageLifecyclePlanError(message);
}
function record(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
(Object.getPrototypeOf(value) !== Object.prototype &&
Object.getPrototypeOf(value) !== null)
) {
return invalid(`${label} must be an object`);
}
const descriptors = Object.getOwnPropertyDescriptors(value);
if (
Object.values(descriptors).some(
(descriptor) =>
descriptor.get !== undefined ||
descriptor.set !== undefined ||
descriptor.enumerable !== true,
)
) {
return invalid(`${label} must contain enumerable data properties`);
}
return value as Record<string, unknown>;
}
function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const keys = Reflect.ownKeys(value);
const canonical = [...expected].sort();
if (
keys.some((key) => typeof key !== 'string') ||
keys.length !== canonical.length ||
keys
.map(String)
.sort()
.some((key, index) => key !== canonical[index])
) {
invalid(`${label} shape is invalid`);
}
}
function actionRef(value: unknown): string {
if (typeof value !== 'string' || !ACTION_REF_PATTERN.test(value)) {
return invalid('actionRef is invalid');
}
return value;
}
function timestamp(value: unknown, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function userSubject(value: SecuritySubject): Readonly<SecuritySubject> {
const candidate = record(value, 'requestedBy');
exactKeys(candidate, ['id', 'type'], 'requestedBy');
if (
value.type !== 'user' ||
typeof value.id !== 'string' ||
value.id.length < 1 ||
Buffer.byteLength(value.id, 'utf8') > 255 ||
SUBJECT_CONTROL_PATTERN.test(value.id)
) {
return invalid('requestedBy must be a User subject');
}
return Object.freeze({ type: 'user', id: value.id });
}
function fields(
value: Omit<PluginPackageLifecyclePlan, 'planDigest'>,
): object {
return {
schema: value.schema,
actionRef: value.actionRef,
impact: value.impact,
requestedBy: value.requestedBy,
plannedAtMs: value.plannedAtMs,
expiresAtMs: value.expiresAtMs,
};
}
export function pluginPackageLifecyclePlanDigest(
value: Omit<PluginPackageLifecyclePlan, 'planDigest'>,
): string {
return createHash('sha256')
.update(PLAN_DIGEST_DOMAIN)
.update(JSON.stringify(fields(value)))
.digest('hex');
}
function normalizedWithoutDigest(
value: Omit<PluginPackageLifecyclePlan, 'planDigest'>,
): Omit<PluginPackageLifecyclePlan, 'planDigest'> {
if (value.schema !== PLUGIN_PACKAGE_LIFECYCLE_PLAN_SCHEMA) {
return invalid('schema is invalid');
}
const plannedAtMs = timestamp(value.plannedAtMs, 'plannedAtMs');
const expiresAtMs = timestamp(value.expiresAtMs, 'expiresAtMs');
if (
expiresAtMs <= plannedAtMs ||
expiresAtMs - plannedAtMs >
MAX_PLUGIN_PACKAGE_LIFECYCLE_PLAN_LIFETIME_MS
) {
return invalid('lifetime is invalid');
}
return Object.freeze({
schema: PLUGIN_PACKAGE_LIFECYCLE_PLAN_SCHEMA,
actionRef: actionRef(value.actionRef),
impact: normalizePluginPackageLifecycleImpact(value.impact),
requestedBy: userSubject(value.requestedBy),
plannedAtMs,
expiresAtMs,
});
}
function boundedPlan(
value: Readonly<PluginPackageLifecyclePlan>,
): Readonly<PluginPackageLifecyclePlan> {
if (
Buffer.byteLength(JSON.stringify(value), 'utf8') >
MAX_PLUGIN_PACKAGE_LIFECYCLE_PLAN_BYTES
) {
return invalid('encoded plan exceeds the size limit');
}
return value;
}
export function createPluginPackageLifecyclePlan(
input: CreatePluginPackageLifecyclePlanInput,
): Readonly<PluginPackageLifecyclePlan> {
const value = record(input, 'lifecycle plan input');
exactKeys(
value,
['actionRef', 'expiresAtMs', 'impact', 'plannedAtMs', 'requestedBy'],
'lifecycle plan input',
);
const unsigned = normalizedWithoutDigest({
schema: PLUGIN_PACKAGE_LIFECYCLE_PLAN_SCHEMA,
actionRef: input.actionRef,
impact: input.impact,
requestedBy: input.requestedBy,
plannedAtMs: input.plannedAtMs,
expiresAtMs: input.expiresAtMs,
});
return boundedPlan(
Object.freeze({
...unsigned,
planDigest: pluginPackageLifecyclePlanDigest(unsigned),
}),
);
}
export function normalizePluginPackageLifecyclePlan(
value: PluginPackageLifecyclePlan,
): Readonly<PluginPackageLifecyclePlan> {
const candidate = record(value, 'lifecycle plan');
exactKeys(
candidate,
[
'actionRef',
'expiresAtMs',
'impact',
'planDigest',
'plannedAtMs',
'requestedBy',
'schema',
],
'lifecycle plan',
);
const unsigned = normalizedWithoutDigest(value);
const planDigest = pluginPackageLifecyclePlanDigest(unsigned);
if (
typeof value.planDigest !== 'string' ||
!PLAN_DIGEST_PATTERN.test(value.planDigest) ||
value.planDigest !== planDigest
) {
return invalid('planDigest does not match lifecycle plan');
}
return boundedPlan(Object.freeze({ ...unsigned, planDigest }));
}
@@ -0,0 +1,709 @@
import { createHash } from 'node:crypto';
import { SECURITY_SUBJECT_TYPES, type SecuritySubject } from '../../security/security';
export const PLUGIN_PACKAGE_QUARANTINE_EVENT_SCHEMA =
'qinglong/plugin-package-quarantine-event@v1' as const;
export const PLUGIN_PACKAGE_WITHDRAWAL_RECEIPT_SCHEMA =
'qinglong/plugin-package-withdrawal-receipt@v1' as const;
export const MAX_PLUGIN_PACKAGE_QUARANTINE_TASK_WITHDRAWALS = 128;
export const MAX_PLUGIN_PACKAGE_QUARANTINE_RETAINED_SOURCES = 128;
export type PluginPackageQuarantineInstallState =
| 'queued'
| 'staged'
| 'activating'
| 'active';
export type PluginPackageQuarantineAuthorizationMode =
| 'dual_control'
| 'break_glass';
export type PluginPackageQuarantineReason =
| 'suspected_key_compromise'
| 'confirmed_key_compromise';
export interface PluginPackageQuarantineTarget {
readonly projectId: string;
readonly packageName: string;
readonly installationId: string;
readonly lockDigest: string;
readonly installState: PluginPackageQuarantineInstallState;
readonly installVersion: number;
readonly installRecordDigest: string;
readonly activeLockDigest: string | null;
}
export interface PluginPackageQuarantineEvent {
readonly schema: typeof PLUGIN_PACKAGE_QUARANTINE_EVENT_SCHEMA;
readonly mutationId: string;
readonly revocationReceiptDigest: string;
readonly impactDigest: string;
readonly target: Readonly<PluginPackageQuarantineTarget>;
readonly proposer: Readonly<SecuritySubject>;
readonly confirmer: Readonly<SecuritySubject>;
readonly authorizationMode: PluginPackageQuarantineAuthorizationMode;
readonly reasonCode: PluginPackageQuarantineReason;
readonly occurredAtMs: number;
readonly eventDigest: string;
}
export type CreatePluginPackageQuarantineEventInput = Omit<
PluginPackageQuarantineEvent,
'eventDigest' | 'schema'
>;
export interface PluginPackageQuarantineTaskWithdrawal {
readonly taskId: string;
readonly previousRevision: number;
readonly disabledRevision: number;
readonly previousContentDigest: string;
readonly disabledContentDigest: string;
}
export interface PluginPackageQuarantineInactiveCapabilityDisposition {
readonly status: 'not_active';
readonly taskWithdrawals: readonly [];
readonly previousActiveVectorDigest: null;
readonly currentActiveVectorDigest: null;
readonly currentToolSnapshotDigest: null;
readonly retainedSourceCount: 0;
}
export interface PluginPackageQuarantineWithdrawnCapabilityDisposition {
readonly status: 'withdrawn';
readonly taskWithdrawals: readonly Readonly<PluginPackageQuarantineTaskWithdrawal>[];
readonly previousActiveVectorDigest: string;
readonly currentActiveVectorDigest: string;
readonly currentToolSnapshotDigest: string;
readonly retainedSourceCount: number;
}
export type PluginPackageQuarantineCapabilityDisposition =
| Readonly<PluginPackageQuarantineInactiveCapabilityDisposition>
| Readonly<PluginPackageQuarantineWithdrawnCapabilityDisposition>;
export interface PluginPackageWithdrawalReceipt {
readonly schema: typeof PLUGIN_PACKAGE_WITHDRAWAL_RECEIPT_SCHEMA;
readonly eventDigest: string;
readonly target: Readonly<PluginPackageQuarantineTarget>;
readonly capability: PluginPackageQuarantineCapabilityDisposition;
readonly committedAtMs: number;
readonly receiptDigest: string;
}
export type CreatePluginPackageWithdrawalReceiptInput = Omit<
PluginPackageWithdrawalReceipt,
'receiptDigest' | 'schema'
>;
export interface PluginPackageQuarantineRepository {
findTargetsByLockDigest(
lockDigest: string,
): Promise<readonly Readonly<PluginPackageQuarantineTarget>[]>;
findByEventDigest(
eventDigest: string,
): Promise<Readonly<PluginPackageWithdrawalReceipt> | null>;
quarantine(
event: Readonly<PluginPackageQuarantineEvent>,
confirmAuthorization: () => void | Promise<void>,
): Promise<
Readonly<{
status: 'created' | 'existing';
receipt: Readonly<PluginPackageWithdrawalReceipt>;
}>
>;
}
export class InvalidPluginPackageQuarantineError extends TypeError {
readonly code = 'PLUGIN_PACKAGE_QUARANTINE_INVALID';
constructor(message: string) {
super(`Plugin Package quarantine is invalid: ${message}`);
this.name = 'InvalidPluginPackageQuarantineError';
}
}
export class PluginPackageQuarantineConflictError extends Error {
readonly code = 'PLUGIN_PACKAGE_QUARANTINE_CONFLICT';
constructor(message: string) {
super(`Plugin Package quarantine conflicts with durable state: ${message}`);
this.name = 'PluginPackageQuarantineConflictError';
}
}
export class PluginPackageQuarantineUnavailableError extends Error {
readonly code = 'PLUGIN_PACKAGE_QUARANTINE_UNAVAILABLE';
constructor(options?: ErrorOptions) {
super('Plugin Package quarantine is unavailable', options);
this.name = 'PluginPackageQuarantineUnavailableError';
}
}
const DIGEST_PATTERN = /^[0-9a-f]{64}$/;
const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const PACKAGE_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const SUBJECT_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
const EVENT_DIGEST_DOMAIN =
'qinglong/plugin-package-quarantine-event-digest@v1\0';
const RECEIPT_DIGEST_DOMAIN =
'qinglong/plugin-package-withdrawal-receipt-digest@v1\0';
const TASK_MUTATION_ID_DOMAIN =
'qinglong/plugin-package-quarantine-task-mutation-id@v1\0';
const QUARANTINE_MUTATION_ID_DOMAIN =
'qinglong/plugin-package-quarantine-mutation-id@v1\0';
function invalid(message: string): never {
throw new InvalidPluginPackageQuarantineError(message);
}
function dataRecord(value: unknown, label: string): Record<string, unknown> {
if (
!value ||
typeof value !== 'object' ||
Array.isArray(value) ||
(Object.getPrototypeOf(value) !== Object.prototype &&
Object.getPrototypeOf(value) !== null)
) {
return invalid(`${label} must be an object`);
}
return value as Record<string, unknown>;
}
function exactKeys(
value: object,
expected: readonly string[],
label: string,
): void {
const actual = Reflect.ownKeys(value);
const sorted = [...expected].sort();
if (
actual.some((key) => typeof key !== 'string') ||
actual.length !== sorted.length ||
actual
.map(String)
.sort()
.some((key, index) => key !== sorted[index])
) {
invalid(`${label} shape is invalid`);
}
}
function identifier(value: unknown, label: string): string {
if (typeof value !== 'string' || !IDENTIFIER_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function digest(value: unknown, label: string): string {
if (typeof value !== 'string' || !DIGEST_PATTERN.test(value)) {
return invalid(`${label} is invalid`);
}
return value;
}
function timestamp(value: unknown, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 0) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function positiveVersion(value: unknown, label: string): number {
if (!Number.isSafeInteger(value) || (value as number) < 1) {
return invalid(`${label} is invalid`);
}
return value as number;
}
function subject(
value: SecuritySubject,
label: string,
): Readonly<SecuritySubject> {
const record = dataRecord(value, label);
exactKeys(record, ['id', 'type'], label);
if (
typeof value.type !== 'string' ||
!SECURITY_SUBJECT_TYPES.includes(
value.type as (typeof SECURITY_SUBJECT_TYPES)[number],
) ||
typeof value.id !== 'string' ||
value.id.length < 1 ||
Buffer.byteLength(value.id, 'utf8') > 255 ||
SUBJECT_CONTROL_PATTERN.test(value.id)
) {
return invalid(`${label} is invalid`);
}
return Object.freeze({
type: value.type as (typeof SECURITY_SUBJECT_TYPES)[number],
id: value.id,
});
}
function sameSubject(
left: Readonly<SecuritySubject>,
right: Readonly<SecuritySubject>,
): boolean {
return left.type === right.type && left.id === right.id;
}
function normalizeTarget(
value: PluginPackageQuarantineTarget,
): Readonly<PluginPackageQuarantineTarget> {
const target = dataRecord(value, 'target');
exactKeys(
target,
[
'activeLockDigest',
'installationId',
'installRecordDigest',
'installState',
'installVersion',
'lockDigest',
'packageName',
'projectId',
],
'target',
);
if (
typeof value.projectId !== 'string' ||
value.projectId.length < 1 ||
Buffer.byteLength(value.projectId, 'utf8') > 128 ||
value.projectId.includes('\0') ||
typeof value.packageName !== 'string' ||
!PACKAGE_NAME_PATTERN.test(value.packageName) ||
!['queued', 'staged', 'activating', 'active'].includes(value.installState)
) {
return invalid('target identity or state is invalid');
}
const lockDigest = digest(value.lockDigest, 'target lockDigest');
const activeLockDigest =
value.activeLockDigest === null
? null
: digest(value.activeLockDigest, 'target activeLockDigest');
if (
(value.installState === 'active' && activeLockDigest !== lockDigest) ||
(value.installState !== 'active' && activeLockDigest === lockDigest)
) {
return invalid('target active lock does not match install state');
}
return Object.freeze({
projectId: value.projectId,
packageName: value.packageName,
installationId: identifier(value.installationId, 'target installationId'),
lockDigest,
installState: value.installState,
installVersion: positiveVersion(
value.installVersion,
'target installVersion',
),
installRecordDigest: digest(
value.installRecordDigest,
'target installRecordDigest',
),
activeLockDigest,
});
}
function eventFields(
value: Omit<PluginPackageQuarantineEvent, 'eventDigest'>,
): object {
return {
schema: value.schema,
mutationId: value.mutationId,
revocationReceiptDigest: value.revocationReceiptDigest,
impactDigest: value.impactDigest,
target: value.target,
proposer: value.proposer,
confirmer: value.confirmer,
authorizationMode: value.authorizationMode,
reasonCode: value.reasonCode,
occurredAtMs: value.occurredAtMs,
};
}
export function pluginPackageQuarantineEventDigest(
value: Omit<PluginPackageQuarantineEvent, 'eventDigest'>,
): string {
return createHash('sha256')
.update(EVENT_DIGEST_DOMAIN)
.update(JSON.stringify(eventFields(value)))
.digest('hex');
}
export function normalizePluginPackageQuarantineEvent(
value: PluginPackageQuarantineEvent,
): Readonly<PluginPackageQuarantineEvent> {
const event = dataRecord(value, 'event');
exactKeys(
event,
[
'authorizationMode',
'confirmer',
'eventDigest',
'impactDigest',
'mutationId',
'occurredAtMs',
'proposer',
'reasonCode',
'revocationReceiptDigest',
'schema',
'target',
],
'event',
);
if (
value.schema !== PLUGIN_PACKAGE_QUARANTINE_EVENT_SCHEMA ||
(value.authorizationMode !== 'dual_control' &&
value.authorizationMode !== 'break_glass') ||
(value.reasonCode !== 'suspected_key_compromise' &&
value.reasonCode !== 'confirmed_key_compromise')
) {
return invalid('event schema or classification is invalid');
}
const proposer = subject(value.proposer, 'proposer');
const confirmer = subject(value.confirmer, 'confirmer');
if (
value.authorizationMode === 'dual_control' &&
sameSubject(proposer, confirmer)
) {
return invalid('dual-control requires distinct subjects');
}
const normalized = Object.freeze({
schema: PLUGIN_PACKAGE_QUARANTINE_EVENT_SCHEMA,
mutationId: identifier(value.mutationId, 'mutationId'),
revocationReceiptDigest: digest(
value.revocationReceiptDigest,
'revocationReceiptDigest',
),
impactDigest: digest(value.impactDigest, 'impactDigest'),
target: normalizeTarget(value.target),
proposer,
confirmer,
authorizationMode: value.authorizationMode,
reasonCode: value.reasonCode,
occurredAtMs: timestamp(value.occurredAtMs, 'occurredAtMs'),
});
const eventDigest = pluginPackageQuarantineEventDigest(normalized);
if (value.eventDigest !== eventDigest) {
return invalid('eventDigest does not match event');
}
return Object.freeze({ ...normalized, eventDigest });
}
export function createPluginPackageQuarantineEvent(
input: CreatePluginPackageQuarantineEventInput,
): Readonly<PluginPackageQuarantineEvent> {
const value = dataRecord(input, 'event input');
exactKeys(
value,
[
'authorizationMode',
'confirmer',
'impactDigest',
'mutationId',
'occurredAtMs',
'proposer',
'reasonCode',
'revocationReceiptDigest',
'target',
],
'event input',
);
if (
(input.authorizationMode !== 'dual_control' &&
input.authorizationMode !== 'break_glass') ||
(input.reasonCode !== 'suspected_key_compromise' &&
input.reasonCode !== 'confirmed_key_compromise')
) {
return invalid('event classification is invalid');
}
const proposer = subject(input.proposer, 'proposer');
const confirmer = subject(input.confirmer, 'confirmer');
if (
input.authorizationMode === 'dual_control' &&
sameSubject(proposer, confirmer)
) {
return invalid('dual-control requires distinct subjects');
}
const unsigned: Omit<PluginPackageQuarantineEvent, 'eventDigest'> = {
schema: PLUGIN_PACKAGE_QUARANTINE_EVENT_SCHEMA,
mutationId: identifier(input.mutationId, 'mutationId'),
revocationReceiptDigest: digest(
input.revocationReceiptDigest,
'revocationReceiptDigest',
),
impactDigest: digest(input.impactDigest, 'impactDigest'),
target: normalizeTarget(input.target),
proposer,
confirmer,
authorizationMode: input.authorizationMode,
reasonCode: input.reasonCode,
occurredAtMs: timestamp(input.occurredAtMs, 'occurredAtMs'),
};
return normalizePluginPackageQuarantineEvent({
...unsigned,
eventDigest: pluginPackageQuarantineEventDigest(unsigned),
});
}
function normalizeTaskWithdrawal(
value: PluginPackageQuarantineTaskWithdrawal,
): Readonly<PluginPackageQuarantineTaskWithdrawal> {
const task = dataRecord(value, 'task withdrawal');
exactKeys(
task,
[
'disabledContentDigest',
'disabledRevision',
'previousContentDigest',
'previousRevision',
'taskId',
],
'task withdrawal',
);
const previousRevision = positiveVersion(
value.previousRevision,
'task previousRevision',
);
const disabledRevision = positiveVersion(
value.disabledRevision,
'task disabledRevision',
);
if (disabledRevision !== previousRevision + 1) {
return invalid('task disabled revision is not consecutive');
}
return Object.freeze({
taskId: identifier(value.taskId, 'taskId'),
previousRevision,
disabledRevision,
previousContentDigest: digest(
value.previousContentDigest,
'task previousContentDigest',
),
disabledContentDigest: digest(
value.disabledContentDigest,
'task disabledContentDigest',
),
});
}
function normalizeCapability(
value: PluginPackageQuarantineCapabilityDisposition,
target: Readonly<PluginPackageQuarantineTarget>,
): PluginPackageQuarantineCapabilityDisposition {
const capability = dataRecord(value, 'capability');
exactKeys(
capability,
[
'currentActiveVectorDigest',
'currentToolSnapshotDigest',
'previousActiveVectorDigest',
'retainedSourceCount',
'status',
'taskWithdrawals',
],
'capability',
);
if (!Array.isArray(value.taskWithdrawals)) {
return invalid('task withdrawals must be an array');
}
if (value.status === 'not_active') {
if (
target.installState === 'active' ||
value.taskWithdrawals.length !== 0 ||
value.previousActiveVectorDigest !== null ||
value.currentActiveVectorDigest !== null ||
value.currentToolSnapshotDigest !== null ||
value.retainedSourceCount !== 0
) {
return invalid('inactive capability disposition is inconsistent');
}
return Object.freeze({
status: 'not_active',
taskWithdrawals: Object.freeze([]) as readonly [],
previousActiveVectorDigest: null,
currentActiveVectorDigest: null,
currentToolSnapshotDigest: null,
retainedSourceCount: 0,
});
}
if (
value.status !== 'withdrawn' ||
target.installState !== 'active' ||
value.taskWithdrawals.length >
MAX_PLUGIN_PACKAGE_QUARANTINE_TASK_WITHDRAWALS ||
!Number.isSafeInteger(value.retainedSourceCount) ||
value.retainedSourceCount < 0 ||
value.retainedSourceCount > MAX_PLUGIN_PACKAGE_QUARANTINE_RETAINED_SOURCES
) {
return invalid('withdrawn capability disposition is invalid');
}
const taskWithdrawals = Object.freeze(
value.taskWithdrawals.map(normalizeTaskWithdrawal),
);
if (
taskWithdrawals.some(
(task, index) =>
index > 0 &&
Buffer.compare(
Buffer.from(taskWithdrawals[index - 1]!.taskId, 'utf8'),
Buffer.from(task.taskId, 'utf8'),
) >= 0,
)
) {
return invalid('task withdrawals must be unique and sorted');
}
const previousActiveVectorDigest = digest(
value.previousActiveVectorDigest,
'previousActiveVectorDigest',
);
const currentActiveVectorDigest = digest(
value.currentActiveVectorDigest,
'currentActiveVectorDigest',
);
if (previousActiveVectorDigest === currentActiveVectorDigest) {
return invalid('active vector did not change');
}
return Object.freeze({
status: 'withdrawn',
taskWithdrawals,
previousActiveVectorDigest,
currentActiveVectorDigest,
currentToolSnapshotDigest: digest(
value.currentToolSnapshotDigest,
'currentToolSnapshotDigest',
),
retainedSourceCount: value.retainedSourceCount,
});
}
function receiptFields(
value: Omit<PluginPackageWithdrawalReceipt, 'receiptDigest'>,
): object {
return {
schema: value.schema,
eventDigest: value.eventDigest,
target: value.target,
capability: value.capability,
committedAtMs: value.committedAtMs,
};
}
export function pluginPackageWithdrawalReceiptDigest(
value: Omit<PluginPackageWithdrawalReceipt, 'receiptDigest'>,
): string {
return createHash('sha256')
.update(RECEIPT_DIGEST_DOMAIN)
.update(JSON.stringify(receiptFields(value)))
.digest('hex');
}
export function normalizePluginPackageWithdrawalReceipt(
value: PluginPackageWithdrawalReceipt,
): Readonly<PluginPackageWithdrawalReceipt> {
const receipt = dataRecord(value, 'withdrawal receipt');
exactKeys(
receipt,
[
'capability',
'committedAtMs',
'eventDigest',
'receiptDigest',
'schema',
'target',
],
'withdrawal receipt',
);
if (value.schema !== PLUGIN_PACKAGE_WITHDRAWAL_RECEIPT_SCHEMA) {
return invalid('withdrawal receipt schema is invalid');
}
const target = normalizeTarget(value.target);
const normalized = Object.freeze({
schema: PLUGIN_PACKAGE_WITHDRAWAL_RECEIPT_SCHEMA,
eventDigest: digest(value.eventDigest, 'eventDigest'),
target,
capability: normalizeCapability(value.capability, target),
committedAtMs: timestamp(value.committedAtMs, 'committedAtMs'),
});
const receiptDigest = pluginPackageWithdrawalReceiptDigest(normalized);
if (value.receiptDigest !== receiptDigest) {
return invalid('receiptDigest does not match withdrawal receipt');
}
return Object.freeze({ ...normalized, receiptDigest });
}
export function createPluginPackageWithdrawalReceipt(
input: CreatePluginPackageWithdrawalReceiptInput,
): Readonly<PluginPackageWithdrawalReceipt> {
const value = dataRecord(input, 'withdrawal receipt input');
exactKeys(
value,
['capability', 'committedAtMs', 'eventDigest', 'target'],
'withdrawal receipt input',
);
const target = normalizeTarget(input.target);
const unsigned: Omit<PluginPackageWithdrawalReceipt, 'receiptDigest'> = {
schema: PLUGIN_PACKAGE_WITHDRAWAL_RECEIPT_SCHEMA,
eventDigest: digest(input.eventDigest, 'eventDigest'),
target,
capability: normalizeCapability(input.capability, target),
committedAtMs: timestamp(input.committedAtMs, 'committedAtMs'),
};
return normalizePluginPackageWithdrawalReceipt({
...unsigned,
receiptDigest: pluginPackageWithdrawalReceiptDigest(unsigned),
});
}
export function assertPluginPackageWithdrawalMatchesEvent(
eventValue: PluginPackageQuarantineEvent,
receiptValue: PluginPackageWithdrawalReceipt,
): void {
const event = normalizePluginPackageQuarantineEvent(eventValue);
const receipt = normalizePluginPackageWithdrawalReceipt(receiptValue);
if (
receipt.eventDigest !== event.eventDigest ||
JSON.stringify(receipt.target) !== JSON.stringify(event.target) ||
receipt.committedAtMs < event.occurredAtMs
) {
invalid('withdrawal receipt does not match quarantine event');
}
}
export function pluginPackageQuarantineTaskMutationId(
eventDigestValue: string,
taskIdValue: string,
): string {
const eventDigest = digest(eventDigestValue, 'eventDigest');
const taskId = identifier(taskIdValue, 'taskId');
const value = createHash('sha256')
.update(TASK_MUTATION_ID_DOMAIN)
.update(eventDigest)
.update('\0')
.update(taskId)
.digest('hex');
return `${value.slice(0, 8)}-${value.slice(8, 12)}-4${value.slice(
13,
16,
)}-a${value.slice(17, 20)}-${value.slice(20, 32)}`;
}
export function pluginPackageQuarantineMutationId(
revocationReceiptDigestValue: string,
targetValue: PluginPackageQuarantineTarget,
): string {
const revocationReceiptDigest = digest(
revocationReceiptDigestValue,
'revocationReceiptDigest',
);
const target = normalizeTarget(targetValue);
const value = createHash('sha256')
.update(QUARANTINE_MUTATION_ID_DOMAIN)
.update(revocationReceiptDigest)
.update('\0')
.update(JSON.stringify(target))
.digest('hex');
return `quarantine:${value}`;
}