feat(ql3): cut alpha.1 candidate milestone

This commit is contained in:
whyour
2026-08-26 01:41:20 +08:00
parent c2df0c7215
commit 07cdc76bae
94 changed files with 1469 additions and 168 deletions
+11 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@qinglong/runtime-core",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.1",
"private": true,
"description": "QingLong 3.0 profile-neutral runtime contracts",
"license": "Apache-2.0",
@@ -961,6 +961,16 @@
"require": "./dist/security/identity-credential/apiCredentialToken.js",
"default": "./dist/security/identity-credential/apiCredentialToken.js"
},
"./api-credential-pepper-keyring": {
"types": "./dist/security/identity-credential/apiCredentialPepperKeyring.d.ts",
"require": "./dist/security/identity-credential/apiCredentialPepperKeyring.js",
"default": "./dist/security/identity-credential/apiCredentialPepperKeyring.js"
},
"./api-credential-pepper-reference": {
"types": "./dist/security/identity-credential/apiCredentialPepperReference.d.ts",
"require": "./dist/security/identity-credential/apiCredentialPepperReference.js",
"default": "./dist/security/identity-credential/apiCredentialPepperReference.js"
},
"./identity-administration": {
"types": "./dist/security/identity-credential/identityAdministration.d.ts",
"require": "./dist/security/identity-credential/identityAdministration.js",
@@ -0,0 +1,139 @@
import { assertApiCredentialPepperKeyId } from './apiCredential';
import { assertApiCredentialPepper } from './apiCredentialToken';
export const MAX_API_CREDENTIAL_PEPPER_KEYS = 2;
export interface ApiCredentialPepperKey {
readonly pepperKeyId: string;
readonly pepper: string;
}
export interface ApiCredentialPepperKeyring {
readonly schemaVersion: 1;
readonly activePepperKeyId: string;
readonly keys: readonly Readonly<ApiCredentialPepperKey>[];
}
export class ApiCredentialPepperKeyringConfigurationError extends TypeError {
constructor(message: string) {
super(`API credential pepper keyring is invalid: ${message}`);
this.name = 'ApiCredentialPepperKeyringConfigurationError';
}
}
function exactObject(
value: unknown,
expectedKeys: readonly string[],
label: string,
): asserts value is Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new ApiCredentialPepperKeyringConfigurationError(
`${label} must be an object`,
);
}
const actual = Object.keys(value).sort();
const expected = [...expectedKeys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
throw new ApiCredentialPepperKeyringConfigurationError(
`${label} shape is invalid`,
);
}
}
function normalizeKey(value: unknown): Readonly<ApiCredentialPepperKey> {
exactObject(value, ['pepper', 'pepperKeyId'], 'key');
try {
assertApiCredentialPepperKeyId(value.pepperKeyId as string);
assertApiCredentialPepper(value.pepper as string);
} catch {
throw new ApiCredentialPepperKeyringConfigurationError(
'key material is invalid',
);
}
return Object.freeze({
pepperKeyId: value.pepperKeyId as string,
pepper: value.pepper as string,
});
}
export function normalizeApiCredentialPepperKeyring(
value: unknown,
): Readonly<ApiCredentialPepperKeyring> {
exactObject(
value,
['activePepperKeyId', 'keys', 'schemaVersion'],
'keyring',
);
if (
value.schemaVersion !== 1 ||
!Array.isArray(value.keys) ||
value.keys.length < 1 ||
value.keys.length > MAX_API_CREDENTIAL_PEPPER_KEYS
) {
throw new ApiCredentialPepperKeyringConfigurationError(
'version or key count is invalid',
);
}
let activePepperKeyId: string;
try {
assertApiCredentialPepperKeyId(value.activePepperKeyId as string);
activePepperKeyId = value.activePepperKeyId as string;
} catch {
throw new ApiCredentialPepperKeyringConfigurationError(
'activePepperKeyId is invalid',
);
}
const keys = value.keys.map(normalizeKey);
const keyIds = new Set(keys.map((key) => key.pepperKeyId));
if (keyIds.size !== keys.length || !keyIds.has(activePepperKeyId)) {
throw new ApiCredentialPepperKeyringConfigurationError(
'keys must be unique and contain the active key',
);
}
return Object.freeze({
schemaVersion: 1,
activePepperKeyId,
keys: Object.freeze(keys),
});
}
export function createSingletonApiCredentialPepperKeyring(
pepper: string,
pepperKeyId: string,
): Readonly<ApiCredentialPepperKeyring> {
return normalizeApiCredentialPepperKeyring({
schemaVersion: 1,
activePepperKeyId: pepperKeyId,
keys: [{ pepperKeyId, pepper }],
});
}
export function resolveApiCredentialPepperKey(
keyring: Readonly<ApiCredentialPepperKeyring>,
pepperKeyId: string,
): Readonly<ApiCredentialPepperKey> | null {
const normalized = normalizeApiCredentialPepperKeyring(keyring);
try {
assertApiCredentialPepperKeyId(pepperKeyId);
} catch {
throw new ApiCredentialPepperKeyringConfigurationError(
'pepperKeyId is invalid',
);
}
return (
normalized.keys.find((key) => key.pepperKeyId === pepperKeyId) ?? null
);
}
export function activeApiCredentialPepperKey(
keyring: Readonly<ApiCredentialPepperKeyring>,
): Readonly<ApiCredentialPepperKey> {
const normalized = normalizeApiCredentialPepperKeyring(keyring);
return resolveApiCredentialPepperKey(
normalized,
normalized.activePepperKeyId,
)!;
}
@@ -0,0 +1,47 @@
import { assertApiCredentialPepperKeyId } from './apiCredential';
export const MAX_API_CREDENTIAL_PEPPER_REFERENCES = 64;
export interface ApiCredentialPepperReferenceInspection {
readonly pepperKeyId: string;
readonly observedAtMs: number;
readonly credentialIds: readonly string[];
readonly hasMore: boolean;
}
export interface ApiCredentialPepperReferenceRepository {
inspect(
pepperKeyId: string,
limit?: number,
): Promise<Readonly<ApiCredentialPepperReferenceInspection>>;
}
export class ApiCredentialPepperReferenceUnavailableError extends Error {
readonly code = 'API_CREDENTIAL_PEPPER_REFERENCE_UNAVAILABLE';
constructor() {
super('API credential pepper references are unavailable');
this.name = 'ApiCredentialPepperReferenceUnavailableError';
}
}
export function normalizeApiCredentialPepperReferenceLimit(
value: number | undefined,
): number {
const resolved = value ?? MAX_API_CREDENTIAL_PEPPER_REFERENCES;
if (
!Number.isSafeInteger(resolved) ||
resolved < 1 ||
resolved > MAX_API_CREDENTIAL_PEPPER_REFERENCES
) {
throw new TypeError('API credential pepper reference limit is invalid');
}
return resolved;
}
export function normalizeApiCredentialPepperReferenceKeyId(
value: string,
): string {
assertApiCredentialPepperKeyId(value);
return value;
}
@@ -0,0 +1,81 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const {
activeApiCredentialPepperKey,
createSingletonApiCredentialPepperKeyring,
normalizeApiCredentialPepperKeyring,
resolveApiCredentialPepperKey,
} = require('../dist/security/identity-credential/apiCredentialPepperKeyring.js');
const LEGACY = 'A'.repeat(43);
const NEXT = Buffer.alloc(32, 2).toString('base64url');
test('normalizes a bounded dual-generation keyring and resolves exact keys', () => {
const keyring = normalizeApiCredentialPepperKeyring({
schemaVersion: 1,
activePepperKeyId: 'rotation-2026-08',
keys: [
{ pepperKeyId: 'legacy-v1', pepper: LEGACY },
{ pepperKeyId: 'rotation-2026-08', pepper: NEXT },
],
});
assert.deepEqual(activeApiCredentialPepperKey(keyring), {
pepperKeyId: 'rotation-2026-08',
pepper: NEXT,
});
assert.deepEqual(resolveApiCredentialPepperKey(keyring, 'legacy-v1'), {
pepperKeyId: 'legacy-v1',
pepper: LEGACY,
});
assert.equal(resolveApiCredentialPepperKey(keyring, 'missing-v1'), null);
});
test('singleton compatibility is explicit and preserves its key id', () => {
assert.deepEqual(
createSingletonApiCredentialPepperKeyring(LEGACY, 'legacy-v1'),
{
schemaVersion: 1,
activePepperKeyId: 'legacy-v1',
keys: [{ pepperKeyId: 'legacy-v1', pepper: LEGACY }],
},
);
});
test('rejects widened, duplicate, missing-active, empty, and oversized keyrings', () => {
const valid = {
schemaVersion: 1,
activePepperKeyId: 'legacy-v1',
keys: [{ pepperKeyId: 'legacy-v1', pepper: LEGACY }],
};
for (const candidate of [
{ ...valid, extra: true },
{ ...valid, keys: [] },
{
...valid,
keys: [valid.keys[0], valid.keys[0]],
},
{ ...valid, activePepperKeyId: 'missing-v1' },
{
...valid,
keys: [
valid.keys[0],
{ pepperKeyId: 'next-v1', pepper: NEXT },
{
pepperKeyId: 'third-v1',
pepper: Buffer.alloc(32, 3).toString('base64url'),
},
],
},
{
...valid,
keys: [{ ...valid.keys[0], extra: true }],
},
]) {
assert.throws(
() => normalizeApiCredentialPepperKeyring(candidate),
/pepper keyring is invalid/,
);
}
});