feat(ql3): prove first usable automation journey

This commit is contained in:
whyour
2026-08-28 08:00:11 +08:00
parent c60ab7e48a
commit 6342e11a10
15 changed files with 800 additions and 26 deletions
@@ -3,6 +3,7 @@ import {
readPrivateLocalCommandFile,
} from '@qinglong/local-command-file';
import {
installLocalOwnerCredentialPresentation,
LocalOwnerSecretDeliveryError,
openLocalOwnerConsole,
type ClaimLocalOwnerFromDeliveriesRequest,
@@ -71,6 +72,16 @@ export interface AcknowledgeLocalOwnerDeliveryCommand {
};
}
export interface InstallLocalOwnerCredentialPresentationCommand {
readonly schemaVersion: 1;
readonly operation: 'owner.credential-presentation.install-from-delivery';
readonly options: OpenLocalOwnerConsoleOptions;
readonly request: {
readonly credentialMutationId: string;
readonly destinationFilePath: string;
};
}
export interface IssueLocalOwnerCredentialRecoveryCommand {
readonly schemaVersion: 1;
readonly operation: 'owner.credential-recovery.issue';
@@ -101,6 +112,7 @@ export type LocalOwnerCommand =
| ClaimLocalOwnerCommand
| InspectLocalOwnerDeliveryCommand
| AcknowledgeLocalOwnerDeliveryCommand
| InstallLocalOwnerCredentialPresentationCommand
| IssueLocalOwnerCredentialRecoveryCommand
| CompleteLocalOwnerCredentialRecoveryCommand;
@@ -153,6 +165,7 @@ function normalizeCommand(value: unknown): Readonly<LocalOwnerCommand> {
'owner.claim.from-deliveries',
'owner.delivery.inspect',
'owner.delivery.acknowledge',
'owner.credential-presentation.install-from-delivery',
'owner.credential-recovery.issue',
'owner.credential-recovery.complete',
];
@@ -222,6 +235,20 @@ function validateAcknowledgementRequest(
}
}
function validatePresentationInstallRequest(
value: InstallLocalOwnerCredentialPresentationCommand['request'],
): void {
if (
!exactKeys(value, ['credentialMutationId', 'destinationFilePath']) ||
!UUID_V4_PATTERN.test(value.credentialMutationId) ||
typeof value.destinationFilePath !== 'string'
) {
throw new LocalOwnerCliConfigurationError(
'credential presentation install request is invalid',
);
}
}
function missing(error: unknown): boolean {
let current = error;
for (let depth = 0; depth < 4; depth += 1) {
@@ -355,6 +382,22 @@ async function execute(
ttlMs: acknowledgement.ttlMs,
});
}
case 'owner.credential-presentation.install-from-delivery': {
validatePresentationInstallRequest(command.request);
const result = installLocalOwnerCredentialPresentation({
deploymentRoot: command.options.deploymentRoot,
deliveryFilePath: console.credentialDeliveryPath(
command.request.credentialMutationId,
),
destinationFilePath: command.request.destinationFilePath,
});
return Object.freeze({
schemaVersion: 1 as const,
operation: command.operation,
status: result.status,
credentialMutationId: result.credentialMutationId,
});
}
case 'owner.credential-recovery.issue': {
const result = await console.credentialRecovery.issue(command.request);
const delivery = deliveryAfterMutation(result.status, () =>
@@ -145,6 +145,64 @@ test('completes fresh Owner and credential recovery ceremonies without returning
assert.equal(claimed.status, 'inserted');
assert.equal(claimed.role, 'owner');
assert.equal(JSON.stringify(claimed).includes('secret'), false);
const credentialFilePath = path.join(
state.options.deploymentRoot,
'owner-credential.json',
);
const installed = await runner.run(
commandFile(
state,
'owner.credential-presentation.install-from-delivery',
{ credentialMutationId, destinationFilePath: credentialFilePath },
'04b-install-presentation',
),
);
assert.deepEqual(installed, {
schemaVersion: 1,
operation: 'owner.credential-presentation.install-from-delivery',
status: 'installed',
credentialMutationId,
});
const presentation = JSON.parse(fs.readFileSync(credentialFilePath, 'utf8'));
assert.equal(
presentation.kind,
'qinglong3-local-identity-credential-presentation',
);
assert.match(
presentation.token,
/^ql3c_own_[A-Za-z0-9_-]{22}_[A-Za-z0-9_-]{43}$/,
);
assert.equal(fs.statSync(credentialFilePath).mode & 0o777, 0o600);
const replayedInstall = await runner.run(
commandFile(
state,
'owner.credential-presentation.install-from-delivery',
{ credentialMutationId, destinationFilePath: credentialFilePath },
'04c-replay-presentation',
),
);
assert.equal(replayedInstall.status, 'existing');
assertNoSecretFields(installed);
assertNoSecretFields(replayedInstall);
const escapedDestination = path.join(
os.tmpdir(),
`ql3-owner-credential-escape-${process.pid}.json`,
);
await assert.rejects(
runner.run(
commandFile(
state,
'owner.credential-presentation.install-from-delivery',
{
credentialMutationId,
destinationFilePath: escapedDestination,
},
'04d-reject-escaped-presentation',
),
),
/destinationFilePath must be a descendant of deploymentRoot/,
);
assert.equal(fs.existsSync(escapedDestination), false);
for (const [purpose, mutationId, digest, suffix] of [
[
'credential-provisioning',