mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:21:50 +08:00
feat(ql3): bind package secrets to generations
This commit is contained in:
@@ -100,6 +100,8 @@ import { local0087RunAttemptLogRetentionMigration } from '../migrations/0087-run
|
||||
import { local0088CapabilityV44Migration } from '../migrations/0088-capability-v44';
|
||||
import { local0089PluginPackageAutomationDispositionEventsMigration } from '../migrations/0089-plugin-package-automation-disposition-events';
|
||||
import { local0090CapabilityV45Migration } from '../migrations/0090-capability-v45';
|
||||
import { local0091PluginPackageSecretBindingsMigration } from '../migrations/0091-plugin-package-secret-bindings';
|
||||
import { local0092CapabilityV46Migration } from '../migrations/0092-capability-v46';
|
||||
import type { LocalSqliteMigrationContext } from '../migrations/sqlMigration';
|
||||
import {
|
||||
LOCAL_SQLITE_MIGRATION_STREAM_ID,
|
||||
@@ -212,6 +214,8 @@ export const localSqliteMigrationDefinition: MigrationStreamDefinition<LocalSqli
|
||||
local0088CapabilityV44Migration,
|
||||
local0089PluginPackageAutomationDispositionEventsMigration,
|
||||
local0090CapabilityV45Migration,
|
||||
local0091PluginPackageSecretBindingsMigration,
|
||||
local0092CapabilityV46Migration,
|
||||
]),
|
||||
});
|
||||
|
||||
|
||||
@@ -462,5 +462,15 @@ export const localSqliteMigrationManifest: MigrationStreamManifest =
|
||||
checksum:
|
||||
'1919987d29ef581e150116c590f1dc98f5d327791fc6425d5f1a27b8f6de5475',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: '0091-plugin-package-secret-bindings',
|
||||
checksum:
|
||||
'21e9957eb4c8cad0e41377d59c9f3226c350f9ae4718642c2caeb88a308f43ee',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: '0092-capability-v46',
|
||||
checksum:
|
||||
'a1b058bb7b0259069202632d27d4a55466828cde831c49821eb862feeba6ce35',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { defineLocalSqliteMigration } from './sqlMigration';
|
||||
|
||||
export const local0091PluginPackageSecretBindingsMigration =
|
||||
defineLocalSqliteMigration({
|
||||
id: '0091-plugin-package-secret-bindings',
|
||||
statements: [
|
||||
`
|
||||
CREATE TABLE "QingLong3PluginPackageSecretBindings" (
|
||||
generation_digest TEXT PRIMARY KEY NOT NULL,
|
||||
project_id TEXT NOT NULL,
|
||||
package_name TEXT NOT NULL,
|
||||
installation_id TEXT NOT NULL,
|
||||
lock_digest TEXT NOT NULL,
|
||||
generation INTEGER NOT NULL,
|
||||
manifest_digest TEXT NOT NULL,
|
||||
authority_kind TEXT NOT NULL,
|
||||
evidence_digest TEXT NOT NULL,
|
||||
bound_at_ms INTEGER NOT NULL,
|
||||
binding_digest TEXT NOT NULL,
|
||||
binding_json TEXT NOT NULL,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_project_fk
|
||||
FOREIGN KEY (project_id) REFERENCES "QingLong3Projects" (id)
|
||||
ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_install_fk
|
||||
FOREIGN KEY (installation_id)
|
||||
REFERENCES "QingLong3PluginPackageInstalls" (installation_id)
|
||||
ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_identity_check CHECK (
|
||||
length(project_id) BETWEEN 1 AND 128 AND
|
||||
length(package_name) BETWEEN 1 AND 63 AND
|
||||
length(installation_id) BETWEEN 1 AND 128 AND
|
||||
generation BETWEEN 1 AND 2147483647 AND
|
||||
authority_kind IN ('approved-action-execution','local-owner-confirmation') AND
|
||||
bound_at_ms >= 0
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_digest_check CHECK (
|
||||
length(generation_digest) = 64 AND generation_digest NOT GLOB '*[^0-9a-f]*' AND
|
||||
length(lock_digest) = 64 AND lock_digest NOT GLOB '*[^0-9a-f]*' AND
|
||||
length(manifest_digest) = 64 AND manifest_digest NOT GLOB '*[^0-9a-f]*' AND
|
||||
length(evidence_digest) = 64 AND evidence_digest NOT GLOB '*[^0-9a-f]*' AND
|
||||
length(binding_digest) = 64 AND binding_digest NOT GLOB '*[^0-9a-f]*'
|
||||
),
|
||||
CONSTRAINT ql3_plugin_package_secret_binding_json_check CHECK (
|
||||
length(CAST(binding_json AS BLOB)) BETWEEN 2 AND 65536 AND
|
||||
json_valid(binding_json) AND json_type(binding_json) = 'object' AND
|
||||
json_extract(binding_json, '$.schema') = 'qinglong/plugin-package-secret-binding@v1' AND
|
||||
json_extract(binding_json, '$.target.generationDigest') = generation_digest AND
|
||||
json_extract(binding_json, '$.target.projectId') = project_id AND
|
||||
json_extract(binding_json, '$.target.packageName') = package_name AND
|
||||
json_extract(binding_json, '$.target.installationId') = installation_id AND
|
||||
json_extract(binding_json, '$.target.lockDigest') = lock_digest AND
|
||||
json_extract(binding_json, '$.target.generation') = generation AND
|
||||
json_extract(binding_json, '$.target.manifestDigest') = manifest_digest AND
|
||||
json_extract(binding_json, '$.authority.kind') = authority_kind AND
|
||||
json_extract(binding_json, '$.authority.evidenceDigest') = evidence_digest AND
|
||||
json_extract(binding_json, '$.boundAtMs') = bound_at_ms AND
|
||||
json_extract(binding_json, '$.bindingDigest') = binding_digest AND
|
||||
json_type(binding_json, '$.entries') = 'array' AND
|
||||
json_array_length(json_extract(binding_json, '$.entries')) BETWEEN 1 AND 64
|
||||
)
|
||||
)
|
||||
`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_secret_binding_generation_uidx ON "QingLong3PluginPackageSecretBindings" (project_id, package_name, generation)`,
|
||||
`CREATE UNIQUE INDEX ql3_plugin_package_secret_binding_digest_uidx ON "QingLong3PluginPackageSecretBindings" (binding_digest)`,
|
||||
`CREATE INDEX ql3_plugin_package_secret_binding_install_idx ON "QingLong3PluginPackageSecretBindings" (installation_id, generation_digest)`,
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CAPABILITIES_V45 } from './0090-capability-v45';
|
||||
import { defineLocalSqliteMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V46 = CAPABILITIES_V45.replace(
|
||||
'"plugin_package_task_reconciliation":1,',
|
||||
'"plugin_package_secret_binding":1,"plugin_package_task_reconciliation":1,',
|
||||
);
|
||||
|
||||
export const local0092CapabilityV46Migration = defineLocalSqliteMigration({
|
||||
id: '0092-capability-v46',
|
||||
statements: [
|
||||
`UPDATE "QingLong3SchemaCapabilities" SET contract_version = 46, migration_id = '0091-plugin-package-secret-bindings', capabilities = '${CAPABILITIES_V46}', updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE contract_name = 'local-control-core' AND contract_version = 45 AND migration_id = '0089-plugin-package-automation-disposition-events' AND capabilities = '${CAPABILITIES_V45}'`,
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,250 @@
|
||||
import type { DatabaseSync } from 'node:sqlite';
|
||||
|
||||
import {
|
||||
InvalidPluginPackageSecretBindingError,
|
||||
MAX_PLUGIN_PACKAGE_SECRET_BINDING_JSON_BYTES,
|
||||
PluginPackageSecretBindingConflictError,
|
||||
PluginPackageSecretBindingUnavailableError,
|
||||
normalizePluginPackageSecretBinding,
|
||||
type PluginPackageSecretBinding,
|
||||
type PluginPackageSecretBindingRepository,
|
||||
} from '@qinglong/runtime-core/plugin-package-secret-binding';
|
||||
|
||||
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
const DIGEST = /^[0-9a-f]{64}$/;
|
||||
|
||||
function invalid(message: string): never {
|
||||
throw new InvalidPluginPackageSecretBindingError(message);
|
||||
}
|
||||
|
||||
function text(row: Row, key: string): string {
|
||||
const value = row[key];
|
||||
if (typeof value !== 'string') {
|
||||
throw new PluginPackageSecretBindingUnavailableError();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integer(row: Row, key: string): number {
|
||||
const value = row[key];
|
||||
if (!Number.isSafeInteger(value) || (value as number) < 0) {
|
||||
throw new PluginPackageSecretBindingUnavailableError();
|
||||
}
|
||||
return value as number;
|
||||
}
|
||||
|
||||
function generationDigest(value: unknown): string {
|
||||
if (typeof value !== 'string' || !DIGEST.test(value)) {
|
||||
return invalid('generation digest is invalid');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function serialize(binding: Readonly<PluginPackageSecretBinding>): string {
|
||||
const value = JSON.stringify(binding);
|
||||
if (
|
||||
Buffer.byteLength(value, 'utf8') >
|
||||
MAX_PLUGIN_PACKAGE_SECRET_BINDING_JSON_BYTES
|
||||
) {
|
||||
return invalid('durable JSON byte budget exceeded');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function mapStorageError(error: unknown): Error {
|
||||
if (
|
||||
error instanceof InvalidPluginPackageSecretBindingError ||
|
||||
error instanceof PluginPackageSecretBindingConflictError ||
|
||||
error instanceof PluginPackageSecretBindingUnavailableError
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
if (
|
||||
error &&
|
||||
typeof error === 'object' &&
|
||||
'code' in error &&
|
||||
typeof error.code === 'string' &&
|
||||
error.code.startsWith('SQLITE_CONSTRAINT')
|
||||
) {
|
||||
return new PluginPackageSecretBindingConflictError(
|
||||
'durable binding identity is already bound',
|
||||
);
|
||||
}
|
||||
return new PluginPackageSecretBindingUnavailableError({
|
||||
cause: error instanceof Error ? error : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export class LocalSqlitePluginPackageSecretBindingRepository
|
||||
implements PluginPackageSecretBindingRepository
|
||||
{
|
||||
private readonly authority: LocalSqliteOperationAuthority;
|
||||
|
||||
constructor(authority: LocalSqliteOperationAuthority | DatabaseSync) {
|
||||
this.authority =
|
||||
authority instanceof LocalSqliteOperationAuthority
|
||||
? authority
|
||||
: new LocalSqliteOperationAuthority(authority);
|
||||
}
|
||||
|
||||
private parse(row: Row): Readonly<PluginPackageSecretBinding> {
|
||||
try {
|
||||
const binding = normalizePluginPackageSecretBinding(
|
||||
JSON.parse(text(row, 'bindingJson')),
|
||||
);
|
||||
if (
|
||||
binding.target.generationDigest !== text(row, 'generationDigest') ||
|
||||
binding.target.projectId !== text(row, 'projectId') ||
|
||||
binding.target.packageName !== text(row, 'packageName') ||
|
||||
binding.target.installationId !== text(row, 'installationId') ||
|
||||
binding.target.lockDigest !== text(row, 'lockDigest') ||
|
||||
binding.target.generation !== integer(row, 'generation') ||
|
||||
binding.target.manifestDigest !== text(row, 'manifestDigest') ||
|
||||
binding.authority.kind !== text(row, 'authorityKind') ||
|
||||
binding.authority.evidenceDigest !== text(row, 'evidenceDigest') ||
|
||||
binding.boundAtMs !== integer(row, 'boundAtMs') ||
|
||||
binding.bindingDigest !== text(row, 'bindingDigest')
|
||||
) {
|
||||
throw new PluginPackageSecretBindingUnavailableError();
|
||||
}
|
||||
return binding;
|
||||
} catch (error) {
|
||||
if (error instanceof PluginPackageSecretBindingUnavailableError) {
|
||||
throw error;
|
||||
}
|
||||
throw new PluginPackageSecretBindingUnavailableError();
|
||||
}
|
||||
}
|
||||
|
||||
private findStored(
|
||||
digest: string,
|
||||
): Readonly<PluginPackageSecretBinding> | null {
|
||||
const row = this.authority.client
|
||||
.prepare(
|
||||
`SELECT generation_digest AS "generationDigest",
|
||||
project_id AS "projectId",
|
||||
package_name AS "packageName",
|
||||
installation_id AS "installationId",
|
||||
lock_digest AS "lockDigest",
|
||||
generation,
|
||||
manifest_digest AS "manifestDigest",
|
||||
authority_kind AS "authorityKind",
|
||||
evidence_digest AS "evidenceDigest",
|
||||
bound_at_ms AS "boundAtMs",
|
||||
binding_digest AS "bindingDigest",
|
||||
binding_json AS "bindingJson"
|
||||
FROM "QingLong3PluginPackageSecretBindings"
|
||||
WHERE generation_digest = ?`,
|
||||
)
|
||||
.get(digest) as Row | undefined;
|
||||
return row ? this.parse(row) : null;
|
||||
}
|
||||
|
||||
private enqueue<T>(work: () => T): Promise<T> {
|
||||
return this.authority.enqueue(
|
||||
async () => {
|
||||
try {
|
||||
return work();
|
||||
} catch (error) {
|
||||
throw mapStorageError(error);
|
||||
}
|
||||
},
|
||||
() => new PluginPackageSecretBindingUnavailableError(),
|
||||
);
|
||||
}
|
||||
|
||||
async find(
|
||||
digest: string,
|
||||
): Promise<Readonly<PluginPackageSecretBinding> | null> {
|
||||
const normalized = generationDigest(digest);
|
||||
return await this.enqueue(() => this.findStored(normalized));
|
||||
}
|
||||
|
||||
publish(value: Readonly<PluginPackageSecretBinding>): Promise<
|
||||
Readonly<{
|
||||
status: 'created' | 'existing';
|
||||
binding: Readonly<PluginPackageSecretBinding>;
|
||||
}>
|
||||
> {
|
||||
const binding = normalizePluginPackageSecretBinding(value);
|
||||
const bindingJson = serialize(binding);
|
||||
return this.enqueue(() => {
|
||||
const existing = this.findStored(binding.target.generationDigest);
|
||||
if (existing) {
|
||||
if (JSON.stringify(existing) !== bindingJson) {
|
||||
throw new PluginPackageSecretBindingConflictError(
|
||||
'generation digest is bound to another Secret mapping',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
status: 'existing' as const,
|
||||
binding: existing,
|
||||
});
|
||||
}
|
||||
|
||||
const result = this.authority.client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3PluginPackageSecretBindings" (
|
||||
generation_digest, project_id, package_name, installation_id,
|
||||
lock_digest, generation, manifest_digest, authority_kind,
|
||||
evidence_digest, bound_at_ms, binding_digest, binding_json
|
||||
)
|
||||
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
FROM "QingLong3PluginPackageInstalls" AS install
|
||||
INNER JOIN "QingLong3PluginPackageInstallHeads" AS head
|
||||
ON head.installation_id = install.installation_id
|
||||
AND head.project_id = install.project_id
|
||||
AND head.package_name = install.package_name
|
||||
WHERE install.installation_id = ?
|
||||
AND install.project_id = ?
|
||||
AND install.package_name = ?
|
||||
AND install.lock_digest = ?
|
||||
AND install.active_lock_digest = ?
|
||||
AND install.target_generation = ?
|
||||
AND install.state = 'active'
|
||||
AND json_extract(install.lock_json, '$.manifestDigest') = ?
|
||||
ON CONFLICT (generation_digest) DO NOTHING`,
|
||||
)
|
||||
.run(
|
||||
binding.target.generationDigest,
|
||||
binding.target.projectId,
|
||||
binding.target.packageName,
|
||||
binding.target.installationId,
|
||||
binding.target.lockDigest,
|
||||
binding.target.generation,
|
||||
binding.target.manifestDigest,
|
||||
binding.authority.kind,
|
||||
binding.authority.evidenceDigest,
|
||||
binding.boundAtMs,
|
||||
binding.bindingDigest,
|
||||
bindingJson,
|
||||
binding.target.installationId,
|
||||
binding.target.projectId,
|
||||
binding.target.packageName,
|
||||
binding.target.lockDigest,
|
||||
binding.target.lockDigest,
|
||||
binding.target.generation,
|
||||
binding.target.manifestDigest,
|
||||
);
|
||||
const stored = this.findStored(binding.target.generationDigest);
|
||||
if (!stored) {
|
||||
throw new PluginPackageSecretBindingConflictError(
|
||||
'binding target is not the current active Package generation',
|
||||
);
|
||||
}
|
||||
if (JSON.stringify(stored) !== bindingJson) {
|
||||
throw new PluginPackageSecretBindingConflictError(
|
||||
'generation digest is bound to another Secret mapping',
|
||||
);
|
||||
}
|
||||
return Object.freeze({
|
||||
status:
|
||||
result.changes === 1 ? ('created' as const) : ('existing' as const),
|
||||
binding: stored,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '../run/stepRunSchemaContract';
|
||||
|
||||
export const LOCAL_SQLITE_CONTRACT_NAME = 'local-control-core';
|
||||
export const LOCAL_SQLITE_CONTRACT_VERSION = 45;
|
||||
export const LOCAL_SQLITE_CONTRACT_VERSION = 46;
|
||||
|
||||
const PLUGIN_PACKAGE_AUTOMATION_DISPOSITION_TRIGGERS = Object.freeze([
|
||||
Object.freeze({
|
||||
@@ -1142,6 +1142,27 @@ const REQUIRED_SCHEMA = Object.freeze({
|
||||
'ql3_plugin_package_materialized_revision_snapshot_source_uidx',
|
||||
]),
|
||||
}),
|
||||
QingLong3PluginPackageSecretBindings: Object.freeze({
|
||||
columns: Object.freeze([
|
||||
'generation_digest',
|
||||
'project_id',
|
||||
'package_name',
|
||||
'installation_id',
|
||||
'lock_digest',
|
||||
'generation',
|
||||
'manifest_digest',
|
||||
'authority_kind',
|
||||
'evidence_digest',
|
||||
'bound_at_ms',
|
||||
'binding_digest',
|
||||
'binding_json',
|
||||
]),
|
||||
indexes: Object.freeze([
|
||||
'ql3_plugin_package_secret_binding_generation_uidx',
|
||||
'ql3_plugin_package_secret_binding_digest_uidx',
|
||||
'ql3_plugin_package_secret_binding_install_idx',
|
||||
]),
|
||||
}),
|
||||
QingLong3ProjectToolDefinitionSnapshots: Object.freeze({
|
||||
columns: Object.freeze([
|
||||
'project_id',
|
||||
@@ -2533,10 +2554,10 @@ export async function auditLocalSqliteReadiness(
|
||||
capability.contract_name !== LOCAL_SQLITE_CONTRACT_NAME ||
|
||||
capability.contract_version !== LOCAL_SQLITE_CONTRACT_VERSION ||
|
||||
capability.migration_id !==
|
||||
'0089-plugin-package-automation-disposition-events' ||
|
||||
'0091-plugin-package-secret-bindings' ||
|
||||
typeof capability.capabilities !== 'string' ||
|
||||
capability.capabilities !==
|
||||
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_security_audit_compaction":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1,"plugin_package_lifecycle":1,"plugin_package_automation_publication":1,"plugin_package_automation_security_withdrawal":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_run_list":1,"run_attempt_log_retention":1,"plugin_package_workflow_task_attempt_admission":1}' ||
|
||||
'{"run_core":1,"run_retry_policy":1,"completion_receipt_journal":1,"local_dispatch_plan":1,"local_secret_envelope":1,"local_project_policy":1,"local_project_administration":1,"local_security_audit":1,"local_security_audit_compaction":1,"local_secret_authorized_mutation":1,"local_identity":1,"local_api_credential":1,"local_identity_provisioning":1,"local_identity_credential_administration":1,"local_owner_bootstrap":1,"local_owner_delivery_acknowledgement":1,"api_credential_pepper_binding":1,"local_owner_pepper_catalog":1,"local_owner_credential_recovery":1,"local_owner_pepper_reference_inspection":1,"local_owner_pepper_material_gc":1,"local_owner_delivery_acknowledgement_gc":1,"task_definition":1,"local_execution_revision_digest":1,"trigger_definition":1,"legacy_adoption_ledger":1,"local_scheduler_admission":1,"plugin_package_install":1,"approved_action":1,"plugin_package_admission":1,"approved_action_execution":1,"plugin_package_proposal":1,"plugin_package_materialized_revision":1,"plugin_package_secret_binding":1,"plugin_package_task_reconciliation":1,"project_tool_definition_snapshot":1,"step_run":1,"tool_execution_evidence":1,"tool_execution_start_barrier":1,"tool_invocation_artifact":1,"tool_execution_artifact_binding":1,"tool_execution_completion":1,"tool_execution_failure_completion":1,"tool_result_key_catalog":1,"tool_result_rekey":1,"plugin_package_quarantine":1,"plugin_package_lifecycle":1,"plugin_package_automation_publication":1,"plugin_package_automation_security_withdrawal":1,"plugin_package_workflow_admission":1,"plugin_package_workflow_run_list":1,"run_attempt_log_retention":1,"plugin_package_workflow_task_attempt_admission":1}' ||
|
||||
typeof capability.updated_at_ms !== 'number' ||
|
||||
!Number.isSafeInteger(capability.updated_at_ms) ||
|
||||
capability.updated_at_ms < 0
|
||||
|
||||
@@ -1217,6 +1217,62 @@ export const pluginPackageMaterializedRevisions = sqliteTable(
|
||||
],
|
||||
);
|
||||
|
||||
export const pluginPackageSecretBindings = sqliteTable(
|
||||
'QingLong3PluginPackageSecretBindings',
|
||||
{
|
||||
generationDigest: text('generation_digest').primaryKey(),
|
||||
projectId: text('project_id')
|
||||
.notNull()
|
||||
.references(() => localProjects.id, {
|
||||
onDelete: 'restrict',
|
||||
onUpdate: 'restrict',
|
||||
}),
|
||||
packageName: text('package_name').notNull(),
|
||||
installationId: text('installation_id')
|
||||
.notNull()
|
||||
.references(() => pluginPackageInstalls.installationId, {
|
||||
onDelete: 'restrict',
|
||||
onUpdate: 'restrict',
|
||||
}),
|
||||
lockDigest: text('lock_digest').notNull(),
|
||||
generation: integer('generation').notNull(),
|
||||
manifestDigest: text('manifest_digest').notNull(),
|
||||
authorityKind: text('authority_kind').notNull(),
|
||||
evidenceDigest: text('evidence_digest').notNull(),
|
||||
boundAtMs: integer('bound_at_ms').notNull(),
|
||||
bindingDigest: text('binding_digest').notNull(),
|
||||
bindingJson: text('binding_json', { mode: 'json' })
|
||||
.$type<Record<string, unknown>>()
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_identity_check',
|
||||
sql`length(${table.projectId}) between 1 and 128 and length(${table.packageName}) between 1 and 63 and length(${table.installationId}) between 1 and 128 and ${table.generation} between 1 and 2147483647 and ${table.authorityKind} in ('approved-action-execution','local-owner-confirmation') and ${table.boundAtMs} >= 0`,
|
||||
),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_digest_check',
|
||||
sql`length(${table.generationDigest}) = 64 and ${table.generationDigest} not glob '*[^0-9a-f]*' and length(${table.lockDigest}) = 64 and ${table.lockDigest} not glob '*[^0-9a-f]*' and length(${table.manifestDigest}) = 64 and ${table.manifestDigest} not glob '*[^0-9a-f]*' and length(${table.evidenceDigest}) = 64 and ${table.evidenceDigest} not glob '*[^0-9a-f]*' and length(${table.bindingDigest}) = 64 and ${table.bindingDigest} not glob '*[^0-9a-f]*'`,
|
||||
),
|
||||
check(
|
||||
'ql3_plugin_package_secret_binding_json_check',
|
||||
sql`length(cast(${table.bindingJson} as blob)) between 2 and 65536 and json_valid(${table.bindingJson}) and json_type(${table.bindingJson}) = 'object' and json_extract(${table.bindingJson}, '$.schema') = 'qinglong/plugin-package-secret-binding@v1' and json_extract(${table.bindingJson}, '$.target.generationDigest') = ${table.generationDigest} and json_extract(${table.bindingJson}, '$.target.projectId') = ${table.projectId} and json_extract(${table.bindingJson}, '$.target.packageName') = ${table.packageName} and json_extract(${table.bindingJson}, '$.target.installationId') = ${table.installationId} and json_extract(${table.bindingJson}, '$.target.lockDigest') = ${table.lockDigest} and json_extract(${table.bindingJson}, '$.target.generation') = ${table.generation} and json_extract(${table.bindingJson}, '$.target.manifestDigest') = ${table.manifestDigest} and json_extract(${table.bindingJson}, '$.authority.kind') = ${table.authorityKind} and json_extract(${table.bindingJson}, '$.authority.evidenceDigest') = ${table.evidenceDigest} and json_extract(${table.bindingJson}, '$.boundAtMs') = ${table.boundAtMs} and json_extract(${table.bindingJson}, '$.bindingDigest') = ${table.bindingDigest} and json_type(${table.bindingJson}, '$.entries') = 'array' and json_array_length(json_extract(${table.bindingJson}, '$.entries')) between 1 and 64`,
|
||||
),
|
||||
uniqueIndex('ql3_plugin_package_secret_binding_generation_uidx').on(
|
||||
table.projectId,
|
||||
table.packageName,
|
||||
table.generation,
|
||||
),
|
||||
uniqueIndex('ql3_plugin_package_secret_binding_digest_uidx').on(
|
||||
table.bindingDigest,
|
||||
),
|
||||
index('ql3_plugin_package_secret_binding_install_idx').on(
|
||||
table.installationId,
|
||||
table.generationDigest,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const projectToolDefinitionSnapshots = sqliteTable(
|
||||
'QingLong3ProjectToolDefinitionSnapshots',
|
||||
{
|
||||
@@ -4888,6 +4944,7 @@ export const localSqliteSchema = Object.freeze({
|
||||
pluginPackageInstallHeads,
|
||||
pluginPackageInstallMutations,
|
||||
pluginPackageMaterializedRevisions,
|
||||
pluginPackageSecretBindings,
|
||||
pluginPackageQuarantineEvents,
|
||||
pluginPackageWithdrawalReceipts,
|
||||
pluginPackageWithdrawalTasks,
|
||||
|
||||
Reference in New Issue
Block a user