From 0872a65a3733c6bfcc7449222cc4bd28bba43807 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 04:53:11 +0000 Subject: [PATCH 1/6] feat: Integrate vendor VFIO vGPUs into the instance lifecycle Stainless-Generated-From: 7082db0435d9e0a2979f9c8f7c1badada3a58274 --- instance.go | 5 ++++- resource.go | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/instance.go b/instance.go index 234c60f..1e10e75 100644 --- a/instance.go +++ b/instance.go @@ -788,12 +788,15 @@ const ( // GPU information attached to the instance type InstanceGPU struct { - // mdev device UUID + // sysfs path of the assigned vGPU device + DevicePath string `json:"device_path"` + // mdev device UUID (mdev hosts only) MdevUuid string `json:"mdev_uuid"` // vGPU profile name Profile string `json:"profile"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + DevicePath respjson.Field MdevUuid respjson.Field Profile respjson.Field ExtraFields map[string]respjson.Field diff --git a/resource.go b/resource.go index e8b34c2..6c61d7c 100644 --- a/resource.go +++ b/resource.go @@ -83,7 +83,9 @@ func (r *DiskBreakdown) UnmarshalJSON(data []byte) error { // Available vGPU profile type GPUProfile struct { - // Number of instances that can be created with this profile + // Number of virtual functions currently able to create this profile. Best-effort: + // creating an instance may reduce availability on sibling functions sharing GPU + // framebuffer. Available int64 `json:"available" api:"required"` // Frame buffer size in MB FramebufferMB int64 `json:"framebuffer_mb" api:"required"` From e6c2b7bc0171807b2c891a26e9ab4f4bb786072b Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:31:36 +0000 Subject: [PATCH 2/6] feat: Add Windows hypervisor primitives Stainless-Generated-From: 2482f9323be247023cdd3b5641050f363df12831 --- instance.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instance.go b/instance.go index 1e10e75..da72f66 100644 --- a/instance.go +++ b/instance.go @@ -1793,7 +1793,7 @@ type InstanceLogsParams struct { // - vmm: Cloud Hypervisor VMM logs (hypervisor stdout+stderr) // - hypeman: Hypeman operations log (actions taken on this instance) // - // Any of "app", "vmm", "hypeman". + // Any of "app", "vmm", "hypeman", "swtpm". Source InstanceLogsParamsSource `query:"source,omitzero" json:"-"` paramObj } @@ -1817,6 +1817,7 @@ const ( InstanceLogsParamsSourceApp InstanceLogsParamsSource = "app" InstanceLogsParamsSourceVmm InstanceLogsParamsSource = "vmm" InstanceLogsParamsSourceHypeman InstanceLogsParamsSource = "hypeman" + InstanceLogsParamsSourceSwtpm InstanceLogsParamsSource = "swtpm" ) type InstanceStandbyParams struct { From 4623fef2ab5bf55dcac4a54ecbd841cc82368558 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:08:32 +0000 Subject: [PATCH 3/6] feat: Update Hypeman build toolchains to Go 1.26.10 Stainless-Generated-From: 0700f4f0d1f39365c134b50e3efa59a5567cb0dd --- device.go | 8 ++++---- ingress.go | 12 ++++++------ instance.go | 21 +++++++++++++-------- resource.go | 30 ++++++++++++++++++++++-------- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/device.go b/device.go index a21fadd..7b69256 100644 --- a/device.go +++ b/device.go @@ -128,10 +128,10 @@ type Device struct { // Whether the device is currently bound to the vfio-pci driver, which is required // for VM passthrough. // - // - true: Device is bound to vfio-pci and ready for (or currently in use by) a VM. - // The device's native driver has been unloaded. - // - false: Device is using its native driver (e.g., nvidia) or no driver. Hypeman - // will automatically bind to vfio-pci when attaching to an instance. + // - true: Device is bound to vfio-pci and ready for (or currently in use by) a VM. + // The device's native driver has been unloaded. + // - false: Device is using its native driver (e.g., nvidia) or no driver. Hypeman + // will automatically bind to vfio-pci when attaching to an instance. BoundToVfio bool `json:"bound_to_vfio" api:"required"` // Registration timestamp (RFC3339) CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"` diff --git a/ingress.go b/ingress.go index eff8a98..46ded56 100644 --- a/ingress.go +++ b/ingress.go @@ -274,9 +274,9 @@ func (r *IngressRuleRequestHeaderAuthParam) UnmarshalJSON(data []byte) error { type IngressTarget struct { // Target instance name, ID, or capture reference. // - // - For literal hostnames: Use the instance name or ID directly (e.g., "my-api") - // - For pattern hostnames: Reference a capture from the hostname (e.g., - // "{instance}") + // - For literal hostnames: Use the instance name or ID directly (e.g., "my-api") + // - For pattern hostnames: Reference a capture from the hostname (e.g., + // "{instance}") // // When using pattern hostnames, the instance is resolved dynamically at request // time. @@ -311,9 +311,9 @@ func (r IngressTarget) ToParam() IngressTargetParam { type IngressTargetParam struct { // Target instance name, ID, or capture reference. // - // - For literal hostnames: Use the instance name or ID directly (e.g., "my-api") - // - For pattern hostnames: Reference a capture from the hostname (e.g., - // "{instance}") + // - For literal hostnames: Use the instance name or ID directly (e.g., "my-api") + // - For pattern hostnames: Reference a capture from the hostname (e.g., + // "{instance}") // // When using pattern hostnames, the instance is resolved dynamically at request // time. diff --git a/instance.go b/instance.go index da72f66..1a4622b 100644 --- a/instance.go +++ b/instance.go @@ -918,7 +918,8 @@ type InstanceStats struct { MemoryRssBytes int64 `json:"memory_rss_bytes" api:"required"` // Virtual Memory Size - total virtual memory allocated in bytes MemoryVmsBytes int64 `json:"memory_vms_bytes" api:"required"` - // Total network bytes received by the VM (from TAP interface) + // Total network bytes received by the VM (from TAP interface). Always 0 for macOS + // guests, which use NAT with no per-instance interface. NetworkRxBytes int64 `json:"network_rx_bytes" api:"required"` // Total network bytes transmitted by the VM (from TAP interface) NetworkTxBytes int64 `json:"network_tx_bytes" api:"required"` @@ -1421,14 +1422,17 @@ type InstanceNewParams struct { // Additional memory for hotplug (human-readable format like "3GB", "1G"). Omit to // disable hotplug memory. HotplugSize param.Opt[string] `json:"hotplug_size,omitzero"` - // Writable overlay disk size (human-readable format like "10GB", "50G") + // Writable overlay disk size (human-readable format like "10GB", "50G"). Omit this + // field for macOS guests, which clone the image's complete boot disk instead. OverlaySize param.Opt[string] `json:"overlay_size,omitzero"` - // Target platform as os/arch[/variant] (e.g. "linux/amd64"), matching Docker - // --platform. Omit for the host platform. Not a fixed enum: the os/arch[/variant] - // grammar is validated server-side and invalid values return 400 invalid_platform. - // Only os "linux" with arch amd64 or arm64 is accepted today. + // Target platform as os/arch[/variant]. Linux container guests support amd64 and + // arm64. Experimental darwin/arm64 guests require an offline-imported macOS disk + // image and vz on Apple silicon. Omit to use the cached native image or host + // platform. Platform param.Opt[string] `json:"platform,omitzero"` - // Base memory size (human-readable format like "1GB", "512MB", "2G") + // Base memory size (human-readable format like "1GB", "512MB", "2G"). macOS guests + // default to their image's memory; explicit sizes must be at least 4 GiB in whole + // MiB and within VZ host limits. Size param.Opt[string] `json:"size,omitzero"` // Skip guest-agent installation during boot. When true, the exec and stat APIs // will not work for this instance. The instance will still run, but remote command @@ -1443,7 +1447,8 @@ type InstanceNewParams struct { // omit both expiration fields to disable automatic expiration. Mutually exclusive // with expires_at. Ttl param.Opt[string] `json:"ttl,omitzero"` - // Number of virtual CPUs + // Number of virtual CPUs. macOS guests default to their image's vCPUs; explicit + // counts must be at least 2 and within VZ host limits. Vcpus param.Opt[int64] `json:"vcpus,omitzero"` // Linux-only automatic standby policy based on active inbound TCP connections // observed from the host conntrack table. diff --git a/resource.go b/resource.go index 6c61d7c..59303de 100644 --- a/resource.go +++ b/resource.go @@ -109,27 +109,41 @@ func (r *GPUProfile) UnmarshalJSON(data []byte) error { // GPU resource status. Null if no GPUs available. type GPUResourceStatus struct { + // Free slots eligible for placement, matching admission control (excludes + // quarantined VFs; 0 while VF health state is unavailable) + AllocatableSlots int64 `json:"allocatable_slots" api:"required"` // GPU mode (vgpu for SR-IOV/mdev, passthrough for whole GPU) // // Any of "vgpu", "passthrough". Mode GPUResourceStatusMode `json:"mode" api:"required"` + // VFs quarantined after guest driver init failures (vGPU mode only). May overlap + // used_slots until the affected instance releases its VF. + QuarantinedSlots int64 `json:"quarantined_slots" api:"required"` // Total slots (VFs for vGPU, physical GPUs for passthrough) TotalSlots int64 `json:"total_slots" api:"required"` - // Slots currently in use + // Slots currently in use. Includes quarantined VFs that are still assigned, so + // this can overlap quarantined_slots. UsedSlots int64 `json:"used_slots" api:"required"` // Physical GPUs (only in passthrough mode) Devices []PassthroughDevice `json:"devices"` + // Present when allocatable_slots is 0 because the VF health state could not be + // read or written rather than because the host is full. vGPU placement is refused + // until the state file is repaired or the next write succeeds. + PlacementDisabledReason string `json:"placement_disabled_reason"` // Available vGPU profiles (only in vGPU mode) Profiles []GPUProfile `json:"profiles"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Mode respjson.Field - TotalSlots respjson.Field - UsedSlots respjson.Field - Devices respjson.Field - Profiles respjson.Field - ExtraFields map[string]respjson.Field - raw string + AllocatableSlots respjson.Field + Mode respjson.Field + QuarantinedSlots respjson.Field + TotalSlots respjson.Field + UsedSlots respjson.Field + Devices respjson.Field + PlacementDisabledReason respjson.Field + Profiles respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } From 6b281760d603033e2f78a87f5c24da8400b84585 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 21:14:16 +0000 Subject: [PATCH 4/6] feat: Place vGPU profiles the driver hides from creatable lists Stainless-Generated-From: 3068ecfc1075e499ab87f1d75caaf980125a605d --- resource.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resource.go b/resource.go index 59303de..9c368ed 100644 --- a/resource.go +++ b/resource.go @@ -83,9 +83,9 @@ func (r *DiskBreakdown) UnmarshalJSON(data []byte) error { // Available vGPU profile type GPUProfile struct { - // Number of virtual functions currently able to create this profile. Best-effort: - // creating an instance may reduce availability on sibling functions sharing GPU - // framebuffer. + // Number of additional instances of this profile the host can take if only this + // profile is added. Counts for different profiles are not additive: an instance of + // one profile can reduce availability of others on the same GPU. Available int64 `json:"available" api:"required"` // Frame buffer size in MB FramebufferMB int64 `json:"framebuffer_mb" api:"required"` From 9ddc6ece770d63e9bdebdb82a36b671e9d65bc57 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 10 Nov 2026 11:24:40 -0500 Subject: [PATCH 5/6] Use federated model credentials instead of static LLM API keys (#3) * Grant id-token: write to semgrep and vuln-remediation callers * Use federated model credentials for pi in CLI coverage updates * Re-run CI after the GitHub Actions outage --------- Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com> --- .../tests/update-coverage-security.test.mjs | 26 ++++++++++++------- .github/workflows/semgrep.yml | 2 ++ .github/workflows/update-cli-coverage.yml | 23 ++++++++++++++-- .github/workflows/vuln-remediation.yml | 2 ++ 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/.github/tests/update-coverage-security.test.mjs b/.github/tests/update-coverage-security.test.mjs index 97e1a59..8790c5d 100644 --- a/.github/tests/update-coverage-security.test.mjs +++ b/.github/tests/update-coverage-security.test.mjs @@ -109,9 +109,11 @@ const applyChanges = (fx, env = {}) => ...env, }); +const llmCredentials = 'uses: kernel/security-workflows/.github/actions/llm-credentials@'; + test('actions are pinned and checkouts do not persist credentials', () => { for (const entry of steps.filter((entry) => entry.includes('uses: '))) { - assert.match(entry, /uses: [\w/-]+@[a-f0-9]{40}(?:\s|$)/); + assert.match(entry, /uses: [\w./-]+@[a-f0-9]{40}(?:\s|$)/); if (entry.includes('uses: actions/checkout@')) { assert.match(entry, /persist-credentials: false/); assert.doesNotMatch(entry, /token:/); @@ -125,20 +127,26 @@ test('credentialed steps only run in the production SDK repository', () => { assert.match(workflow, /\n generate:\n needs: prefetch\n if: github\.repository == 'kernel\/hypeman-go'\n/); }); -test('the Pi job references no secrets other than the model API keys', () => { +test('the Pi job references no secrets', () => { // Every secret a job references is loaded into the runner at job start, and - // Pi could read it from runner memory with passwordless sudo. + // Pi could read it from runner memory with passwordless sudo. Pi gets model + // access from federated credentials, not secrets. const piJobs = Object.keys(jobTexts).filter((name) => /^\s*pi --print\b/m.test(jobTexts[name])); assert.deepEqual(piJobs, ['generate']); const referenced = (text) => [...text.matchAll(/\bsecrets\b(?:\.([\w-]+))?/g)].map((match) => match[1] ?? ''); assert.deepEqual(referenced(workflow.slice(0, workflow.indexOf('\njobs:\n'))), []); for (const name of piJobs) { - assert.deepEqual( - [...new Set(referenced(jobTexts[name]))].sort(), - ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY'], - `${name} may only reference the model API keys`, - ); - assert.doesNotMatch(jobTexts[name], /github\.token|create-github-app-token|^ permissions:/m); + assert.deepEqual(referenced(jobTexts[name]), [], `${name} may not reference secrets`); + assert.doesNotMatch(jobTexts[name], /github\.token|create-github-app-token/); + // The only permission beyond reading the repository is the OIDC token for model credentials. + assert.match(jobTexts[name], /\n permissions:\n contents: read\n(?: #.*\n)? id-token: write\n outputs:/); + // Model credentials exist only while Pi runs. + const pi = jobs[name].indexOf(step(name, 'Update CLI coverage')); + assert.equal(jobs[name][pi - 1], step(name, 'Model credentials')); + const stop = jobs[name][pi + 1]; + assert.equal(stop, step(name, 'Stop model credentials')); + assert.match(stop, /if: always\(\)\n[\s\S]*stop: true/); + assert.ok(jobs[name][pi - 1].includes(llmCredentials) && stop.includes(llmCredentials)); } assert.match(workflow, /^permissions:\n contents: read\n\n/m); }); diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 6b8216d..cd96009 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -7,6 +7,8 @@ on: permissions: contents: read pull-requests: write + # semgrep exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: scan: diff --git a/.github/workflows/update-cli-coverage.yml b/.github/workflows/update-cli-coverage.yml index a1b2c7f..3c98784 100644 --- a/.github/workflows/update-cli-coverage.yml +++ b/.github/workflows/update-cli-coverage.yml @@ -203,6 +203,10 @@ jobs: if: github.repository == 'kernel/hypeman-go' runs-on: ubuntu-latest timeout-minutes: 55 + permissions: + contents: read + # The model credentials step exchanges the job's OIDC token for short-lived model credentials. + id-token: write outputs: changed: ${{ steps.snapshot.outputs.changed }} main_sha: ${{ steps.cli-clone.outputs.main_sha }} @@ -290,10 +294,19 @@ jobs: echo "agent_base=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" echo "agent_base_tree=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT" + - name: Model credentials + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + model: ${{ vars.PI_PREFERRED_MODEL }} + anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + anthropic-organization-id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + anthropic-service-account-id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + openai-identity-provider-id: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }} + openai-service-account-id: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }} + openai-audience: ${{ vars.OPENAI_WIF_AUDIENCE }} + - name: Update CLI coverage env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }} PI_SKIP_VERSION_CHECK: '1' PI_TELEMETRY: '0' @@ -505,6 +518,12 @@ jobs: - Ensure code compiles before finishing " + - name: Stop model credentials + if: always() + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + stop: true + - name: Snapshot publishable changes id: snapshot working-directory: /tmp/hypeman-cli diff --git a/.github/workflows/vuln-remediation.yml b/.github/workflows/vuln-remediation.yml index 22ee065..35f30c8 100644 --- a/.github/workflows/vuln-remediation.yml +++ b/.github/workflows/vuln-remediation.yml @@ -8,6 +8,8 @@ on: permissions: contents: write pull-requests: write + # vuln-remediation exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: remediate: From be35c7988506eb5b2587d57b5332daca5bbe210f Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:26:05 +0000 Subject: [PATCH 6/6] release: 0.29.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8935e93..b8dda9b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.28.0" + ".": "0.29.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ebae6c7..cb55e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.29.0](https://github.com/kernel/hypeman-go/compare/v0.28.0...v0.29.0) (2026-11-10) + + +### Features + +* Add Windows hypervisor primitives ([e6c2b7b](https://github.com/kernel/hypeman-go/commit/e6c2b7bc0171807b2c891a26e9ab4f4bb786072b)) +* Integrate vendor VFIO vGPUs into the instance lifecycle ([0872a65](https://github.com/kernel/hypeman-go/commit/0872a65a3733c6bfcc7449222cc4bd28bba43807)) +* Place vGPU profiles the driver hides from creatable lists ([6b28176](https://github.com/kernel/hypeman-go/commit/6b281760d603033e2f78a87f5c24da8400b84585)) +* Update Hypeman build toolchains to Go 1.26.10 ([4623fef](https://github.com/kernel/hypeman-go/commit/4623fef2ab5bf55dcac4a54ecbd841cc82368558)) + ## [0.28.0](https://github.com/kernel/hypeman-go/compare/v0.27.0...v0.28.0) (2026-09-01) diff --git a/README.md b/README.md index 1508f19..9755916 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Or to pin the version: ```sh -go get -u 'github.com/kernel/hypeman-go@v0.28.0' +go get -u 'github.com/kernel/hypeman-go@v0.29.0' ``` diff --git a/internal/version.go b/internal/version.go index 9efbcca..ecc4b71 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.28.0" // x-release-please-version +const PackageVersion = "0.29.0" // x-release-please-version