From 8bfa00ed394b6b0828db3056fe1f6e53167aa5d3 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 7 Oct 2026 03:11:07 +1100 Subject: [PATCH 1/3] perf(artifact-delivery): serve a warm run from its ready build A warm run requested a new Resolution, waited for the queue, then waited again while the build re-signed the same bytes: 5.6 to 9.2 s in 14 production runs on 2026-10-07. When a ready public build of the same Repository, commit, Skill folder and policy passes every reuse check, the POST now answers with that ready Resolution, as GET would, and queues nothing. A request that names no commit resolves on GitHub first, within 2.5 s, using the cached commit and tree reads. When no ready build fits, the build gets the request pinned to that commit and folder, as a registry name already is. The run sweep keeps building every check. --- .../server/api/v1/resolutions/index.post.ts | 13 +- .../artifact-delivery/server/utils/build.ts | 8 +- .../server/utils/request-resolution.ts | 16 +- .../server/utils/served-resolution.ts | 131 +++++++ .../artifact-delivery/server/utils/state.ts | 6 +- test/unit/artifact-served-resolution.test.ts | 366 ++++++++++++++++++ 6 files changed, 532 insertions(+), 8 deletions(-) create mode 100644 layers/artifact-delivery/server/utils/served-resolution.ts create mode 100644 test/unit/artifact-served-resolution.test.ts diff --git a/layers/artifact-delivery/server/api/v1/resolutions/index.post.ts b/layers/artifact-delivery/server/api/v1/resolutions/index.post.ts index fdbc3bff..9b4cc0ed 100644 --- a/layers/artifact-delivery/server/api/v1/resolutions/index.post.ts +++ b/layers/artifact-delivery/server/api/v1/resolutions/index.post.ts @@ -9,8 +9,9 @@ import { import { withArtifactProblems } from '../../../utils/artifact-problem' import { findPrivateRepositoryAccess } from '../../../utils/private-access' import { privateArtifactAccessEnabled } from '../../../utils/private-feature' -import { enqueueArtifactBuild } from '../../../utils/queue' +import { createArtifactBuildDependencies, enqueueArtifactBuild } from '../../../utils/queue' import { fetchAdmittedSkillIdentity, requestResolution } from '../../../utils/request-resolution' +import { serveReadyResolution } from '../../../utils/served-resolution' import { setSkillPageUrlHeader } from '../../../utils/skill-page' export default withArtifactProblems(defineApiHandler({ @@ -33,11 +34,21 @@ export default withArtifactProblems(defineApiHandler({ body.source.repository, ) : { _tag: 'not-found' as const } + const build = createArtifactBuildDependencies(platform.env) const result = await requestResolution({ db: platform.db, lookupAdmitted: fetchAdmittedSkillIdentity(event.context), enqueue: resolutionId => enqueueArtifactBuild(platform.env, resolutionId), now: () => Math.floor(Date.now() / 1000), + // A warm run takes the ready Resolution of its commit and skips the + // queue and the build. + serveReady: serveReadyResolution({ + db: platform.db, + bucket: build.bucket, + trustedRoot: build.trustedRoot, + now: build.now, + resolveOnGithub: source => build.github.resolve(source), + }), }, { source: body.source, idempotencyKey, diff --git a/layers/artifact-delivery/server/utils/build.ts b/layers/artifact-delivery/server/utils/build.ts index 2d3cce45..a4b7247a 100644 --- a/layers/artifact-delivery/server/utils/build.ts +++ b/layers/artifact-delivery/server/utils/build.ts @@ -108,7 +108,7 @@ export const LEADING_BUILD_WAIT_SECONDS = 2 * root and still stored in R2. Its files and check results come from its signed * attestation, never from unsigned columns. */ -interface ReusableBuild { +export interface ReusableBuild { resolutionId: string source: ResolvedSource artifactId: string @@ -118,7 +118,7 @@ interface ReusableBuild { checkResults: CheckResult[] } -type ReuseDecision +export type ReuseDecision = { _tag: 'hit', build: ReusableBuild } | { _tag: 'miss', reason: ReuseMissReason } @@ -592,8 +592,8 @@ async function decideReuse( * - the statement matches the stored row; * - R2 still holds the exact bytes. */ -async function findReusableBuild( - dependencies: ArtifactBuildDependencies, +export async function findReusableBuild( + dependencies: Pick, lookup: ReadyBuildLookup, ): Promise { const ready = await findReadyPublicBuild(dependencies.db, lookup) diff --git a/layers/artifact-delivery/server/utils/request-resolution.ts b/layers/artifact-delivery/server/utils/request-resolution.ts index 7a775eb2..fb65c2c0 100644 --- a/layers/artifact-delivery/server/utils/request-resolution.ts +++ b/layers/artifact-delivery/server/utils/request-resolution.ts @@ -1,6 +1,7 @@ import type { H3EventContext } from 'h3' import type { SourceRequest } from '../schemas/contracts' import type { FetchAdmittedSkillIdentity } from './admitted-identity' +import type { ServedResolution } from './served-resolution' import type { CreateResolutionResult } from './state' import { admittedSourceRequest } from './admitted-identity' import { recordResolutionRequester } from './requester-github' @@ -12,6 +13,12 @@ export interface ResolutionRequestDependencies { lookupAdmitted: FetchAdmittedSkillIdentity enqueue: (resolutionId: string) => Promise now: () => number + /** + * Answer a public request from a ready build, or name the source to build. + * `POST /api/v1/resolutions` passes it. The run sweep does not, so every + * sweep check still runs the build. + */ + serveReady?: (source: SourceRequest) => Promise } export type ResolutionAccess @@ -62,10 +69,15 @@ export async function requestResolution( ): Promise { const accountId = input.access.visibility === 'private' ? input.access.accountId : undefined const identity = await resolutionRequestIdentity(input.source, input.idempotencyKey, accountId) - const source = input.access.visibility === 'private' + const admitted = input.access.visibility === 'private' ? input.source : await admittedSourceRequest(input.source, dependencies.lookupAdmitted) - const result = await createResolution(dependencies.db, source, identity, dependencies.now(), input.access) + const served: ServedResolution = input.access.visibility === 'public' && dependencies.serveReady + ? await dependencies.serveReady(admitted) + : { _tag: 'build', source: admitted } + if (served._tag === 'served') + return served + const result = await createResolution(dependencies.db, served.source, identity, dependencies.now(), input.access) if (result._tag === 'created' && input.access.visibility === 'public' && input.requesterAccountId) { await recordResolutionRequester(dependencies.db, { resolutionId: result.row.id, diff --git a/layers/artifact-delivery/server/utils/served-resolution.ts b/layers/artifact-delivery/server/utils/served-resolution.ts new file mode 100644 index 00000000..ee50727b --- /dev/null +++ b/layers/artifact-delivery/server/utils/served-resolution.ts @@ -0,0 +1,131 @@ +import type { SourceRequest } from '../schemas/contracts' +import type { ReuseMissReason } from './build' +import type { ResolveSourceResult } from './github-source' +import type { ResolutionRow } from './state' +import type { TrustedRoot } from './trusted-root' +import { createWideEvent } from '@harlan-zw/nuxt-wide-events/standalone' +import { emitOperationalEvent } from '#server/utils/operational-event' +import { findReusableBuild } from './build' +import { getResolution } from './state' + +/** + * How long a Resolution request waits for GitHub to name the commit of a + * branch. A slower answer builds the request as sent, and the build resolves + * the branch itself. + */ +export const SERVE_RESOLVE_TIMEOUT_MS = 2_500 + +export interface ServedResolutionDependencies { + db: D1Database + bucket: R2Bucket + trustedRoot: TrustedRoot + /** Unix seconds. */ + now: () => number + /** Resolve a public request that names no commit, as the build would. */ + resolveOnGithub: (source: SourceRequest) => Promise + resolveTimeoutMs?: number + /** Receives each decision. The default emits the `artifact-resolution-served` wide event. */ + report?: (report: ServedResolutionReport) => void +} + +export type ServedResolution + = | { _tag: 'served', row: ResolutionRow } + /** No ready build fits. Build `source`, pinned to its commit when GitHub named one. */ + | { _tag: 'build', source: SourceRequest } + +export type ServedResolutionReport + = | { _tag: 'hit', reusedFrom: string } + | { _tag: 'miss', reason: ReuseMissReason | 'github-unresolved' | 'github-timeout' | 'not-ready' } + +/** + * Answer a public request from the ready build of the same Repository, + * commit, Skill folder and policy, with no queue and no build. + * + * A warm run used to request a new Resolution, wait for the queue, and wait + * again while the build re-signed the same bytes: 5.6 to 9.2 s in production + * on 2026-10-07. The ready Resolution answers it now, exactly as `GET + * /api/v1/resolutions/:id` would. It passes every check a reused build passes: + * the trusted root verifies its attestation, it names the current policy, its + * check results pass, and R2 still holds its bytes. + * + * A request that names no commit resolves on GitHub first. When no ready + * build fits, the build gets the request pinned to that commit and folder, as + * a registry name already is, so it reads no branch again. + */ +export function serveReadyResolution( + dependencies: ServedResolutionDependencies, +): (source: SourceRequest) => Promise { + const report = dependencies.report ?? emitServedEvent + return async (source) => { + const pinned = await pinToCommit(dependencies, source) + if (pinned._tag !== 'pinned') { + report({ _tag: 'miss', reason: pinned._tag }) + return { _tag: 'build', source } + } + const decision = await findReusableBuild(dependencies, { + _tag: 'pinned', + owner: pinned.source.owner, + repository: pinned.source.repository, + commitSha: pinned.commitSha, + selector: pinned.source.selector, + }) + if (decision._tag === 'miss') { + report({ _tag: 'miss', reason: decision.reason }) + return { _tag: 'build', source: pinned.source } + } + const row = await getResolution(dependencies.db, decision.build.resolutionId) + if (row?.state !== 'ready') { + report({ _tag: 'miss', reason: 'not-ready' }) + return { _tag: 'build', source: pinned.source } + } + report({ _tag: 'hit', reusedFrom: row.id }) + return { _tag: 'served', row } + } +} + +type PinnedRequest + = | { _tag: 'pinned', source: SourceRequest, commitSha: string } + | { _tag: 'github-unresolved' | 'github-timeout' } + +async function pinToCommit( + dependencies: ServedResolutionDependencies, + source: SourceRequest, +): Promise { + if (source.ref?.type === 'commit') + return { _tag: 'pinned', source, commitSha: source.ref.value } + let timer: ReturnType | undefined + const timeout = new Promise<'timeout'>((resolve) => { + timer = setTimeout(resolve, dependencies.resolveTimeoutMs ?? SERVE_RESOLVE_TIMEOUT_MS, 'timeout') + }) + const answer = await Promise.race([ + dependencies.resolveOnGithub(source).catch(() => { + // Safe to drop: the build reads GitHub again with its own retries, + // and reports the failure it finds. + return null + }), + timeout, + ]).finally(() => clearTimeout(timer)) + if (answer === 'timeout') + return { _tag: 'github-timeout' } + if (answer?._tag !== 'resolved' || answer.source.visibility !== 'public') + return { _tag: 'github-unresolved' } + return { + _tag: 'pinned', + commitSha: answer.source.commitSha, + source: { + provider: source.provider, + owner: source.owner, + repository: source.repository, + selector: { type: 'path', path: answer.source.skillPath }, + ref: { type: 'commit', value: answer.source.commitSha }, + }, + } +} + +function emitServedEvent(report: ServedResolutionReport): void { + emitOperationalEvent(createWideEvent({ + operation: 'artifact-resolution-served', + outcome: report._tag, + ...(report._tag === 'hit' ? { 'artifact.reusedFrom': report.reusedFrom } : { reason: report.reason }), + }), 'info') +} diff --git a/layers/artifact-delivery/server/utils/state.ts b/layers/artifact-delivery/server/utils/state.ts index a2b2664d..03daf13d 100644 --- a/layers/artifact-delivery/server/utils/state.ts +++ b/layers/artifact-delivery/server/utils/state.ts @@ -149,8 +149,12 @@ export async function resolutionRequestIdentity(source: SourceRequest, idempoten } } +/** + * `served` is a ready Resolution that answers a new request for the same + * Repository, commit, Skill folder and policy. See `serveReadyResolution`. + */ export type CreateResolutionResult - = { _tag: 'created' | 'existing', row: ResolutionRow } + = { _tag: 'created' | 'existing' | 'served', row: ResolutionRow } | { _tag: 'idempotency-conflict' } export async function createResolution( diff --git a/test/unit/artifact-served-resolution.test.ts b/test/unit/artifact-served-resolution.test.ts new file mode 100644 index 00000000..ca424001 --- /dev/null +++ b/test/unit/artifact-served-resolution.test.ts @@ -0,0 +1,366 @@ +import type { ArtifactAttestationStatement, ResolvedSource, SourceRequest } from '../../layers/artifact-delivery/server/schemas/contracts' +import type { ArtifactBuildDependencies } from '../../layers/artifact-delivery/server/utils/build' +import type { ArtifactSourceFile, PublicGithubSourceClient, ResolveSourceResult } from '../../layers/artifact-delivery/server/utils/github-source' +import type { ResolutionAccess } from '../../layers/artifact-delivery/server/utils/request-resolution' +import type { ServedResolutionReport } from '../../layers/artifact-delivery/server/utils/served-resolution' +import type { TrustedRoot } from '../../layers/artifact-delivery/server/utils/trusted-root' +import type { ArtifactSignerBindings } from '../../workers/artifact-signer/src/handler' +import { createHash } from 'node:crypto' +import { describe, expect, it, vi } from 'vitest' +import { artifactAttestationSchema, createResolutionResponseSchema } from '../../layers/artifact-delivery/server/schemas/contracts' +import { + completeAttestation, + createArtifactSigner, + createAttestationSignaturePayload, + encodeAttestationStatement, +} from '../../layers/artifact-delivery/server/utils/attestation' +import { processArtifactBuild } from '../../layers/artifact-delivery/server/utils/build' +import { bytesToBase64Url } from '../../layers/artifact-delivery/server/utils/encoding' +import { requestResolution } from '../../layers/artifact-delivery/server/utils/request-resolution' +import { serveReadyResolution } from '../../layers/artifact-delivery/server/utils/served-resolution' +import { getResolution, presentResolution } from '../../layers/artifact-delivery/server/utils/state' +import { handleArtifactSignerRequest } from '../../workers/artifact-signer/src/handler' +import { createSqliteD1 } from './helpers/d1-sqlite' + +const NOW = 1_787_227_200 +const MIGRATIONS = [ + 'migrations/0017_users.sql', + 'migrations/0110_artifact_delivery.sql', + 'migrations/0111_github_app_delivery.sql', + 'migrations/0112_private_artifact_keys.sql', + 'migrations/0122_artifact_resolution_retry_after.sql', + 'migrations/0131_artifact_build_reuse.sql', + 'migrations/0144_artifact_resolution_requesters.sql', +] +const COMMIT = '0123456789abcdef0123456789abcdef01234567' +const resolved: ResolvedSource = { + provider: 'github', + repositoryId: 123456789, + owner: 'skilld-dev', + repository: 'skills', + visibility: 'public', + commitSha: COMMIT, + treeSha: '89abcdef0123456789abcdef0123456789abcdef', + skillPath: 'skills/demo', +} +const pinned: SourceRequest = { + provider: 'github', + owner: 'skilld-dev', + repository: 'skills', + selector: { type: 'path', path: 'skills/demo' }, + ref: { type: 'commit', value: COMMIT }, +} +const onBranch: SourceRequest = { ...pinned, ref: { type: 'branch', value: 'main' } } +const byName: SourceRequest = { + provider: 'github', + owner: 'skilld-dev', + repository: 'skills', + selector: { type: 'named-skill', name: 'demo' }, +} +const files: ArtifactSourceFile[] = [{ + path: 'SKILL.md', + mode: 420, + bytes: new TextEncoder().encode('---\nname: demo\ndescription: Use this Skill for demo work.\n---\n\n# Demo\n'), + gitBlobSha: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', +}] +const PUBLIC: ResolutionAccess = { visibility: 'public' } + +describe('serving a warm run from its ready build', () => { + it('answers a request pinned to a built commit with the ready Resolution, and queues nothing', async () => { + const harness = await createHarness() + const first = await harness.buildReady(pinned) + const enqueue = vi.fn(async () => {}) + + const result = await harness.request(pinned, { enqueue }) + + expect(result._tag).toBe('served') + if (result._tag !== 'served') + throw new Error('not served') + expect(result.row.id).toBe(first.id) + expect(enqueue).not.toHaveBeenCalled() + const answer = createResolutionResponseSchema.parse(presentResolution(result.row)) + expect(answer.state).toBe('ready') + expect(harness.unbuilt()).toBe(0) + harness.close() + }) + + it('serves a registry name from its admitted folder and commit, with no GitHub read', async () => { + const harness = await createHarness() + await harness.buildReady(pinned) + const github = vi.fn(async (): Promise => ({ _tag: 'resolved', source: resolved })) + + const result = await harness.request(byName, { + admitted: { skillPath: 'skills/demo', commitSha: COMMIT }, + resolveOnGithub: github, + }) + + expect(result._tag).toBe('served') + expect(github).not.toHaveBeenCalled() + harness.close() + }) + + it('resolves a branch to its commit on GitHub, then serves the ready build of that commit', async () => { + const harness = await createHarness() + const first = await harness.buildReady(pinned) + const github = vi.fn(async (): Promise => ({ _tag: 'resolved', source: resolved })) + + const result = await harness.request(onBranch, { resolveOnGithub: github }) + + expect(github).toHaveBeenCalledWith(onBranch) + expect(result._tag === 'served' && result.row.id).toBe(first.id) + harness.close() + }) + + it('builds a resolved branch pinned to its commit when no ready build exists', async () => { + const harness = await createHarness() + const enqueue = vi.fn(async () => {}) + + const result = await harness.request(onBranch, { + enqueue, + resolveOnGithub: async () => ({ _tag: 'resolved', source: resolved }), + }) + + expect(result._tag).toBe('created') + if (result._tag !== 'created') + throw new Error('not created') + expect(result.row).toMatchObject({ ref_type: 'commit', ref_value: COMMIT, selector_type: 'path', selector_value: 'skills/demo' }) + expect(enqueue).toHaveBeenCalledWith(result.row.id) + expect(harness.reports).toContainEqual({ _tag: 'miss', reason: 'no-ready-build' }) + harness.close() + }) + + it('builds the request as sent when GitHub cannot resolve it in time', async () => { + const harness = await createHarness() + await harness.buildReady(pinned) + + const result = await harness.request(onBranch, { + resolveOnGithub: () => new Promise(() => {}), + resolveTimeoutMs: 5, + }) + + expect(result._tag).toBe('created') + if (result._tag !== 'created') + throw new Error('not created') + expect(result.row).toMatchObject({ ref_type: 'branch', ref_value: 'main' }) + expect(harness.reports).toContainEqual({ _tag: 'miss', reason: 'github-timeout' }) + harness.close() + }) + + it('does not serve a build signed under another policy', async () => { + const harness = await createHarness() + const first = await harness.buildReady(pinned) + await harness.resign(first.id, statement => ({ ...statement, policyVersion: '2026-01-01.1' })) + + const result = await harness.request(pinned) + + expect(result._tag).toBe('created') + expect(harness.reports).toContainEqual({ _tag: 'miss', reason: 'policy-changed' }) + harness.close() + }) + + it('does not serve a ready build whose bytes left R2', async () => { + const harness = await createHarness() + const first = await harness.buildReady(pinned) + harness.storage.remove(first.r2_key!) + + const result = await harness.request(pinned) + + expect(result._tag).toBe('created') + expect(harness.reports).toContainEqual({ _tag: 'miss', reason: 'bytes-missing' }) + harness.close() + }) + + it('never serves a private request', async () => { + const harness = await createHarness() + await harness.buildReady(pinned) + harness.raw.prepare('INSERT INTO users (id, github_id, login, created_at, last_login_at) VALUES (1, 1, ?, ?, ?)').run('private-owner', NOW, NOW) + const serve = vi.fn() + + const result = await requestResolution({ + db: harness.db, + lookupAdmitted: async () => null, + enqueue: async () => {}, + now: () => NOW, + serveReady: serve, + }, { + source: pinned, + idempotencyKey: 'served-private-request-key', + access: { visibility: 'private', accountId: 1, installationId: 2, repositoryId: 3 }, + }) + + expect(result._tag).toBe('created') + expect(serve).not.toHaveBeenCalled() + harness.close() + }) +}) + +async function createHarness() { + const sqlite = createSqliteD1(MIGRATIONS) + const storage = memoryBucket() + const signing = await signingKey('skilld-production-2026-08') + const reports: ServedResolutionReport[] = [] + let requests = 0 + const signerBindings: ArtifactSignerBindings = { + DB: sqlite.db, + PUBLIC_ARTIFACTS: storage.bucket, + PRIVATE_ARTIFACTS: storage.bucket, + ARTIFACT_SIGNING_MAX_AGE_SECONDS: '300', + ...signing.bindings, + } + const buildDependencies: ArtifactBuildDependencies = { + db: sqlite.db, + github: { + resolve: async () => ({ _tag: 'resolved', source: resolved }), + load: async () => ({ _tag: 'loaded', value: { source: resolved, files } }), + } satisfies PublicGithubSourceClient, + bucket: storage.bucket, + signer: createArtifactSigner({ + fetch: async (request: Request) => await handleArtifactSignerRequest(request, signerBindings, () => NOW), + } as unknown as Fetcher), + trustedRoot: signing.trustedRoot, + now: () => NOW, + reportReuse: () => {}, + } + + async function request( + source: SourceRequest, + options: { + enqueue?: (resolutionId: string) => Promise + admitted?: { skillPath: string, commitSha: string | null } + resolveOnGithub?: (source: SourceRequest) => Promise + resolveTimeoutMs?: number + } = {}, + ) { + requests += 1 + return await requestResolution({ + db: sqlite.db, + lookupAdmitted: async () => options.admitted ?? null, + enqueue: options.enqueue ?? (async () => {}), + now: () => NOW, + serveReady: serveReadyResolution({ + db: sqlite.db, + bucket: storage.bucket, + trustedRoot: signing.trustedRoot, + now: () => NOW, + resolveOnGithub: options.resolveOnGithub ?? (async () => ({ _tag: 'resolved', source: resolved })), + resolveTimeoutMs: options.resolveTimeoutMs, + report: report => reports.push(report), + }), + }, { source, idempotencyKey: `served-request-key-${requests}`, access: PUBLIC }) + } + + async function buildReady(source: SourceRequest) { + const created = await requestResolution({ + db: sqlite.db, + lookupAdmitted: async () => null, + enqueue: async () => {}, + now: () => NOW, + }, { source, idempotencyKey: `served-build-key-${requests++}`, access: PUBLIC }) + if (created._tag === 'idempotency-conflict') + throw new Error('Test Resolution conflicted') + await processArtifactBuild(buildDependencies, created.row.id) + const row = await getResolution(sqlite.db, created.row.id) + if (row?.state !== 'ready') + throw new Error(`Test build ended ${row?.state}`) + return row + } + + /** Replace a ready build's attestation with a changed statement the same key signs. */ + async function resign(resolutionId: string, edit: (statement: ArtifactAttestationStatement) => ArtifactAttestationStatement) { + const row = await getResolution(sqlite.db, resolutionId) + const { statement: _statement, signature: _signature, ...statement } = artifactAttestationSchema.parse(JSON.parse(row!.attestation_json!)) + const rawStatement = encodeAttestationStatement(edit(statement)) + const signature = await crypto.subtle.sign( + 'Ed25519', + signing.privateKey, + await createAttestationSignaturePayload(new TextEncoder().encode(rawStatement)), + ) + const attestation = completeAttestation(rawStatement, { + algorithm: 'Ed25519', + keyId: signing.keyId, + value: bytesToBase64Url(new Uint8Array(signature)), + }) + sqlite.raw.prepare('UPDATE artifact_resolutions SET attestation_json = ? WHERE id = ?').run(JSON.stringify(attestation), resolutionId) + } + + return { + db: sqlite.db, + raw: sqlite.raw, + storage, + reports, + request, + buildReady, + resign, + /** Resolutions still waiting for a build. */ + unbuilt: () => (sqlite.raw.prepare(`SELECT count(*) AS n FROM artifact_resolutions WHERE state = 'requested'`).get() as { n: number }).n, + close: sqlite.close, + } +} + +async function signingKey(keyId: string) { + const keyPair = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']) + const publicKey = bytesToBase64Url(new Uint8Array(await crypto.subtle.exportKey('raw', keyPair.publicKey))) + const privateKey = bytesToBase64Url(new Uint8Array(await crypto.subtle.exportKey('pkcs8', keyPair.privateKey))) + const notBefore = new Date((NOW - 60) * 1000).toISOString() + const notAfter = new Date((NOW + 3600) * 1000).toISOString() + const trustedRoot: TrustedRoot = { + version: 1, + rootKeyId: 'skilld-root-2026', + rootPublicKey: publicKey, + keys: [{ + keyId, + algorithm: 'Ed25519', + publicKey, + notBefore, + notAfter, + status: 'active', + statement: bytesToBase64Url(new TextEncoder().encode(JSON.stringify({ keyId }))), + rootSignature: 'C'.repeat(86), + }], + fetchedAt: new Date(NOW * 1000).toISOString(), + } + return { + keyId, + privateKey: keyPair.privateKey, + trustedRoot, + bindings: { + ARTIFACT_SIGNING_KEY_ID: keyId, + ARTIFACT_SIGNING_KEY_NOT_BEFORE: notBefore, + ARTIFACT_SIGNING_KEY_NOT_AFTER: notAfter, + ARTIFACT_SIGNING_PRIVATE_KEY_PKCS8: privateKey, + }, + } +} + +function memoryBucket() { + const objects = new Map }>() + const describeObject = (key: string) => { + const object = objects.get(key) + if (!object) + return null + const digest = createHash('sha256').update(object.bytes).digest() + return { + key, + size: object.bytes.byteLength, + checksums: { sha256: Uint8Array.from(digest).buffer }, + customMetadata: object.customMetadata, + } + } + const bucket = { + put: async (key: string, value: Uint8Array, options?: R2PutOptions) => { + if (objects.has(key)) + return null + objects.set(key, { bytes: Uint8Array.from(value), customMetadata: options?.customMetadata ?? {} }) + return describeObject(key) + }, + head: async (key: string) => describeObject(key), + get: async (key: string) => { + const object = describeObject(key) + const stored = objects.get(key) + return object && stored + ? { ...object, arrayBuffer: async () => Uint8Array.from(stored.bytes).buffer } + : null + }, + } as unknown as R2Bucket + return { bucket, remove: (key: string) => objects.delete(key) } +} From c7b2f3a6f64fe03061aff512eee2062d7aec33a9 Mon Sep 17 00:00:00 2001 From: Harlan GitHub Agent Date: Wed, 7 Oct 2026 03:42:31 +1100 Subject: [PATCH 2/3] fix(artifact-delivery): emit the served event without an object spread --- .../server/utils/served-resolution.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/layers/artifact-delivery/server/utils/served-resolution.ts b/layers/artifact-delivery/server/utils/served-resolution.ts index ee50727b..54aec24c 100644 --- a/layers/artifact-delivery/server/utils/served-resolution.ts +++ b/layers/artifact-delivery/server/utils/served-resolution.ts @@ -123,9 +123,17 @@ async function pinToCommit( } function emitServedEvent(report: ServedResolutionReport): void { + if (report._tag === 'hit') { + emitOperationalEvent(createWideEvent({ + 'operation': 'artifact-resolution-served', + 'outcome': 'hit', + 'artifact.reusedFrom': report.reusedFrom, + }), 'info') + return + } emitOperationalEvent(createWideEvent({ operation: 'artifact-resolution-served', - outcome: report._tag, - ...(report._tag === 'hit' ? { 'artifact.reusedFrom': report.reusedFrom } : { reason: report.reason }), + outcome: 'miss', + reason: report.reason, }), 'info') } From 4a76b09abb56b43e787a8584fc50ae9b1b65d769 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 7 Oct 2026 12:04:57 +1100 Subject: [PATCH 3/3] test: give the warm run tests the streamed build shape #510 loads a Skill through a streaming read and the signer hashes the stored object as a stream. --- test/unit/artifact-served-resolution.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/artifact-served-resolution.test.ts b/test/unit/artifact-served-resolution.test.ts index 6dd4632b..df731333 100644 --- a/test/unit/artifact-served-resolution.test.ts +++ b/test/unit/artifact-served-resolution.test.ts @@ -20,6 +20,7 @@ import { requestResolution } from '../../layers/artifact-delivery/server/utils/r import { serveReadyResolution } from '../../layers/artifact-delivery/server/utils/served-resolution' import { getResolution, presentResolution } from '../../layers/artifact-delivery/server/utils/state' import { handleArtifactSignerRequest } from '../../workers/artifact-signer/src/handler' +import { loadedFromFiles } from '../fixtures/loaded-source' import { createSqliteD1 } from './helpers/d1-sqlite' const NOW = 1_787_227_200 @@ -212,7 +213,7 @@ async function createHarness() { db: sqlite.db, github: { resolve: async () => ({ _tag: 'resolved', source: resolved }), - load: async () => ({ _tag: 'loaded', value: { source: resolved, files } }), + load: async () => ({ _tag: 'loaded', value: loadedFromFiles(resolved, files) }), } satisfies PublicGithubSourceClient, bucket: storage.bucket, signer: createArtifactSigner({ @@ -359,7 +360,7 @@ function memoryBucket() { const object = describeObject(key) const stored = objects.get(key) return object && stored - ? { ...object, arrayBuffer: async () => Uint8Array.from(stored.bytes).buffer } + ? { ...object, arrayBuffer: async () => Uint8Array.from(stored.bytes).buffer, body: new Blob([Uint8Array.from(stored.bytes)]).stream() } : null }, } as unknown as R2Bucket