mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 10:32:40 +08:00
feat(ql3): materialize package secret bindings
This commit is contained in:
@@ -102,6 +102,8 @@ import { local0089PluginPackageAutomationDispositionEventsMigration } from '../m
|
||||
import { local0090CapabilityV45Migration } from '../migrations/0090-capability-v45';
|
||||
import { local0091PluginPackageSecretBindingsMigration } from '../migrations/0091-plugin-package-secret-bindings';
|
||||
import { local0092CapabilityV46Migration } from '../migrations/0092-capability-v46';
|
||||
import { local0093PluginPackageSecretMaterializationGuardMigration } from '../migrations/0093-plugin-package-secret-materialization-guard';
|
||||
import { local0094CapabilityV47Migration } from '../migrations/0094-capability-v47';
|
||||
import type { LocalSqliteMigrationContext } from '../migrations/sqlMigration';
|
||||
import {
|
||||
LOCAL_SQLITE_MIGRATION_STREAM_ID,
|
||||
@@ -216,6 +218,8 @@ export const localSqliteMigrationDefinition: MigrationStreamDefinition<LocalSqli
|
||||
local0090CapabilityV45Migration,
|
||||
local0091PluginPackageSecretBindingsMigration,
|
||||
local0092CapabilityV46Migration,
|
||||
local0093PluginPackageSecretMaterializationGuardMigration,
|
||||
local0094CapabilityV47Migration,
|
||||
]),
|
||||
});
|
||||
|
||||
|
||||
@@ -472,5 +472,15 @@ export const localSqliteMigrationManifest: MigrationStreamManifest =
|
||||
checksum:
|
||||
'a1b058bb7b0259069202632d27d4a55466828cde831c49821eb862feeba6ce35',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: '0093-plugin-package-secret-materialization-guard',
|
||||
checksum:
|
||||
'e549ea0932c5b3e48f954ff72f205d84795ae03d473312070c3a9c4939169c42',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: '0094-capability-v47',
|
||||
checksum:
|
||||
'6ecbbef0b9d9b3c738cc47a80868e0871c47916fb67e7c37f8620f9099de735e',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { defineLocalSqliteMigration } from './sqlMigration';
|
||||
import { LOCAL_PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGER_SQL } from '../plugin-package/pluginPackageSecretMaterializationSchemaContract';
|
||||
|
||||
export const local0093PluginPackageSecretMaterializationGuardMigration =
|
||||
defineLocalSqliteMigration({
|
||||
id: '0093-plugin-package-secret-materialization-guard',
|
||||
statements: [LOCAL_PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGER_SQL],
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CAPABILITIES_V46 } from './0092-capability-v46';
|
||||
import { defineLocalSqliteMigration } from './sqlMigration';
|
||||
|
||||
export const CAPABILITIES_V47 = CAPABILITIES_V46.replace(
|
||||
'"plugin_package_secret_binding":1,',
|
||||
'"plugin_package_secret_binding":1,"plugin_package_secret_materialization":1,',
|
||||
);
|
||||
|
||||
export const local0094CapabilityV47Migration = defineLocalSqliteMigration({
|
||||
id: '0094-capability-v47',
|
||||
statements: [
|
||||
`UPDATE "QingLong3SchemaCapabilities" SET contract_version = 47, migration_id = '0093-plugin-package-secret-materialization-guard', capabilities = '${CAPABILITIES_V47}', updated_at_ms = CAST(unixepoch('subsec') * 1000 AS INTEGER) WHERE contract_name = 'local-control-core' AND contract_version = 46 AND migration_id = '0091-plugin-package-secret-bindings' AND capabilities = '${CAPABILITIES_V46}'`,
|
||||
],
|
||||
});
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Immutable DDL contract shared by the migration writer and read-only
|
||||
* readiness auditor. Keeping it outside /migrations preserves lazy DDL
|
||||
* loading on constrained Local Profiles.
|
||||
*/
|
||||
export const LOCAL_PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGER_SQL = `
|
||||
CREATE TRIGGER ql3_plugin_package_secret_materialization_guard
|
||||
BEFORE INSERT ON "QingLong3PluginPackageMaterializedRevisions"
|
||||
BEGIN
|
||||
SELECT CASE
|
||||
WHEN json_type(
|
||||
NEW.revision_json,
|
||||
'$.manifest.spec.permissions.secrets'
|
||||
) IS NOT 'array'
|
||||
THEN RAISE(ABORT, 'Package Secret permission declarations are malformed')
|
||||
WHEN json_array_length(json_extract(
|
||||
NEW.revision_json,
|
||||
'$.manifest.spec.permissions.secrets'
|
||||
)) = 0 AND json_type(NEW.revision_json, '$.secretBinding') IS NOT NULL
|
||||
THEN RAISE(ABORT, 'unexpected Package Secret binding')
|
||||
WHEN json_array_length(json_extract(
|
||||
NEW.revision_json,
|
||||
'$.manifest.spec.permissions.secrets'
|
||||
)) > 0 AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "QingLong3PluginPackageSecretBindings" AS binding
|
||||
WHERE binding.generation_digest = NEW.generation_digest
|
||||
AND json(binding.binding_json) = json_extract(
|
||||
NEW.revision_json,
|
||||
'$.secretBinding'
|
||||
)
|
||||
)
|
||||
THEN RAISE(ABORT, 'Package Secret binding is absent or mismatched')
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(NEW.revision_json, '$.resources') AS resource
|
||||
JOIN json_each(
|
||||
resource.value,
|
||||
'$.value.spec.config.environment'
|
||||
) AS environment
|
||||
WHERE json_extract(resource.value, '$.kind') = 'task'
|
||||
AND json_extract(environment.value, '$.kind') = 'package-secret'
|
||||
)
|
||||
THEN RAISE(ABORT, 'unresolved Package Secret placeholder')
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(NEW.revision_json, '$.resources') AS resource
|
||||
JOIN json_each(
|
||||
resource.value,
|
||||
'$.value.spec.config.environment'
|
||||
) AS environment
|
||||
WHERE json_extract(resource.value, '$.kind') = 'task'
|
||||
AND json_extract(environment.value, '$.kind') = 'secret'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM json_each(
|
||||
NEW.revision_json,
|
||||
'$.secretBinding.entries'
|
||||
) AS binding_entry
|
||||
WHERE json_extract(binding_entry.value, '$.secretRef') =
|
||||
json_extract(environment.value, '$.secretRef')
|
||||
)
|
||||
)
|
||||
THEN RAISE(ABORT, 'Task SecretRef is outside Package binding')
|
||||
END;
|
||||
END
|
||||
`.trim();
|
||||
@@ -56,6 +56,7 @@ export type LocalProfileStorageBootstrapResult =
|
||||
readonly ownerPepper: LocalSqliteRuntimeDatabase['ownerPepper'];
|
||||
readonly pluginPackageInstalls: LocalSqliteRuntimeDatabase['pluginPackageInstalls'];
|
||||
readonly pluginPackageMaterializedRevisions: LocalSqliteRuntimeDatabase['pluginPackageMaterializedRevisions'];
|
||||
readonly pluginPackageSecretBindings: LocalSqliteRuntimeDatabase['pluginPackageSecretBindings'];
|
||||
readonly pluginPackageTaskReconciliations: LocalSqliteRuntimeDatabase['pluginPackageTaskReconciliations'];
|
||||
readonly pluginPackageAutomationPublications: LocalSqliteRuntimeDatabase['pluginPackageAutomationPublications'];
|
||||
readonly projectToolDefinitionSnapshots: LocalSqliteRuntimeDatabase['projectToolDefinitionSnapshots'];
|
||||
@@ -151,6 +152,7 @@ export async function bootstrapLocalProfileStorage(
|
||||
pluginPackageInstalls: database.pluginPackageInstalls,
|
||||
pluginPackageMaterializedRevisions:
|
||||
database.pluginPackageMaterializedRevisions,
|
||||
pluginPackageSecretBindings: database.pluginPackageSecretBindings,
|
||||
pluginPackageTaskReconciliations:
|
||||
database.pluginPackageTaskReconciliations,
|
||||
pluginPackageAutomationPublications:
|
||||
|
||||
@@ -2,13 +2,22 @@ import { auditMigrationStreamHistory } from '@qinglong/runtime-core/migration-st
|
||||
import type { DatabaseSync } from 'node:sqlite';
|
||||
import { localSqliteMigrationManifest } from '../migration/migrationManifest';
|
||||
import { LocalSqliteMigrationStreamStore } from '../migration/migrationStreamStore';
|
||||
import { LOCAL_PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGER_SQL } from '../plugin-package/pluginPackageSecretMaterializationSchemaContract';
|
||||
import {
|
||||
LOCAL_STEP_RUN_REFERENCE_TRIGGERS,
|
||||
normalizeLocalSqliteSchemaSql,
|
||||
} from '../run/stepRunSchemaContract';
|
||||
|
||||
export const LOCAL_SQLITE_CONTRACT_NAME = 'local-control-core';
|
||||
export const LOCAL_SQLITE_CONTRACT_VERSION = 46;
|
||||
export const LOCAL_SQLITE_CONTRACT_VERSION = 47;
|
||||
|
||||
const PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGERS = Object.freeze([
|
||||
Object.freeze({
|
||||
name: 'ql3_plugin_package_secret_materialization_guard',
|
||||
tableName: 'QingLong3PluginPackageMaterializedRevisions',
|
||||
sql: LOCAL_PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGER_SQL,
|
||||
}),
|
||||
]);
|
||||
|
||||
const PLUGIN_PACKAGE_AUTOMATION_DISPOSITION_TRIGGERS = Object.freeze([
|
||||
Object.freeze({
|
||||
@@ -1743,6 +1752,7 @@ function assertRequiredSchema(client: DatabaseSync): number {
|
||||
const expectedTriggers = [
|
||||
...LOCAL_STEP_RUN_REFERENCE_TRIGGERS,
|
||||
...PLUGIN_PACKAGE_AUTOMATION_DISPOSITION_TRIGGERS,
|
||||
...PLUGIN_PACKAGE_SECRET_MATERIALIZATION_TRIGGERS,
|
||||
].sort((left, right) => left.name.localeCompare(right.name));
|
||||
if (
|
||||
triggerRows.length !== expectedTriggers.length ||
|
||||
@@ -2554,10 +2564,10 @@ export async function auditLocalSqliteReadiness(
|
||||
capability.contract_name !== LOCAL_SQLITE_CONTRACT_NAME ||
|
||||
capability.contract_version !== LOCAL_SQLITE_CONTRACT_VERSION ||
|
||||
capability.migration_id !==
|
||||
'0091-plugin-package-secret-bindings' ||
|
||||
'0093-plugin-package-secret-materialization-guard' ||
|
||||
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_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}' ||
|
||||
'{"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_secret_materialization":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
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
PluginPackageAutomationPublicationRecoverySource,
|
||||
} from '@qinglong/runtime-core/plugin-package-automation-publication';
|
||||
import type { PluginPackageMaterializedRevisionRepository } from '@qinglong/runtime-core/plugin-package-resource-materialization';
|
||||
import type { PluginPackageSecretBindingRepository } from '@qinglong/runtime-core/plugin-package-secret-binding';
|
||||
import type { PluginPackageTaskReconciliationRepository } from '@qinglong/runtime-core/plugin-package-task-reconciliation';
|
||||
import type { PluginPackageTaskPublicationRecoverySource } from '@qinglong/runtime-core/plugin-package-task-publication';
|
||||
import type { StepRunRepository } from '@qinglong/runtime-core/step-run';
|
||||
@@ -109,6 +110,7 @@ export interface LocalSqliteRuntimeDatabase {
|
||||
readonly ownerPepper: LocalOwnerPepperRepository;
|
||||
pluginPackageInstalls(): Promise<PluginPackageInstallRepository>;
|
||||
pluginPackageMaterializedRevisions(): Promise<PluginPackageMaterializedRevisionRepository>;
|
||||
pluginPackageSecretBindings(): Promise<PluginPackageSecretBindingRepository>;
|
||||
pluginPackageTaskReconciliations(): Promise<
|
||||
PluginPackageTaskReconciliationRepository &
|
||||
PluginPackageTaskPublicationRecoverySource
|
||||
@@ -195,6 +197,9 @@ export async function openLocalSqliteRuntimeDatabase(
|
||||
let pluginPackageMaterializedRevisionsPromise:
|
||||
| Promise<PluginPackageMaterializedRevisionRepository>
|
||||
| undefined;
|
||||
let pluginPackageSecretBindingsPromise:
|
||||
| Promise<PluginPackageSecretBindingRepository>
|
||||
| undefined;
|
||||
let pluginPackageTaskReconciliationsPromise:
|
||||
| Promise<
|
||||
PluginPackageTaskReconciliationRepository &
|
||||
@@ -271,6 +276,15 @@ export async function openLocalSqliteRuntimeDatabase(
|
||||
);
|
||||
return pluginPackageMaterializedRevisionsPromise;
|
||||
},
|
||||
pluginPackageSecretBindings() {
|
||||
pluginPackageSecretBindingsPromise ??= import(
|
||||
'../plugin-package/pluginPackageSecretBindingRepository.js'
|
||||
).then(
|
||||
({ LocalSqlitePluginPackageSecretBindingRepository }) =>
|
||||
new LocalSqlitePluginPackageSecretBindingRepository(authority),
|
||||
);
|
||||
return pluginPackageSecretBindingsPromise;
|
||||
},
|
||||
pluginPackageTaskReconciliations() {
|
||||
pluginPackageTaskReconciliationsPromise ??= import(
|
||||
'../plugin-package/pluginPackageTaskReconciliationRepository.js'
|
||||
|
||||
Reference in New Issue
Block a user