Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: HELM AI Kernel Changelog
last_reviewed: 2026-07-20
last_reviewed: 2026-08-10
---

# Changelog
Expand Down Expand Up @@ -89,6 +89,34 @@ hardware-backed enforcement language out of the public changelog until a tagged
release ships source-owned tests, verifier evidence, and release artifacts for
that exact capability.

### Changed — EU AI Act mapping and applicability dates corrected

Regulation (EU) 2026/1744 deferred Chapter III, Sections 1-3 of Regulation
(EU) 2024/1689 to 2027-12-02 for Annex III systems and 2028-08-02 for Annex I
systems, expressly excluding Article 6(5). This narrow amendment does not move
Article 50, Article 73 incident reporting, or CE-marking/registration provisions
outside Sections 1-3 to the same dates.

- Added `reference_packs/eu_ai_act_high_risk.v2.json` as an explicit
`COMPLIANCE_MAPPING`. It contains candidate mappings and evidence names but no
supported `runtime_actions` or `actions`; the sample policy therefore remains
fail-closed with zero runtime rules.
- Preserved the previously released
`reference_packs/eu_ai_act_high_risk.v1.json` byte-for-byte at SHA-256
`8a33ad51441d6d939d74da2be388c1d11c12da1e055f1aeca72ca2763ebc05c4`.
Supersession metadata lives in v2 and documentation, not a rewritten v1.
- Split general, Article 50, Annex III and Annex I applicability dates. The v2
mapping records the Article 50(2) transition for specified pre-existing
systems and the Article 6(5) exception.
- Corrected serious-incident reporting from Article 62/72 hours to Article 73:
15 days generally, two days for the specified accelerated tier, and 10 days
where a person dies. The compliance API now records the incident tier.
- Removed unsupported pack-driven QTSP, LOTL freshness and budget enforcement
claims. QTSP is an optional evidence mapping; operator/library verification
remains a separate explicitly configured path.

Primary sources: CELEX 32024R1689 and CELEX 32026R1744 on EUR-Lex.

## [0.8.3] - pending tag

