mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): enforce staged secret binding persistence
This commit is contained in:
@@ -144,9 +144,11 @@ test('creates a reviewed edge database and opens runtime only after readiness',
|
||||
'0092-capability-v46',
|
||||
'0093-plugin-package-secret-materialization-guard',
|
||||
'0094-capability-v47',
|
||||
'0095-plugin-package-secret-binding-target-guard',
|
||||
'0096-capability-v48',
|
||||
]);
|
||||
assert.equal(migrated.readiness.contractName, 'local-control-core');
|
||||
assert.equal(migrated.readiness.contractVersion, 47);
|
||||
assert.equal(migrated.readiness.contractVersion, 48);
|
||||
assert.equal(migrated.readiness.journalMode, 'delete');
|
||||
assert.equal(fs.statSync(databasePath).mode & 0o777, 0o600);
|
||||
|
||||
@@ -592,8 +594,8 @@ test('backfills v14 execution revisions with a verified independent digest', asy
|
||||
.get(),
|
||||
},
|
||||
{
|
||||
contract_version: 47,
|
||||
migration_id: '0093-plugin-package-secret-materialization-guard',
|
||||
contract_version: 48,
|
||||
migration_id: '0095-plugin-package-secret-binding-target-guard',
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
|
||||
@@ -80,11 +80,38 @@ function fixture(boundAtMs = 100) {
|
||||
return { binding, generation };
|
||||
}
|
||||
|
||||
async function harness(active = true) {
|
||||
async function harness(state = 'active') {
|
||||
const client = new DatabaseSync(':memory:');
|
||||
client.exec('PRAGMA foreign_keys = ON');
|
||||
await migrateLocalSqliteDatabase(client);
|
||||
const { binding, generation } = fixture();
|
||||
const isActive = state === 'active';
|
||||
const isStaged = state === 'staged';
|
||||
const source = fixture();
|
||||
const previousLockDigest = 'f'.repeat(64);
|
||||
const generation = isStaged
|
||||
? createPluginPackageResourceGeneration({
|
||||
installationId: 'install-2',
|
||||
projectId: 'project-1',
|
||||
packageName: 'example-monitor',
|
||||
lockDigest: LOCK_DIGEST,
|
||||
generation: 2,
|
||||
previousActiveLockDigest: previousLockDigest,
|
||||
contentDigest: 'b'.repeat(64),
|
||||
contents: MANIFEST.spec.contents,
|
||||
})
|
||||
: source.generation;
|
||||
const binding = isStaged
|
||||
? createPluginPackageSecretBinding({
|
||||
generation,
|
||||
manifest: MANIFEST,
|
||||
assignments: source.binding.entries.map(({ name, secretRef }) => ({
|
||||
name,
|
||||
secretRef,
|
||||
})),
|
||||
authority: source.binding.authority,
|
||||
boundAtMs: source.binding.boundAtMs,
|
||||
})
|
||||
: source.binding;
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3Projects"
|
||||
@@ -100,11 +127,11 @@ async function harness(active = true) {
|
||||
manifestDigest: binding.target.manifestDigest,
|
||||
});
|
||||
const recordJson = JSON.stringify({
|
||||
installationId: 'install-1',
|
||||
installationId: isStaged ? 'install-2' : 'install-1',
|
||||
projectId: 'project-1',
|
||||
packageName: 'example-monitor',
|
||||
lockDigest: LOCK_DIGEST,
|
||||
state: active ? 'active' : 'failed',
|
||||
state,
|
||||
version: 1,
|
||||
recordDigest,
|
||||
});
|
||||
@@ -116,16 +143,18 @@ async function harness(active = true) {
|
||||
previous_active_lock_digest, active_lock_digest, state, version,
|
||||
last_mutation_id, last_mutation_digest, lock_json, record_json,
|
||||
record_digest, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, '1.0.0', 'install', ?, 1, NULL, ?, ?, 1,
|
||||
) VALUES (?, ?, ?, '1.0.0', 'install', ?, ?, ?, ?, ?, 1,
|
||||
'mutation-1', ?, ?, ?, ?, 1, 1)`,
|
||||
)
|
||||
.run(
|
||||
'install-1',
|
||||
isStaged ? 'install-2' : 'install-1',
|
||||
'project-1',
|
||||
'example-monitor',
|
||||
LOCK_DIGEST,
|
||||
active ? LOCK_DIGEST : null,
|
||||
active ? 'active' : 'failed',
|
||||
generation.generation,
|
||||
isStaged ? previousLockDigest : null,
|
||||
isActive ? LOCK_DIGEST : isStaged ? previousLockDigest : null,
|
||||
state,
|
||||
'e'.repeat(64),
|
||||
lockJson,
|
||||
recordJson,
|
||||
@@ -135,9 +164,47 @@ async function harness(active = true) {
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3PluginPackageInstallHeads"
|
||||
(project_id, package_name, installation_id)
|
||||
VALUES ('project-1', 'example-monitor', 'install-1')`,
|
||||
VALUES ('project-1', 'example-monitor', ?)`,
|
||||
)
|
||||
.run();
|
||||
.run(isStaged ? 'install-2' : 'install-1');
|
||||
if (isStaged) {
|
||||
const previousRecordDigest = '9'.repeat(64);
|
||||
const previousLockJson = JSON.stringify({
|
||||
lockDigest: previousLockDigest,
|
||||
projectId: 'project-1',
|
||||
packageName: 'example-monitor',
|
||||
manifestDigest: '8'.repeat(64),
|
||||
});
|
||||
const previousRecordJson = JSON.stringify({
|
||||
installationId: 'install-1',
|
||||
projectId: 'project-1',
|
||||
packageName: 'example-monitor',
|
||||
lockDigest: previousLockDigest,
|
||||
state: 'active',
|
||||
version: 1,
|
||||
recordDigest: previousRecordDigest,
|
||||
});
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3PluginPackageInstalls" (
|
||||
installation_id, project_id, package_name, package_version,
|
||||
operation, lock_digest, target_generation,
|
||||
previous_active_lock_digest, active_lock_digest, state, version,
|
||||
last_mutation_id, last_mutation_digest, lock_json, record_json,
|
||||
record_digest, created_at_ms, updated_at_ms
|
||||
) VALUES ('install-1', 'project-1', 'example-monitor', '0.9.0',
|
||||
'install', ?, 1, NULL, ?, 'active', 1,
|
||||
'mutation-previous', ?, ?, ?, ?, 0, 0)`,
|
||||
)
|
||||
.run(
|
||||
previousLockDigest,
|
||||
previousLockDigest,
|
||||
'7'.repeat(64),
|
||||
previousLockJson,
|
||||
previousRecordJson,
|
||||
previousRecordDigest,
|
||||
);
|
||||
}
|
||||
return {
|
||||
client,
|
||||
binding,
|
||||
@@ -161,7 +228,7 @@ test('publishes and exact-replays one active generation binding', async (t) => {
|
||||
});
|
||||
|
||||
test('rejects inactive targets and conflicting content', async (t) => {
|
||||
const inactive = await harness(false);
|
||||
const inactive = await harness('failed');
|
||||
t.after(() => inactive.client.close());
|
||||
await assert.rejects(
|
||||
inactive.repository.publish(inactive.binding),
|
||||
@@ -177,6 +244,21 @@ test('rejects inactive targets and conflicting content', async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test('publishes a reviewed current staged generation but rejects post-stage states', async (t) => {
|
||||
const staged = await harness('staged');
|
||||
t.after(() => staged.client.close());
|
||||
assert.equal((await staged.repository.publish(staged.binding)).status, 'created');
|
||||
|
||||
for (const state of ['queued', 'activating']) {
|
||||
const rejected = await harness(state);
|
||||
t.after(() => rejected.client.close());
|
||||
await assert.rejects(
|
||||
rejected.repository.publish(rejected.binding),
|
||||
PluginPackageSecretBindingConflictError,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('fails closed when durable binding JSON is changed in place', async (t) => {
|
||||
const value = await harness();
|
||||
t.after(() => value.client.close());
|
||||
|
||||
@@ -156,7 +156,7 @@ test('atomically admits one generation-bound Workflow Run and exactly replays it
|
||||
},
|
||||
{ runs: 1, steps: 2, events: 3, mutations: 2, admissions: 1 },
|
||||
);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 47);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 48);
|
||||
});
|
||||
|
||||
test('runs an optional authorization guard inside new and replay transactions', async (t) => {
|
||||
@@ -288,7 +288,7 @@ test('exactly replays immutable admission after the Workflow StepRun advances',
|
||||
},
|
||||
{ status: 'running', version: 5, eventSequence: 5 },
|
||||
);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 47);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 48);
|
||||
});
|
||||
|
||||
test('fails closed before writing when the exact installation is not active', async (t) => {
|
||||
|
||||
+1
-1
@@ -231,7 +231,7 @@ test('atomically admits the exact reconciled local Task revision and replays it'
|
||||
stepAttemptCount: 0,
|
||||
},
|
||||
);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 47);
|
||||
assert.equal((await auditLocalSqliteReadiness(client)).contractVersion, 48);
|
||||
});
|
||||
|
||||
test('bounds candidate paging before SQL and fences cancellation', async (t) => {
|
||||
|
||||
@@ -40,9 +40,9 @@ test('creates and exactly replays a reviewed rollout backup', async (t) => {
|
||||
await migrateLocalSqlitePath(state);
|
||||
const prepared = await createLocalSqliteRolloutBackup(state);
|
||||
assert.equal(prepared.status, 'prepared');
|
||||
assert.equal(prepared.contractVersion, 47);
|
||||
assert.equal(prepared.writeContractVersion, 47);
|
||||
assert.equal(LOCAL_SQLITE_WRITE_CONTRACT_VERSION, 47);
|
||||
assert.equal(prepared.contractVersion, 48);
|
||||
assert.equal(prepared.writeContractVersion, 48);
|
||||
assert.equal(LOCAL_SQLITE_WRITE_CONTRACT_VERSION, 48);
|
||||
assert.match(prepared.sha256, /^[0-9a-f]{64}$/);
|
||||
assert.equal(prepared.bytes > 0, true);
|
||||
assert.equal(prepared.pageCount > 0, true);
|
||||
|
||||
Reference in New Issue
Block a user