Completing release target for the v0.8 train. v0.8.0, v0.8.1 and v0.8.2 all
Expand Down
122 changes: 121 additions & 1 deletion core/cmd/helm-ai-kernel/quickstart_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestLoadServePolicyTOML(t *testing.T) {
if err := os.WriteFile(path, []byte(`
name = "release.high_risk.v3"
profile = "high_risk"
reference_pack = "./reference_packs/eu_ai_act_high_risk.v1.json"
reference_pack = "./reference_packs/eu_ai_act_high_risk.v2.json"

[server]
bind = "127.0.0.1"
Expand Down Expand Up @@ -98,6 +98,126 @@ path = "./data/receipts.db"
}
}

func TestCanonicalEUAIActMappingPackContract(t *testing.T) {
repoRoot := filepath.Clean(filepath.Join("..", "..", ".."))

v1, err := os.ReadFile(filepath.Join(repoRoot, "reference_packs", "eu_ai_act_high_risk.v1.json"))
if err != nil {
t.Fatalf("read immutable v1 pack: %v", err)
}
v1Digest := sha256.Sum256(v1)
if got, want := hex.EncodeToString(v1Digest[:]), "8a33ad51441d6d939d74da2be388c1d11c12da1e055f1aeca72ca2763ebc05c4"; got != want {
t.Fatalf("published v1 bytes changed: sha256=%s, want %s", got, want)
}

type mappingControl struct {
ControlID string `json:"control_id"`
MappingStatus string `json:"mapping_status"`
Enforcement json.RawMessage `json:"enforcement"`
}
type candidateMapping struct {
CandidateOutcome string `json:"candidate_outcome"`
CandidateCondition string `json:"candidate_condition"`
Rationale string `json:"rationale"`
}
type mappingProgram struct {
ProgramID string `json:"program_id"`
Controls []mappingControl `json:"controls"`
CandidatePolicyMappings []candidateMapping `json:"candidate_policy_mappings"`
PolicyRules json.RawMessage `json:"policy_rules"`
}
type mappingPack struct {
PackID string `json:"pack_id"`
Version int `json:"version"`
ArtifactKind string `json:"artifact_kind"`
RuntimeEnforcement *bool `json:"runtime_enforcement"`
Description string `json:"description"`
Programs []mappingProgram `json:"programs"`
ApplicabilityDates struct {
GeneralApplication string `json:"general_application"`
Article50GeneralApplication string `json:"article_50_general_application"`
Article50PreexistingSystemTransition string `json:"article_50_2_preexisting_system_transition"`
AnnexIIISectionsOneToThree string `json:"chapter_iii_sections_1_3_annex_iii"`
AnnexISectionsOneToThree string `json:"chapter_iii_sections_1_3_annex_i"`
Article6Paragraph5Deferred *bool `json:"article_6_5_deferred"`
} `json:"applicability_dates"`
ApplicabilityDatesProvenance struct {
Sources []string `json:"sources"`
} `json:"applicability_dates_provenance"`
EvidenceMappings map[string]string `json:"evidence_mappings"`
RuntimeActions json.RawMessage `json:"runtime_actions"`
Actions json.RawMessage `json:"actions"`
BudgetConstraints json.RawMessage `json:"budget_constraints"`
EvidenceRequirements json.RawMessage `json:"evidence_requirements"`
}

v2, err := os.ReadFile(filepath.Join(repoRoot, "reference_packs", "eu_ai_act_high_risk.v2.json"))
if err != nil {
t.Fatalf("read v2 mapping pack: %v", err)
}
var pack mappingPack
if err := json.Unmarshal(v2, &pack); err != nil {
t.Fatalf("decode v2 mapping pack: %v", err)
}
if pack.PackID != "eu-ai-act-high-risk-v2" || pack.Version != 2 {
t.Fatalf("unexpected v2 identity: %q version %d", pack.PackID, pack.Version)
}
if pack.ArtifactKind != "COMPLIANCE_MAPPING" || pack.RuntimeEnforcement == nil || *pack.RuntimeEnforcement {
t.Fatalf("v2 must explicitly be mapping-only: kind=%q runtime_enforcement=%v", pack.ArtifactKind, pack.RuntimeEnforcement)
}
if !strings.Contains(strings.ToLower(pack.Description), "does not configure runtime policy") {
t.Fatalf("v2 description must disclose mapping-only semantics: %q", pack.Description)
}
if len(pack.RuntimeActions) != 0 || len(pack.Actions) != 0 || len(pack.BudgetConstraints) != 0 || len(pack.EvidenceRequirements) != 0 {
t.Fatal("mapping-only v2 must not contain runtime actions, budgets, or enforcement-shaped evidence requirements")
}
if len(pack.Programs) == 0 {
t.Fatal("v2 mapping must contain at least one program")
}
for _, program := range pack.Programs {
if strings.TrimSpace(program.ProgramID) == "" || len(program.Controls) == 0 || len(program.CandidatePolicyMappings) == 0 {
t.Fatalf("program must contain identity, controls, and candidate mappings: %+v", program)
}
if len(program.PolicyRules) != 0 {
t.Fatalf("program %q must not misrepresent candidate mappings as policy_rules", program.ProgramID)
}
for _, control := range program.Controls {
if strings.TrimSpace(control.ControlID) == "" || control.MappingStatus != "MAPPED" || len(control.Enforcement) != 0 {
t.Fatalf("program %q has a misrepresented control: %+v", program.ProgramID, control)
}
}
for _, mapping := range program.CandidatePolicyMappings {
if mapping.CandidateOutcome == "" || mapping.CandidateCondition == "" || mapping.Rationale == "" {
t.Fatalf("program %q has an empty candidate mapping: %+v", program.ProgramID, mapping)
}
}
}

dates := pack.ApplicabilityDates
if dates.GeneralApplication != "2026-08-02" || dates.Article50GeneralApplication != "2026-08-02" ||
dates.Article50PreexistingSystemTransition != "2026-12-02" ||
dates.AnnexIIISectionsOneToThree != "2027-12-02" || dates.AnnexISectionsOneToThree != "2028-08-02" ||
dates.Article6Paragraph5Deferred == nil || *dates.Article6Paragraph5Deferred {
t.Fatalf("unexpected v2 applicability dates or Article 6(5) carve-out: %+v", dates)
}
if len(pack.ApplicabilityDatesProvenance.Sources) != 2 ||
!strings.Contains(pack.ApplicabilityDatesProvenance.Sources[0], "data.europa.eu/eli/reg/2024/1689") ||
!strings.Contains(pack.ApplicabilityDatesProvenance.Sources[1], "data.europa.eu/eli/reg/2026/1744") {
t.Fatalf("v2 must retain both official ELI sources: %v", pack.ApplicabilityDatesProvenance.Sources)
}
if pack.EvidenceMappings["qtsp_timestamp_anchor"] != "OPTIONAL_MAPPING_ONLY" {
t.Fatalf("QTSP must remain an optional evidence mapping, got %q", pack.EvidenceMappings["qtsp_timestamp_anchor"])
}

runtime, err := loadServePolicyRuntime(filepath.Join(repoRoot, "release.high_risk.v3.toml"))
if err != nil {
t.Fatalf("load canonical mapping-only policy: %v", err)
}
if runtime.ReferencePack.PackID != pack.PackID || len(runtime.Graph.Rules) != 0 || len(runtime.AllowMap()) != 0 {
t.Fatalf("mapping-only pack must compile fail-closed with zero runtime rules: pack=%q rules=%d allow=%v", runtime.ReferencePack.PackID, len(runtime.Graph.Rules), runtime.AllowMap())
}
}

func TestCompileServePolicySnapshotRequiresReferencePackDigest(t *testing.T) {
dir := t.TempDir()
refDir := filepath.Join(dir, "reference_packs")
Expand Down
37 changes: 24 additions & 13 deletions core/cmd/helm-ai-kernel/verify_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func runVerifyCmd(args []string, stdout, stderr io.Writer) int {
cmd.StringVar(&externalHostKey, "external-host-public-key", strings.TrimSpace(os.Getenv("HELM_EXTERNAL_HOST_PUBLIC_KEY_HEX")), "Trusted Ed25519 public key hex for external host evidence chains")
cmd.StringVar(&trustedPublicKey, "trusted-public-key", strings.TrimSpace(os.Getenv("HELM_VERIFY_PUBLIC_KEY_HEX")), "Trusted Ed25519 public key hex for conformance report signatures")
cmd.StringVar(&managedAgentKey, "managed-agent-receipt-public-key", strings.TrimSpace(os.Getenv("HELM_MANAGED_AGENT_RECEIPT_PUBLIC_KEY_HEX")), "Trusted Ed25519 public key hex for embedded managed-agent receipt signatures")
cmd.BoolVar(&requireEIDAS, "require-eidas", false, "Require every receipt to carry an eIDAS-qualified RFC 3161 anchor")
cmd.IntVar(&eidasMaxAgeHours, "eidas-max-age-hours", 24, "Maximum age in hours of an anchor's integrated_time before --require-eidas treats it as stale")
cmd.BoolVar(&requireEIDAS, "require-eidas", false, "Require eIDAS-labelled anchor metadata (backend=eidas-qtsp with a parseable integrated_time); metadata-only, not RFC 3161 or EU Trusted List cryptographic verification")
cmd.IntVar(&eidasMaxAgeHours, "eidas-max-age-hours", 24, "Maximum age in hours of the declared integrated_time checked by --require-eidas; this does not verify the timestamp token")
cmd.StringVar(&requireTEE, "require-tee", "", "Require every receipt to carry a TEE attestation; one of sevsnp|tdx|nitro|any (empty = no requirement)")

normalizedArgs, normalizeErr := normalizeVerifyArgs(args)
Expand Down Expand Up @@ -189,7 +189,7 @@ func runVerifyCmd(args []string, stdout, stderr io.Writer) int {
}

if requireEIDAS {
eidasResults := checkEIDASAnchors(verifyTarget, time.Duration(eidasMaxAgeHours)*time.Hour)
eidasResults := checkEIDASAnchorMetadata(verifyTarget, time.Duration(eidasMaxAgeHours)*time.Hour)
report.Checks = append(report.Checks, eidasResults...)
for _, r := range eidasResults {
if !r.Pass {
Expand Down Expand Up @@ -566,15 +566,17 @@ func finalizeVerifyReport(report *verifier.VerifyReport) {
report.Summary = fmt.Sprintf("PASS: %d/%d checks passed", len(report.Checks), len(report.Checks))
}

// checkEIDASAnchors verifies that every receipt in the bundle carries an
// eIDAS-qualified RFC 3161 anchor (backend == "eidas-qtsp") and that the
// integrated_time of each anchor is fresher than maxAge.
// checkEIDASAnchorMetadata inventories anchor records whose metadata declares
// backend == "eidas-qtsp" and checks that integrated_time is parseable and no
// older than maxAge. It does not parse or verify an RFC 3161 token, validate a
// message imprint or signature, consult an EU Trusted List, establish receipt
// coverage, or prove eIDAS/QTSP qualification.
//
// Anchor receipts are looked up under <bundle>/02_PROOFGRAPH/anchors/*.json
// and as embedded shapes inside <bundle>/00_INDEX.json (key "anchor"). The
// receipt JSON shape mirrors anchor.AnchorReceipt: {backend, log_id,
// log_index, integrated_time, signature, request:{...}}.
func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.CheckResult {
func checkEIDASAnchorMetadata(bundleRoot string, maxAge time.Duration) []verifier.CheckResult {
const eidasBackend = "eidas-qtsp"

results := make([]verifier.CheckResult, 0, 4)
Expand All @@ -586,6 +588,7 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
type anchorMeta struct {
Path string
Backend string
IntegratedRaw string
IntegratedTime time.Time
}
var anchors []anchorMeta
Expand Down Expand Up @@ -616,7 +619,7 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
backend, _ := doc["backend"].(string)
ts, _ := doc["integrated_time"].(string)
parsed, _ := time.Parse(time.RFC3339, ts)
anchors = append(anchors, anchorMeta{Path: path, Backend: backend, IntegratedTime: parsed})
anchors = append(anchors, anchorMeta{Path: path, Backend: backend, IntegratedRaw: ts, IntegratedTime: parsed})
}

// Also check 00_INDEX.json's embedded anchor field, when present.
Expand All @@ -627,7 +630,7 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
backend, _ := a["backend"].(string)
ts, _ := a["integrated_time"].(string)
parsed, _ := time.Parse(time.RFC3339, ts)
anchors = append(anchors, anchorMeta{Path: "00_INDEX.json#anchor", Backend: backend, IntegratedTime: parsed})
anchors = append(anchors, anchorMeta{Path: "00_INDEX.json#anchor", Backend: backend, IntegratedRaw: ts, IntegratedTime: parsed})
}
}
}
Expand All @@ -636,7 +639,7 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
results = append(results, verifier.CheckResult{
Name: "eidas:require",
Pass: false,
Reason: "no anchor receipts found under 02_PROOFGRAPH/anchors/ or 00_INDEX.json#anchor; --require-eidas needs at least one eIDAS-qualified anchor",
Reason: "no anchor metadata found under 02_PROOFGRAPH/anchors/ or 00_INDEX.json#anchor; --require-eidas needs at least one record declaring backend=eidas-qtsp and does not establish eIDAS qualification",
})
return results
}
Expand All @@ -653,7 +656,15 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
continue
}
hasEIDAS = true
if maxAge > 0 && !a.IntegratedTime.IsZero() && now.Sub(a.IntegratedTime) > maxAge {
if strings.TrimSpace(a.IntegratedRaw) == "" || a.IntegratedTime.IsZero() {
results = append(results, verifier.CheckResult{
Name: "eidas:anchor_metadata",
Pass: false,
Reason: fmt.Sprintf("anchor %s must declare integrated_time as RFC3339 metadata; no timestamp token or EU Trusted List verification was performed", a.Path),
})
continue
}
if maxAge > 0 && now.Sub(a.IntegratedTime) > maxAge {
results = append(results, verifier.CheckResult{
Name: "eidas:anchor_freshness",
Pass: false,
Expand All @@ -663,9 +674,9 @@ func checkEIDASAnchors(bundleRoot string, maxAge time.Duration) []verifier.Check
continue
}
results = append(results, verifier.CheckResult{
Name: "eidas:anchor_qualified",
Name: "eidas:anchor_metadata",
Pass: true,
Detail: fmt.Sprintf("%s carries eIDAS-qualified anchor at %s", a.Path, a.IntegratedTime.Format(time.RFC3339)),
Detail: fmt.Sprintf("%s declares backend=%s and integrated_time=%s; metadata only, RFC 3161 token and EU Trusted List were not verified", a.Path, eidasBackend, a.IntegratedTime.Format(time.RFC3339)),
})
}

Expand Down
98 changes: 98 additions & 0 deletions core/cmd/helm-ai-kernel/verify_eidas_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

// quantum_posture: these tests pin a metadata-only CLI boundary and make no
// post-quantum or classical timestamp-verification assurance.

import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
)

func TestVerifyEIDASHelpStatesMetadataOnlyBoundary(t *testing.T) {
var stdout, stderr bytes.Buffer
code := runVerifyCmd([]string{"--help"}, &stdout, &stderr)
if code != 2 {
t.Fatalf("verify --help exit=%d, want 2; stdout=%q stderr=%q", code, stdout.String(), stderr.String())
}

help := stderr.String()
for _, want := range []string{
"Require eIDAS-labelled anchor metadata",
"metadata-only, not RFC 3161 or EU Trusted List cryptographic verification",
"this does not verify the timestamp token",
} {
if !strings.Contains(help, want) {
t.Fatalf("verify --help missing truthful eIDAS boundary %q:\n%s", want, help)
}
}
if strings.Contains(help, "Require every receipt to carry an eIDAS-qualified") {
t.Fatalf("verify --help still claims cryptographic/legal qualification:\n%s", help)
}
}

func TestCheckEIDASAnchorMetadataDoesNotClaimQualification(t *testing.T) {
root := t.TempDir()
anchorsDir := filepath.Join(root, "02_PROOFGRAPH", "anchors")
if err := os.MkdirAll(anchorsDir, 0o750); err != nil {
t.Fatal(err)
}
writeEIDASMetadataFixture(t, filepath.Join(anchorsDir, "declared.json"), map[string]any{
"backend": "eidas-qtsp",
"integrated_time": time.Now().UTC().Format(time.RFC3339),
// Deliberately not an RFC 3161 token. A pass proves this CLI gate only
// inventories metadata and must never be presented as token verification.
"signature": "not-an-rfc3161-token",
})

results := checkEIDASAnchorMetadata(root, 24*time.Hour)
if len(results) != 1 {
t.Fatalf("metadata results=%+v, want exactly one", results)
}
result := results[0]
if !result.Pass || result.Name != "eidas:anchor_metadata" {
t.Fatalf("metadata result=%+v, want a metadata-only pass", result)
}
detail := strings.ToLower(result.Detail)
if !strings.Contains(detail, "metadata only") || !strings.Contains(detail, "were not verified") {
t.Fatalf("metadata result does not disclose verification boundary: %+v", result)
}
if strings.Contains(detail, "qualified") {
t.Fatalf("metadata-only result claims qualification: %+v", result)
}
}

func TestCheckEIDASAnchorMetadataRejectsInvalidIntegratedTime(t *testing.T) {
root := t.TempDir()
anchorsDir := filepath.Join(root, "02_PROOFGRAPH", "anchors")
if err := os.MkdirAll(anchorsDir, 0o750); err != nil {
t.Fatal(err)
}
writeEIDASMetadataFixture(t, filepath.Join(anchorsDir, "invalid-time.json"), map[string]any{
"backend": "eidas-qtsp",
"integrated_time": "not-rfc3339",
})

results := checkEIDASAnchorMetadata(root, 24*time.Hour)
if len(results) != 1 || results[0].Pass || results[0].Name != "eidas:anchor_metadata" {
t.Fatalf("invalid integrated_time must fail metadata-shape check: %+v", results)
}
if !strings.Contains(results[0].Reason, "must declare integrated_time as RFC3339 metadata") {
t.Fatalf("invalid integrated_time failure is not actionable: %+v", results[0])
}
}

func writeEIDASMetadataFixture(t *testing.T, path string, value map[string]any) {
t.Helper()
data, err := json.Marshal(value)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil {
t.Fatal(err)
}
}
Loading
Loading