@sanctuary-framework/mcp-server 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/cli.cjs +19954 -15551
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +19958 -15555
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +3348 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1605 -1589
- package/dist/index.d.ts +1605 -1589
- package/dist/index.js +3348 -152
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -1332,6 +1332,7 @@ var IdentityManager = class {
|
|
|
1332
1332
|
masterKey;
|
|
1333
1333
|
identities = /* @__PURE__ */ new Map();
|
|
1334
1334
|
primaryIdentityId = null;
|
|
1335
|
+
metadataKey = "primary_identity_id";
|
|
1335
1336
|
constructor(storage, masterKey) {
|
|
1336
1337
|
this.storage = storage;
|
|
1337
1338
|
this.masterKey = masterKey;
|
|
@@ -1354,13 +1355,26 @@ var IdentityManager = class {
|
|
|
1354
1355
|
const decrypted = decrypt(encrypted, this.encryptionKey);
|
|
1355
1356
|
const identity = JSON.parse(bytesToString(decrypted));
|
|
1356
1357
|
this.identities.set(identity.identity_id, identity);
|
|
1357
|
-
if (!this.primaryIdentityId) {
|
|
1358
|
-
this.primaryIdentityId = identity.identity_id;
|
|
1359
|
-
}
|
|
1360
1358
|
} catch {
|
|
1361
1359
|
failed++;
|
|
1362
1360
|
}
|
|
1363
1361
|
}
|
|
1362
|
+
try {
|
|
1363
|
+
const metaRaw = await this.storage.read("_meta", this.metadataKey);
|
|
1364
|
+
if (metaRaw) {
|
|
1365
|
+
const metaStr = bytesToString(metaRaw);
|
|
1366
|
+
const primaryId = JSON.parse(metaStr);
|
|
1367
|
+
if (this.identities.has(primaryId)) {
|
|
1368
|
+
this.primaryIdentityId = primaryId;
|
|
1369
|
+
} else {
|
|
1370
|
+
this.primaryIdentityId = this.identities.keys().next().value || null;
|
|
1371
|
+
}
|
|
1372
|
+
} else {
|
|
1373
|
+
this.primaryIdentityId = this.identities.keys().next().value || null;
|
|
1374
|
+
}
|
|
1375
|
+
} catch {
|
|
1376
|
+
this.primaryIdentityId = this.identities.keys().next().value || null;
|
|
1377
|
+
}
|
|
1364
1378
|
return { total: entries.length, loaded: this.identities.size, failed };
|
|
1365
1379
|
}
|
|
1366
1380
|
/** Save an identity to storage */
|
|
@@ -1375,6 +1389,25 @@ var IdentityManager = class {
|
|
|
1375
1389
|
this.identities.set(identity.identity_id, identity);
|
|
1376
1390
|
if (!this.primaryIdentityId) {
|
|
1377
1391
|
this.primaryIdentityId = identity.identity_id;
|
|
1392
|
+
this.savePrimaryIdentityId();
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/** Set which identity is the default/primary identity */
|
|
1396
|
+
async setPrimary(identityId) {
|
|
1397
|
+
if (!this.identities.has(identityId)) {
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
this.primaryIdentityId = identityId;
|
|
1401
|
+
await this.savePrimaryIdentityId();
|
|
1402
|
+
return true;
|
|
1403
|
+
}
|
|
1404
|
+
/** Persist the primary identity ID to storage */
|
|
1405
|
+
async savePrimaryIdentityId() {
|
|
1406
|
+
if (!this.primaryIdentityId) return;
|
|
1407
|
+
try {
|
|
1408
|
+
const serialized = stringToBytes(JSON.stringify(this.primaryIdentityId));
|
|
1409
|
+
await this.storage.write("_meta", this.metadataKey, serialized);
|
|
1410
|
+
} catch {
|
|
1378
1411
|
}
|
|
1379
1412
|
}
|
|
1380
1413
|
get(id) {
|
|
@@ -1384,6 +1417,9 @@ var IdentityManager = class {
|
|
|
1384
1417
|
if (!this.primaryIdentityId) return void 0;
|
|
1385
1418
|
return this.identities.get(this.primaryIdentityId);
|
|
1386
1419
|
}
|
|
1420
|
+
getPrimaryIdentityId() {
|
|
1421
|
+
return this.primaryIdentityId;
|
|
1422
|
+
}
|
|
1387
1423
|
list() {
|
|
1388
1424
|
return Array.from(this.identities.values()).map((si) => ({
|
|
1389
1425
|
identity_id: si.identity_id,
|
|
@@ -1589,6 +1625,44 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
|
|
|
1589
1625
|
});
|
|
1590
1626
|
}
|
|
1591
1627
|
},
|
|
1628
|
+
{
|
|
1629
|
+
name: "identity_set_primary",
|
|
1630
|
+
description: "Set which identity is the default/primary identity. This identity will be used by shr_generate, reputation_publish, and other tools when no explicit identity_id parameter is provided. The selection is persisted across server restarts.",
|
|
1631
|
+
inputSchema: {
|
|
1632
|
+
type: "object",
|
|
1633
|
+
properties: {
|
|
1634
|
+
identity_id: {
|
|
1635
|
+
type: "string",
|
|
1636
|
+
description: "The identity ID to set as primary"
|
|
1637
|
+
}
|
|
1638
|
+
},
|
|
1639
|
+
required: ["identity_id"]
|
|
1640
|
+
},
|
|
1641
|
+
handler: async (args) => {
|
|
1642
|
+
const identityId = args.identity_id;
|
|
1643
|
+
const identity = identityMgr.get(identityId);
|
|
1644
|
+
if (!identity) {
|
|
1645
|
+
return toolResult({
|
|
1646
|
+
error: `Identity "${identityId}" not found.`
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
const success = await identityMgr.setPrimary(identityId);
|
|
1650
|
+
if (!success) {
|
|
1651
|
+
return toolResult({
|
|
1652
|
+
error: `Failed to set primary identity to "${identityId}".`
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
auditLog?.append("l1", "identity_set_primary", identityId, {
|
|
1656
|
+
previous_primary: identityMgr.getPrimaryIdentityId()
|
|
1657
|
+
});
|
|
1658
|
+
return toolResult({
|
|
1659
|
+
primary_identity_id: identityId,
|
|
1660
|
+
label: identity.label,
|
|
1661
|
+
did: identity.did,
|
|
1662
|
+
set_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1592
1666
|
// ── State Tools ─────────────────────────────────────────────────────
|
|
1593
1667
|
{
|
|
1594
1668
|
name: "state_write",
|
|
@@ -3701,14 +3775,14 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3701
3775
|
const publishType = args.type;
|
|
3702
3776
|
const configuredVerascoreUrl = verascoreUrl || "https://verascore.ai";
|
|
3703
3777
|
const veracoreUrl = args.verascore_url || configuredVerascoreUrl;
|
|
3704
|
-
const
|
|
3778
|
+
const ALLOWED_VERASCORE_HOSTS2 = /* @__PURE__ */ new Set([
|
|
3705
3779
|
"verascore.ai",
|
|
3706
3780
|
"www.verascore.ai",
|
|
3707
3781
|
"api.verascore.ai"
|
|
3708
3782
|
]);
|
|
3709
3783
|
try {
|
|
3710
3784
|
const configuredHost = new URL(configuredVerascoreUrl).hostname;
|
|
3711
|
-
|
|
3785
|
+
ALLOWED_VERASCORE_HOSTS2.add(configuredHost);
|
|
3712
3786
|
} catch {
|
|
3713
3787
|
}
|
|
3714
3788
|
try {
|
|
@@ -3718,9 +3792,9 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3718
3792
|
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
3719
3793
|
});
|
|
3720
3794
|
}
|
|
3721
|
-
if (!
|
|
3795
|
+
if (!ALLOWED_VERASCORE_HOSTS2.has(parsed.hostname)) {
|
|
3722
3796
|
return toolResult({
|
|
3723
|
-
error: `verascore_url must point to a known Verascore domain (${[...
|
|
3797
|
+
error: `verascore_url must point to a known Verascore domain (${[...ALLOWED_VERASCORE_HOSTS2].join(", ")}). Got: ${parsed.hostname}`
|
|
3724
3798
|
});
|
|
3725
3799
|
}
|
|
3726
3800
|
} catch {
|
|
@@ -3907,6 +3981,7 @@ var DEFAULT_POLICY = {
|
|
|
3907
3981
|
"l2_hardening_status",
|
|
3908
3982
|
"l2_verify_isolation",
|
|
3909
3983
|
"sovereignty_audit",
|
|
3984
|
+
"audit_export_siem",
|
|
3910
3985
|
"shr_gateway_export",
|
|
3911
3986
|
"bridge_commit",
|
|
3912
3987
|
"bridge_verify",
|
|
@@ -3919,8 +3994,16 @@ var DEFAULT_POLICY = {
|
|
|
3919
3994
|
"governor_status",
|
|
3920
3995
|
"reputation_publish",
|
|
3921
3996
|
// Auto-allow: publishing sovereignty data to Verascore is routine
|
|
3922
|
-
"sanctuary_policy_status"
|
|
3997
|
+
"sanctuary_policy_status",
|
|
3923
3998
|
// Read-only policy summary
|
|
3999
|
+
"identity_set_primary",
|
|
4000
|
+
// One-time set, persists via _meta storage — safe at Tier 3
|
|
4001
|
+
"memory_attest",
|
|
4002
|
+
// Read-only audit attestation — records that a memory op happened
|
|
4003
|
+
"compliance_generate_eu_ai_act_bundle",
|
|
4004
|
+
// Read-only; emits signed compliance documents from existing state
|
|
4005
|
+
"compliance_eu_ai_act_annex_iii_classify"
|
|
4006
|
+
// Read-only; rule-based Annex III classifier
|
|
3924
4007
|
],
|
|
3925
4008
|
approval_channel: DEFAULT_CHANNEL
|
|
3926
4009
|
};
|
|
@@ -4093,6 +4176,7 @@ tier3_always_allow:
|
|
|
4093
4176
|
- context_gate_filter
|
|
4094
4177
|
- context_gate_list_policies
|
|
4095
4178
|
- sovereignty_audit
|
|
4179
|
+
- audit_export_siem
|
|
4096
4180
|
- shr_gateway_export
|
|
4097
4181
|
- bridge_commit
|
|
4098
4182
|
- bridge_verify
|
|
@@ -4102,7 +4186,9 @@ tier3_always_allow:
|
|
|
4102
4186
|
- governor_status
|
|
4103
4187
|
- reputation_publish
|
|
4104
4188
|
- sanctuary_policy_status
|
|
4105
|
-
|
|
4189
|
+
- memory_attest
|
|
4190
|
+
- compliance_generate_eu_ai_act_bundle
|
|
4191
|
+
- compliance_eu_ai_act_annex_iii_classify
|
|
4106
4192
|
# \u2500\u2500\u2500 Approval Channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4107
4193
|
# How Sanctuary reaches you when approval is needed.
|
|
4108
4194
|
# NOTE: Timeout always results in denial. This is not configurable (SEC-002).
|
|
@@ -13783,7 +13869,7 @@ function createSIEMTools(auditLog) {
|
|
|
13783
13869
|
const tools = [
|
|
13784
13870
|
{
|
|
13785
13871
|
name: "audit_export_siem",
|
|
13786
|
-
description: "Export audit log events in SIEM-standard formats (CEF or OCSF) for ingestion into Splunk, Datadog, QRadar, and other security information and event management (SIEM) platforms. Encrypted audit entries are decrypted and formatted according to your chosen standard. Tier
|
|
13872
|
+
description: "Export audit log events in SIEM-standard formats (CEF or OCSF) for ingestion into Splunk, Datadog, QRadar, and other security information and event management (SIEM) platforms. Encrypted audit entries are decrypted and formatted according to your chosen standard. Tier 3 \u2014 auto-allow (read-only, audit logging only).",
|
|
13787
13873
|
inputSchema: {
|
|
13788
13874
|
type: "object",
|
|
13789
13875
|
properties: {
|
|
@@ -17099,6 +17185,7 @@ function createGovernorTools(governor, auditLog) {
|
|
|
17099
17185
|
init_identity();
|
|
17100
17186
|
init_encoding();
|
|
17101
17187
|
init_identity();
|
|
17188
|
+
var PASSPHRASE_BACKUP_WARNING = "\u26A0\uFE0F IMPORTANT: Your Sanctuary passphrase is the only way to decrypt your agent's state. If lost, all encrypted data is unrecoverable by design. Back up your passphrase now to a password manager, encrypted USB, or other secure location separate from this machine.";
|
|
17102
17189
|
function validateVerascoreUrl(urlStr, configuredUrl) {
|
|
17103
17190
|
const allowed = /* @__PURE__ */ new Set([
|
|
17104
17191
|
"verascore.ai",
|
|
@@ -17188,7 +17275,8 @@ function createSanctuaryTools(opts) {
|
|
|
17188
17275
|
identity_id: publicIdentity.identity_id,
|
|
17189
17276
|
profileUrl,
|
|
17190
17277
|
tier: "self-attested",
|
|
17191
|
-
published: false
|
|
17278
|
+
published: false,
|
|
17279
|
+
passphrase_warning: PASSPHRASE_BACKUP_WARNING
|
|
17192
17280
|
});
|
|
17193
17281
|
}
|
|
17194
17282
|
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
@@ -17249,7 +17337,8 @@ function createSanctuaryTools(opts) {
|
|
|
17249
17337
|
tier: "self-attested",
|
|
17250
17338
|
published: response.ok,
|
|
17251
17339
|
verascore_status: response.status,
|
|
17252
|
-
verascore_response: result
|
|
17340
|
+
verascore_response: result,
|
|
17341
|
+
passphrase_warning: PASSPHRASE_BACKUP_WARNING
|
|
17253
17342
|
});
|
|
17254
17343
|
} catch (err) {
|
|
17255
17344
|
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
@@ -17262,7 +17351,8 @@ function createSanctuaryTools(opts) {
|
|
|
17262
17351
|
profileUrl,
|
|
17263
17352
|
tier: "self-attested",
|
|
17264
17353
|
published: false,
|
|
17265
|
-
warning: `Identity created but Verascore publish failed: ${err instanceof Error ? err.message : String(err)}
|
|
17354
|
+
warning: `Identity created but Verascore publish failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
17355
|
+
passphrase_warning: PASSPHRASE_BACKUP_WARNING
|
|
17266
17356
|
});
|
|
17267
17357
|
}
|
|
17268
17358
|
}
|
|
@@ -17524,147 +17614,3232 @@ function createSanctuaryTools(opts) {
|
|
|
17524
17614
|
return { tools };
|
|
17525
17615
|
}
|
|
17526
17616
|
|
|
17527
|
-
// src/
|
|
17528
|
-
|
|
17617
|
+
// src/l1-cognitive/memory-attest.ts
|
|
17618
|
+
init_identity();
|
|
17619
|
+
init_hashing();
|
|
17529
17620
|
init_encoding();
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
|
|
17556
|
-
|
|
17557
|
-
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17621
|
+
var VALID_OPERATIONS = [
|
|
17622
|
+
"store",
|
|
17623
|
+
"retrieve",
|
|
17624
|
+
"update",
|
|
17625
|
+
"delete",
|
|
17626
|
+
"search"
|
|
17627
|
+
];
|
|
17628
|
+
var VALID_SCOPES = ["user", "agent", "session", "app"];
|
|
17629
|
+
function createMemoryAttestTools(identityManager, masterKey, auditLog) {
|
|
17630
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
17631
|
+
const tools = [
|
|
17632
|
+
{
|
|
17633
|
+
name: "memory_attest",
|
|
17634
|
+
description: "Create an Ed25519-signed attestation for a memory operation. Records who accessed what (content hash, not content), when, and through which memory provider \u2014 without exposing the memory content itself. Works with any MCP-compatible memory provider (Mem0, Zep, Letta, etc.). Use after add_memory, search_memories, or any memory CRUD operation to build a cryptographically verifiable audit trail of memory access.",
|
|
17635
|
+
inputSchema: {
|
|
17636
|
+
type: "object",
|
|
17637
|
+
properties: {
|
|
17638
|
+
operation: {
|
|
17639
|
+
type: "string",
|
|
17640
|
+
enum: VALID_OPERATIONS,
|
|
17641
|
+
description: "The memory operation that was performed: store, retrieve, update, delete, or search."
|
|
17642
|
+
},
|
|
17643
|
+
provider: {
|
|
17644
|
+
type: "string",
|
|
17645
|
+
description: 'Name of the memory provider (e.g., "mem0", "zep", "letta", "graphiti").'
|
|
17646
|
+
},
|
|
17647
|
+
content_hash: {
|
|
17648
|
+
type: "string",
|
|
17649
|
+
description: "SHA-256 hash of the memory content (hex or base64url). If you have raw content, hash it first \u2014 never pass raw memory content to this tool."
|
|
17650
|
+
},
|
|
17651
|
+
memory_id: {
|
|
17652
|
+
type: "string",
|
|
17653
|
+
description: "Optional: ID returned by the memory provider for this memory entry."
|
|
17654
|
+
},
|
|
17655
|
+
user_id: {
|
|
17656
|
+
type: "string",
|
|
17657
|
+
description: "Optional: user ID associated with this memory operation."
|
|
17658
|
+
},
|
|
17659
|
+
agent_id: {
|
|
17660
|
+
type: "string",
|
|
17661
|
+
description: "Optional: agent ID that performed the operation."
|
|
17662
|
+
},
|
|
17663
|
+
session_id: {
|
|
17664
|
+
type: "string",
|
|
17665
|
+
description: "Optional: session ID in which the operation occurred."
|
|
17666
|
+
},
|
|
17667
|
+
scope: {
|
|
17668
|
+
type: "string",
|
|
17669
|
+
enum: VALID_SCOPES,
|
|
17670
|
+
description: 'Optional: memory scope \u2014 "user" (personal), "agent" (agent-specific), "session" (session-scoped), or "app" (application-wide).'
|
|
17671
|
+
},
|
|
17672
|
+
identity_id: {
|
|
17673
|
+
type: "string",
|
|
17674
|
+
description: "Optional: Sanctuary identity to sign with. Defaults to primary identity."
|
|
17675
|
+
}
|
|
17676
|
+
},
|
|
17677
|
+
required: ["operation", "provider", "content_hash"]
|
|
17678
|
+
},
|
|
17679
|
+
handler: async (args) => {
|
|
17680
|
+
const operation = args.operation;
|
|
17681
|
+
if (!VALID_OPERATIONS.includes(operation)) {
|
|
17682
|
+
return toolResult({
|
|
17683
|
+
error: `Invalid operation: "${operation}". Must be one of: ${VALID_OPERATIONS.join(", ")}`
|
|
17684
|
+
});
|
|
17685
|
+
}
|
|
17686
|
+
const provider = args.provider;
|
|
17687
|
+
if (!provider || provider.trim().length === 0) {
|
|
17688
|
+
return toolResult({ error: "Provider name is required." });
|
|
17689
|
+
}
|
|
17690
|
+
if (provider.length > 128) {
|
|
17691
|
+
return toolResult({
|
|
17692
|
+
error: "Provider name exceeds 128 character limit."
|
|
17693
|
+
});
|
|
17694
|
+
}
|
|
17695
|
+
const contentHash = args.content_hash;
|
|
17696
|
+
if (!contentHash || contentHash.trim().length === 0) {
|
|
17697
|
+
return toolResult({ error: "Content hash is required." });
|
|
17698
|
+
}
|
|
17699
|
+
if (contentHash.length > 128) {
|
|
17700
|
+
return toolResult({
|
|
17701
|
+
error: "Content hash exceeds 128 character limit."
|
|
17702
|
+
});
|
|
17703
|
+
}
|
|
17704
|
+
const identityId = args.identity_id;
|
|
17705
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
17706
|
+
if (!identity) {
|
|
17707
|
+
return toolResult({
|
|
17708
|
+
error: identityId ? `Identity not found: ${identityId}` : "No default identity. Create one with identity_create first."
|
|
17709
|
+
});
|
|
17710
|
+
}
|
|
17711
|
+
const scope = args.scope;
|
|
17712
|
+
if (scope && !VALID_SCOPES.includes(scope)) {
|
|
17713
|
+
return toolResult({
|
|
17714
|
+
error: `Invalid scope: "${scope}". Must be one of: ${VALID_SCOPES.join(", ")}`
|
|
17715
|
+
});
|
|
17716
|
+
}
|
|
17717
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
17718
|
+
const payload = {
|
|
17719
|
+
version: 1,
|
|
17720
|
+
operation,
|
|
17721
|
+
provider: provider.trim(),
|
|
17722
|
+
content_hash: contentHash.trim(),
|
|
17723
|
+
metadata: {
|
|
17724
|
+
...args.user_id ? { user_id: args.user_id } : {},
|
|
17725
|
+
...args.agent_id ? { agent_id: args.agent_id } : {},
|
|
17726
|
+
...args.session_id ? { session_id: args.session_id } : {},
|
|
17727
|
+
...scope ? { scope } : {}
|
|
17728
|
+
},
|
|
17729
|
+
...args.memory_id ? { memory_id: args.memory_id } : {},
|
|
17730
|
+
identity_id: identity.identity_id,
|
|
17731
|
+
identity_did: identity.did,
|
|
17732
|
+
timestamp
|
|
17733
|
+
};
|
|
17734
|
+
const payloadBytes = stringToBytes(JSON.stringify(payload));
|
|
17735
|
+
const signature = sign(
|
|
17736
|
+
payloadBytes,
|
|
17737
|
+
identity.encrypted_private_key,
|
|
17738
|
+
identityEncKey
|
|
17739
|
+
);
|
|
17740
|
+
const attestationId = hashToString(payloadBytes).slice(0, 22);
|
|
17741
|
+
auditLog.append("l1", `memory_${operation}`, identity.identity_id, {
|
|
17742
|
+
attestation_id: attestationId,
|
|
17743
|
+
provider: payload.provider,
|
|
17744
|
+
content_hash: payload.content_hash,
|
|
17745
|
+
memory_id: payload.memory_id,
|
|
17746
|
+
scope: payload.metadata.scope
|
|
17747
|
+
});
|
|
17748
|
+
const attestation = {
|
|
17749
|
+
attestation_id: attestationId,
|
|
17750
|
+
payload,
|
|
17751
|
+
signature: toBase64url(signature),
|
|
17752
|
+
public_key: identity.public_key
|
|
17753
|
+
};
|
|
17754
|
+
return toolResult(attestation);
|
|
17755
|
+
}
|
|
17563
17756
|
}
|
|
17564
|
-
|
|
17565
|
-
}
|
|
17566
|
-
}
|
|
17567
|
-
var MODEL_PRESETS = {
|
|
17568
|
-
/**
|
|
17569
|
-
* Claude Opus 4 via Anthropic API (cloud inference, closed weights/source)
|
|
17570
|
-
*/
|
|
17571
|
-
claudeOpus4: () => ({
|
|
17572
|
-
model_id: "claude-opus-4",
|
|
17573
|
-
model_name: "Claude Opus 4",
|
|
17574
|
-
model_version: "4.0",
|
|
17575
|
-
provider: "Anthropic",
|
|
17576
|
-
license: "proprietary",
|
|
17577
|
-
open_weights: false,
|
|
17578
|
-
open_source: false,
|
|
17579
|
-
local_inference: false,
|
|
17580
|
-
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
17581
|
-
}),
|
|
17582
|
-
/**
|
|
17583
|
-
* Qwen 3.5 via local inference (open weights, proprietary training)
|
|
17584
|
-
*/
|
|
17585
|
-
qwen35Local: () => ({
|
|
17586
|
-
model_id: "qwen-3.5-35b",
|
|
17587
|
-
model_name: "Qwen 3.5 35B",
|
|
17588
|
-
model_version: "3.5",
|
|
17589
|
-
provider: "Alibaba Cloud",
|
|
17590
|
-
license: "Apache-2.0",
|
|
17591
|
-
open_weights: true,
|
|
17592
|
-
open_source: false,
|
|
17593
|
-
local_inference: true,
|
|
17594
|
-
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
17595
|
-
}),
|
|
17596
|
-
/**
|
|
17597
|
-
* Llama 3.3 70B via local inference (open weights and code)
|
|
17598
|
-
*/
|
|
17599
|
-
llama33Local: () => ({
|
|
17600
|
-
model_id: "llama-3.3-70b-instruct",
|
|
17601
|
-
model_name: "Llama 3.3 70B Instruct",
|
|
17602
|
-
model_version: "3.3",
|
|
17603
|
-
provider: "Meta",
|
|
17604
|
-
license: "Apache-2.0",
|
|
17605
|
-
open_weights: true,
|
|
17606
|
-
open_source: true,
|
|
17607
|
-
local_inference: true,
|
|
17608
|
-
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
17609
|
-
}),
|
|
17610
|
-
/**
|
|
17611
|
-
* Mistral 7B (open weights, open code, local inference)
|
|
17612
|
-
*/
|
|
17613
|
-
mistral7bLocal: () => ({
|
|
17614
|
-
model_id: "mistral-7b-instruct",
|
|
17615
|
-
model_name: "Mistral 7B Instruct",
|
|
17616
|
-
model_version: "7",
|
|
17617
|
-
provider: "Mistral AI",
|
|
17618
|
-
license: "Apache-2.0",
|
|
17619
|
-
open_weights: true,
|
|
17620
|
-
open_source: true,
|
|
17621
|
-
local_inference: true,
|
|
17622
|
-
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
17623
|
-
})
|
|
17624
|
-
};
|
|
17757
|
+
];
|
|
17758
|
+
return { tools };
|
|
17759
|
+
}
|
|
17625
17760
|
|
|
17626
|
-
// src/
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
|
|
17652
|
-
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17761
|
+
// src/compliance/eu_ai_act/generator.ts
|
|
17762
|
+
init_identity();
|
|
17763
|
+
init_hashing();
|
|
17764
|
+
init_encoding();
|
|
17765
|
+
|
|
17766
|
+
// src/compliance/eu_ai_act/coverage_matrix.ts
|
|
17767
|
+
var REGULATION_VERSION = "EU AI Act Regulation (EU) 2024/1689, as of 2026-04-10";
|
|
17768
|
+
var REVIEW_DATE = "2026-04-10";
|
|
17769
|
+
var REVIEWER = "Erik Newton";
|
|
17770
|
+
var ROWS = [
|
|
17771
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
17772
|
+
// ANNEX IV — Technical Documentation Requirements (per Article 11)
|
|
17773
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
17774
|
+
// ─── §1 General description ─────────────────────────────────────
|
|
17775
|
+
{
|
|
17776
|
+
id: "annex_iv_1_a_general_description",
|
|
17777
|
+
citation: {
|
|
17778
|
+
instrument: "Annex IV",
|
|
17779
|
+
section: "\xA71(a)",
|
|
17780
|
+
clause_id: "annex-iv-1-a",
|
|
17781
|
+
title: "General description: intended purpose, provider, version"
|
|
17782
|
+
},
|
|
17783
|
+
requirement: '"Its intended purpose, the name of the provider and the version of the system [...]."',
|
|
17784
|
+
coverage: "partial",
|
|
17785
|
+
evidence_emitter: [
|
|
17786
|
+
"identity_list",
|
|
17787
|
+
"identity_set_primary",
|
|
17788
|
+
"manifest",
|
|
17789
|
+
"shr_generate"
|
|
17790
|
+
],
|
|
17791
|
+
evidence_emitted: "Auto-filled: cryptographic provider identity (primary Ed25519 DID + public key via identity_list and identity_set_primary), Sanctuary version and implementation metadata (via manifest), and signed SHR instance_id. The template pre-populates the version-and-identity portion of this row with machine-verifiable cryptographic evidence.",
|
|
17792
|
+
manual_carveout: "Intended purpose of the agent (business function), legal provider name and registered entity, and version-naming conventions used by the enterprise.",
|
|
17793
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17794
|
+
last_reviewed_by: REVIEWER,
|
|
17795
|
+
review_notes: "Partial is honest here: Sanctuary can emit a cryptographic provider identity and a software version, but 'intended purpose' is inherently a business-function narrative that Sanctuary cannot infer. Resist any temptation to call this full."
|
|
17796
|
+
},
|
|
17797
|
+
{
|
|
17798
|
+
id: "annex_iv_1_b_hardware_software_interaction",
|
|
17799
|
+
citation: {
|
|
17800
|
+
instrument: "Annex IV",
|
|
17801
|
+
section: "\xA71(b)",
|
|
17802
|
+
clause_id: "annex-iv-1-b",
|
|
17803
|
+
title: "Interaction with external hardware or software"
|
|
17804
|
+
},
|
|
17805
|
+
requirement: '"How the AI system interacts, or can be used to interact, with hardware or software [...] that is not part of the AI system itself [...]."',
|
|
17806
|
+
coverage: "partial",
|
|
17807
|
+
evidence_emitter: [
|
|
17808
|
+
"shr_generate",
|
|
17809
|
+
"manifest",
|
|
17810
|
+
"context_gate_list_policies"
|
|
17811
|
+
],
|
|
17812
|
+
evidence_emitted: "Auto-filled: SHR layers.l2.model_provenance (provider, open-weights flag, local-inference flag, optional weights hash), MCP tool inventory (via manifest) listing every integration point the agent exposes, and the outbound context-gating policy manifest (via context_gate_list_policies) showing which provider endpoints the agent is permitted to contact and under what field-level constraints.",
|
|
17813
|
+
manual_carveout: "Upstream LLM contracts and data processing agreements, third-party API integrations, downstream systems consuming agent output, and any hardware peripherals the agent orchestrates.",
|
|
17814
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17815
|
+
last_reviewed_by: REVIEWER,
|
|
17816
|
+
review_notes: "Sanctuary genuinely covers ~60% of this row when model_provenance is populated by the integrator. The template emits a structured interaction manifest and marks the business-context narrative as [MANUAL INPUT REQUIRED]."
|
|
17817
|
+
},
|
|
17818
|
+
{
|
|
17819
|
+
id: "annex_iv_1_c_software_versions",
|
|
17820
|
+
citation: {
|
|
17821
|
+
instrument: "Annex IV",
|
|
17822
|
+
section: "\xA71(c)",
|
|
17823
|
+
clause_id: "annex-iv-1-c",
|
|
17824
|
+
title: "Versions of relevant software and firmware"
|
|
17825
|
+
},
|
|
17826
|
+
requirement: '"The versions of relevant software or firmware [...]."',
|
|
17827
|
+
coverage: "partial",
|
|
17828
|
+
evidence_emitter: ["manifest", "shr_generate", "monitor_health"],
|
|
17829
|
+
evidence_emitted: "Auto-filled: Sanctuary MCP server version (from manifest and SHR implementation block), Node.js runtime version, MCP SDK version, and platform string. The template emits a versioned software manifest for the Sanctuary layer itself.",
|
|
17830
|
+
manual_carveout: "LLM model version and weights hash (if not set in model_provenance), agent harness version (OpenClaw or other), operating system patch level, container image digest, and any other 'relevant' software outside Sanctuary's runtime scope.",
|
|
17831
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17832
|
+
last_reviewed_by: REVIEWER,
|
|
17833
|
+
review_notes: "Downgraded from full during verification (2026-04-10). Sanctuary auto-emits its own version + Node version, but 'relevant software' extends beyond Sanctuary and the enterprise must enumerate the rest. Partial is the honest call."
|
|
17834
|
+
},
|
|
17835
|
+
{
|
|
17836
|
+
id: "annex_iv_1_d_forms_on_market",
|
|
17837
|
+
citation: {
|
|
17838
|
+
instrument: "Annex IV",
|
|
17839
|
+
section: "\xA71(d)",
|
|
17840
|
+
clause_id: "annex-iv-1-d",
|
|
17841
|
+
title: "Forms in which the system is placed on the market"
|
|
17842
|
+
},
|
|
17843
|
+
requirement: '"The description of all the forms in which the AI system is placed on the market or put into service [...]."',
|
|
17844
|
+
coverage: "manual_only",
|
|
17845
|
+
evidence_emitter: [],
|
|
17846
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: forms in which the AI system is placed on the market or put into service].",
|
|
17847
|
+
manual_carveout: "Sanctuary is a runtime sovereignty layer and has no visibility into the commercial forms in which the enterprise distributes or operates the agent.",
|
|
17848
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17849
|
+
last_reviewed_by: REVIEWER,
|
|
17850
|
+
review_notes: "Structural manual row \u2014 commercial distribution is not a Sanctuary concern and will not become one."
|
|
17851
|
+
},
|
|
17852
|
+
{
|
|
17853
|
+
id: "annex_iv_1_e_hardware_description",
|
|
17854
|
+
citation: {
|
|
17855
|
+
instrument: "Annex IV",
|
|
17856
|
+
section: "\xA71(e)",
|
|
17857
|
+
clause_id: "annex-iv-1-e",
|
|
17858
|
+
title: "Description of the hardware on which the system runs"
|
|
17859
|
+
},
|
|
17860
|
+
requirement: '"The description of the hardware on which the AI system is intended to run [...]."',
|
|
17861
|
+
coverage: "partial",
|
|
17862
|
+
evidence_emitter: ["exec_attest", "monitor_health", "sovereignty_audit"],
|
|
17863
|
+
evidence_emitted: "Auto-filled: execution environment attestation (via exec_attest) reporting CPU vendor, TEE availability, operating system string, and Node.js runtime; sovereignty_audit environment fingerprint. The template emits the detected hardware context as structured evidence.",
|
|
17864
|
+
manual_carveout: "Production hardware specifications, TEE attestation evidence from the actual deployment environment, geographic location of the execution environment, and any hardware security modules or trusted hardware used in production.",
|
|
17865
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17866
|
+
last_reviewed_by: REVIEWER,
|
|
17867
|
+
review_notes: "In local-process mode Sanctuary emits tee_available=false, which is honest but incomplete \u2014 production may run on TEE-capable hardware that Sanctuary does not detect. SHR degradation NO_TEE is flagged automatically."
|
|
17868
|
+
},
|
|
17869
|
+
{
|
|
17870
|
+
id: "annex_iv_1_f_instructions_of_use",
|
|
17871
|
+
citation: {
|
|
17872
|
+
instrument: "Annex IV",
|
|
17873
|
+
section: "\xA71(f)",
|
|
17874
|
+
clause_id: "annex-iv-1-f",
|
|
17875
|
+
title: "Basic description of the user interface"
|
|
17876
|
+
},
|
|
17877
|
+
requirement: '"A basic description of the user interface provided to the deployer [...]."',
|
|
17878
|
+
coverage: "manual_only",
|
|
17879
|
+
evidence_emitter: [],
|
|
17880
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: description of the user interface provided to the deployer].",
|
|
17881
|
+
manual_carveout: "User interface design is an agent-level or enterprise-level product decision outside Sanctuary's scope.",
|
|
17882
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17883
|
+
last_reviewed_by: REVIEWER,
|
|
17884
|
+
review_notes: "Structural manual row."
|
|
17885
|
+
},
|
|
17886
|
+
// ─── §2 Detailed description of elements ────────────────────────
|
|
17887
|
+
{
|
|
17888
|
+
id: "annex_iv_2_a_development_methods",
|
|
17889
|
+
citation: {
|
|
17890
|
+
instrument: "Annex IV",
|
|
17891
|
+
section: "\xA72(a)",
|
|
17892
|
+
clause_id: "annex-iv-2-a",
|
|
17893
|
+
title: "Methods and steps performed for development"
|
|
17894
|
+
},
|
|
17895
|
+
requirement: '"The methods and steps performed for the development of the AI system [...]."',
|
|
17896
|
+
coverage: "manual_only",
|
|
17897
|
+
evidence_emitter: [],
|
|
17898
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: development methodology, design iterations, training procedures, validation steps].",
|
|
17899
|
+
manual_carveout: "Sanctuary is a runtime sovereignty layer and is not involved in model development, training, or pre-deployment validation.",
|
|
17900
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17901
|
+
last_reviewed_by: REVIEWER,
|
|
17902
|
+
review_notes: "Structural manual row \u2014 model development is outside Sanctuary's architectural scope."
|
|
17903
|
+
},
|
|
17904
|
+
{
|
|
17905
|
+
id: "annex_iv_2_b_design_specifications",
|
|
17906
|
+
citation: {
|
|
17907
|
+
instrument: "Annex IV",
|
|
17908
|
+
section: "\xA72(b)",
|
|
17909
|
+
clause_id: "annex-iv-2-b",
|
|
17910
|
+
title: "Design specifications and key design choices"
|
|
17911
|
+
},
|
|
17912
|
+
requirement: '"The design specifications of the system, namely the general logic of the AI system [...] the key design choices including the rationale and assumptions made [...]."',
|
|
17913
|
+
coverage: "partial",
|
|
17914
|
+
evidence_emitter: [
|
|
17915
|
+
"principal_policy_view",
|
|
17916
|
+
"context_gate_list_policies",
|
|
17917
|
+
"sovereignty_profile_get",
|
|
17918
|
+
"manifest"
|
|
17919
|
+
],
|
|
17920
|
+
evidence_emitted: "Auto-filled: machine-readable Principal Policy YAML (tier rules, approval channel configuration, anomaly thresholds), context gating policy manifest, sovereignty profile, and the full MCP tool inventory. Together these constitute the declarative design specification of the Sanctuary sovereignty layer.",
|
|
17921
|
+
manual_carveout: "Agent-level business logic, decision algorithms, the rationale for key design choices (why this policy, why these thresholds), assumptions made during development, and any non-Sanctuary architectural components.",
|
|
17922
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17923
|
+
last_reviewed_by: REVIEWER,
|
|
17924
|
+
review_notes: "Borderline partial \u2014 Sanctuary emits the policy and gating specifications as structured data, but 'key design choices including rationale' is inherently a narrative field that requires enterprise authorship. Kept partial deliberately."
|
|
17925
|
+
},
|
|
17926
|
+
{
|
|
17927
|
+
id: "annex_iv_2_c_system_architecture",
|
|
17928
|
+
citation: {
|
|
17929
|
+
instrument: "Annex IV",
|
|
17930
|
+
section: "\xA72(c)",
|
|
17931
|
+
clause_id: "annex-iv-2-c",
|
|
17932
|
+
title: "Description of system architecture"
|
|
17933
|
+
},
|
|
17934
|
+
requirement: '"The description of the system architecture explaining how software components build on or feed into each other and integrate into the overall processing [...]."',
|
|
17935
|
+
coverage: "partial",
|
|
17936
|
+
evidence_emitter: [
|
|
17937
|
+
"shr_generate",
|
|
17938
|
+
"manifest",
|
|
17939
|
+
"sovereignty_audit",
|
|
17940
|
+
"federation_status"
|
|
17941
|
+
],
|
|
17942
|
+
evidence_emitted: "Auto-filled: SHR four-layer architecture description (L1 Cognitive Sovereignty, L2 Operational Isolation, L3 Selective Disclosure, L4 Verifiable Reputation) with status flags per layer, tool inventory showing every component interface, sovereignty audit environment analysis, and federation peer topology if configured.",
|
|
17943
|
+
manual_carveout: "Agent-level architecture (LLM orchestration, prompt templates, tool-use loops), upstream data flows into the agent, downstream systems consuming agent output, and the integration story between Sanctuary and the rest of the enterprise stack.",
|
|
17944
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17945
|
+
last_reviewed_by: REVIEWER,
|
|
17946
|
+
review_notes: "Borderline partial \u2014 Sanctuary emits a complete architectural description of its own layer but this row requires the architecture of the AI system as a whole. Enterprise wraps the Sanctuary layer in a broader architecture narrative."
|
|
17947
|
+
},
|
|
17948
|
+
{
|
|
17949
|
+
id: "annex_iv_2_d_data_requirements",
|
|
17950
|
+
citation: {
|
|
17951
|
+
instrument: "Annex IV",
|
|
17952
|
+
section: "\xA72(d)",
|
|
17953
|
+
clause_id: "annex-iv-2-d",
|
|
17954
|
+
title: "Data requirements: training data datasheets"
|
|
17955
|
+
},
|
|
17956
|
+
requirement: '"Where applicable, the data requirements in terms of datasheets describing the training methodologies and techniques and the training data sets used, including [...] provenance, scope and main characteristics; how the data was obtained and selected; labelling procedures [...] and data cleaning methodologies [...]."',
|
|
17957
|
+
coverage: "manual_only",
|
|
17958
|
+
evidence_emitter: [],
|
|
17959
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: training data description, provenance, scope, labelling, cleaning, and governance].",
|
|
17960
|
+
manual_carveout: "Sanctuary is a runtime sovereignty layer and has no visibility into model training. Training data governance is entirely the responsibility of the model provider or enterprise data team.",
|
|
17961
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17962
|
+
last_reviewed_by: REVIEWER,
|
|
17963
|
+
review_notes: "Classification is structurally stable across EU AI Act revisions."
|
|
17964
|
+
},
|
|
17965
|
+
{
|
|
17966
|
+
id: "annex_iv_2_e_human_oversight_assessment",
|
|
17967
|
+
citation: {
|
|
17968
|
+
instrument: "Annex IV",
|
|
17969
|
+
section: "\xA72(e)",
|
|
17970
|
+
clause_id: "annex-iv-2-e",
|
|
17971
|
+
title: "Assessment of human oversight measures per Article 14"
|
|
17972
|
+
},
|
|
17973
|
+
requirement: '"Assessment of the human oversight measures needed in accordance with Article 14, including an assessment of the technical measures needed to facilitate the interpretation of the outputs of AI systems by the deployers [...]."',
|
|
17974
|
+
coverage: "partial",
|
|
17975
|
+
evidence_emitter: [
|
|
17976
|
+
"principal_policy_view",
|
|
17977
|
+
"sovereignty_audit",
|
|
17978
|
+
"shr_generate",
|
|
17979
|
+
"principal_baseline_view"
|
|
17980
|
+
],
|
|
17981
|
+
evidence_emitted: "Auto-filled: Principal Policy approval channel configuration (stderr / dashboard / webhook), Tier 1 require-approval rule count, Tier 2 anomaly-triggered approval rule count, Tier 3 auto-allow tool list, baseline anomaly tracker status, and denial-on-timeout behaviour. The template pre-populates the technical-measures half of the human oversight assessment.",
|
|
17982
|
+
manual_carveout: "Operator identities, roles, training, authority, and escalation procedures; mapping between Sanctuary's approval channel and the enterprise's stop-button workflow; rationale for why the chosen oversight tier is appropriate to the risk profile of the deployed agent.",
|
|
17983
|
+
last_reviewed_date: REVIEW_DATE,
|
|
17984
|
+
last_reviewed_by: REVIEWER,
|
|
17985
|
+
review_notes: "Borderline partial. Sanctuary provides the oversight mechanism inventory with zero enterprise input (~70% of the row). Enterprise provides the people-and-process assessment."
|
|
17986
|
+
},
|
|
17987
|
+
{
|
|
17988
|
+
id: "annex_iv_2_f_predetermined_changes",
|
|
17989
|
+
citation: {
|
|
17990
|
+
instrument: "Annex IV",
|
|
17991
|
+
section: "\xA72(f)",
|
|
17992
|
+
clause_id: "annex-iv-2-f",
|
|
17993
|
+
title: "Pre-determined changes to the system and performance"
|
|
17994
|
+
},
|
|
17995
|
+
requirement: '"Where applicable, a description of pre-determined changes to the AI system and its performance [...]."',
|
|
17996
|
+
coverage: "manual_only",
|
|
17997
|
+
evidence_emitter: [],
|
|
17998
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: pre-determined changes to the AI system and its performance, if any].",
|
|
17999
|
+
manual_carveout: "Pre-determined changes are a product-roadmap and provider-level concept outside Sanctuary's runtime scope.",
|
|
18000
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18001
|
+
last_reviewed_by: REVIEWER,
|
|
18002
|
+
review_notes: "Structural manual row."
|
|
18003
|
+
},
|
|
18004
|
+
{
|
|
18005
|
+
id: "annex_iv_2_g_validation_and_testing",
|
|
18006
|
+
citation: {
|
|
18007
|
+
instrument: "Annex IV",
|
|
18008
|
+
section: "\xA72(g)",
|
|
18009
|
+
clause_id: "annex-iv-2-g",
|
|
18010
|
+
title: "Validation and testing procedures, metrics"
|
|
18011
|
+
},
|
|
18012
|
+
requirement: '"The validation and testing procedures used, including information about the validation and testing data used [...] and the main metrics used to measure [...] accuracy, robustness and compliance with other relevant requirements [...]."',
|
|
18013
|
+
coverage: "partial",
|
|
18014
|
+
evidence_emitter: [
|
|
18015
|
+
"monitor_audit_log",
|
|
18016
|
+
"audit_export_siem",
|
|
18017
|
+
"sovereignty_audit"
|
|
18018
|
+
],
|
|
18019
|
+
evidence_emitted: "Auto-filled: audit log entries within the reporting period showing tool-call success/failure rates, gate decision counts (approve / deny / auto-allow), and any injection_detected entries triggered by the prompt injection detector. These provide runtime validation evidence from actual deployment.",
|
|
18020
|
+
manual_carveout: "Model evaluation metrics (accuracy, precision, recall, F1), bias testing results, robustness testing against adversarial inputs, the validation dataset description, and the metric methodology the enterprise used to assess the agent before deployment.",
|
|
18021
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18022
|
+
last_reviewed_by: REVIEWER,
|
|
18023
|
+
review_notes: "Sanctuary emits runtime operational evidence but does not participate in pre-deployment model validation."
|
|
18024
|
+
},
|
|
18025
|
+
// ─── §2(h) Cybersecurity measures (FULL) ────────────────────────
|
|
18026
|
+
{
|
|
18027
|
+
id: "annex_iv_2_h_cybersecurity",
|
|
18028
|
+
citation: {
|
|
18029
|
+
instrument: "Annex IV",
|
|
18030
|
+
section: "\xA72(h)",
|
|
18031
|
+
clause_id: "annex-iv-2-h",
|
|
18032
|
+
title: "Cybersecurity measures"
|
|
18033
|
+
},
|
|
18034
|
+
requirement: '"A detailed description of the cybersecurity measures put in place [...]."',
|
|
18035
|
+
coverage: "full",
|
|
18036
|
+
evidence_emitter: [
|
|
18037
|
+
"sovereignty_audit",
|
|
18038
|
+
"shr_generate",
|
|
18039
|
+
"manifest",
|
|
18040
|
+
"principal_policy_view",
|
|
18041
|
+
"context_gate_list_policies",
|
|
18042
|
+
"context_gate_enforcer_status",
|
|
18043
|
+
"exec_attest"
|
|
18044
|
+
],
|
|
18045
|
+
evidence_emitted: "Machine-verifiable inventory of Sanctuary's cybersecurity primitives, every item reproducible by running the listed tools against a live instance: (1) L1 Cognitive Sovereignty \u2014 AES-256-GCM namespace encryption with HKDF per-namespace key derivation, Argon2id master key derivation, Ed25519 self-custodied identity, Merkle integrity tracking; reported by sovereignty_audit and shr_generate under layers.l1. (2) L2 Operational Isolation \u2014 three-tier Principal Policy gate with out-of-band approval channel, tool-call audit logging, and denial-on-timeout semantics; reported by principal_policy_view and shr_generate under layers.l2. (3) L2 Outbound context gating \u2014 per-provider field policies classifying agent context as allow / redact / hash / summarize / deny before any outbound call; reported by context_gate_list_policies and context_gate_enforcer_status. (4) L3 Selective Disclosure \u2014 Pedersen commitments on Ristretto255, Schnorr proofs, and bit-decomposition range proofs; reported by sovereignty_audit and shr_generate under layers.l3. (5) L4 Verifiable Reputation \u2014 Ed25519-signed attestations in EAS-compatible format with sovereignty-gated trust tiers; reported by sovereignty_audit and shr_generate under layers.l4. (6) Execution attestation \u2014 cryptographic execution attestation via exec_attest. The full tool inventory of this Sanctuary instance is reproducible by running manifest.",
|
|
18046
|
+
manual_carveout: null,
|
|
18047
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18048
|
+
last_reviewed_by: REVIEWER,
|
|
18049
|
+
review_notes: "Verified against v0.7.0 source on 2026-04-10: every emitter in the array corresponds to a registered MCP tool in index.ts, and every primitive named in the prose is reported by at least one listed tool. DELIBERATELY EXCLUDED: prompt injection detection. The InjectionDetector subsystem (server/src/security/injection-detector.ts) is wired into the Principal Policy gate and is a real runtime control, but in v0.7.0 its configuration state is not exposed via any MCP tool \u2014 it is only indirectly evidenced through `injection_detected:*` entries in the audit log (visible via monitor_audit_log / audit_export_siem). Claiming injection detection as part of this row's full coverage would require source-code inspection, which violates the full-coverage bar. Injection detection runtime activity is evidenced via the Art. 12 risk management support row instead. If v0.8.0+ adds an `injection_detector_status` MCP tool, revisit and add to this row."
|
|
18050
|
+
},
|
|
18051
|
+
// ─── §3–§9 ───────────────────────────────────────────────────────
|
|
18052
|
+
{
|
|
18053
|
+
id: "annex_iv_3_monitoring_functioning_control",
|
|
18054
|
+
citation: {
|
|
18055
|
+
instrument: "Annex IV",
|
|
18056
|
+
section: "\xA73",
|
|
18057
|
+
clause_id: "annex-iv-3",
|
|
18058
|
+
title: "Monitoring, functioning and control of the system"
|
|
18059
|
+
},
|
|
18060
|
+
requirement: '"Detailed information about the monitoring, functioning and control of the AI system [...]."',
|
|
18061
|
+
coverage: "partial",
|
|
18062
|
+
evidence_emitter: [
|
|
18063
|
+
"monitor_audit_log",
|
|
18064
|
+
"audit_export_siem",
|
|
18065
|
+
"monitor_health",
|
|
18066
|
+
"context_gate_enforcer_status",
|
|
18067
|
+
"principal_baseline_view"
|
|
18068
|
+
],
|
|
18069
|
+
evidence_emitted: "Auto-filled: encrypted audit log queryable via monitor_audit_log and exportable in CEF/OCSF via audit_export_siem; health dashboard via monitor_health; outbound context gating enforcer status; Principal Policy baseline anomaly tracker state. Together these constitute the runtime monitoring substrate for the Sanctuary layer.",
|
|
18070
|
+
manual_carveout: "Operational SLAs, on-call rotation, incident response procedures, escalation workflows, monitoring dashboards outside Sanctuary, and the enterprise's overall observability stack.",
|
|
18071
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18072
|
+
last_reviewed_by: REVIEWER,
|
|
18073
|
+
review_notes: "Sanctuary provides the monitoring substrate; the enterprise wraps it in operational processes. Borderline partial."
|
|
18074
|
+
},
|
|
18075
|
+
{
|
|
18076
|
+
id: "annex_iv_4_performance_metrics",
|
|
18077
|
+
citation: {
|
|
18078
|
+
instrument: "Annex IV",
|
|
18079
|
+
section: "\xA74",
|
|
18080
|
+
clause_id: "annex-iv-4",
|
|
18081
|
+
title: "Appropriateness of performance metrics"
|
|
18082
|
+
},
|
|
18083
|
+
requirement: '"A description of the appropriateness of the performance metrics for the specific AI system [...]."',
|
|
18084
|
+
coverage: "manual_only",
|
|
18085
|
+
evidence_emitter: [],
|
|
18086
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: performance metrics and their appropriateness for the specific agent deployment].",
|
|
18087
|
+
manual_carveout: "Performance metrics are agent-specific and model-specific; Sanctuary does not measure model accuracy or task performance.",
|
|
18088
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18089
|
+
last_reviewed_by: REVIEWER,
|
|
18090
|
+
review_notes: "Structural manual row."
|
|
18091
|
+
},
|
|
18092
|
+
{
|
|
18093
|
+
id: "annex_iv_5_risk_management",
|
|
18094
|
+
citation: {
|
|
18095
|
+
instrument: "Annex IV",
|
|
18096
|
+
section: "\xA75",
|
|
18097
|
+
clause_id: "annex-iv-5",
|
|
18098
|
+
title: "Risk management system per Article 9"
|
|
18099
|
+
},
|
|
18100
|
+
requirement: '"A detailed description of the risk management system in accordance with Article 9 [...]."',
|
|
18101
|
+
coverage: "partial",
|
|
18102
|
+
evidence_emitter: [
|
|
18103
|
+
"principal_policy_view",
|
|
18104
|
+
"principal_baseline_view",
|
|
18105
|
+
"sovereignty_audit",
|
|
18106
|
+
"context_gate_list_policies"
|
|
18107
|
+
],
|
|
18108
|
+
evidence_emitted: "Auto-filled: Principal Policy tier structure (Tier 1 block, Tier 2 anomaly-triggered, Tier 3 auto-allow), baseline anomaly tracker configuration, outbound context gating policies, and sovereignty audit gap analysis with prioritised recommendations. These constitute Sanctuary's runtime risk management controls.",
|
|
18109
|
+
manual_carveout: "Enterprise-level risk register, residual risk analysis, risk treatment plan, acceptance criteria for residual risks, periodic risk review cadence, and the link between identified risks and the Sanctuary controls above.",
|
|
18110
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18111
|
+
last_reviewed_by: REVIEWER,
|
|
18112
|
+
review_notes: "Sanctuary emits runtime controls; enterprise maps them to an Article 9 risk management framework."
|
|
18113
|
+
},
|
|
18114
|
+
{
|
|
18115
|
+
id: "annex_iv_6_lifecycle_changes",
|
|
18116
|
+
citation: {
|
|
18117
|
+
instrument: "Annex IV",
|
|
18118
|
+
section: "\xA76",
|
|
18119
|
+
clause_id: "annex-iv-6",
|
|
18120
|
+
title: "Description of relevant changes made through lifecycle"
|
|
18121
|
+
},
|
|
18122
|
+
requirement: '"A description of any change made to the system through its lifecycle [...]."',
|
|
18123
|
+
coverage: "partial",
|
|
18124
|
+
evidence_emitter: [
|
|
18125
|
+
"monitor_audit_log",
|
|
18126
|
+
"audit_export_siem",
|
|
18127
|
+
"identity_list"
|
|
18128
|
+
],
|
|
18129
|
+
evidence_emitted: "Auto-filled: audit log entries within the reporting period showing all policy changes, identity operations (create, rotate, set_primary), state operations, and any configuration changes; identity rotation chain via identity_list. These provide a cryptographically anchored timeline of Sanctuary-layer changes during the period.",
|
|
18130
|
+
manual_carveout: "Agent-level version changes (model updates, prompt changes), business-context narrative for why changes were made, and any changes to non-Sanctuary components of the stack.",
|
|
18131
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18132
|
+
last_reviewed_by: REVIEWER,
|
|
18133
|
+
review_notes: "Sanctuary captures its own layer's change timeline; enterprise provides the broader change narrative."
|
|
18134
|
+
},
|
|
18135
|
+
{
|
|
18136
|
+
id: "annex_iv_7_standards_applied",
|
|
18137
|
+
citation: {
|
|
18138
|
+
instrument: "Annex IV",
|
|
18139
|
+
section: "\xA77",
|
|
18140
|
+
clause_id: "annex-iv-7",
|
|
18141
|
+
title: "Harmonised standards applied"
|
|
18142
|
+
},
|
|
18143
|
+
requirement: '"A list of the harmonised standards applied in full or in part [...] and, where such harmonised standards have not been applied, a detailed description of the solutions adopted to meet the requirements set out in Chapter III, Section 2 [...]."',
|
|
18144
|
+
coverage: "manual_only",
|
|
18145
|
+
evidence_emitter: [],
|
|
18146
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: list of harmonised standards applied, or description of alternative solutions].",
|
|
18147
|
+
manual_carveout: "Standards conformance is a legal declaration made by the provider. Sanctuary does not assert conformance to any harmonised standard.",
|
|
18148
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18149
|
+
last_reviewed_by: REVIEWER,
|
|
18150
|
+
review_notes: "Structural manual row \u2014 standards conformance is a provider legal declaration, not a Sanctuary primitive."
|
|
18151
|
+
},
|
|
18152
|
+
{
|
|
18153
|
+
id: "annex_iv_8_eu_declaration_of_conformity",
|
|
18154
|
+
citation: {
|
|
18155
|
+
instrument: "Annex IV",
|
|
18156
|
+
section: "\xA78",
|
|
18157
|
+
clause_id: "annex-iv-8",
|
|
18158
|
+
title: "Copy of the EU declaration of conformity"
|
|
18159
|
+
},
|
|
18160
|
+
requirement: '"A copy of the EU declaration of conformity referred to in Article 47 [...]."',
|
|
18161
|
+
coverage: "manual_only",
|
|
18162
|
+
evidence_emitter: [],
|
|
18163
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: EU declaration of conformity per Article 47].",
|
|
18164
|
+
manual_carveout: "The EU declaration of conformity is a formal legal document signed by the provider. Sanctuary cannot generate or provide it.",
|
|
18165
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18166
|
+
last_reviewed_by: REVIEWER,
|
|
18167
|
+
review_notes: "Structural manual row \u2014 legal document only."
|
|
18168
|
+
},
|
|
18169
|
+
{
|
|
18170
|
+
id: "annex_iv_9_post_market_monitoring_plan",
|
|
18171
|
+
citation: {
|
|
18172
|
+
instrument: "Annex IV",
|
|
18173
|
+
section: "\xA79",
|
|
18174
|
+
clause_id: "annex-iv-9",
|
|
18175
|
+
title: "Post-market monitoring plan per Article 72"
|
|
18176
|
+
},
|
|
18177
|
+
requirement: '"A detailed description of the system in place to evaluate the AI system performance in the post-market phase in accordance with Article 72, including the post-market monitoring plan referred to in Article 72(3) [...]."',
|
|
18178
|
+
coverage: "manual_only",
|
|
18179
|
+
evidence_emitter: [],
|
|
18180
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: post-market monitoring plan per Article 72(3)].",
|
|
18181
|
+
manual_carveout: "The post-market monitoring plan is a provider-authored governance document. Sanctuary's SIEM exporter can feed the monitoring pipeline, but the plan itself is enterprise-authored.",
|
|
18182
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18183
|
+
last_reviewed_by: REVIEWER,
|
|
18184
|
+
review_notes: "See the Art. 12 post-market monitoring support row for Sanctuary's concrete contribution to this area."
|
|
18185
|
+
},
|
|
18186
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18187
|
+
// ARTICLE 12 — Automatic Record-Keeping (logs)
|
|
18188
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18189
|
+
{
|
|
18190
|
+
id: "art_12_automatic_logging",
|
|
18191
|
+
citation: {
|
|
18192
|
+
instrument: "Article",
|
|
18193
|
+
section: "12(1)",
|
|
18194
|
+
clause_id: "art-12-1",
|
|
18195
|
+
title: "Automatic logging of events over lifetime"
|
|
18196
|
+
},
|
|
18197
|
+
requirement: `"High-risk AI systems shall technically allow for the automatic recording of events ('logs') over the lifetime of the system."`,
|
|
18198
|
+
coverage: "full",
|
|
18199
|
+
evidence_emitter: ["monitor_audit_log", "audit_export_siem"],
|
|
18200
|
+
evidence_emitted: "Every tool call in the Sanctuary runtime automatically produces an audit entry via the Principal Policy gate. Router.ts wraps all tool invocations through gate.evaluate(), which appends to the audit log on every outcome path (gate_allow, gate_allow_proxy, gate_deny, gate_escalate, gate_unclassified, injection_detected). Entries are persisted as AES-256-GCM authenticated ciphertext under an HKDF-derived audit-log key and are queryable via monitor_audit_log or exportable in CEF/OCSF via audit_export_siem. No tool call bypasses the audit path.",
|
|
18201
|
+
manual_carveout: null,
|
|
18202
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18203
|
+
last_reviewed_by: REVIEWER,
|
|
18204
|
+
review_notes: "Verified against v0.7.0 source on 2026-04-10: router.ts:249 wraps every tool call through gate.evaluate(); gate.ts lines 86, 155, 186, 202, 358 append to the audit log on every outcome. CORRECTIONS APPLIED during verification: earlier drafts claimed 'Ed25519-signed entries' and 'hash-chained transcript' \u2014 neither is true. The AuditEntry schema in l2-operational/audit-log.ts has no signature field, and entries are stored as individual encrypted files with no inter-entry hash chain. AES-256-GCM authenticated encryption provides integrity against third-party tampering; that is the accurate claim. Art. 12(1) requires the system to technically *allow* automatic recording, which is satisfied. DURABILITY CAVEAT: audit-log.ts:59 uses fire-and-forget persistence \u2014 if disk write fails, the entry lives only in memory and is lost at process exit. This is a quality-under-failure concern but does not defeat the Art. 12(1) capability claim. If v0.8.0+ adds synchronous persistence or Ed25519 signing on audit entries, update this row's prose."
|
|
18205
|
+
},
|
|
18206
|
+
{
|
|
18207
|
+
id: "art_12_post_market_monitoring_support",
|
|
18208
|
+
citation: {
|
|
18209
|
+
instrument: "Article",
|
|
18210
|
+
section: "12(2)(a)",
|
|
18211
|
+
clause_id: "art-12-2-a",
|
|
18212
|
+
title: "Logs enable identification of Article 79(1) risks"
|
|
18213
|
+
},
|
|
18214
|
+
requirement: '"The logging capabilities shall enable the recording of events relevant for identifying situations that may result in the AI system presenting a risk within the meaning of Article 79(1) [...] and for facilitating the post-market monitoring referred to in Article 72."',
|
|
18215
|
+
coverage: "full",
|
|
18216
|
+
evidence_emitter: ["audit_export_siem", "monitor_audit_log"],
|
|
18217
|
+
evidence_emitted: "The audit_export_siem tool exports audit log entries in two SIEM-standard formats: Common Event Format (CEF, newline-delimited) and Open Cybersecurity Schema Framework (OCSF, JSON array). Both formats are directly ingestible by Splunk, Datadog, QRadar, and any other enterprise SIEM platform, which are the standard substrate for Article 72 post-market monitoring pipelines. Exports support time-window filters (since / until), tool name filters, gate decision filters (approve / deny / auto-allow), layer filters (l1 / l2 / l3 / l4), and result filters (success / failure). Bulk exports up to 1000 events per call.",
|
|
18218
|
+
manual_carveout: null,
|
|
18219
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18220
|
+
last_reviewed_by: REVIEWER,
|
|
18221
|
+
review_notes: "Verified against v0.7.0 source on 2026-04-10: audit/siem-tools.ts:18-77 registers audit_export_siem with CEF and OCSF format support and the full filter set. Classified Tier 3 (auto-allow, read-only) in loader.ts. No corrections to the original draft."
|
|
18222
|
+
},
|
|
18223
|
+
{
|
|
18224
|
+
id: "art_12_risk_management_support",
|
|
18225
|
+
citation: {
|
|
18226
|
+
instrument: "Article",
|
|
18227
|
+
section: "12(2)(b)",
|
|
18228
|
+
clause_id: "art-12-2-b",
|
|
18229
|
+
title: "Logs facilitate monitoring operation per Article 26(5)"
|
|
18230
|
+
},
|
|
18231
|
+
requirement: '"The logging capabilities shall enable the recording of events relevant for [...] facilitating the monitoring of the operation of the high-risk AI system referred to in Article 26(5)."',
|
|
18232
|
+
coverage: "full",
|
|
18233
|
+
evidence_emitter: [
|
|
18234
|
+
"monitor_audit_log",
|
|
18235
|
+
"audit_export_siem",
|
|
18236
|
+
"principal_baseline_view"
|
|
18237
|
+
],
|
|
18238
|
+
evidence_emitted: "Gate decisions are logged with structured operation prefixes (gate_allow:, gate_allow_proxy:, gate_deny:, gate_escalate:, gate_unclassified:, injection_detected:) directly filterable via audit_export_siem's filter_decision enum (approve / deny / auto-allow) and via monitor_audit_log's operation_type parameter. The Principal Policy baseline anomaly tracker state (behavioural model, known-namespaces, frequency baselines, anomaly thresholds) is queryable via principal_baseline_view. Together these provide the machine-queryable substrate for deployer operation monitoring under Article 26(5).",
|
|
18239
|
+
manual_carveout: null,
|
|
18240
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18241
|
+
last_reviewed_by: REVIEWER,
|
|
18242
|
+
review_notes: "Verified against v0.7.0 source on 2026-04-10. gate.ts writes structured operation strings on every gate outcome; baseline.ts is the anomaly source and is exposed via principal_baseline_view. Minor correction during verification: principal_baseline_view added to emitter list (original draft listed only monitor_audit_log)."
|
|
18243
|
+
},
|
|
18244
|
+
{
|
|
18245
|
+
id: "art_12_log_content",
|
|
18246
|
+
citation: {
|
|
18247
|
+
instrument: "Article",
|
|
18248
|
+
section: "12(3)",
|
|
18249
|
+
clause_id: "art-12-3",
|
|
18250
|
+
title: "Required log content for Annex III \xA71(a) systems"
|
|
18251
|
+
},
|
|
18252
|
+
requirement: '"For high-risk AI systems referred to in point 1(a) of Annex III, the logging capabilities shall provide, at a minimum: (a) recording of the period of each use of the system [...]; (b) the reference database against which input data has been checked by the system; (c) the input data for which the search has led to a match; (d) the identification of the natural persons involved in the verification of the results [...]."',
|
|
18253
|
+
coverage: "partial",
|
|
18254
|
+
evidence_emitter: [
|
|
18255
|
+
"monitor_audit_log",
|
|
18256
|
+
"audit_export_siem",
|
|
18257
|
+
"identity_list"
|
|
18258
|
+
],
|
|
18259
|
+
evidence_emitted: "Auto-filled where the agent routes through Sanctuary: period of use (audit log timestamps with since/until filters), tool inputs (captured in audit entry details field), and identification of principals who approved Tier 1 operations (captured via identity_id in audit entries and identity provenance via identity_list). Gate decisions are bound to the identity that requested the operation.",
|
|
18260
|
+
manual_carveout: "Reference database identifier (the external database the agent queries) \u2014 only captured if the agent explicitly logs it to Sanctuary state. Natural-person verifier identification beyond the Sanctuary principal identity (e.g., the human operator's HR record or employee ID). Input data captured only when the agent passes it through a Sanctuary tool call.",
|
|
18261
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18262
|
+
last_reviewed_by: REVIEWER,
|
|
18263
|
+
review_notes: "Row applies only to Annex III \xA71(a) (biometric remote identification) systems. For other Annex III categories this row is not triggered. Coverage depends on whether the agent actually routes its biometric checks through Sanctuary-tracked tool calls."
|
|
18264
|
+
},
|
|
18265
|
+
{
|
|
18266
|
+
id: "art_12_retention",
|
|
18267
|
+
citation: {
|
|
18268
|
+
instrument: "Article",
|
|
18269
|
+
section: "19(1)",
|
|
18270
|
+
clause_id: "art-19-1",
|
|
18271
|
+
title: "Log retention for at least six months"
|
|
18272
|
+
},
|
|
18273
|
+
requirement: '"Providers of high-risk AI systems shall keep the logs referred to in Article 12(1), automatically generated by their high-risk AI systems, to the extent such logs are under their control. [...] the logs shall be kept for a period appropriate to the intended purpose of the high-risk AI system, of at least six months [...]."',
|
|
18274
|
+
coverage: "partial",
|
|
18275
|
+
evidence_emitter: ["monitor_audit_log", "audit_export_siem"],
|
|
18276
|
+
evidence_emitted: "Auto-filled: Sanctuary's audit log persists entries indefinitely by default (no automatic purge). Entries are exportable at any time via audit_export_siem for archival to enterprise storage.",
|
|
18277
|
+
manual_carveout: "The enterprise's declared log retention policy (how long logs are kept, in which storage tier, who has access), the archival pipeline configuration feeding enterprise long-term storage, and the written policy document satisfying the 'period appropriate to the intended purpose' requirement.",
|
|
18278
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18279
|
+
last_reviewed_by: REVIEWER,
|
|
18280
|
+
review_notes: "Note: this row cites Article 19(1) rather than Article 12 \u2014 retention is specifically an Article 19 provider obligation, not an Article 12 logging capability. The capability to retain exists in Sanctuary (no automatic purge); the declared policy is an enterprise artefact."
|
|
18281
|
+
},
|
|
18282
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18283
|
+
// ARTICLE 13 — Transparency and provision of information to deployers
|
|
18284
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18285
|
+
{
|
|
18286
|
+
id: "art_13_3_a_provider_identity",
|
|
18287
|
+
citation: {
|
|
18288
|
+
instrument: "Article",
|
|
18289
|
+
section: "13(3)(a)",
|
|
18290
|
+
clause_id: "art-13-3-a",
|
|
18291
|
+
title: "Identity and contact details of the provider"
|
|
18292
|
+
},
|
|
18293
|
+
requirement: '"The instructions for use shall contain at least the following information: (a) the identity and the contact details of the provider and, where applicable, of its authorised representative [...]."',
|
|
18294
|
+
coverage: "partial",
|
|
18295
|
+
evidence_emitter: ["identity_list", "identity_set_primary", "shr_generate"],
|
|
18296
|
+
evidence_emitted: "Auto-filled: cryptographic provider identity \u2014 primary Ed25519 public key, DID, instance_id, and key creation timestamp via identity_list and identity_set_primary. The signed SHR carries the same identity in its signed_by field, providing a verifiable link between the provider identity and the capability assertions of the system.",
|
|
18297
|
+
manual_carveout: "Legal provider name, registered legal entity, registered business address, contact email, and authorised representative details (if applicable). These are legal-entity facts that must be supplied by the enterprise.",
|
|
18298
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18299
|
+
last_reviewed_by: REVIEWER,
|
|
18300
|
+
review_notes: "Sanctuary emits the cryptographic identity; the enterprise supplies the legal identity. Both are required for complete Art. 13(3)(a) coverage."
|
|
18301
|
+
},
|
|
18302
|
+
{
|
|
18303
|
+
id: "art_13_3_b_ii_capabilities_and_limitations",
|
|
18304
|
+
citation: {
|
|
18305
|
+
instrument: "Article",
|
|
18306
|
+
section: "13(3)(b)(ii)",
|
|
18307
|
+
clause_id: "art-13-3-b-ii",
|
|
18308
|
+
title: "Performance characteristics, capabilities and limitations"
|
|
18309
|
+
},
|
|
18310
|
+
requirement: '"The characteristics, capabilities and limitations of performance of the high-risk AI system, including [...] its intended purpose [...] the level of accuracy, including its metrics [...] and any known or foreseeable circumstance [...] which may lead to risks to the health and safety or fundamental rights [...]."',
|
|
18311
|
+
coverage: "partial",
|
|
18312
|
+
evidence_emitter: ["shr_generate", "manifest", "sovereignty_audit"],
|
|
18313
|
+
evidence_emitted: "Auto-filled: SHR four-layer capability report with explicit status flags per layer (active / degraded / inactive), the SHR degradations[] array listing honest self-declared gaps (e.g., NO_TEE, PROCESS_ISOLATION_ONLY), the full MCP tool inventory (via manifest), and the sovereignty audit gap analysis. Together these constitute a machine-readable capability manifest with explicit, honest limitations.",
|
|
18314
|
+
manual_carveout: "Agent-level accuracy metrics and their measurement methodology, false-positive and false-negative rates on the agent's business task, known failure modes specific to the deployment, and the link between technical capabilities and fundamental-rights risk.",
|
|
18315
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18316
|
+
last_reviewed_by: REVIEWER,
|
|
18317
|
+
review_notes: "Sanctuary's SHR is structurally a transparency artefact \u2014 it is designed to honestly declare capabilities and degradations. This row is where SHR data feeds user-facing disclosures."
|
|
18318
|
+
},
|
|
18319
|
+
{
|
|
18320
|
+
id: "art_13_3_b_iv_output_interpretation",
|
|
18321
|
+
citation: {
|
|
18322
|
+
instrument: "Article",
|
|
18323
|
+
section: "13(3)(b)(iv)",
|
|
18324
|
+
clause_id: "art-13-3-b-iv",
|
|
18325
|
+
title: "Technical capabilities to interpret system output"
|
|
18326
|
+
},
|
|
18327
|
+
requirement: '"Where appropriate, its performance regarding specific persons or groups of persons on which the system is intended to be used; [...] where appropriate, specifications for the input data, or any other relevant information in terms of the training, validation and testing data sets used, taking into account the intended purpose of the AI system [...]."',
|
|
18328
|
+
coverage: "partial",
|
|
18329
|
+
evidence_emitter: [
|
|
18330
|
+
"shr_generate",
|
|
18331
|
+
"monitor_audit_log",
|
|
18332
|
+
"audit_export_siem",
|
|
18333
|
+
"bridge_verify"
|
|
18334
|
+
],
|
|
18335
|
+
evidence_emitted: "Auto-filled: signed SHR + Ed25519-signed audit trail + Concordia bridge attestations (where used) give machine-readable provenance for every tool call the agent made during the reporting period. This enables output interpretation of the form 'this agent action came from these inputs at this time, cryptographically signed and independently verifiable.'",
|
|
18336
|
+
manual_carveout: "Business-facing explanation translating the cryptographic trail into user-comprehensible narrative, performance characteristics on specific populations, and intended-purpose-specific input specifications.",
|
|
18337
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18338
|
+
last_reviewed_by: REVIEWER,
|
|
18339
|
+
review_notes: "Sanctuary provides the cryptographic substrate for output provenance; enterprise renders it into user-facing disclosure text. Art. 13 is fundamentally a disclosure UX obligation that Sanctuary does not render."
|
|
18340
|
+
},
|
|
18341
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18342
|
+
// ARTICLE 14 — Human Oversight
|
|
18343
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18344
|
+
{
|
|
18345
|
+
id: "art_14_interpret_outputs",
|
|
18346
|
+
citation: {
|
|
18347
|
+
instrument: "Article",
|
|
18348
|
+
section: "14(4)(a)-(c)",
|
|
18349
|
+
clause_id: "art-14-4-a-c",
|
|
18350
|
+
title: "Oversight enables understanding and interpretation"
|
|
18351
|
+
},
|
|
18352
|
+
requirement: `"[The measures shall enable the persons to whom human oversight is assigned to]: (a) [...] properly understand the relevant capacities and limitations of the high-risk AI system [...]; (b) [...] remain aware of [...] automation bias [...]; (c) [...] correctly interpret the high-risk AI system's output [...]."`,
|
|
18353
|
+
coverage: "partial",
|
|
18354
|
+
evidence_emitter: ["shr_generate", "principal_policy_view", "manifest"],
|
|
18355
|
+
evidence_emitted: "Auto-filled: SHR is designed to be human-readable with explicit per-layer status flags and honest degradation declarations. Principal Policy is inspectable YAML that a human overseer can read directly. Tool inventory (via manifest) gives the oversight persons a complete list of what the agent can do.",
|
|
18356
|
+
manual_carveout: "Training materials for human overseers, the specific automation-bias awareness program, how oversight persons are trained to correctly interpret agent output, and any UI tools the enterprise provides to support oversight.",
|
|
18357
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18358
|
+
last_reviewed_by: REVIEWER,
|
|
18359
|
+
review_notes: "Sanctuary emits artefacts designed to be interpretable; the enterprise builds the training-and-support layer around them."
|
|
18360
|
+
},
|
|
18361
|
+
{
|
|
18362
|
+
id: "art_14_intervene_interrupt",
|
|
18363
|
+
citation: {
|
|
18364
|
+
instrument: "Article",
|
|
18365
|
+
section: "14(4)(e)",
|
|
18366
|
+
clause_id: "art-14-4-e",
|
|
18367
|
+
title: "Intervention, interruption, and stop button"
|
|
18368
|
+
},
|
|
18369
|
+
requirement: `"[The measures shall enable the persons to whom human oversight is assigned to]: (e) [...] intervene on the operation of the high-risk AI system or interrupt the system through a 'stop' button or similar procedure that allows the system to come to a halt in a safe state."`,
|
|
18370
|
+
coverage: "partial",
|
|
18371
|
+
evidence_emitter: ["principal_policy_view", "sovereignty_audit"],
|
|
18372
|
+
evidence_emitted: "Auto-filled: Principal Policy approval channel configuration (stderr / dashboard / webhook), Tier 1 require-approval rule count, denial-on-timeout behaviour, and gate decision semantics. These collectively document Sanctuary's pre-execution intervention capability \u2014 every Tier 1 tool call is halted pending human approval.",
|
|
18373
|
+
manual_carveout: "The enterprise's declared mapping between Sanctuary's approval channel and its Article 14(4)(e) stop-button workflow, the operational procedure for invoking the stop button, and acceptance that pre-execution gating satisfies the 'halt in a safe state' requirement for the specific agent.",
|
|
18374
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18375
|
+
last_reviewed_by: REVIEWER,
|
|
18376
|
+
review_notes: "Downgraded from full during audit (2026-04-10). Sanctuary's approval gate is a pre-execution intervention, not a mid-stream kill switch. Whether pre-execution gating satisfies Art. 14(4)(e) is an enterprise-declared operational judgement. Honest partial."
|
|
18377
|
+
},
|
|
18378
|
+
{
|
|
18379
|
+
id: "art_14_decide_not_to_use",
|
|
18380
|
+
citation: {
|
|
18381
|
+
instrument: "Article",
|
|
18382
|
+
section: "14(4)(d)",
|
|
18383
|
+
clause_id: "art-14-4-d",
|
|
18384
|
+
title: "Decide not to use or to disregard the output"
|
|
18385
|
+
},
|
|
18386
|
+
requirement: '"[The measures shall enable the persons to whom human oversight is assigned to]: (d) [...] decide, in any particular situation, not to use the high-risk AI system or to otherwise disregard, override or reverse the output of the high-risk AI system [...]."',
|
|
18387
|
+
coverage: "partial",
|
|
18388
|
+
evidence_emitter: ["principal_policy_view"],
|
|
18389
|
+
evidence_emitted: "Auto-filled: Principal Policy denial semantics \u2014 every Tier 1 operation defaults to deny on approval timeout, providing a structural 'decide not to use' control at the policy layer. Tier 1 tools include export, import, key rotation, and secure delete.",
|
|
18390
|
+
manual_carveout: "The enterprise's declared mapping between Sanctuary's deny semantics and its Art. 14(4)(d) decision-not-to-use workflow, and operator authority to invoke the decision.",
|
|
18391
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18392
|
+
last_reviewed_by: REVIEWER,
|
|
18393
|
+
review_notes: "Downgraded from full during audit (2026-04-10). Sanctuary provides the capability; enterprise provides the operational declaration."
|
|
18394
|
+
},
|
|
18395
|
+
{
|
|
18396
|
+
id: "art_14_operator_training",
|
|
18397
|
+
citation: {
|
|
18398
|
+
instrument: "Article",
|
|
18399
|
+
section: "14(4) chapeau",
|
|
18400
|
+
clause_id: "art-14-4-chapeau",
|
|
18401
|
+
title: "Operator competence, training, and authority"
|
|
18402
|
+
},
|
|
18403
|
+
requirement: '"[Human oversight measures shall be commensurate with the risks, level of autonomy and context of use of the high-risk AI system] [...] ensured through [...] the following types of measures, as appropriate to the circumstances [...]."',
|
|
18404
|
+
coverage: "manual_only",
|
|
18405
|
+
evidence_emitter: [],
|
|
18406
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: operator identities, roles, competence, training program, and authority to override].",
|
|
18407
|
+
manual_carveout: "People-and-process facts that Sanctuary cannot observe or emit.",
|
|
18408
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18409
|
+
last_reviewed_by: REVIEWER,
|
|
18410
|
+
review_notes: "Structural manual row \u2014 operator governance is an HR function."
|
|
18411
|
+
},
|
|
18412
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18413
|
+
// ARTICLE 15 — Accuracy, Robustness, Cybersecurity
|
|
18414
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18415
|
+
{
|
|
18416
|
+
id: "art_15_cybersecurity_appropriate",
|
|
18417
|
+
citation: {
|
|
18418
|
+
instrument: "Article",
|
|
18419
|
+
section: "15(5)",
|
|
18420
|
+
clause_id: "art-15-5",
|
|
18421
|
+
title: "Cybersecurity measures appropriate to the risks"
|
|
18422
|
+
},
|
|
18423
|
+
requirement: '"High-risk AI systems shall be resilient against attempts by unauthorised third parties to alter their use, outputs or performance by exploiting system vulnerabilities. The technical solutions aiming to ensure the cybersecurity of high-risk AI systems shall be appropriate to the relevant circumstances and the risks."',
|
|
18424
|
+
coverage: "partial",
|
|
18425
|
+
evidence_emitter: [
|
|
18426
|
+
"sovereignty_audit",
|
|
18427
|
+
"shr_generate",
|
|
18428
|
+
"principal_policy_view",
|
|
18429
|
+
"context_gate_list_policies"
|
|
18430
|
+
],
|
|
18431
|
+
evidence_emitted: "Auto-filled: the full cybersecurity measures inventory (see Annex IV \xA72(h) row for complete description). Sanctuary emits the list of measures with structured status flags.",
|
|
18432
|
+
manual_carveout: "Appropriateness assertion: the enterprise must declare that the Sanctuary-reported measures are appropriate to the specific risks of the deployment context (risk-matched narrative linking identified risks to selected controls).",
|
|
18433
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18434
|
+
last_reviewed_by: REVIEWER,
|
|
18435
|
+
review_notes: "Downgraded from full during audit (2026-04-10). Art. 15(5) requires measures 'appropriate to the risks' \u2014 the appropriateness claim is a risk-matched narrative the enterprise owns. The pure-description version survives as a full row at Annex IV \xA72(h); this row is the risk-adequacy half of the same content."
|
|
18436
|
+
},
|
|
18437
|
+
{
|
|
18438
|
+
id: "art_15_resilience_alteration",
|
|
18439
|
+
citation: {
|
|
18440
|
+
instrument: "Article",
|
|
18441
|
+
section: "15(5) first subparagraph",
|
|
18442
|
+
clause_id: "art-15-5-resilience",
|
|
18443
|
+
title: "Resilience against unauthorised third-party alteration"
|
|
18444
|
+
},
|
|
18445
|
+
requirement: '"High-risk AI systems shall be resilient against attempts by unauthorised third parties to alter their use, outputs or performance by exploiting system vulnerabilities."',
|
|
18446
|
+
coverage: "full",
|
|
18447
|
+
evidence_emitter: [
|
|
18448
|
+
"sovereignty_audit",
|
|
18449
|
+
"shr_generate",
|
|
18450
|
+
"monitor_health",
|
|
18451
|
+
"state_list",
|
|
18452
|
+
"principal_policy_view",
|
|
18453
|
+
"context_gate_enforcer_status"
|
|
18454
|
+
],
|
|
18455
|
+
evidence_emitted: "Resilience against unauthorised third-party alteration is enforced across multiple subsystems, each independently verifiable via an MCP tool call: (1) L1 state store \u2014 AES-256-GCM authenticated encryption with HKDF per-namespace keys, Merkle root per namespace, monotonic version counter per (namespace, key), and anti-rollback checks on every read; reported by monitor_health (state_integrity flag) and via version numbers returned by state_list. (2) L1 audit log \u2014 AES-256-GCM authenticated ciphertext persisted under an HKDF-derived audit-log key; confidentiality and integrity against third-party tampering (no signature-based non-repudiation \u2014 see review_notes). (3) L1 identity \u2014 Ed25519 self-custodied keypairs; signed identity operations (sign, rotate, verify) and signed SHR generation; reported by shr_generate signature block. (4) L2 execution gate \u2014 every tool call routed through router.ts -> ApprovalGate.evaluate() -> Principal Policy tier check before execution; no bypass path; reported by principal_policy_view and the audit log trail of gate_* entries. (5) L2 outbound context gating \u2014 per-provider field policies applied before any outbound call; reported by context_gate_enforcer_status.",
|
|
18456
|
+
manual_carveout: null,
|
|
18457
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18458
|
+
last_reviewed_by: REVIEWER,
|
|
18459
|
+
review_notes: "Verified against v0.7.0 source on 2026-04-10. MAJOR CORRECTIONS APPLIED from earlier drafts: (1) Earlier drafts claimed Merkle integrity on the audit log \u2014 that is wrong. Merkle trees exist on the state store (l1-cognitive/state-store.ts) but NOT on the audit log. (2) Earlier drafts claimed Ed25519 signatures on audit entries \u2014 that is wrong. The AuditEntry schema has no signature field; entries are AES-256-GCM ciphertext only. Ed25519 signatures exist on identity operations and SHR, not audit entries. (3) Earlier drafts claimed 'tamper-evident transcript' on the audit log \u2014 this is only true against third parties without the master key; there is no hash chain. The row still survives as full because Art. 15(5) specifies 'unauthorised third parties,' and AES-256-GCM with an HKDF-derived key protects against that threat model. Non-repudiation against a compromised-master-key insider is a different threat model not covered by Art. 15(5)."
|
|
18460
|
+
},
|
|
18461
|
+
{
|
|
18462
|
+
id: "art_15_resilience_poisoning",
|
|
18463
|
+
citation: {
|
|
18464
|
+
instrument: "Article",
|
|
18465
|
+
section: "15(5) second subparagraph",
|
|
18466
|
+
clause_id: "art-15-5-poisoning",
|
|
18467
|
+
title: "Resilience against data and model poisoning"
|
|
18468
|
+
},
|
|
18469
|
+
requirement: `"The technical solutions to address AI specific vulnerabilities shall include, where appropriate, measures to prevent, detect, respond to, resolve and control for attacks trying to manipulate the training data set ('data poisoning'), or pre-trained components used in training ('model poisoning'), inputs designed to cause the AI model to make a mistake ('adversarial examples' or 'model evasion'), [...]."`,
|
|
18470
|
+
coverage: "partial",
|
|
18471
|
+
evidence_emitter: ["monitor_audit_log", "audit_export_siem"],
|
|
18472
|
+
evidence_emitted: "Auto-filled: the Principal Policy gate runs a prompt injection detector pre-check on every tool call; when flagged, the gate appends 'injection_detected:*' entries to the audit log, which are visible via monitor_audit_log and exportable via audit_export_siem. The detector covers role override, security bypass, encoding evasion, data exfiltration, and prompt stuffing signals at runtime.",
|
|
18473
|
+
manual_carveout: "Training-time threats (data poisoning, model poisoning) are entirely outside Sanctuary's runtime scope \u2014 training data governance is the model provider's responsibility. The runtime adversarial-input detection is partial coverage of the 'adversarial examples' subset of Art. 15(5).",
|
|
18474
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18475
|
+
last_reviewed_by: REVIEWER,
|
|
18476
|
+
review_notes: "The injection detector exists (security/injection-detector.ts) and its activity IS evidenced via audit log entries, even though its configuration state is not directly queryable. Partial is honest: runtime adversarial-input detection is covered; training-time poisoning is not."
|
|
18477
|
+
},
|
|
18478
|
+
{
|
|
18479
|
+
id: "art_15_accuracy_metrics",
|
|
18480
|
+
citation: {
|
|
18481
|
+
instrument: "Article",
|
|
18482
|
+
section: "15(3)",
|
|
18483
|
+
clause_id: "art-15-3",
|
|
18484
|
+
title: "Declared accuracy levels and metrics"
|
|
18485
|
+
},
|
|
18486
|
+
requirement: '"The levels of accuracy and the relevant accuracy metrics of high-risk AI systems shall be declared in the accompanying instructions of use."',
|
|
18487
|
+
coverage: "manual_only",
|
|
18488
|
+
evidence_emitter: [],
|
|
18489
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: accuracy levels and metrics on the agent's business task].",
|
|
18490
|
+
manual_carveout: "Sanctuary does not measure agent task accuracy.",
|
|
18491
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18492
|
+
last_reviewed_by: REVIEWER,
|
|
18493
|
+
review_notes: "Structural manual row."
|
|
18494
|
+
},
|
|
18495
|
+
{
|
|
18496
|
+
id: "art_15_redundancy_failsafe",
|
|
18497
|
+
citation: {
|
|
18498
|
+
instrument: "Article",
|
|
18499
|
+
section: "15(4)",
|
|
18500
|
+
clause_id: "art-15-4",
|
|
18501
|
+
title: "Technical redundancy and fail-safe measures"
|
|
18502
|
+
},
|
|
18503
|
+
requirement: '"High-risk AI systems shall be as resilient as possible [...] The robustness of high-risk AI systems may be achieved through technical redundancy solutions, which may include backup or fail-safe plans."',
|
|
18504
|
+
coverage: "manual_only",
|
|
18505
|
+
evidence_emitter: [],
|
|
18506
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: technical redundancy and fail-safe plans for the deployment].",
|
|
18507
|
+
manual_carveout: "Redundancy architecture is an operational decision at the deployment level outside Sanctuary's scope.",
|
|
18508
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18509
|
+
last_reviewed_by: REVIEWER,
|
|
18510
|
+
review_notes: "Structural manual row."
|
|
18511
|
+
},
|
|
18512
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18513
|
+
// ARTICLE 26 — Deployer Obligations
|
|
18514
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
18515
|
+
{
|
|
18516
|
+
id: "art_26_per_instructions",
|
|
18517
|
+
citation: {
|
|
18518
|
+
instrument: "Article",
|
|
18519
|
+
section: "26(1)",
|
|
18520
|
+
clause_id: "art-26-1",
|
|
18521
|
+
title: "Use in accordance with instructions for use"
|
|
18522
|
+
},
|
|
18523
|
+
requirement: '"Deployers of high-risk AI systems shall take appropriate technical and organisational measures to ensure they use such systems in accordance with the instructions for use accompanying the systems [...]."',
|
|
18524
|
+
coverage: "manual_only",
|
|
18525
|
+
evidence_emitter: [],
|
|
18526
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: declaration of use in accordance with instructions].",
|
|
18527
|
+
manual_carveout: "Deployer-facing attestation, not a Sanctuary primitive.",
|
|
18528
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18529
|
+
last_reviewed_by: REVIEWER,
|
|
18530
|
+
review_notes: "Structural manual row."
|
|
18531
|
+
},
|
|
18532
|
+
{
|
|
18533
|
+
id: "art_26_human_oversight_assigned",
|
|
18534
|
+
citation: {
|
|
18535
|
+
instrument: "Article",
|
|
18536
|
+
section: "26(2)",
|
|
18537
|
+
clause_id: "art-26-2",
|
|
18538
|
+
title: "Assign human oversight to competent natural persons"
|
|
18539
|
+
},
|
|
18540
|
+
requirement: '"Deployers shall assign human oversight to natural persons who have the necessary competence, training and authority, as well as the necessary support."',
|
|
18541
|
+
coverage: "partial",
|
|
18542
|
+
evidence_emitter: ["principal_policy_view"],
|
|
18543
|
+
evidence_emitted: "Auto-filled: Sanctuary's approval channel configuration (stderr / dashboard / webhook) provides the technical substrate to which the enterprise binds its assigned human overseers. The Principal Policy Tier 1 rule list documents which operations require human approval.",
|
|
18544
|
+
manual_carveout: "Identity of assigned overseers, their competence assessment, training records, authority scope, and the support infrastructure provided to them.",
|
|
18545
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18546
|
+
last_reviewed_by: REVIEWER,
|
|
18547
|
+
review_notes: "Sanctuary provides the technical oversight surface; enterprise assigns the people."
|
|
18548
|
+
},
|
|
18549
|
+
{
|
|
18550
|
+
id: "art_26_input_data_relevance",
|
|
18551
|
+
citation: {
|
|
18552
|
+
instrument: "Article",
|
|
18553
|
+
section: "26(4)",
|
|
18554
|
+
clause_id: "art-26-4",
|
|
18555
|
+
title: "Input data relevance and representativeness"
|
|
18556
|
+
},
|
|
18557
|
+
requirement: '"[...] deployers shall ensure that input data is relevant and sufficiently representative in view of the intended purpose of the high-risk AI system."',
|
|
18558
|
+
coverage: "manual_only",
|
|
18559
|
+
evidence_emitter: [],
|
|
18560
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: declaration of input data relevance and representativeness].",
|
|
18561
|
+
manual_carveout: "Input data governance is a deployer responsibility outside Sanctuary's runtime scope.",
|
|
18562
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18563
|
+
last_reviewed_by: REVIEWER,
|
|
18564
|
+
review_notes: "Structural manual row."
|
|
18565
|
+
},
|
|
18566
|
+
{
|
|
18567
|
+
id: "art_26_monitor_and_inform",
|
|
18568
|
+
citation: {
|
|
18569
|
+
instrument: "Article",
|
|
18570
|
+
section: "26(5)",
|
|
18571
|
+
clause_id: "art-26-5",
|
|
18572
|
+
title: "Monitor operation and inform provider of incidents"
|
|
18573
|
+
},
|
|
18574
|
+
requirement: '"Deployers shall monitor the operation of the high-risk AI system on the basis of the instructions for use [...]. When deployers have reason to consider that the use in accordance with the instructions for use may result in that AI system presenting a risk [...] they shall, without undue delay, inform the provider [...] and suspend the use of that system."',
|
|
18575
|
+
coverage: "partial",
|
|
18576
|
+
evidence_emitter: [
|
|
18577
|
+
"monitor_audit_log",
|
|
18578
|
+
"audit_export_siem",
|
|
18579
|
+
"monitor_health",
|
|
18580
|
+
"principal_baseline_view"
|
|
18581
|
+
],
|
|
18582
|
+
evidence_emitted: "Auto-filled: the runtime monitoring substrate \u2014 encrypted audit log queryable and exportable in SIEM-standard formats, health dashboard, and anomaly baseline tracker. Provides the technical means to monitor and detect risk situations.",
|
|
18583
|
+
manual_carveout: "The enterprise's incident response workflow, the 'inform provider' communication channel and contacts, the suspension procedure, and the documented risk-detection criteria.",
|
|
18584
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18585
|
+
last_reviewed_by: REVIEWER,
|
|
18586
|
+
review_notes: "Sanctuary provides the detect-and-export half; enterprise provides the report-and-suspend half."
|
|
18587
|
+
},
|
|
18588
|
+
{
|
|
18589
|
+
id: "art_26_retain_logs",
|
|
18590
|
+
citation: {
|
|
18591
|
+
instrument: "Article",
|
|
18592
|
+
section: "26(6)",
|
|
18593
|
+
clause_id: "art-26-6",
|
|
18594
|
+
title: "Deployers keep logs for at least six months"
|
|
18595
|
+
},
|
|
18596
|
+
requirement: '"Deployers of high-risk AI systems shall keep the logs automatically generated by that high-risk AI system, to the extent such logs are under their control, for a period appropriate to the intended purpose of the high-risk AI system, of at least six months [...]."',
|
|
18597
|
+
coverage: "partial",
|
|
18598
|
+
evidence_emitter: ["monitor_audit_log", "audit_export_siem"],
|
|
18599
|
+
evidence_emitted: "Auto-filled: Sanctuary's audit log persists entries indefinitely by default and is exportable for archival at any time via audit_export_siem in CEF/OCSF formats suitable for long-term SIEM retention.",
|
|
18600
|
+
manual_carveout: "The enterprise's declared log retention policy, long-term archival pipeline, access control on archived logs, and written policy document satisfying the 'period appropriate to the intended purpose' requirement.",
|
|
18601
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18602
|
+
last_reviewed_by: REVIEWER,
|
|
18603
|
+
review_notes: "Downgraded from full during audit (2026-04-10). Sanctuary captures and exports; enterprise declares and archives. The retention policy is an enterprise governance artefact."
|
|
18604
|
+
},
|
|
18605
|
+
{
|
|
18606
|
+
id: "art_26_workers_representatives",
|
|
18607
|
+
citation: {
|
|
18608
|
+
instrument: "Article",
|
|
18609
|
+
section: "26(7)",
|
|
18610
|
+
clause_id: "art-26-7",
|
|
18611
|
+
title: "Inform workers and workers' representatives (employment)"
|
|
18612
|
+
},
|
|
18613
|
+
requirement: `"Before putting into service or using a high-risk AI system at the workplace, deployers who are employers shall inform workers' representatives and the affected workers that they will be subject to the use of the high-risk AI system."`,
|
|
18614
|
+
coverage: "manual_only",
|
|
18615
|
+
evidence_emitter: [],
|
|
18616
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: worker and workers' representative notification evidence].",
|
|
18617
|
+
manual_carveout: "Labour-relations obligation outside Sanctuary's scope.",
|
|
18618
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18619
|
+
last_reviewed_by: REVIEWER,
|
|
18620
|
+
review_notes: "Structural manual row. Applies only to employment-context deployments (Annex III \xA74)."
|
|
18621
|
+
},
|
|
18622
|
+
{
|
|
18623
|
+
id: "art_26_dpia",
|
|
18624
|
+
citation: {
|
|
18625
|
+
instrument: "Article",
|
|
18626
|
+
section: "26(9)",
|
|
18627
|
+
clause_id: "art-26-9",
|
|
18628
|
+
title: "Data protection impact assessment per GDPR Art. 35"
|
|
18629
|
+
},
|
|
18630
|
+
requirement: '"Where applicable, deployers of high-risk AI systems shall use the information provided under Article 13 of this Regulation to comply with their obligation to carry out a data protection impact assessment under Article 35 of Regulation (EU) 2016/679 [...]."',
|
|
18631
|
+
coverage: "manual_only",
|
|
18632
|
+
evidence_emitter: [],
|
|
18633
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: DPIA under GDPR Article 35, where applicable].",
|
|
18634
|
+
manual_carveout: "DPIA is a GDPR governance document. Sanctuary's transparency artefacts (SHR, audit log) may feed into a DPIA, but the assessment itself is enterprise-authored.",
|
|
18635
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18636
|
+
last_reviewed_by: REVIEWER,
|
|
18637
|
+
review_notes: "Structural manual row."
|
|
18638
|
+
},
|
|
18639
|
+
{
|
|
18640
|
+
id: "art_26_eu_database_registration",
|
|
18641
|
+
citation: {
|
|
18642
|
+
instrument: "Article",
|
|
18643
|
+
section: "26(8)",
|
|
18644
|
+
clause_id: "art-26-8",
|
|
18645
|
+
title: "Registration in EU database for Annex III systems"
|
|
18646
|
+
},
|
|
18647
|
+
requirement: '"Deployers of high-risk AI systems referred to in Annex III that [...] are public authorities, [...] or deployers acting on their behalf, shall register themselves, select the system and register its use in the EU database referred to in Article 71 [...]."',
|
|
18648
|
+
coverage: "manual_only",
|
|
18649
|
+
evidence_emitter: [],
|
|
18650
|
+
evidence_emitted: "[MANUAL INPUT REQUIRED: EU database registration under Article 71, where applicable].",
|
|
18651
|
+
manual_carveout: "Registration in the EU database is a legal procedural step the enterprise performs directly with the European Commission.",
|
|
18652
|
+
last_reviewed_date: REVIEW_DATE,
|
|
18653
|
+
last_reviewed_by: REVIEWER,
|
|
18654
|
+
review_notes: "Structural manual row. Applies only to public authorities and those acting on their behalf."
|
|
18655
|
+
}
|
|
18656
|
+
];
|
|
18657
|
+
var COVERAGE_MATRIX_V1 = {
|
|
18658
|
+
matrix_version: "v1",
|
|
18659
|
+
rows: ROWS
|
|
18660
|
+
};
|
|
18661
|
+
function rowsByCoverage(matrix, flag) {
|
|
18662
|
+
return matrix.rows.filter((r) => r.coverage === flag);
|
|
18663
|
+
}
|
|
18664
|
+
function coverageStats(matrix) {
|
|
18665
|
+
const total = matrix.rows.length;
|
|
18666
|
+
const full = rowsByCoverage(matrix, "full").length;
|
|
18667
|
+
const partial = rowsByCoverage(matrix, "partial").length;
|
|
18668
|
+
const manual = rowsByCoverage(matrix, "manual_only").length;
|
|
18669
|
+
return {
|
|
18670
|
+
total_rows: total,
|
|
18671
|
+
full,
|
|
18672
|
+
partial,
|
|
18673
|
+
manual_only: manual,
|
|
18674
|
+
full_pct: Math.round(full / total * 100),
|
|
18675
|
+
partial_pct: Math.round(partial / total * 100),
|
|
18676
|
+
manual_only_pct: Math.round(manual / total * 100)
|
|
18677
|
+
};
|
|
18678
|
+
}
|
|
18679
|
+
|
|
18680
|
+
// src/compliance/eu_ai_act/templates/render.ts
|
|
18681
|
+
var INTERPOLATION_RE = /\{\{\s*([a-z_][a-z0-9_]*)(?:\s*\|\s*([^}]*?))?\s*\}\}/gi;
|
|
18682
|
+
var MANUAL_INPUT_MARKER_PREFIX = "[MANUAL INPUT REQUIRED:";
|
|
18683
|
+
var MANUAL_INPUT_MARKER_SUFFIX = "]";
|
|
18684
|
+
function manualInputMarker(hint) {
|
|
18685
|
+
return `${MANUAL_INPUT_MARKER_PREFIX} ${hint}${MANUAL_INPUT_MARKER_SUFFIX}`;
|
|
18686
|
+
}
|
|
18687
|
+
function isEmpty(value) {
|
|
18688
|
+
return value === void 0 || value === null || value === "";
|
|
18689
|
+
}
|
|
18690
|
+
function render(template, context) {
|
|
18691
|
+
return template.replace(INTERPOLATION_RE, (_match, rawKey, rawHint) => {
|
|
18692
|
+
const key = String(rawKey).trim();
|
|
18693
|
+
const value = context[key];
|
|
18694
|
+
if (isEmpty(value)) {
|
|
18695
|
+
const hint = rawHint !== void 0 ? String(rawHint).trim() : key;
|
|
18696
|
+
return manualInputMarker(hint || key);
|
|
18697
|
+
}
|
|
18698
|
+
return String(value);
|
|
18699
|
+
});
|
|
18700
|
+
}
|
|
18701
|
+
|
|
18702
|
+
// src/compliance/eu_ai_act/templates/shared.ts
|
|
18703
|
+
var HEADER_TEMPLATE = `# {{ document_title }}
|
|
18704
|
+
|
|
18705
|
+
*{{ document_subtitle }}*
|
|
18706
|
+
|
|
18707
|
+
---
|
|
18708
|
+
|
|
18709
|
+
| Field | Value |
|
|
18710
|
+
|---|---|
|
|
18711
|
+
| **Regulation** | {{ regulation_version }} |
|
|
18712
|
+
| **Coverage matrix version** | {{ matrix_version }} |
|
|
18713
|
+
| **Bundle generated** | {{ generated_at }} |
|
|
18714
|
+
| **Reporting period** | {{ period_start }} \u2192 {{ period_end }} |
|
|
18715
|
+
| **Agent DID** | \`{{ agent_did }}\` |
|
|
18716
|
+
| **Legal provider** | {{ provider_legal_name }} |
|
|
18717
|
+
| **Provider contact** | {{ provider_contact }} |
|
|
18718
|
+
| **Intended purpose** | {{ intended_purpose }} |
|
|
18719
|
+
| **Annex III classification** | {{ annex_iii_class }} |
|
|
18720
|
+
| **Signer DID** | \`{{ signer_did }}\` |
|
|
18721
|
+
| **Signer public key (base64url)** | \`{{ signer_pubkey }}\` |
|
|
18722
|
+
|
|
18723
|
+
---
|
|
18724
|
+
|
|
18725
|
+
`;
|
|
18726
|
+
var FOOTER_TEMPLATE = `
|
|
18727
|
+
---
|
|
18728
|
+
|
|
18729
|
+
## Document Signature
|
|
18730
|
+
|
|
18731
|
+
This document is cryptographically signed by the provider's primary
|
|
18732
|
+
Ed25519 identity (DID \`{{ signer_did }}\`, public key
|
|
18733
|
+
\`{{ signer_pubkey }}\`). The signature for this document is recorded
|
|
18734
|
+
in the bundle manifest \`00_bundle_manifest.json\` under the entry
|
|
18735
|
+
with this filename, alongside its SHA-256 digest.
|
|
18736
|
+
|
|
18737
|
+
**Verification procedure:** compute the SHA-256 of this file's raw
|
|
18738
|
+
byte content, compare it against the \`sha256\` field for this file
|
|
18739
|
+
in the bundle manifest, then verify the \`signature\` field against
|
|
18740
|
+
the SHA-256 using the signer's public key with Ed25519. A successful
|
|
18741
|
+
check proves this document was emitted by the named Sanctuary
|
|
18742
|
+
instance and has not been altered since generation.
|
|
18743
|
+
|
|
18744
|
+
---
|
|
18745
|
+
|
|
18746
|
+
## Disclaimer
|
|
18747
|
+
|
|
18748
|
+
**This document is not legal advice.** It is a technical artifact
|
|
18749
|
+
generated by the Sanctuary Framework EU AI Act Compliance Artifact
|
|
18750
|
+
Generator. It is not a legal interpretation of Regulation (EU)
|
|
18751
|
+
2024/1689 and does not constitute a legal opinion. Consult qualified
|
|
18752
|
+
legal counsel before filing or relying on this document for
|
|
18753
|
+
regulatory submissions, self-assessment, or CE marking procedures.
|
|
18754
|
+
|
|
18755
|
+
The coverage claims in this document reflect the state of the
|
|
18756
|
+
Sanctuary Framework v{{ sanctuary_version }} runtime as of the
|
|
18757
|
+
generation timestamp above. The coverage matrix is versioned
|
|
18758
|
+
(\`{{ matrix_version }}\`) and aligned to the regulation text
|
|
18759
|
+
identified by \`{{ regulation_version }}\`; if the European Commission
|
|
18760
|
+
publishes implementing acts, delegated acts, or guidance that
|
|
18761
|
+
modifies the applicable requirements, this document must be
|
|
18762
|
+
regenerated against the updated matrix.
|
|
18763
|
+
|
|
18764
|
+
---
|
|
18765
|
+
|
|
18766
|
+
*Generated by [Sanctuary Framework](https://github.com/eriknewton/sanctuary-framework)
|
|
18767
|
+
v{{ sanctuary_version }} \xB7 Author: Erik Newton \xB7 License: Apache-2.0*
|
|
18768
|
+
`;
|
|
18769
|
+
var ROW_BLOCK_TEMPLATE = `### {{ row_citation }} \u2014 {{ row_title }}
|
|
18770
|
+
|
|
18771
|
+
**Coverage:** {{ row_coverage_badge }}
|
|
18772
|
+
|
|
18773
|
+
**Regulation text (verbatim, {{ regulation_version }}):**
|
|
18774
|
+
|
|
18775
|
+
> {{ row_requirement }}
|
|
18776
|
+
|
|
18777
|
+
**Evidence emitted by Sanctuary:**
|
|
18778
|
+
|
|
18779
|
+
{{ row_evidence_emitted }}
|
|
18780
|
+
|
|
18781
|
+
**Evidence emitter tools:**
|
|
18782
|
+
|
|
18783
|
+
{{ row_evidence_emitter_list }}
|
|
18784
|
+
|
|
18785
|
+
**Enterprise input required:**
|
|
18786
|
+
|
|
18787
|
+
{{ row_manual_carveout }}
|
|
18788
|
+
|
|
18789
|
+
---
|
|
18790
|
+
|
|
18791
|
+
`;
|
|
18792
|
+
function coverageBadge(coverage) {
|
|
18793
|
+
switch (coverage) {
|
|
18794
|
+
case "full":
|
|
18795
|
+
return "**FULL** \u2014 auto-emitted from Sanctuary, zero enterprise input required, machine-verifiable";
|
|
18796
|
+
case "partial":
|
|
18797
|
+
return "**PARTIAL** \u2014 Sanctuary emits structured evidence, enterprise supplies business context";
|
|
18798
|
+
case "manual_only":
|
|
18799
|
+
return "**MANUAL ONLY** \u2014 Sanctuary has no visibility, enterprise authors this section";
|
|
18800
|
+
}
|
|
18801
|
+
}
|
|
18802
|
+
function formatEmitterList(emitters) {
|
|
18803
|
+
if (emitters.length === 0) {
|
|
18804
|
+
return "_(none \u2014 this row is manual_only)_";
|
|
18805
|
+
}
|
|
18806
|
+
return emitters.map((e) => `- \`${e}\``).join("\n");
|
|
18807
|
+
}
|
|
18808
|
+
|
|
18809
|
+
// src/compliance/eu_ai_act/templates/01_annex_iv_technical_documentation.ts
|
|
18810
|
+
var ANNEX_IV_TECHNICAL_DOCUMENTATION_TEMPLATE = `${HEADER_TEMPLATE}
|
|
18811
|
+
## Introduction
|
|
18812
|
+
|
|
18813
|
+
This document is the Annex IV Technical Documentation for the
|
|
18814
|
+
high-risk AI system identified above, prepared under Article 11 of
|
|
18815
|
+
Regulation (EU) 2024/1689 (the "EU AI Act").
|
|
18816
|
+
|
|
18817
|
+
The document maps each numbered Annex IV section to the evidence
|
|
18818
|
+
that the Sanctuary Framework auto-emits for that section, and
|
|
18819
|
+
identifies exactly where enterprise-supplied business context is
|
|
18820
|
+
required. Sections marked **FULL** are machine-verifiable against
|
|
18821
|
+
the live Sanctuary instance that generated this bundle. Sections
|
|
18822
|
+
marked **PARTIAL** carry auto-filled structured evidence alongside
|
|
18823
|
+
explicit \`[MANUAL INPUT REQUIRED: ...]\` markers for enterprise
|
|
18824
|
+
completion. Sections marked **MANUAL ONLY** are outside Sanctuary's
|
|
18825
|
+
architectural scope and must be authored by the enterprise in full.
|
|
18826
|
+
|
|
18827
|
+
**How to use this document:**
|
|
18828
|
+
|
|
18829
|
+
1. Review each section below in order.
|
|
18830
|
+
2. For every \`[MANUAL INPUT REQUIRED: ...]\` marker, replace the
|
|
18831
|
+
marker with the relevant enterprise fact.
|
|
18832
|
+
3. Verify the auto-filled evidence against the live Sanctuary
|
|
18833
|
+
instance by running the listed \`evidence_emitter\` tools.
|
|
18834
|
+
4. Sign the completed document with the provider's legal signature
|
|
18835
|
+
(the Sanctuary cryptographic signature below is the runtime
|
|
18836
|
+
authenticity attestation, not a substitute for a signed legal
|
|
18837
|
+
declaration).
|
|
18838
|
+
|
|
18839
|
+
---
|
|
18840
|
+
|
|
18841
|
+
## \xA71 \u2014 General Description
|
|
18842
|
+
|
|
18843
|
+
{{ sections_1 }}
|
|
18844
|
+
|
|
18845
|
+
## \xA72 \u2014 Detailed Description of Elements
|
|
18846
|
+
|
|
18847
|
+
{{ sections_2 }}
|
|
18848
|
+
|
|
18849
|
+
## \xA73 \u2014 Monitoring, Functioning and Control
|
|
18850
|
+
|
|
18851
|
+
{{ sections_3 }}
|
|
18852
|
+
|
|
18853
|
+
## \xA74 \u2014 Performance Metrics
|
|
18854
|
+
|
|
18855
|
+
{{ sections_4 }}
|
|
18856
|
+
|
|
18857
|
+
## \xA75 \u2014 Risk Management System (Article 9)
|
|
18858
|
+
|
|
18859
|
+
{{ sections_5 }}
|
|
18860
|
+
|
|
18861
|
+
## \xA76 \u2014 Changes Through Lifecycle
|
|
18862
|
+
|
|
18863
|
+
{{ sections_6 }}
|
|
18864
|
+
|
|
18865
|
+
## \xA77 \u2014 Standards Applied
|
|
18866
|
+
|
|
18867
|
+
{{ sections_7 }}
|
|
18868
|
+
|
|
18869
|
+
## \xA78 \u2014 EU Declaration of Conformity
|
|
18870
|
+
|
|
18871
|
+
{{ sections_8 }}
|
|
18872
|
+
|
|
18873
|
+
## \xA79 \u2014 Post-Market Monitoring Plan (Article 72)
|
|
18874
|
+
|
|
18875
|
+
{{ sections_9 }}
|
|
18876
|
+
|
|
18877
|
+
${FOOTER_TEMPLATE}`;
|
|
18878
|
+
|
|
18879
|
+
// src/compliance/eu_ai_act/templates/02_article_26_deployer_log.ts
|
|
18880
|
+
var ARTICLE_26_DEPLOYER_LOG_TEMPLATE = `${HEADER_TEMPLATE}
|
|
18881
|
+
## Introduction
|
|
18882
|
+
|
|
18883
|
+
This document is the Article 26 Deployer Log for the high-risk AI
|
|
18884
|
+
system identified above, prepared in accordance with the deployer
|
|
18885
|
+
obligations of Article 26 of Regulation (EU) 2024/1689.
|
|
18886
|
+
|
|
18887
|
+
Unlike the Annex IV Technical Documentation (which is authored by
|
|
18888
|
+
the **provider**), this document is authored by the **deployer** \u2014
|
|
18889
|
+
the natural or legal person using the high-risk AI system under its
|
|
18890
|
+
authority. In many enterprise deployments the provider and deployer
|
|
18891
|
+
are the same legal entity; in others they are distinct. The
|
|
18892
|
+
\`{{ provider_legal_name }}\` field in the header identifies the
|
|
18893
|
+
entity acting as deployer for the purposes of this document.
|
|
18894
|
+
|
|
18895
|
+
The document maps each Article 26 obligation to the evidence that
|
|
18896
|
+
the Sanctuary Framework auto-emits, and identifies the operational
|
|
18897
|
+
and governance facts that only the deployer can supply.
|
|
18898
|
+
|
|
18899
|
+
---
|
|
18900
|
+
|
|
18901
|
+
## Deployer Operation Summary
|
|
18902
|
+
|
|
18903
|
+
During the reporting period **{{ period_start }} \u2192 {{ period_end }}**,
|
|
18904
|
+
the Sanctuary Framework runtime for this agent recorded the
|
|
18905
|
+
following aggregate operation metrics. These are auto-filled from
|
|
18906
|
+
the encrypted audit log via \`monitor_audit_log\` and
|
|
18907
|
+
\`audit_export_siem\`:
|
|
18908
|
+
|
|
18909
|
+
| Metric | Value |
|
|
18910
|
+
|---|---|
|
|
18911
|
+
| Total audit entries | {{ audit_total_entries }} |
|
|
18912
|
+
| L1 (Cognitive) entries | {{ audit_l1_count }} |
|
|
18913
|
+
| L2 (Operational) entries | {{ audit_l2_count }} |
|
|
18914
|
+
| L3 (Disclosure) entries | {{ audit_l3_count }} |
|
|
18915
|
+
| L4 (Reputation) entries | {{ audit_l4_count }} |
|
|
18916
|
+
| Gate decisions \u2014 allow | {{ gate_allow_count }} |
|
|
18917
|
+
| Gate decisions \u2014 allow_proxy | {{ gate_allow_proxy_count }} |
|
|
18918
|
+
| Gate decisions \u2014 deny | {{ gate_deny_count }} |
|
|
18919
|
+
| Gate decisions \u2014 escalate | {{ gate_escalate_count }} |
|
|
18920
|
+
| Gate decisions \u2014 unclassified | {{ gate_unclassified_count }} |
|
|
18921
|
+
| Injection-detection events | {{ injection_detected_count }} |
|
|
18922
|
+
| Unique identities involved | {{ unique_identity_count }} |
|
|
18923
|
+
| Unique operation types | {{ unique_operation_count }} |
|
|
18924
|
+
|
|
18925
|
+
Detailed event data is available in the companion file
|
|
18926
|
+
\`03_article_12_automatic_logs.md\` and its associated SIEM export.
|
|
18927
|
+
|
|
18928
|
+
---
|
|
18929
|
+
|
|
18930
|
+
## Article 26 Obligations \u2014 Row-by-Row
|
|
18931
|
+
|
|
18932
|
+
{{ rows_rendered }}
|
|
18933
|
+
|
|
18934
|
+
${FOOTER_TEMPLATE}`;
|
|
18935
|
+
|
|
18936
|
+
// src/compliance/eu_ai_act/templates/03_article_12_automatic_logs.ts
|
|
18937
|
+
var ARTICLE_12_AUTOMATIC_LOGS_TEMPLATE = `${HEADER_TEMPLATE}
|
|
18938
|
+
## Introduction
|
|
18939
|
+
|
|
18940
|
+
This document is the Article 12 Automatic Record-Keeping narrative
|
|
18941
|
+
for the high-risk AI system identified above, prepared in
|
|
18942
|
+
accordance with Article 12 and Article 19(1) of Regulation (EU)
|
|
18943
|
+
2024/1689.
|
|
18944
|
+
|
|
18945
|
+
The Sanctuary Framework provides structural automatic logging via
|
|
18946
|
+
its Principal Policy gate, which intercepts every MCP tool call
|
|
18947
|
+
before execution and appends an authenticated entry to the L2
|
|
18948
|
+
audit log. No code path exists to bypass this logging. The full
|
|
18949
|
+
implementation is described below; the raw audit export in
|
|
18950
|
+
SIEM-compatible format accompanies this document as a separate
|
|
18951
|
+
file.
|
|
18952
|
+
|
|
18953
|
+
---
|
|
18954
|
+
|
|
18955
|
+
## Logging Architecture Summary (Auto-Filled)
|
|
18956
|
+
|
|
18957
|
+
Sanctuary's Article 12 implementation has the following structural
|
|
18958
|
+
properties, each independently verifiable against the v{{ sanctuary_version }}
|
|
18959
|
+
source:
|
|
18960
|
+
|
|
18961
|
+
| Property | Mechanism | Verifiable via |
|
|
18962
|
+
|---|---|---|
|
|
18963
|
+
| Automatic capture on every tool call | \`router.ts\` wraps all tool invocations through \`ApprovalGate.evaluate()\`; the gate appends an audit entry on every outcome path (\`gate_allow\`, \`gate_allow_proxy\`, \`gate_deny\`, \`gate_escalate\`, \`gate_unclassified\`, \`injection_detected\`) | Source: \`server/src/router.ts\`, \`server/src/principal-policy/gate.ts\` |
|
|
18964
|
+
| Append-only API | The \`AuditLog\` class exposes only \`append()\` and read methods (\`query()\`, \`size\`). No update or delete method is exposed. | Source: \`server/src/l2-operational/audit-log.ts\` |
|
|
18965
|
+
| Confidentiality at rest | AES-256-GCM authenticated encryption with an HKDF-derived per-purpose key (\`audit-log\` purpose string) | Source: \`server/src/core/encryption.ts\`, \`server/src/core/key-derivation.ts\` |
|
|
18966
|
+
| SIEM-compatible export | CEF (Common Event Format, newline-delimited) and OCSF (Open Cybersecurity Schema Framework, JSON array) emitted by the \`audit_export_siem\` tool | Run: \`audit_export_siem\` with \`format: "cef"\` or \`format: "ocsf"\` |
|
|
18967
|
+
| Gate decision taxonomy | Every entry carries a structured operation string prefixed with \`gate_*\` or \`injection_detected:\`, directly filterable via the \`filter_decision\` and \`operation_type\` parameters | Run: \`audit_export_siem\` with \`filter_decision\` set |
|
|
18968
|
+
|
|
18969
|
+
---
|
|
18970
|
+
|
|
18971
|
+
## Reporting Period Summary (Auto-Filled)
|
|
18972
|
+
|
|
18973
|
+
| Field | Value |
|
|
18974
|
+
|---|---|
|
|
18975
|
+
| Period start | {{ period_start }} |
|
|
18976
|
+
| Period end | {{ period_end }} |
|
|
18977
|
+
| Total entries captured | {{ audit_total_entries }} |
|
|
18978
|
+
| L1 entries | {{ audit_l1_count }} |
|
|
18979
|
+
| L2 entries | {{ audit_l2_count }} |
|
|
18980
|
+
| L3 entries | {{ audit_l3_count }} |
|
|
18981
|
+
| L4 entries | {{ audit_l4_count }} |
|
|
18982
|
+
| Gate allow | {{ gate_allow_count }} |
|
|
18983
|
+
| Gate allow_proxy | {{ gate_allow_proxy_count }} |
|
|
18984
|
+
| Gate deny | {{ gate_deny_count }} |
|
|
18985
|
+
| Gate escalate | {{ gate_escalate_count }} |
|
|
18986
|
+
| Gate unclassified | {{ gate_unclassified_count }} |
|
|
18987
|
+
| Injection-detection events | {{ injection_detected_count }} |
|
|
18988
|
+
|
|
18989
|
+
**Full SIEM export:** see the accompanying file
|
|
18990
|
+
\`03_article_12_automatic_logs_siem.json\` in this bundle for the
|
|
18991
|
+
complete OCSF-formatted audit entries. Ingest into your SIEM
|
|
18992
|
+
(Splunk, Datadog, QRadar, or equivalent) for post-market monitoring
|
|
18993
|
+
workflows under Article 72.
|
|
18994
|
+
|
|
18995
|
+
---
|
|
18996
|
+
|
|
18997
|
+
## Article 12 Obligations \u2014 Row-by-Row
|
|
18998
|
+
|
|
18999
|
+
{{ rows_rendered }}
|
|
19000
|
+
|
|
19001
|
+
---
|
|
19002
|
+
|
|
19003
|
+
## Known Caveats and Residual Risk
|
|
19004
|
+
|
|
19005
|
+
The following caveats are disclosed honestly and are specific to
|
|
19006
|
+
Sanctuary Framework v{{ sanctuary_version }}:
|
|
19007
|
+
|
|
19008
|
+
1. **Audit log entries are not Ed25519-signed.** The authenticated
|
|
19009
|
+
encryption provides integrity against unauthorised third parties
|
|
19010
|
+
without the master key. It does not provide non-repudiation
|
|
19011
|
+
against a compromised-master-key insider, because the encryption
|
|
19012
|
+
is symmetric. If your threat model requires per-entry
|
|
19013
|
+
non-repudiation, supplement this logging with an external append-
|
|
19014
|
+
only log service (e.g., a separate SIEM with write-once storage).
|
|
19015
|
+
|
|
19016
|
+
2. **Persistence is fire-and-forget.** If disk write fails, the
|
|
19017
|
+
entry lives only in memory and is lost at process exit. This is
|
|
19018
|
+
a durability concern under Article 12(1)'s "over the lifetime of
|
|
19019
|
+
the system" clause. The mitigation is to ensure the Sanctuary
|
|
19020
|
+
storage path is on reliable storage and monitored for write
|
|
19021
|
+
failures.
|
|
19022
|
+
|
|
19023
|
+
3. **Retention policy is deployer-declared.** Sanctuary persists
|
|
19024
|
+
entries indefinitely by default but does not enforce the
|
|
19025
|
+
Article 19(1) six-month minimum retention. The deployer must
|
|
19026
|
+
configure archival to meet the statutory retention.
|
|
19027
|
+
|
|
19028
|
+
These caveats are documented for audit transparency and do not
|
|
19029
|
+
affect the Article 12(1) capability claim that the system
|
|
19030
|
+
"technically allows for the automatic recording of events over
|
|
19031
|
+
the lifetime of the system."
|
|
19032
|
+
|
|
19033
|
+
${FOOTER_TEMPLATE}`;
|
|
19034
|
+
|
|
19035
|
+
// src/compliance/eu_ai_act/templates/04_risk_management_summary.ts
|
|
19036
|
+
var RISK_MANAGEMENT_SUMMARY_TEMPLATE = `${HEADER_TEMPLATE}
|
|
19037
|
+
## Introduction
|
|
19038
|
+
|
|
19039
|
+
This document is the Risk Management Summary for the high-risk AI
|
|
19040
|
+
system identified above, prepared in accordance with Article 9 of
|
|
19041
|
+
Regulation (EU) 2024/1689. It aggregates the Sanctuary Framework
|
|
19042
|
+
evidence relevant to risk identification, risk analysis, risk
|
|
19043
|
+
treatment, and residual risk acceptance for the deployed agent.
|
|
19044
|
+
|
|
19045
|
+
This document does not replace the enterprise's overall risk
|
|
19046
|
+
management system documentation. It supplies the runtime-control
|
|
19047
|
+
half of that system \u2014 the mechanisms, policies, and monitoring
|
|
19048
|
+
data that Sanctuary automatically emits. The enterprise must wrap
|
|
19049
|
+
these mechanisms in an Article 9-compliant risk management
|
|
19050
|
+
framework with identified risks, residual risk analysis, risk
|
|
19051
|
+
treatment plans, and periodic review cadence.
|
|
19052
|
+
|
|
19053
|
+
---
|
|
19054
|
+
|
|
19055
|
+
## Runtime Control Inventory (Auto-Filled)
|
|
19056
|
+
|
|
19057
|
+
The following Sanctuary controls are active for this agent and
|
|
19058
|
+
collectively form the runtime risk-mitigation substrate referenced
|
|
19059
|
+
by Article 9:
|
|
19060
|
+
|
|
19061
|
+
### Principal Policy Gate (L2 Operational Isolation)
|
|
19062
|
+
|
|
19063
|
+
Every MCP tool call passes through the \`ApprovalGate.evaluate()\`
|
|
19064
|
+
method before execution. The gate applies the three-tier Principal
|
|
19065
|
+
Policy:
|
|
19066
|
+
|
|
19067
|
+
- **Tier 1 \u2014 Always require human approval:** {{ tier1_rule_count }} rules defined.
|
|
19068
|
+
Operations in this tier (state export/import, key rotation, secure
|
|
19069
|
+
delete, reputation import, and similar irreversible or sensitive
|
|
19070
|
+
actions) are blocked pending out-of-band approval via the
|
|
19071
|
+
configured channel (\`{{ approval_channel_type }}\`). Default behaviour
|
|
19072
|
+
on timeout: **deny**.
|
|
19073
|
+
- **Tier 2 \u2014 Anomaly-triggered approval:** {{ tier2_rule_count }} rules
|
|
19074
|
+
defined. Baseline tracker at \`principal_baseline_view\` monitors
|
|
19075
|
+
new namespaces, new counterparties, and frequency spikes. First-
|
|
19076
|
+
session policy: \`{{ first_session_policy }}\`.
|
|
19077
|
+
- **Tier 3 \u2014 Auto-allow with audit logging:** {{ tier3_rule_count }}
|
|
19078
|
+
tools listed. These are read-only or low-risk operations that
|
|
19079
|
+
proceed without approval but are still captured in the audit log.
|
|
19080
|
+
|
|
19081
|
+
### L2 Process Hardening
|
|
19082
|
+
|
|
19083
|
+
Runtime hardening of the Sanctuary process via seccomp/entitlement
|
|
19084
|
+
restrictions where supported by the host operating system.
|
|
19085
|
+
|
|
19086
|
+
### L2 Outbound Context Gating
|
|
19087
|
+
|
|
19088
|
+
Per-provider field-level policies applied to agent context before
|
|
19089
|
+
any outbound call. {{ context_gate_policy_count }} context gate
|
|
19090
|
+
policies are currently configured, enforced via the context gate
|
|
19091
|
+
enforcer (active: {{ context_gate_enforcer_active }}).
|
|
19092
|
+
|
|
19093
|
+
### Prompt Injection Detector
|
|
19094
|
+
|
|
19095
|
+
The \`InjectionDetector\` subsystem runs as a pre-check inside the
|
|
19096
|
+
Principal Policy gate on every tool call. Detection signals are
|
|
19097
|
+
written to the audit log with the prefix \`injection_detected:\` and
|
|
19098
|
+
are filterable via \`monitor_audit_log\` and \`audit_export_siem\`.
|
|
19099
|
+
During the reporting period {{ period_start }} \u2192 {{ period_end }},
|
|
19100
|
+
the detector flagged **{{ injection_detected_count }}** events.
|
|
19101
|
+
|
|
19102
|
+
### L3 Selective Disclosure
|
|
19103
|
+
|
|
19104
|
+
Pedersen commitments on Ristretto255, Schnorr proofs, and bit-
|
|
19105
|
+
decomposition range proofs. Allows the agent to prove claims about
|
|
19106
|
+
its data without revealing the underlying values \u2014 a core control
|
|
19107
|
+
for data minimisation under Article 10 and GDPR data minimisation
|
|
19108
|
+
obligations.
|
|
19109
|
+
|
|
19110
|
+
### L4 Verifiable Reputation
|
|
19111
|
+
|
|
19112
|
+
Ed25519-signed attestations in EAS-compatible format with
|
|
19113
|
+
sovereignty-gated trust tiers. Supports attestation-based trust
|
|
19114
|
+
decisions without requiring trust in any single attestor.
|
|
19115
|
+
|
|
19116
|
+
---
|
|
19117
|
+
|
|
19118
|
+
## Risk-Management-Relevant Coverage Rows
|
|
19119
|
+
|
|
19120
|
+
{{ rows_rendered }}
|
|
19121
|
+
|
|
19122
|
+
---
|
|
19123
|
+
|
|
19124
|
+
## Residual Risk and Mitigations
|
|
19125
|
+
|
|
19126
|
+
The following residual risks are disclosed honestly and are
|
|
19127
|
+
specific to Sanctuary Framework v{{ sanctuary_version }}:
|
|
19128
|
+
|
|
19129
|
+
- **No TEE attestation.** The runtime self-reports its environment
|
|
19130
|
+
type without a hardware root of trust. The SHR degradation flag
|
|
19131
|
+
\`NO_TEE\` is set automatically. Mitigation: deploy Sanctuary on
|
|
19132
|
+
TEE-capable hardware in production, or accept the process-level
|
|
19133
|
+
isolation boundary as sufficient for the deployment context.
|
|
19134
|
+
- **Training-time threats are out of scope.** Data poisoning,
|
|
19135
|
+
model poisoning, and backdoor injection at training time are
|
|
19136
|
+
outside Sanctuary's runtime scope. Mitigation: rely on the
|
|
19137
|
+
model provider's training-pipeline controls and declare the
|
|
19138
|
+
provider's governance in the Annex IV \xA72(d) manual section.
|
|
19139
|
+
- **Audit log retention is deployer-declared.** See the Article 12
|
|
19140
|
+
automatic logs document for details and mitigation.
|
|
19141
|
+
|
|
19142
|
+
---
|
|
19143
|
+
|
|
19144
|
+
## Enterprise-Supplied Risk Framework
|
|
19145
|
+
|
|
19146
|
+
The enterprise must supply the following to complete the Article 9
|
|
19147
|
+
risk management documentation. These are listed as
|
|
19148
|
+
\`[MANUAL INPUT REQUIRED: ...]\` markers throughout this document
|
|
19149
|
+
and the Annex IV \xA75 row below:
|
|
19150
|
+
|
|
19151
|
+
- Risk register for the specific deployment
|
|
19152
|
+
- Residual risk analysis and acceptance criteria
|
|
19153
|
+
- Risk treatment plan linking identified risks to the Sanctuary
|
|
19154
|
+
controls above (or compensating controls)
|
|
19155
|
+
- Periodic risk review cadence
|
|
19156
|
+
- Risk ownership and accountability structure
|
|
19157
|
+
|
|
19158
|
+
${FOOTER_TEMPLATE}`;
|
|
19159
|
+
|
|
19160
|
+
// src/compliance/eu_ai_act/templates/05_human_oversight_statement.ts
|
|
19161
|
+
var HUMAN_OVERSIGHT_STATEMENT_TEMPLATE = `${HEADER_TEMPLATE}
|
|
19162
|
+
## Introduction
|
|
19163
|
+
|
|
19164
|
+
This document is the Human Oversight Statement for the high-risk
|
|
19165
|
+
AI system identified above, prepared in accordance with Article 14
|
|
19166
|
+
of Regulation (EU) 2024/1689. It documents the technical substrate
|
|
19167
|
+
Sanctuary provides for human oversight and identifies the
|
|
19168
|
+
operational facts (operator roles, training, authority) that the
|
|
19169
|
+
enterprise must supply.
|
|
19170
|
+
|
|
19171
|
+
Sanctuary's human oversight substrate is the Principal Policy gate
|
|
19172
|
+
with its approval channel. Every operation classified as Tier 1
|
|
19173
|
+
blocks execution until a human principal approves or denies via
|
|
19174
|
+
the configured out-of-band channel. Every Tier 2 anomaly triggers
|
|
19175
|
+
the same approval path when baseline deviation is detected. Tier 3
|
|
19176
|
+
operations proceed automatically but are captured in the audit log
|
|
19177
|
+
for after-the-fact human review.
|
|
19178
|
+
|
|
19179
|
+
**Important scope note:** Sanctuary's intervention model is
|
|
19180
|
+
**pre-execution gating**, not mid-stream interruption. An approval
|
|
19181
|
+
denial halts the next tool call before it executes; it does not
|
|
19182
|
+
kill an in-flight LLM stream or subprocess. Whether this model
|
|
19183
|
+
satisfies Article 14(4)(e) ("intervene on the operation [...] or
|
|
19184
|
+
interrupt the system through a 'stop' button or similar procedure
|
|
19185
|
+
that allows the system to come to a halt in a safe state") is an
|
|
19186
|
+
operational judgement the enterprise must make for the specific
|
|
19187
|
+
deployment. The Sanctuary claim is: pre-execution gating provides
|
|
19188
|
+
a safe-state halt before the next consequential action; mid-stream
|
|
19189
|
+
interruption requires additional controls outside Sanctuary.
|
|
19190
|
+
|
|
19191
|
+
---
|
|
19192
|
+
|
|
19193
|
+
## Principal Policy Configuration (Auto-Filled)
|
|
19194
|
+
|
|
19195
|
+
| Field | Value |
|
|
19196
|
+
|---|---|
|
|
19197
|
+
| Approval channel type | \`{{ approval_channel_type }}\` |
|
|
19198
|
+
| Approval channel timeout | {{ approval_channel_timeout_seconds }} seconds |
|
|
19199
|
+
| On timeout | **deny** (SEC-002: \`auto_deny\` removed \u2014 timeout always denies) |
|
|
19200
|
+
| Tier 1 rule count (always require approval) | {{ tier1_rule_count }} |
|
|
19201
|
+
| Tier 2 anomaly rules | new_namespace_access: \`{{ tier2_new_namespace_access }}\`, new_counterparty: \`{{ tier2_new_counterparty }}\`, frequency_spike_multiplier: {{ tier2_frequency_spike_multiplier }}, first_session_policy: \`{{ tier2_first_session_policy }}\` |
|
|
19202
|
+
| Tier 3 auto-allow tool count | {{ tier3_rule_count }} |
|
|
19203
|
+
| Baseline tracker state | \`{{ baseline_tracker_state }}\` |
|
|
19204
|
+
|
|
19205
|
+
The full Principal Policy is machine-readable via
|
|
19206
|
+
\`principal_policy_view\`. The baseline tracker state is exposed via
|
|
19207
|
+
\`principal_baseline_view\`. Both tools are Tier 3 (read-only) and
|
|
19208
|
+
can be invoked by an auditor against a live Sanctuary instance to
|
|
19209
|
+
independently verify every value above.
|
|
19210
|
+
|
|
19211
|
+
---
|
|
19212
|
+
|
|
19213
|
+
## Reporting Period Oversight Activity (Auto-Filled)
|
|
19214
|
+
|
|
19215
|
+
During the reporting period **{{ period_start }} \u2192 {{ period_end }}**,
|
|
19216
|
+
the Sanctuary oversight gate recorded the following activity:
|
|
19217
|
+
|
|
19218
|
+
| Outcome | Count |
|
|
19219
|
+
|---|---|
|
|
19220
|
+
| Gate allow (Tier 3 auto-allow or approved Tier 1/2) | {{ gate_allow_count }} |
|
|
19221
|
+
| Gate allow_proxy (Cocoon MCP-proxy pass-through) | {{ gate_allow_proxy_count }} |
|
|
19222
|
+
| Gate deny (approval denied or timeout) | {{ gate_deny_count }} |
|
|
19223
|
+
| Gate escalate (Tier 2 anomaly raised for human review) | {{ gate_escalate_count }} |
|
|
19224
|
+
| Gate unclassified (no matching rule, default behaviour applied) | {{ gate_unclassified_count }} |
|
|
19225
|
+
| Injection-detection events | {{ injection_detected_count }} |
|
|
19226
|
+
|
|
19227
|
+
Individual entries are queryable via \`monitor_audit_log\` with
|
|
19228
|
+
\`layer: "l2"\` filter and exportable in SIEM-compatible format via
|
|
19229
|
+
\`audit_export_siem\`.
|
|
19230
|
+
|
|
19231
|
+
---
|
|
19232
|
+
|
|
19233
|
+
## Article 14 and Annex IV \xA72(e) Row Coverage
|
|
19234
|
+
|
|
19235
|
+
{{ rows_rendered }}
|
|
19236
|
+
|
|
19237
|
+
---
|
|
19238
|
+
|
|
19239
|
+
## Enterprise-Supplied Oversight Facts
|
|
19240
|
+
|
|
19241
|
+
The following human-oversight facts are the enterprise's
|
|
19242
|
+
responsibility and are not emitted by Sanctuary. They must be
|
|
19243
|
+
filled in before this document is used for regulatory submission:
|
|
19244
|
+
|
|
19245
|
+
- **Identity of assigned overseers:** who are the natural persons
|
|
19246
|
+
with Article 14 oversight authority for this deployment?
|
|
19247
|
+
- **Competence and training:** what training have they received,
|
|
19248
|
+
and how is their competence assessed?
|
|
19249
|
+
- **Authority scope:** what is the written authority of each
|
|
19250
|
+
overseer \u2014 can they halt the system, override outputs, reverse
|
|
19251
|
+
decisions?
|
|
19252
|
+
- **Automation-bias mitigation:** what training, interface
|
|
19253
|
+
design, or process measures address automation bias per Article
|
|
19254
|
+
14(4)(b)?
|
|
19255
|
+
- **Stop-button workflow mapping:** how does the Sanctuary approval
|
|
19256
|
+
channel integrate with the enterprise's operational stop-button
|
|
19257
|
+
procedure?
|
|
19258
|
+
- **Output interpretation support:** what tools, dashboards, or
|
|
19259
|
+
reference materials support the overseers in correctly
|
|
19260
|
+
interpreting the agent's outputs per Article 14(4)(c)?
|
|
19261
|
+
- **Escalation procedure:** when does an overseer escalate to a
|
|
19262
|
+
different authority (security, legal, C-suite)?
|
|
19263
|
+
|
|
19264
|
+
${FOOTER_TEMPLATE}`;
|
|
19265
|
+
|
|
19266
|
+
// src/compliance/eu_ai_act/templates/06_cryptographic_attestations.ts
|
|
19267
|
+
var CRYPTOGRAPHIC_ATTESTATIONS_TEMPLATE = `${HEADER_TEMPLATE}
|
|
19268
|
+
## Introduction
|
|
19269
|
+
|
|
19270
|
+
This document is the Cryptographic Attestations summary for the
|
|
19271
|
+
compliance bundle identified above. Every file in the bundle \u2014
|
|
19272
|
+
including this document \u2014 is individually hashed with SHA-256 and
|
|
19273
|
+
signed with the provider's primary Ed25519 identity. The entire
|
|
19274
|
+
bundle manifest is additionally signed as a single canonical
|
|
19275
|
+
document so an auditor can verify "this exact set of files, in
|
|
19276
|
+
this exact state, was attested by this identity at this exact
|
|
19277
|
+
timestamp" with a single signature check.
|
|
19278
|
+
|
|
19279
|
+
**Why this matters under Article 11 and Article 47:** the Annex IV
|
|
19280
|
+
technical documentation and the EU declaration of conformity are
|
|
19281
|
+
typically signed by the provider's legal representative. This
|
|
19282
|
+
document does not substitute for the legal signature \u2014 it provides
|
|
19283
|
+
the **runtime authenticity attestation** that proves the technical
|
|
19284
|
+
contents of the bundle were generated by the named Sanctuary
|
|
19285
|
+
instance and have not been altered since generation. The legal
|
|
19286
|
+
signature and the cryptographic attestation are complementary
|
|
19287
|
+
controls.
|
|
19288
|
+
|
|
19289
|
+
---
|
|
19290
|
+
|
|
19291
|
+
## Signer Identity
|
|
19292
|
+
|
|
19293
|
+
| Field | Value |
|
|
19294
|
+
|---|---|
|
|
19295
|
+
| Signer DID | \`{{ signer_did }}\` |
|
|
19296
|
+
| Signer public key (base64url) | \`{{ signer_pubkey }}\` |
|
|
19297
|
+
| Signer key type | Ed25519 |
|
|
19298
|
+
| Signer role | Provider primary identity |
|
|
19299
|
+
| Key custody | Self-custodied (L1 Cognitive Sovereignty) |
|
|
19300
|
+
|
|
19301
|
+
The signer identity is the provider's primary Ed25519 identity as
|
|
19302
|
+
reported by \`identity_set_primary\` on the generating Sanctuary
|
|
19303
|
+
instance. To verify any signature in this bundle, retrieve the
|
|
19304
|
+
public key above and check the base64url signature against the
|
|
19305
|
+
SHA-256 digest of the corresponding file using standard Ed25519
|
|
19306
|
+
verification.
|
|
19307
|
+
|
|
19308
|
+
---
|
|
19309
|
+
|
|
19310
|
+
## Bundle File Attestations
|
|
19311
|
+
|
|
19312
|
+
Each file in the bundle rendered **before** this attestations
|
|
19313
|
+
document is listed below with its SHA-256 digest and Ed25519
|
|
19314
|
+
signature. Recompute the SHA-256 of any file and verify the
|
|
19315
|
+
signature to confirm integrity.
|
|
19316
|
+
|
|
19317
|
+
**The complete signed bundle \u2014 including this attestations document
|
|
19318
|
+
and any content documents that follow it (such as
|
|
19319
|
+
\`07_annex_iii_classification.md\` if the bundle was generated with an
|
|
19320
|
+
intended purpose) \u2014 is authoritatively indexed in
|
|
19321
|
+
\`00_bundle_manifest.json\` with SHA-256 and Ed25519 signatures for
|
|
19322
|
+
every file.** Verification against the manifest is the canonical
|
|
19323
|
+
integrity check; the table below is a human-readable companion.
|
|
19324
|
+
|
|
19325
|
+
{{ file_attestation_table }}
|
|
19326
|
+
|
|
19327
|
+
---
|
|
19328
|
+
|
|
19329
|
+
## Manifest Signature
|
|
19330
|
+
|
|
19331
|
+
The complete bundle manifest (\`00_bundle_manifest.json\`) is
|
|
19332
|
+
canonically serialised (sorted keys, no whitespace) and signed as
|
|
19333
|
+
a single document. Verifying the manifest signature establishes
|
|
19334
|
+
authenticity of the entire bundle in one check.
|
|
19335
|
+
|
|
19336
|
+
| Field | Value |
|
|
19337
|
+
|---|---|
|
|
19338
|
+
| Manifest SHA-256 | \`{{ manifest_sha256 }}\` |
|
|
19339
|
+
| Manifest signature (base64url) | \`{{ manifest_signature }}\` |
|
|
19340
|
+
| Canonicalisation | Sorted keys, no whitespace (JSON canonical form) |
|
|
19341
|
+
|
|
19342
|
+
---
|
|
19343
|
+
|
|
19344
|
+
## Verification Procedure
|
|
19345
|
+
|
|
19346
|
+
To verify this bundle end-to-end:
|
|
19347
|
+
|
|
19348
|
+
1. **Verify each file individually.** For each file in the bundle:
|
|
19349
|
+
(a) read the file bytes, (b) compute SHA-256, (c) compare against
|
|
19350
|
+
the digest in the table above, (d) verify the Ed25519 signature
|
|
19351
|
+
against the SHA-256 digest using the signer's public key.
|
|
19352
|
+
2. **Verify the manifest.** Canonically serialise
|
|
19353
|
+
\`00_bundle_manifest.json\` (sorted keys, no whitespace), compute
|
|
19354
|
+
SHA-256, and verify the \`manifest_signature\` field against the
|
|
19355
|
+
signer's public key.
|
|
19356
|
+
3. **Cross-check the manifest against the files.** Ensure every
|
|
19357
|
+
file listed in the manifest exists in the bundle and every
|
|
19358
|
+
file in the bundle is listed in the manifest, with matching
|
|
19359
|
+
SHA-256 digests.
|
|
19360
|
+
4. **Verify the signer identity.** Confirm the signer DID and
|
|
19361
|
+
public key match the expected provider identity (for example,
|
|
19362
|
+
via a trusted identity registry or via direct confirmation
|
|
19363
|
+
with the provider).
|
|
19364
|
+
|
|
19365
|
+
If all four checks pass, the bundle is cryptographically authentic
|
|
19366
|
+
as-generated by the named Sanctuary instance. The cryptographic
|
|
19367
|
+
check does not establish the **legal validity** of the contents \u2014
|
|
19368
|
+
that remains the responsibility of the provider's legal signature
|
|
19369
|
+
and the applicable conformity assessment procedure under
|
|
19370
|
+
Regulation (EU) 2024/1689.
|
|
19371
|
+
|
|
19372
|
+
${FOOTER_TEMPLATE}`;
|
|
19373
|
+
|
|
19374
|
+
// src/compliance/eu_ai_act/templates/07_annex_iii_classification.ts
|
|
19375
|
+
var ANNEX_III_CLASSIFICATION_TEMPLATE = `${HEADER_TEMPLATE}
|
|
19376
|
+
## Introduction
|
|
19377
|
+
|
|
19378
|
+
This document reports the Annex III candidate classifications for
|
|
19379
|
+
the agent identified above, produced by the Sanctuary rule-based
|
|
19380
|
+
classification helper. It is **not** a legal determination \u2014 it is
|
|
19381
|
+
a keyword-weighted narrowing of the search space intended to help
|
|
19382
|
+
the enterprise reviewer focus on the relevant sections of Annex III
|
|
19383
|
+
of Regulation (EU) 2024/1689.
|
|
19384
|
+
|
|
19385
|
+
The classifier compares the agent's intended purpose against a
|
|
19386
|
+
structured catalog of the eight Annex III high-risk categories and
|
|
19387
|
+
their sub-points. Every keyword has a coarse weight (1.0 high, 0.6
|
|
19388
|
+
medium, 0.3 low); the sum is clamped to [0, 1] and reported as the
|
|
19389
|
+
**rule_based_confidence** field. The name is deliberately awkward so
|
|
19390
|
+
downstream consumers cannot mistake it for a machine-learning model
|
|
19391
|
+
prediction.
|
|
19392
|
+
|
|
19393
|
+
---
|
|
19394
|
+
|
|
19395
|
+
## Classified intended purpose (deployer-supplied)
|
|
19396
|
+
|
|
19397
|
+
> {{ classified_intended_purpose }}
|
|
19398
|
+
|
|
19399
|
+
---
|
|
19400
|
+
|
|
19401
|
+
## Candidate categories
|
|
19402
|
+
|
|
19403
|
+
{{ candidates_rendered }}
|
|
19404
|
+
|
|
19405
|
+
---
|
|
19406
|
+
|
|
19407
|
+
## Final classification determination
|
|
19408
|
+
|
|
19409
|
+
{{ final_classification | final Annex III category determined by legal review of the regulation text, including the category number and sub-point and a one-paragraph justification linking the agent's intended purpose to the verbatim regulation language }}
|
|
19410
|
+
|
|
19411
|
+
---
|
|
19412
|
+
|
|
19413
|
+
## Why the classifier is rule-based, not model-based
|
|
19414
|
+
|
|
19415
|
+
The classification helper uses a keyword-weighted rule-based scoring
|
|
19416
|
+
system with no trained model, no machine learning, and no probability
|
|
19417
|
+
distribution. The decision to use a rule-based classifier is
|
|
19418
|
+
deliberate for three reasons:
|
|
19419
|
+
|
|
19420
|
+
1. **Auditability.** Every match is traceable to a specific keyword
|
|
19421
|
+
in a checked-in catalog file (\`server/src/compliance/eu_ai_act/annex_iii.ts\`).
|
|
19422
|
+
An auditor can grep for the keyword and see why the category
|
|
19423
|
+
matched.
|
|
19424
|
+
|
|
19425
|
+
2. **No training data contamination.** A trained classifier would
|
|
19426
|
+
inherit whatever biases its training set contained, and the
|
|
19427
|
+
provenance of such a training set would itself become part of
|
|
19428
|
+
the compliance surface.
|
|
19429
|
+
|
|
19430
|
+
3. **Honest uncertainty.** A rule-based score of 0.9 means "nine
|
|
19431
|
+
weighted keywords matched" \u2014 a concrete, reproducible signal.
|
|
19432
|
+
A model prediction of 0.9 means "the model is confident" \u2014 a
|
|
19433
|
+
signal whose meaning depends on the model's calibration, which
|
|
19434
|
+
the deployer cannot independently verify.
|
|
19435
|
+
|
|
19436
|
+
If no candidate category cleared the minimum threshold (0.4), the
|
|
19437
|
+
"Candidate categories" section above is empty. **This does not mean
|
|
19438
|
+
the agent is out of scope of the EU AI Act.** It means the keyword
|
|
19439
|
+
catalog did not find a clear match against the deployer-supplied
|
|
19440
|
+
intended purpose. The deployer is still responsible for reviewing
|
|
19441
|
+
Annex III in full and determining whether any category applies.
|
|
19442
|
+
|
|
19443
|
+
---
|
|
19444
|
+
|
|
19445
|
+
## Advisory
|
|
19446
|
+
|
|
19447
|
+
{{ classifier_advisory }}
|
|
19448
|
+
|
|
19449
|
+
${FOOTER_TEMPLATE}`;
|
|
19450
|
+
|
|
19451
|
+
// src/compliance/eu_ai_act/templates/08_delta.ts
|
|
19452
|
+
var DELTA_TEMPLATE = `${HEADER_TEMPLATE}
|
|
19453
|
+
## Introduction
|
|
19454
|
+
|
|
19455
|
+
This document reports what changed in the Sanctuary EU AI Act
|
|
19456
|
+
coverage matrix between the **prior** bundle referenced below and
|
|
19457
|
+
the **current** bundle identified in the header table above.
|
|
19458
|
+
|
|
19459
|
+
A delta document is intended for enterprises that maintain a
|
|
19460
|
+
rolling compliance record \u2014 e.g., a quarterly or monthly
|
|
19461
|
+
regeneration cadence \u2014 and want to review only the changes since
|
|
19462
|
+
the last bundle without re-reading every document.
|
|
19463
|
+
|
|
19464
|
+
**The delta is computed from the two manifests, not from the
|
|
19465
|
+
document bodies.** Specifically, it compares:
|
|
19466
|
+
|
|
19467
|
+
1. The \`regulation_version\` field (external regulation text changes)
|
|
19468
|
+
2. The \`matrix_version\` field (structural matrix schema changes)
|
|
19469
|
+
3. The per-row \`coverage\` flag in \`coverage_rows[]\`
|
|
19470
|
+
4. The per-row \`evidence_emitter[]\` array
|
|
19471
|
+
|
|
19472
|
+
Row \`last_reviewed_date\` is compared when both bundles expose it
|
|
19473
|
+
in the coverage_rows summary. A delta row may reflect any
|
|
19474
|
+
combination of these changes \u2014 they are not mutually exclusive.
|
|
19475
|
+
|
|
19476
|
+
---
|
|
19477
|
+
|
|
19478
|
+
## Prior bundle reference
|
|
19479
|
+
|
|
19480
|
+
| Field | Value |
|
|
19481
|
+
|---|---|
|
|
19482
|
+
| **Path** | \`{{ prior_bundle_path }}\` |
|
|
19483
|
+
| **Generated at** | {{ prior_generated_at }} |
|
|
19484
|
+
| **Signer DID** | \`{{ prior_signer_did }}\` |
|
|
19485
|
+
| **Regulation version** | {{ prior_regulation_version }} |
|
|
19486
|
+
| **Matrix version** | \`{{ prior_matrix_version }}\` |
|
|
19487
|
+
|
|
19488
|
+
## Current bundle reference
|
|
19489
|
+
|
|
19490
|
+
| Field | Value |
|
|
19491
|
+
|---|---|
|
|
19492
|
+
| **Generated at** | {{ current_generated_at }} |
|
|
19493
|
+
| **Signer DID** | \`{{ current_signer_did }}\` |
|
|
19494
|
+
| **Regulation version** | {{ current_regulation_version }} |
|
|
19495
|
+
| **Matrix version** | \`{{ current_matrix_version }}\` |
|
|
19496
|
+
|
|
19497
|
+
---
|
|
19498
|
+
|
|
19499
|
+
## Summary
|
|
19500
|
+
|
|
19501
|
+
| Change category | Count |
|
|
19502
|
+
|---|---|
|
|
19503
|
+
| Regulation version bumped | {{ regulation_version_changed }} |
|
|
19504
|
+
| Matrix version bumped | {{ matrix_version_changed }} |
|
|
19505
|
+
| Rows added | {{ rows_added_count }} |
|
|
19506
|
+
| Rows removed | {{ rows_removed_count }} |
|
|
19507
|
+
| Rows changed | {{ rows_changed_count }} |
|
|
19508
|
+
|
|
19509
|
+
{{ no_changes_note }}
|
|
19510
|
+
|
|
19511
|
+
---
|
|
19512
|
+
|
|
19513
|
+
## Rows added
|
|
19514
|
+
|
|
19515
|
+
{{ rows_added_rendered }}
|
|
19516
|
+
|
|
19517
|
+
## Rows removed
|
|
19518
|
+
|
|
19519
|
+
{{ rows_removed_rendered }}
|
|
19520
|
+
|
|
19521
|
+
## Rows changed
|
|
19522
|
+
|
|
19523
|
+
{{ rows_changed_rendered }}
|
|
19524
|
+
|
|
19525
|
+
---
|
|
19526
|
+
|
|
19527
|
+
## Warnings
|
|
19528
|
+
|
|
19529
|
+
{{ warnings_rendered }}
|
|
19530
|
+
|
|
19531
|
+
---
|
|
19532
|
+
|
|
19533
|
+
## How to use this delta
|
|
19534
|
+
|
|
19535
|
+
1. If **regulation version bumped**, the coverage matrix has been
|
|
19536
|
+
re-aligned to updated regulation text (implementing acts,
|
|
19537
|
+
delegated acts, or OJ errata). Every \`full\` row should be
|
|
19538
|
+
re-verified against the new text before the current bundle is
|
|
19539
|
+
treated as authoritative.
|
|
19540
|
+
2. If **matrix version bumped**, the matrix schema changed (row
|
|
19541
|
+
set, coverage classification rules, or row structure). Review
|
|
19542
|
+
the v1 \u2192 v2 (or later) migration notes in the repository's
|
|
19543
|
+
\`docs/audit/\` directory.
|
|
19544
|
+
3. For each **row changed**, walk the \`review_notes\` field of the
|
|
19545
|
+
current coverage matrix entry for that row (available in
|
|
19546
|
+
\`docs/compliance/eu_ai_act_coverage_matrix_v1.md\`) \u2014 the reason
|
|
19547
|
+
for the change is recorded there.
|
|
19548
|
+
4. For **rows added** or **rows removed**, the matrix structure
|
|
19549
|
+
itself evolved. Treat these as matrix_version changes even if
|
|
19550
|
+
the matrix_version field happens not to have bumped.
|
|
19551
|
+
|
|
19552
|
+
This delta document is cryptographically signed into the current
|
|
19553
|
+
bundle's manifest, so an auditor can verify that the delta reflects
|
|
19554
|
+
the exact state claimed by the current bundle.
|
|
19555
|
+
|
|
19556
|
+
${FOOTER_TEMPLATE}`;
|
|
19557
|
+
|
|
19558
|
+
// src/compliance/eu_ai_act/annex_iii.ts
|
|
19559
|
+
var MIN_CONFIDENCE_THRESHOLD = 0.4;
|
|
19560
|
+
var W_HIGH = 1;
|
|
19561
|
+
var W_MED = 0.6;
|
|
19562
|
+
var W_LOW = 0.3;
|
|
19563
|
+
var ANNEX_III_CATEGORIES = [
|
|
19564
|
+
// §1 — Biometrics
|
|
19565
|
+
{
|
|
19566
|
+
id: "annex_iii_1_a_remote_biometric_identification",
|
|
19567
|
+
citation: "Annex III \xA71(a)",
|
|
19568
|
+
title: "Remote biometric identification systems",
|
|
19569
|
+
verbatim: '"Remote biometric identification systems [...] excluding AI systems [...] for biometric verification the sole purpose of which is to confirm that a specific natural person is the person he or she claims to be."',
|
|
19570
|
+
keywords: [
|
|
19571
|
+
{ term: "facial recognition", weight: W_HIGH },
|
|
19572
|
+
{ term: "biometric identification", weight: W_HIGH },
|
|
19573
|
+
{ term: "face recognition", weight: W_HIGH },
|
|
19574
|
+
{ term: "identify people", weight: W_MED },
|
|
19575
|
+
{ term: "identify individuals", weight: W_MED },
|
|
19576
|
+
{ term: "surveillance camera", weight: W_MED },
|
|
19577
|
+
{ term: "cctv", weight: W_LOW },
|
|
19578
|
+
{ term: "camera", weight: W_LOW },
|
|
19579
|
+
{ term: "biometric", weight: W_LOW }
|
|
19580
|
+
]
|
|
19581
|
+
},
|
|
19582
|
+
{
|
|
19583
|
+
id: "annex_iii_1_b_biometric_categorisation",
|
|
19584
|
+
citation: "Annex III \xA71(b)",
|
|
19585
|
+
title: "Biometric categorisation by sensitive attributes",
|
|
19586
|
+
verbatim: '"Biometric categorisation systems, according to sensitive or protected attributes or characteristics based on the inference of those attributes or characteristics."',
|
|
19587
|
+
keywords: [
|
|
19588
|
+
{ term: "biometric categorisation", weight: W_HIGH },
|
|
19589
|
+
{ term: "biometric categorization", weight: W_HIGH },
|
|
19590
|
+
{ term: "classify people by", weight: W_MED },
|
|
19591
|
+
{ term: "categorize individuals", weight: W_MED },
|
|
19592
|
+
{ term: "ethnicity", weight: W_MED },
|
|
19593
|
+
{ term: "gender classification", weight: W_MED },
|
|
19594
|
+
{ term: "age classification", weight: W_LOW },
|
|
19595
|
+
{ term: "sensitive attributes", weight: W_LOW }
|
|
19596
|
+
]
|
|
19597
|
+
},
|
|
19598
|
+
{
|
|
19599
|
+
id: "annex_iii_1_c_emotion_recognition",
|
|
19600
|
+
citation: "Annex III \xA71(c)",
|
|
19601
|
+
title: "Emotion recognition systems",
|
|
19602
|
+
verbatim: '"Emotion recognition systems."',
|
|
19603
|
+
keywords: [
|
|
19604
|
+
{ term: "emotion recognition", weight: W_HIGH },
|
|
19605
|
+
{ term: "emotion detection", weight: W_HIGH },
|
|
19606
|
+
{ term: "affect recognition", weight: W_HIGH },
|
|
19607
|
+
{ term: "sentiment analysis", weight: W_LOW },
|
|
19608
|
+
{ term: "facial expression", weight: W_MED },
|
|
19609
|
+
{ term: "mood detection", weight: W_MED }
|
|
19610
|
+
]
|
|
19611
|
+
},
|
|
19612
|
+
// §2 — Critical infrastructure
|
|
19613
|
+
{
|
|
19614
|
+
id: "annex_iii_2_critical_infrastructure",
|
|
19615
|
+
citation: "Annex III \xA72",
|
|
19616
|
+
title: "Critical infrastructure safety components",
|
|
19617
|
+
verbatim: '"AI systems intended to be used as safety components in the management and operation of critical digital infrastructure, road traffic, or in the supply of water, gas, heating or electricity."',
|
|
19618
|
+
keywords: [
|
|
19619
|
+
{ term: "critical infrastructure", weight: W_HIGH },
|
|
19620
|
+
{ term: "safety component", weight: W_HIGH },
|
|
19621
|
+
{ term: "power grid", weight: W_HIGH },
|
|
19622
|
+
{ term: "water supply", weight: W_HIGH },
|
|
19623
|
+
{ term: "gas supply", weight: W_HIGH },
|
|
19624
|
+
{ term: "electricity grid", weight: W_HIGH },
|
|
19625
|
+
{ term: "road traffic", weight: W_MED },
|
|
19626
|
+
{ term: "traffic management", weight: W_MED },
|
|
19627
|
+
{ term: "energy distribution", weight: W_MED },
|
|
19628
|
+
{ term: "scada", weight: W_MED },
|
|
19629
|
+
{ term: "utility network", weight: W_LOW }
|
|
19630
|
+
]
|
|
19631
|
+
},
|
|
19632
|
+
// §3 — Education
|
|
19633
|
+
{
|
|
19634
|
+
id: "annex_iii_3_a_education_admission",
|
|
19635
|
+
citation: "Annex III \xA73(a)",
|
|
19636
|
+
title: "Education \u2014 access and admission decisions",
|
|
19637
|
+
verbatim: '"AI systems intended to be used to determine access or admission or to assign natural persons to educational and vocational training institutions at all levels."',
|
|
19638
|
+
keywords: [
|
|
19639
|
+
{ term: "admissions", weight: W_HIGH },
|
|
19640
|
+
{ term: "admission decisions", weight: W_HIGH },
|
|
19641
|
+
{ term: "school admission", weight: W_HIGH },
|
|
19642
|
+
{ term: "university admission", weight: W_HIGH },
|
|
19643
|
+
{ term: "student selection", weight: W_MED },
|
|
19644
|
+
{ term: "enrollment", weight: W_MED },
|
|
19645
|
+
{ term: "enrolment", weight: W_MED },
|
|
19646
|
+
{ term: "applicant scoring education", weight: W_LOW }
|
|
19647
|
+
]
|
|
19648
|
+
},
|
|
19649
|
+
{
|
|
19650
|
+
id: "annex_iii_3_b_education_assessment",
|
|
19651
|
+
citation: "Annex III \xA73(b)",
|
|
19652
|
+
title: "Education \u2014 learning outcome evaluation",
|
|
19653
|
+
verbatim: '"AI systems intended to be used to evaluate learning outcomes [...] where those outcomes are used to steer the learning process of natural persons [...]."',
|
|
19654
|
+
keywords: [
|
|
19655
|
+
{ term: "grading", weight: W_HIGH },
|
|
19656
|
+
{ term: "grade students", weight: W_HIGH },
|
|
19657
|
+
{ term: "learning outcomes", weight: W_HIGH },
|
|
19658
|
+
{ term: "student assessment", weight: W_HIGH },
|
|
19659
|
+
{ term: "evaluate students", weight: W_MED },
|
|
19660
|
+
{ term: "evaluate learners", weight: W_MED },
|
|
19661
|
+
{ term: "educational assessment", weight: W_MED },
|
|
19662
|
+
{ term: "exam scoring", weight: W_MED }
|
|
19663
|
+
]
|
|
19664
|
+
},
|
|
19665
|
+
{
|
|
19666
|
+
id: "annex_iii_3_d_education_proctoring",
|
|
19667
|
+
citation: "Annex III \xA73(d)",
|
|
19668
|
+
title: "Education \u2014 test proctoring and behaviour monitoring",
|
|
19669
|
+
verbatim: '"AI systems intended to be used for monitoring and detecting prohibited behaviour of students during tests."',
|
|
19670
|
+
keywords: [
|
|
19671
|
+
{ term: "proctoring", weight: W_HIGH },
|
|
19672
|
+
{ term: "exam monitoring", weight: W_HIGH },
|
|
19673
|
+
{ term: "test proctoring", weight: W_HIGH },
|
|
19674
|
+
{ term: "cheating detection", weight: W_HIGH },
|
|
19675
|
+
{ term: "plagiarism detection", weight: W_MED },
|
|
19676
|
+
{ term: "monitor students", weight: W_MED }
|
|
19677
|
+
]
|
|
19678
|
+
},
|
|
19679
|
+
// §4 — Employment
|
|
19680
|
+
{
|
|
19681
|
+
id: "annex_iii_4_a_employment_recruitment",
|
|
19682
|
+
citation: "Annex III \xA74(a)",
|
|
19683
|
+
title: "Employment \u2014 recruitment and candidate evaluation",
|
|
19684
|
+
verbatim: '"AI systems intended to be used for the recruitment or selection of natural persons, in particular to place targeted job advertisements, to analyse and filter job applications, and to evaluate candidates."',
|
|
19685
|
+
keywords: [
|
|
19686
|
+
{ term: "cv screening", weight: W_HIGH },
|
|
19687
|
+
{ term: "resume screening", weight: W_HIGH },
|
|
19688
|
+
{ term: "candidate shortlist", weight: W_HIGH },
|
|
19689
|
+
{ term: "recruitment", weight: W_HIGH },
|
|
19690
|
+
{ term: "hiring decisions", weight: W_HIGH },
|
|
19691
|
+
{ term: "applicant filtering", weight: W_HIGH },
|
|
19692
|
+
{ term: "job application", weight: W_MED },
|
|
19693
|
+
{ term: "interview scoring", weight: W_MED },
|
|
19694
|
+
{ term: "targeted job ad", weight: W_MED },
|
|
19695
|
+
{ term: "hr screening", weight: W_MED },
|
|
19696
|
+
{ term: "talent acquisition", weight: W_LOW },
|
|
19697
|
+
{ term: "hiring", weight: W_LOW }
|
|
19698
|
+
]
|
|
19699
|
+
},
|
|
19700
|
+
{
|
|
19701
|
+
id: "annex_iii_4_b_employment_management",
|
|
19702
|
+
citation: "Annex III \xA74(b)",
|
|
19703
|
+
title: "Employment \u2014 performance management and termination",
|
|
19704
|
+
verbatim: '"AI systems intended to be used to make decisions affecting terms of work-related relationships, the promotion or termination of work-related contractual relationships, to allocate tasks [...] and to monitor and evaluate the performance and behaviour of persons in such relationships."',
|
|
19705
|
+
keywords: [
|
|
19706
|
+
{ term: "performance review", weight: W_HIGH },
|
|
19707
|
+
{ term: "performance evaluation", weight: W_HIGH },
|
|
19708
|
+
{ term: "termination decisions", weight: W_HIGH },
|
|
19709
|
+
{ term: "worker monitoring", weight: W_HIGH },
|
|
19710
|
+
{ term: "employee monitoring", weight: W_HIGH },
|
|
19711
|
+
{ term: "task allocation", weight: W_MED },
|
|
19712
|
+
{ term: "promotion decisions", weight: W_MED },
|
|
19713
|
+
{ term: "gig worker", weight: W_MED },
|
|
19714
|
+
{ term: "productivity tracking", weight: W_LOW }
|
|
19715
|
+
]
|
|
19716
|
+
},
|
|
19717
|
+
// §5 — Essential services
|
|
19718
|
+
{
|
|
19719
|
+
id: "annex_iii_5_a_public_benefits",
|
|
19720
|
+
citation: "Annex III \xA75(a)",
|
|
19721
|
+
title: "Essential services \u2014 public benefit eligibility",
|
|
19722
|
+
verbatim: '"AI systems intended to be used by public authorities or on behalf of public authorities to evaluate the eligibility of natural persons for essential public assistance benefits and services [...]."',
|
|
19723
|
+
keywords: [
|
|
19724
|
+
{ term: "welfare eligibility", weight: W_HIGH },
|
|
19725
|
+
{ term: "benefit eligibility", weight: W_HIGH },
|
|
19726
|
+
{ term: "public benefits", weight: W_HIGH },
|
|
19727
|
+
{ term: "social security", weight: W_HIGH },
|
|
19728
|
+
{ term: "public assistance", weight: W_HIGH },
|
|
19729
|
+
{ term: "housing benefit", weight: W_MED },
|
|
19730
|
+
{ term: "unemployment benefit", weight: W_MED }
|
|
19731
|
+
]
|
|
19732
|
+
},
|
|
19733
|
+
{
|
|
19734
|
+
id: "annex_iii_5_b_creditworthiness",
|
|
19735
|
+
citation: "Annex III \xA75(b)",
|
|
19736
|
+
title: "Essential services \u2014 creditworthiness and credit scoring",
|
|
19737
|
+
verbatim: '"AI systems intended to be used to evaluate the creditworthiness of natural persons or establish their credit score, with the exception of AI systems used for the purpose of detecting financial fraud."',
|
|
19738
|
+
keywords: [
|
|
19739
|
+
{ term: "credit scoring", weight: W_HIGH },
|
|
19740
|
+
{ term: "creditworthiness", weight: W_HIGH },
|
|
19741
|
+
{ term: "loan approval", weight: W_HIGH },
|
|
19742
|
+
{ term: "credit decisions", weight: W_HIGH },
|
|
19743
|
+
{ term: "credit risk", weight: W_HIGH },
|
|
19744
|
+
{ term: "lending decisions", weight: W_HIGH },
|
|
19745
|
+
{ term: "mortgage approval", weight: W_MED }
|
|
19746
|
+
]
|
|
19747
|
+
},
|
|
19748
|
+
{
|
|
19749
|
+
id: "annex_iii_5_c_insurance_pricing",
|
|
19750
|
+
citation: "Annex III \xA75(c)",
|
|
19751
|
+
title: "Essential services \u2014 insurance risk assessment and pricing",
|
|
19752
|
+
verbatim: '"AI systems intended to be used for risk assessment and pricing in relation to natural persons in the case of life and health insurance."',
|
|
19753
|
+
keywords: [
|
|
19754
|
+
{ term: "life insurance", weight: W_HIGH },
|
|
19755
|
+
{ term: "health insurance", weight: W_HIGH },
|
|
19756
|
+
{ term: "insurance pricing", weight: W_HIGH },
|
|
19757
|
+
{ term: "underwriting", weight: W_HIGH },
|
|
19758
|
+
{ term: "insurance risk", weight: W_HIGH },
|
|
19759
|
+
{ term: "actuarial", weight: W_MED }
|
|
19760
|
+
]
|
|
19761
|
+
},
|
|
19762
|
+
{
|
|
19763
|
+
id: "annex_iii_5_d_emergency_call_dispatch",
|
|
19764
|
+
citation: "Annex III \xA75(d)",
|
|
19765
|
+
title: "Essential services \u2014 emergency call triage and dispatch",
|
|
19766
|
+
verbatim: '"AI systems intended to be used to evaluate and classify emergency calls by natural persons or to be used to dispatch, or to establish priority in the dispatching of, emergency first response services [...]."',
|
|
19767
|
+
keywords: [
|
|
19768
|
+
{ term: "emergency dispatch", weight: W_HIGH },
|
|
19769
|
+
{ term: "emergency triage", weight: W_HIGH },
|
|
19770
|
+
{ term: "911 triage", weight: W_HIGH },
|
|
19771
|
+
{ term: "112 triage", weight: W_HIGH },
|
|
19772
|
+
{ term: "first responder dispatch", weight: W_HIGH },
|
|
19773
|
+
{ term: "ambulance dispatch", weight: W_MED }
|
|
19774
|
+
]
|
|
19775
|
+
},
|
|
19776
|
+
// §6 — Law enforcement
|
|
19777
|
+
{
|
|
19778
|
+
id: "annex_iii_6_law_enforcement",
|
|
19779
|
+
citation: "Annex III \xA76",
|
|
19780
|
+
title: "Law enforcement \u2014 risk assessment, polygraphs, profiling",
|
|
19781
|
+
verbatim: '"AI systems intended to be used by or on behalf of law enforcement authorities [...] to assess the risk of a natural person becoming the victim of criminal offences [...] as polygraphs or similar tools [...] to evaluate the reliability of evidence [...] to assess the risk of [...] offending or re-offending [...] for profiling of natural persons [...]."',
|
|
19782
|
+
keywords: [
|
|
19783
|
+
{ term: "predictive policing", weight: W_HIGH },
|
|
19784
|
+
{ term: "recidivism prediction", weight: W_HIGH },
|
|
19785
|
+
{ term: "reoffending risk", weight: W_HIGH },
|
|
19786
|
+
{ term: "law enforcement", weight: W_HIGH },
|
|
19787
|
+
{ term: "criminal profiling", weight: W_HIGH },
|
|
19788
|
+
{ term: "polygraph", weight: W_HIGH },
|
|
19789
|
+
{ term: "lie detector", weight: W_MED },
|
|
19790
|
+
{ term: "crime prediction", weight: W_MED },
|
|
19791
|
+
{ term: "parole decision", weight: W_MED },
|
|
19792
|
+
{ term: "evidence reliability", weight: W_LOW }
|
|
19793
|
+
]
|
|
19794
|
+
},
|
|
19795
|
+
// §7 — Migration, asylum, border control
|
|
19796
|
+
{
|
|
19797
|
+
id: "annex_iii_7_migration_border",
|
|
19798
|
+
citation: "Annex III \xA77",
|
|
19799
|
+
title: "Migration, asylum and border control",
|
|
19800
|
+
verbatim: '"AI systems intended to be used by or on behalf of competent public authorities [...] as polygraphs or similar tools; to assess a risk, including a security risk, a risk of irregular migration, or a health risk [...]; to assist [...] for the examination of applications for asylum, visa or residence permits [...]; for the purpose of detecting, recognising or identifying natural persons [...]."',
|
|
19801
|
+
keywords: [
|
|
19802
|
+
{ term: "asylum", weight: W_HIGH },
|
|
19803
|
+
{ term: "visa application", weight: W_HIGH },
|
|
19804
|
+
{ term: "residence permit", weight: W_HIGH },
|
|
19805
|
+
{ term: "border control", weight: W_HIGH },
|
|
19806
|
+
{ term: "migration risk", weight: W_HIGH },
|
|
19807
|
+
{ term: "immigration", weight: W_MED },
|
|
19808
|
+
{ term: "refugee processing", weight: W_MED }
|
|
19809
|
+
]
|
|
19810
|
+
},
|
|
19811
|
+
// §8 — Administration of justice and democratic processes
|
|
19812
|
+
{
|
|
19813
|
+
id: "annex_iii_8_a_administration_of_justice",
|
|
19814
|
+
citation: "Annex III \xA78(a)",
|
|
19815
|
+
title: "Administration of justice \u2014 assistance to judicial authorities",
|
|
19816
|
+
verbatim: '"AI systems intended to be used by a judicial authority or on their behalf to assist a judicial authority in researching and interpreting facts and the law and in applying the law to a concrete set of facts [...]."',
|
|
19817
|
+
keywords: [
|
|
19818
|
+
{ term: "judicial research", weight: W_HIGH },
|
|
19819
|
+
{ term: "legal research", weight: W_HIGH },
|
|
19820
|
+
{ term: "case law research", weight: W_HIGH },
|
|
19821
|
+
{ term: "judge assistant", weight: W_HIGH },
|
|
19822
|
+
{ term: "court decision support", weight: W_HIGH },
|
|
19823
|
+
{ term: "legal research assistant", weight: W_MED }
|
|
19824
|
+
]
|
|
19825
|
+
},
|
|
19826
|
+
{
|
|
19827
|
+
id: "annex_iii_8_b_democratic_processes",
|
|
19828
|
+
citation: "Annex III \xA78(b)",
|
|
19829
|
+
title: "Democratic processes \u2014 influencing elections and voting",
|
|
19830
|
+
verbatim: '"AI systems intended to be used for influencing the outcome of an election or referendum or the voting behaviour of natural persons [...]."',
|
|
19831
|
+
keywords: [
|
|
19832
|
+
{ term: "election campaign", weight: W_HIGH },
|
|
19833
|
+
{ term: "voter targeting", weight: W_HIGH },
|
|
19834
|
+
{ term: "political microtargeting", weight: W_HIGH },
|
|
19835
|
+
{ term: "referendum campaign", weight: W_HIGH },
|
|
19836
|
+
{ term: "voter persuasion", weight: W_HIGH },
|
|
19837
|
+
{ term: "election outcome", weight: W_MED }
|
|
19838
|
+
]
|
|
19839
|
+
}
|
|
19840
|
+
];
|
|
19841
|
+
var CLASSIFIER_ADVISORY = "This classification is a RULE-BASED keyword match, NOT a machine-learning model prediction, and NOT legal advice. The rule_based_confidence score reflects the sum of matched keyword weights, clamped to [0, 1]. Use this to narrow the deployer's review \u2014 the final Annex III classification determination must be made by a human reviewer against the full regulation text (Annex III of Regulation (EU) 2024/1689). An empty result means no category cleared the minimum threshold; it does NOT mean the agent is out of scope of the Act.";
|
|
19842
|
+
var ANNEX_III_REGULATION_VERSION = "EU AI Act Regulation (EU) 2024/1689, as of 2026-04-10";
|
|
19843
|
+
function normalizeForMatching(text) {
|
|
19844
|
+
return text.toLowerCase().replace(/[,.;:!?()\[\]{}"']/g, " ").replace(/\s+/g, " ").trim();
|
|
19845
|
+
}
|
|
19846
|
+
function classifyAgentDescription(description) {
|
|
19847
|
+
const normalized = normalizeForMatching(description);
|
|
19848
|
+
const candidates = [];
|
|
19849
|
+
for (const category of ANNEX_III_CATEGORIES) {
|
|
19850
|
+
let score = 0;
|
|
19851
|
+
const matched = [];
|
|
19852
|
+
for (const { term, weight } of category.keywords) {
|
|
19853
|
+
if (normalized.includes(term)) {
|
|
19854
|
+
score += weight;
|
|
19855
|
+
matched.push(term);
|
|
19856
|
+
}
|
|
19857
|
+
}
|
|
19858
|
+
if (score >= MIN_CONFIDENCE_THRESHOLD) {
|
|
19859
|
+
candidates.push({
|
|
19860
|
+
category_id: category.id,
|
|
19861
|
+
citation: category.citation,
|
|
19862
|
+
title: category.title,
|
|
19863
|
+
rule_based_confidence: Math.min(1, score),
|
|
19864
|
+
matched_keywords: matched
|
|
19865
|
+
});
|
|
19866
|
+
}
|
|
19867
|
+
}
|
|
19868
|
+
candidates.sort(
|
|
19869
|
+
(a, b) => b.rule_based_confidence - a.rule_based_confidence
|
|
19870
|
+
);
|
|
19871
|
+
const fragment = description.length > 200 ? description.slice(0, 200) + "..." : description;
|
|
19872
|
+
return {
|
|
19873
|
+
description_fragment: fragment,
|
|
19874
|
+
candidates,
|
|
19875
|
+
advisory: CLASSIFIER_ADVISORY,
|
|
19876
|
+
regulation_version: ANNEX_III_REGULATION_VERSION
|
|
19877
|
+
};
|
|
19878
|
+
}
|
|
19879
|
+
|
|
19880
|
+
// src/compliance/eu_ai_act/generator.ts
|
|
19881
|
+
function bytesToHex2(bytes) {
|
|
19882
|
+
let hex = "";
|
|
19883
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
19884
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
19885
|
+
}
|
|
19886
|
+
return hex;
|
|
19887
|
+
}
|
|
19888
|
+
var NOT_LEGAL_ADVICE_DISCLAIMER = "This bundle is a technical compliance artifact generated by the Sanctuary Framework EU AI Act Compliance Artifact Generator. It is NOT legal advice and does not constitute a legal interpretation of Regulation (EU) 2024/1689. Consult qualified legal counsel before filing or relying on this bundle for regulatory submissions.";
|
|
19889
|
+
function canonicalJSON(value) {
|
|
19890
|
+
return JSON.stringify(sortKeys(value));
|
|
19891
|
+
}
|
|
19892
|
+
function sortKeys(value) {
|
|
19893
|
+
if (value === null || typeof value !== "object") return value;
|
|
19894
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
19895
|
+
const sorted = {};
|
|
19896
|
+
const obj = value;
|
|
19897
|
+
for (const key of Object.keys(obj).sort()) {
|
|
19898
|
+
sorted[key] = sortKeys(obj[key]);
|
|
19899
|
+
}
|
|
19900
|
+
return sorted;
|
|
19901
|
+
}
|
|
19902
|
+
async function computeAuditPeriodSummary(auditLog, periodStart, periodEnd) {
|
|
19903
|
+
const { entries: rawEntries } = await auditLog.query({
|
|
19904
|
+
since: periodStart,
|
|
19905
|
+
limit: 1e6
|
|
19906
|
+
// effectively unbounded for this period
|
|
19907
|
+
});
|
|
19908
|
+
const endTime = new Date(periodEnd).getTime();
|
|
19909
|
+
const entries = rawEntries.filter(
|
|
19910
|
+
(e) => new Date(e.timestamp).getTime() <= endTime
|
|
19911
|
+
);
|
|
19912
|
+
const byLayer = {
|
|
19913
|
+
l1: 0,
|
|
19914
|
+
l2: 0,
|
|
19915
|
+
l3: 0,
|
|
19916
|
+
l4: 0
|
|
19917
|
+
};
|
|
19918
|
+
const gate = {
|
|
19919
|
+
allow: 0,
|
|
19920
|
+
allow_proxy: 0,
|
|
19921
|
+
deny: 0,
|
|
19922
|
+
escalate: 0,
|
|
19923
|
+
unclassified: 0
|
|
19924
|
+
};
|
|
19925
|
+
let injection = 0;
|
|
19926
|
+
const operationSet = /* @__PURE__ */ new Set();
|
|
19927
|
+
const identitySet = /* @__PURE__ */ new Set();
|
|
19928
|
+
for (const entry of entries) {
|
|
19929
|
+
byLayer[entry.layer]++;
|
|
19930
|
+
operationSet.add(entry.operation);
|
|
19931
|
+
identitySet.add(entry.identity_id);
|
|
19932
|
+
if (entry.operation.startsWith("gate_allow_proxy:")) {
|
|
19933
|
+
gate.allow_proxy++;
|
|
19934
|
+
} else if (entry.operation.startsWith("gate_allow:")) {
|
|
19935
|
+
gate.allow++;
|
|
19936
|
+
} else if (entry.operation.startsWith("gate_deny:")) {
|
|
19937
|
+
gate.deny++;
|
|
19938
|
+
} else if (entry.operation.startsWith("gate_escalate:")) {
|
|
19939
|
+
gate.escalate++;
|
|
19940
|
+
} else if (entry.operation.startsWith("gate_unclassified:")) {
|
|
19941
|
+
gate.unclassified++;
|
|
19942
|
+
} else if (entry.operation.startsWith("injection_detected:")) {
|
|
19943
|
+
injection++;
|
|
19944
|
+
}
|
|
19945
|
+
}
|
|
19946
|
+
return {
|
|
19947
|
+
period_start: periodStart,
|
|
19948
|
+
period_end: periodEnd,
|
|
19949
|
+
total_entries: entries.length,
|
|
19950
|
+
entries_by_layer: byLayer,
|
|
19951
|
+
gate_decisions: gate,
|
|
19952
|
+
injection_detected: injection,
|
|
19953
|
+
unique_operations: Array.from(operationSet).sort(),
|
|
19954
|
+
unique_identities: Array.from(identitySet).sort()
|
|
19955
|
+
};
|
|
19956
|
+
}
|
|
19957
|
+
function renderRowBlock(row) {
|
|
19958
|
+
const citation = `${row.citation.instrument} ${row.citation.section}`;
|
|
19959
|
+
return render(ROW_BLOCK_TEMPLATE, {
|
|
19960
|
+
row_citation: citation,
|
|
19961
|
+
row_title: row.citation.title,
|
|
19962
|
+
row_coverage_badge: coverageBadge(row.coverage),
|
|
19963
|
+
regulation_version: REGULATION_VERSION,
|
|
19964
|
+
row_requirement: row.requirement,
|
|
19965
|
+
row_evidence_emitted: row.evidence_emitted,
|
|
19966
|
+
row_evidence_emitter_list: formatEmitterList(row.evidence_emitter),
|
|
19967
|
+
row_manual_carveout: row.manual_carveout ?? "_(none \u2014 this row is fully auto-emitted)_"
|
|
19968
|
+
});
|
|
19969
|
+
}
|
|
19970
|
+
function renderRowBlocks(rows) {
|
|
19971
|
+
return rows.map(renderRowBlock).join("");
|
|
19972
|
+
}
|
|
19973
|
+
function rowsWithCitationPrefix(prefix) {
|
|
19974
|
+
return COVERAGE_MATRIX_V1.rows.filter(
|
|
19975
|
+
(r) => r.citation.clause_id.startsWith(prefix)
|
|
19976
|
+
);
|
|
19977
|
+
}
|
|
19978
|
+
function rowsById(ids) {
|
|
19979
|
+
const set = new Set(ids);
|
|
19980
|
+
return COVERAGE_MATRIX_V1.rows.filter((r) => set.has(r.id));
|
|
19981
|
+
}
|
|
19982
|
+
function buildCommonContext(input, deps, signer, auditSummary, generatedAt) {
|
|
19983
|
+
const dc = input.deployment_context;
|
|
19984
|
+
const policy = deps.policy;
|
|
19985
|
+
return {
|
|
19986
|
+
// Header fields
|
|
19987
|
+
document_title: "",
|
|
19988
|
+
// overridden per document
|
|
19989
|
+
document_subtitle: "",
|
|
19990
|
+
// overridden per document
|
|
19991
|
+
regulation_version: REGULATION_VERSION,
|
|
19992
|
+
matrix_version: COVERAGE_MATRIX_V1.matrix_version,
|
|
19993
|
+
generated_at: generatedAt,
|
|
19994
|
+
period_start: input.period_start,
|
|
19995
|
+
period_end: input.period_end,
|
|
19996
|
+
agent_did: input.agent_did,
|
|
19997
|
+
provider_legal_name: dc.provider_legal_name ?? "",
|
|
19998
|
+
provider_contact: dc.provider_contact ?? "",
|
|
19999
|
+
intended_purpose: dc.intended_purpose ?? "",
|
|
20000
|
+
annex_iii_class: dc.annex_iii_class ?? "",
|
|
20001
|
+
signer_did: signer.did,
|
|
20002
|
+
signer_pubkey: signer.public_key,
|
|
20003
|
+
sanctuary_version: deps.config.version,
|
|
20004
|
+
// Audit period stats
|
|
20005
|
+
audit_total_entries: auditSummary.total_entries,
|
|
20006
|
+
audit_l1_count: auditSummary.entries_by_layer.l1,
|
|
20007
|
+
audit_l2_count: auditSummary.entries_by_layer.l2,
|
|
20008
|
+
audit_l3_count: auditSummary.entries_by_layer.l3,
|
|
20009
|
+
audit_l4_count: auditSummary.entries_by_layer.l4,
|
|
20010
|
+
gate_allow_count: auditSummary.gate_decisions.allow,
|
|
20011
|
+
gate_allow_proxy_count: auditSummary.gate_decisions.allow_proxy,
|
|
20012
|
+
gate_deny_count: auditSummary.gate_decisions.deny,
|
|
20013
|
+
gate_escalate_count: auditSummary.gate_decisions.escalate,
|
|
20014
|
+
gate_unclassified_count: auditSummary.gate_decisions.unclassified,
|
|
20015
|
+
injection_detected_count: auditSummary.injection_detected,
|
|
20016
|
+
unique_identity_count: auditSummary.unique_identities.length,
|
|
20017
|
+
unique_operation_count: auditSummary.unique_operations.length,
|
|
20018
|
+
// Principal Policy fields
|
|
20019
|
+
tier1_rule_count: policy.tier1_always_approve.length,
|
|
20020
|
+
tier2_rule_count: Object.keys(policy.tier2_anomaly).length,
|
|
20021
|
+
// count of tier2 keys as rule count
|
|
20022
|
+
tier3_rule_count: policy.tier3_always_allow.length,
|
|
20023
|
+
tier2_new_namespace_access: policy.tier2_anomaly.new_namespace_access,
|
|
20024
|
+
tier2_new_counterparty: policy.tier2_anomaly.new_counterparty,
|
|
20025
|
+
tier2_frequency_spike_multiplier: policy.tier2_anomaly.frequency_spike_multiplier,
|
|
20026
|
+
tier2_first_session_policy: policy.tier2_anomaly.first_session_policy,
|
|
20027
|
+
first_session_policy: policy.tier2_anomaly.first_session_policy,
|
|
20028
|
+
approval_channel_type: policy.approval_channel.type,
|
|
20029
|
+
approval_channel_timeout_seconds: policy.approval_channel.timeout_seconds,
|
|
20030
|
+
baseline_tracker_state: "loaded",
|
|
20031
|
+
// static — baseline is always loaded at startup
|
|
20032
|
+
// Context gate placeholder (filled in by tool handler if available)
|
|
20033
|
+
context_gate_policy_count: 0,
|
|
20034
|
+
context_gate_enforcer_active: "unknown"
|
|
20035
|
+
};
|
|
20036
|
+
}
|
|
20037
|
+
function makeSigner(signer, masterKey) {
|
|
20038
|
+
const encryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
20039
|
+
return {
|
|
20040
|
+
signContent: (content) => {
|
|
20041
|
+
const digest = hash(stringToBytes(content));
|
|
20042
|
+
const sig = sign(digest, signer.encrypted_private_key, encryptionKey);
|
|
20043
|
+
return toBase64url(sig);
|
|
20044
|
+
},
|
|
20045
|
+
sha256Hex: (content) => bytesToHex2(hash(stringToBytes(content)))
|
|
20046
|
+
};
|
|
20047
|
+
}
|
|
20048
|
+
function finaliseDocument(template, context, filename, ds) {
|
|
20049
|
+
const content = render(template, context);
|
|
20050
|
+
const sha256Hex2 = ds.sha256Hex(content);
|
|
20051
|
+
const signature = ds.signContent(content);
|
|
20052
|
+
return {
|
|
20053
|
+
filename,
|
|
20054
|
+
content,
|
|
20055
|
+
content_type: "text/markdown",
|
|
20056
|
+
sha256: sha256Hex2,
|
|
20057
|
+
signature
|
|
20058
|
+
};
|
|
20059
|
+
}
|
|
20060
|
+
function renderAnnexIV(baseContext, ds) {
|
|
20061
|
+
const sections = {};
|
|
20062
|
+
for (let n = 1; n <= 9; n++) {
|
|
20063
|
+
const sectionRows = rowsWithCitationPrefix(`annex-iv-${n}`);
|
|
20064
|
+
sections[`sections_${n}`] = sectionRows.length > 0 ? renderRowBlocks(sectionRows) : "_(no rows in this section)_\n\n";
|
|
20065
|
+
}
|
|
20066
|
+
const context = {
|
|
20067
|
+
...baseContext,
|
|
20068
|
+
...sections,
|
|
20069
|
+
document_title: "Annex IV Technical Documentation",
|
|
20070
|
+
document_subtitle: "Prepared under Article 11 of Regulation (EU) 2024/1689"
|
|
20071
|
+
};
|
|
20072
|
+
return finaliseDocument(
|
|
20073
|
+
ANNEX_IV_TECHNICAL_DOCUMENTATION_TEMPLATE,
|
|
20074
|
+
context,
|
|
20075
|
+
"01_annex_iv_technical_documentation.md",
|
|
20076
|
+
ds
|
|
20077
|
+
);
|
|
20078
|
+
}
|
|
20079
|
+
function renderArticle26(baseContext, ds) {
|
|
20080
|
+
const rows = rowsWithCitationPrefix("art-26");
|
|
20081
|
+
const context = {
|
|
20082
|
+
...baseContext,
|
|
20083
|
+
rows_rendered: renderRowBlocks(rows),
|
|
20084
|
+
document_title: "Article 26 Deployer Log",
|
|
20085
|
+
document_subtitle: "Deployer obligations under Article 26 of Regulation (EU) 2024/1689"
|
|
20086
|
+
};
|
|
20087
|
+
return finaliseDocument(
|
|
20088
|
+
ARTICLE_26_DEPLOYER_LOG_TEMPLATE,
|
|
20089
|
+
context,
|
|
20090
|
+
"02_article_26_deployer_log.md",
|
|
20091
|
+
ds
|
|
20092
|
+
);
|
|
20093
|
+
}
|
|
20094
|
+
function renderArticle12(baseContext, ds) {
|
|
20095
|
+
const rows = [
|
|
20096
|
+
...rowsWithCitationPrefix("art-12"),
|
|
20097
|
+
...rowsWithCitationPrefix("art-19")
|
|
20098
|
+
];
|
|
20099
|
+
const context = {
|
|
20100
|
+
...baseContext,
|
|
20101
|
+
rows_rendered: renderRowBlocks(rows),
|
|
20102
|
+
document_title: "Article 12 Automatic Record-Keeping",
|
|
20103
|
+
document_subtitle: "Automatic logging and retention under Articles 12 and 19(1) of Regulation (EU) 2024/1689"
|
|
20104
|
+
};
|
|
20105
|
+
return finaliseDocument(
|
|
20106
|
+
ARTICLE_12_AUTOMATIC_LOGS_TEMPLATE,
|
|
20107
|
+
context,
|
|
20108
|
+
"03_article_12_automatic_logs.md",
|
|
20109
|
+
ds
|
|
20110
|
+
);
|
|
20111
|
+
}
|
|
20112
|
+
function renderRiskManagement(baseContext, ds) {
|
|
20113
|
+
const rows = rowsById([
|
|
20114
|
+
"annex_iv_5_risk_management",
|
|
20115
|
+
"annex_iv_2_h_cybersecurity",
|
|
20116
|
+
"annex_iv_2_b_design_specifications",
|
|
20117
|
+
"art_15_cybersecurity_appropriate",
|
|
20118
|
+
"art_15_resilience_alteration",
|
|
20119
|
+
"art_15_resilience_poisoning"
|
|
20120
|
+
]);
|
|
20121
|
+
const context = {
|
|
20122
|
+
...baseContext,
|
|
20123
|
+
rows_rendered: renderRowBlocks(rows),
|
|
20124
|
+
document_title: "Risk Management Summary",
|
|
20125
|
+
document_subtitle: "Risk management posture under Article 9 of Regulation (EU) 2024/1689"
|
|
20126
|
+
};
|
|
20127
|
+
return finaliseDocument(
|
|
20128
|
+
RISK_MANAGEMENT_SUMMARY_TEMPLATE,
|
|
20129
|
+
context,
|
|
20130
|
+
"04_risk_management_summary.md",
|
|
20131
|
+
ds
|
|
20132
|
+
);
|
|
20133
|
+
}
|
|
20134
|
+
function formatClassificationCandidates(result) {
|
|
20135
|
+
if (result.candidates.length === 0) {
|
|
20136
|
+
return "_No candidate category cleared the minimum rule-based confidence threshold. This does **not** mean the agent is out of scope of the EU AI Act \u2014 the deployer must still review Annex III in full._";
|
|
20137
|
+
}
|
|
20138
|
+
return result.candidates.map((c, i) => {
|
|
20139
|
+
const confPct = (c.rule_based_confidence * 100).toFixed(0);
|
|
20140
|
+
const kwList = c.matched_keywords.length === 0 ? "_(none)_" : c.matched_keywords.map((k) => `\`${k}\``).join(", ");
|
|
20141
|
+
return [
|
|
20142
|
+
`#### Candidate ${i + 1}: ${c.citation} \u2014 ${c.title}`,
|
|
20143
|
+
"",
|
|
20144
|
+
`- **Category ID:** \`${c.category_id}\``,
|
|
20145
|
+
`- **rule_based_confidence:** ${c.rule_based_confidence.toFixed(2)} (${confPct}%)`,
|
|
20146
|
+
`- **Matched keywords:** ${kwList}`,
|
|
20147
|
+
""
|
|
20148
|
+
].join("\n");
|
|
20149
|
+
}).join("\n");
|
|
20150
|
+
}
|
|
20151
|
+
function renderAnnexIIIClassification(baseContext, intendedPurpose, ds) {
|
|
20152
|
+
const classification = intendedPurpose ? classifyAgentDescription(intendedPurpose) : null;
|
|
20153
|
+
const context = {
|
|
20154
|
+
...baseContext,
|
|
20155
|
+
classified_intended_purpose: intendedPurpose || "_(no intended purpose supplied in deployment_context; the classifier was not run)_",
|
|
20156
|
+
candidates_rendered: classification ? formatClassificationCandidates(classification) : "_(classifier not run \u2014 no intended purpose supplied)_",
|
|
20157
|
+
classifier_advisory: classification ? classification.advisory : "The classifier was not invoked because the deployment_context.intended_purpose field was empty. The enterprise must perform manual Annex III review.",
|
|
20158
|
+
document_title: "Annex III Classification Candidates",
|
|
20159
|
+
document_subtitle: "Rule-based candidate classifications for Annex III of Regulation (EU) 2024/1689"
|
|
20160
|
+
};
|
|
20161
|
+
return finaliseDocument(
|
|
20162
|
+
ANNEX_III_CLASSIFICATION_TEMPLATE,
|
|
20163
|
+
context,
|
|
20164
|
+
"07_annex_iii_classification.md",
|
|
20165
|
+
ds
|
|
20166
|
+
);
|
|
20167
|
+
}
|
|
20168
|
+
function renderHumanOversight(baseContext, ds) {
|
|
20169
|
+
const rows = [
|
|
20170
|
+
...rowsWithCitationPrefix("art-14"),
|
|
20171
|
+
...rowsById(["annex_iv_2_e_human_oversight_assessment"])
|
|
20172
|
+
];
|
|
20173
|
+
const context = {
|
|
20174
|
+
...baseContext,
|
|
20175
|
+
rows_rendered: renderRowBlocks(rows),
|
|
20176
|
+
document_title: "Human Oversight Statement",
|
|
20177
|
+
document_subtitle: "Human oversight measures under Article 14 of Regulation (EU) 2024/1689"
|
|
20178
|
+
};
|
|
20179
|
+
return finaliseDocument(
|
|
20180
|
+
HUMAN_OVERSIGHT_STATEMENT_TEMPLATE,
|
|
20181
|
+
context,
|
|
20182
|
+
"05_human_oversight_statement.md",
|
|
20183
|
+
ds
|
|
20184
|
+
);
|
|
20185
|
+
}
|
|
20186
|
+
function renderAttestations(baseContext, priorFiles, manifestSha256, manifestSignature, ds) {
|
|
20187
|
+
const table = [
|
|
20188
|
+
"| Filename | SHA-256 | Signature (base64url) |",
|
|
20189
|
+
"|---|---|---|",
|
|
20190
|
+
...priorFiles.map(
|
|
20191
|
+
(f) => `| \`${f.filename}\` | \`${f.sha256}\` | \`${f.signature ?? ""}\` |`
|
|
20192
|
+
)
|
|
20193
|
+
].join("\n");
|
|
20194
|
+
const context = {
|
|
20195
|
+
...baseContext,
|
|
20196
|
+
file_attestation_table: table,
|
|
20197
|
+
manifest_sha256: manifestSha256,
|
|
20198
|
+
manifest_signature: manifestSignature,
|
|
20199
|
+
document_title: "Cryptographic Attestations",
|
|
20200
|
+
document_subtitle: "Bundle integrity attestations signed by the provider's primary Ed25519 identity"
|
|
20201
|
+
};
|
|
20202
|
+
return finaliseDocument(
|
|
20203
|
+
CRYPTOGRAPHIC_ATTESTATIONS_TEMPLATE,
|
|
20204
|
+
context,
|
|
20205
|
+
"06_cryptographic_attestations.md",
|
|
20206
|
+
ds
|
|
20207
|
+
);
|
|
20208
|
+
}
|
|
20209
|
+
function buildManifest(input, generatedAt, signer, files, ds) {
|
|
20210
|
+
const stats = coverageStats(COVERAGE_MATRIX_V1);
|
|
20211
|
+
const rowSummaries = COVERAGE_MATRIX_V1.rows.map(
|
|
20212
|
+
(r) => ({
|
|
20213
|
+
id: r.id,
|
|
20214
|
+
clause_id: r.citation.clause_id,
|
|
20215
|
+
coverage: r.coverage,
|
|
20216
|
+
evidence_emitter: [...r.evidence_emitter]
|
|
20217
|
+
})
|
|
20218
|
+
);
|
|
20219
|
+
const manifestBody = {
|
|
20220
|
+
bundle_version: "1.0",
|
|
20221
|
+
matrix_version: "v1",
|
|
20222
|
+
regulation_version: REGULATION_VERSION,
|
|
20223
|
+
generated_at: generatedAt,
|
|
20224
|
+
agent_did: input.agent_did,
|
|
20225
|
+
period_start: input.period_start,
|
|
20226
|
+
period_end: input.period_end,
|
|
20227
|
+
deployment_context: input.deployment_context,
|
|
20228
|
+
signer: {
|
|
20229
|
+
did: signer.did,
|
|
20230
|
+
public_key_base64url: signer.public_key
|
|
20231
|
+
},
|
|
20232
|
+
files: files.map((f) => ({
|
|
20233
|
+
filename: f.filename,
|
|
20234
|
+
content_type: f.content_type,
|
|
20235
|
+
sha256: f.sha256,
|
|
20236
|
+
signature: f.signature ?? ""
|
|
20237
|
+
})),
|
|
20238
|
+
coverage_summary: {
|
|
20239
|
+
total_rows: stats.total_rows,
|
|
20240
|
+
full: stats.full,
|
|
20241
|
+
partial: stats.partial,
|
|
20242
|
+
manual_only: stats.manual_only,
|
|
20243
|
+
full_pct: stats.full_pct,
|
|
20244
|
+
partial_pct: stats.partial_pct,
|
|
20245
|
+
manual_only_pct: stats.manual_only_pct
|
|
20246
|
+
},
|
|
20247
|
+
coverage_rows: rowSummaries,
|
|
20248
|
+
disclaimer: NOT_LEGAL_ADVICE_DISCLAIMER
|
|
20249
|
+
};
|
|
20250
|
+
const canonical = canonicalJSON(manifestBody);
|
|
20251
|
+
const manifestSignature = ds.signContent(canonical);
|
|
20252
|
+
return {
|
|
20253
|
+
...manifestBody,
|
|
20254
|
+
manifest_signature: manifestSignature
|
|
20255
|
+
};
|
|
20256
|
+
}
|
|
20257
|
+
async function loadPriorBundleManifest(bundlePath, warnings) {
|
|
20258
|
+
try {
|
|
20259
|
+
const manifestPath = path.join(bundlePath, "00_bundle_manifest.json");
|
|
20260
|
+
const bytes = await promises.readFile(manifestPath, "utf-8");
|
|
20261
|
+
const parsed = JSON.parse(bytes);
|
|
20262
|
+
if (typeof parsed.bundle_version !== "string" || typeof parsed.regulation_version !== "string" || !Array.isArray(parsed.coverage_rows)) {
|
|
20263
|
+
warnings.push(
|
|
20264
|
+
`Prior bundle at ${bundlePath} is missing required manifest fields; delta skipped.`
|
|
20265
|
+
);
|
|
20266
|
+
return null;
|
|
20267
|
+
}
|
|
20268
|
+
return parsed;
|
|
20269
|
+
} catch (e) {
|
|
20270
|
+
warnings.push(
|
|
20271
|
+
`Prior bundle at ${bundlePath} could not be read: ${e.message}. Delta skipped.`
|
|
20272
|
+
);
|
|
20273
|
+
return null;
|
|
20274
|
+
}
|
|
20275
|
+
}
|
|
20276
|
+
function computeDelta(prior, current, priorBundlePath, warnings) {
|
|
20277
|
+
const priorRows = new Map(prior.coverage_rows.map((r) => [r.id, r]));
|
|
20278
|
+
const currentRows = new Map(current.coverage_rows.map((r) => [r.id, r]));
|
|
20279
|
+
const rows_added = [];
|
|
20280
|
+
const rows_removed = [];
|
|
20281
|
+
const rows_changed = [];
|
|
20282
|
+
for (const [id] of currentRows) {
|
|
20283
|
+
if (!priorRows.has(id)) {
|
|
20284
|
+
rows_added.push(id);
|
|
20285
|
+
}
|
|
20286
|
+
}
|
|
20287
|
+
for (const [id] of priorRows) {
|
|
20288
|
+
if (!currentRows.has(id)) {
|
|
20289
|
+
rows_removed.push(id);
|
|
20290
|
+
}
|
|
20291
|
+
}
|
|
20292
|
+
for (const [id, currentRow] of currentRows) {
|
|
20293
|
+
const priorRow = priorRows.get(id);
|
|
20294
|
+
if (!priorRow) continue;
|
|
20295
|
+
const changes = {};
|
|
20296
|
+
if (priorRow.coverage !== currentRow.coverage) {
|
|
20297
|
+
changes.coverage = {
|
|
20298
|
+
from: priorRow.coverage,
|
|
20299
|
+
to: currentRow.coverage
|
|
20300
|
+
};
|
|
20301
|
+
}
|
|
20302
|
+
const priorEmitters = [...priorRow.evidence_emitter || []].sort();
|
|
20303
|
+
const currentEmitters = [...currentRow.evidence_emitter || []].sort();
|
|
20304
|
+
if (JSON.stringify(priorEmitters) !== JSON.stringify(currentEmitters)) {
|
|
20305
|
+
changes.evidence_emitter = {
|
|
20306
|
+
from: priorRow.evidence_emitter || [],
|
|
20307
|
+
to: currentRow.evidence_emitter || []
|
|
20308
|
+
};
|
|
20309
|
+
}
|
|
20310
|
+
if (Object.keys(changes).length > 0) {
|
|
20311
|
+
rows_changed.push({
|
|
20312
|
+
row_id: id,
|
|
20313
|
+
clause_id: currentRow.clause_id,
|
|
20314
|
+
changes
|
|
20315
|
+
});
|
|
20316
|
+
}
|
|
20317
|
+
}
|
|
20318
|
+
rows_added.sort();
|
|
20319
|
+
rows_removed.sort();
|
|
20320
|
+
rows_changed.sort((a, b) => a.row_id.localeCompare(b.row_id));
|
|
20321
|
+
return {
|
|
20322
|
+
prior_bundle_path: priorBundlePath,
|
|
20323
|
+
prior_generated_at: prior.generated_at,
|
|
20324
|
+
prior_signer_did: prior.signer.did,
|
|
20325
|
+
prior_regulation_version: prior.regulation_version,
|
|
20326
|
+
prior_matrix_version: prior.matrix_version,
|
|
20327
|
+
current_generated_at: current.generated_at,
|
|
20328
|
+
current_signer_did: current.signer.did,
|
|
20329
|
+
current_regulation_version: current.regulation_version,
|
|
20330
|
+
current_matrix_version: current.matrix_version,
|
|
20331
|
+
regulation_version_changed: prior.regulation_version !== current.regulation_version,
|
|
20332
|
+
matrix_version_changed: prior.matrix_version !== current.matrix_version,
|
|
20333
|
+
rows_added,
|
|
20334
|
+
rows_removed,
|
|
20335
|
+
rows_changed,
|
|
20336
|
+
warnings
|
|
20337
|
+
};
|
|
20338
|
+
}
|
|
20339
|
+
function formatDeltaSections(report) {
|
|
20340
|
+
const added = report.rows_added.length === 0 ? "_(none)_" : report.rows_added.map((id) => `- \`${id}\``).join("\n");
|
|
20341
|
+
const removed = report.rows_removed.length === 0 ? "_(none)_" : report.rows_removed.map((id) => `- \`${id}\``).join("\n");
|
|
20342
|
+
const changed = report.rows_changed.length === 0 ? "_(none)_" : report.rows_changed.map((rc) => {
|
|
20343
|
+
const lines = [
|
|
20344
|
+
`#### \`${rc.row_id}\` (${rc.clause_id})`,
|
|
20345
|
+
""
|
|
20346
|
+
];
|
|
20347
|
+
if (rc.changes.coverage) {
|
|
20348
|
+
lines.push(
|
|
20349
|
+
`- **coverage:** \`${rc.changes.coverage.from}\` \u2192 \`${rc.changes.coverage.to}\``
|
|
20350
|
+
);
|
|
20351
|
+
}
|
|
20352
|
+
if (rc.changes.evidence_emitter) {
|
|
20353
|
+
lines.push(
|
|
20354
|
+
`- **evidence_emitter from:** [${rc.changes.evidence_emitter.from.map((e) => `\`${e}\``).join(", ")}]`
|
|
20355
|
+
);
|
|
20356
|
+
lines.push(
|
|
20357
|
+
`- **evidence_emitter to:** [${rc.changes.evidence_emitter.to.map((e) => `\`${e}\``).join(", ")}]`
|
|
20358
|
+
);
|
|
20359
|
+
}
|
|
20360
|
+
if (rc.changes.last_reviewed_date) {
|
|
20361
|
+
lines.push(
|
|
20362
|
+
`- **last_reviewed_date:** ${rc.changes.last_reviewed_date.from} \u2192 ${rc.changes.last_reviewed_date.to}`
|
|
20363
|
+
);
|
|
20364
|
+
}
|
|
20365
|
+
lines.push("");
|
|
20366
|
+
return lines.join("\n");
|
|
20367
|
+
}).join("\n");
|
|
20368
|
+
const warningsList = report.warnings.length === 0 ? "_(none)_" : report.warnings.map((w) => `- ${w}`).join("\n");
|
|
20369
|
+
const hasAnyChanges = report.regulation_version_changed || report.matrix_version_changed || report.rows_added.length > 0 || report.rows_removed.length > 0 || report.rows_changed.length > 0;
|
|
20370
|
+
const noChangesNote = hasAnyChanges ? "" : "**No changes detected between prior and current bundle.** Both bundles reference the same regulation_version, the same matrix_version, and the same row set with identical coverage flags and evidence_emitter arrays. The only differences are the generation timestamps and the audit period activity captured in documents 02-05.\n\n";
|
|
20371
|
+
return {
|
|
20372
|
+
rows_added_rendered: added,
|
|
20373
|
+
rows_removed_rendered: removed,
|
|
20374
|
+
rows_changed_rendered: changed,
|
|
20375
|
+
warnings_rendered: warningsList,
|
|
20376
|
+
no_changes_note: noChangesNote
|
|
20377
|
+
};
|
|
20378
|
+
}
|
|
20379
|
+
function renderDelta(baseContext, report, ds) {
|
|
20380
|
+
const sections = formatDeltaSections(report);
|
|
20381
|
+
const context = {
|
|
20382
|
+
...baseContext,
|
|
20383
|
+
prior_bundle_path: report.prior_bundle_path,
|
|
20384
|
+
prior_generated_at: report.prior_generated_at,
|
|
20385
|
+
prior_signer_did: report.prior_signer_did,
|
|
20386
|
+
prior_regulation_version: report.prior_regulation_version,
|
|
20387
|
+
prior_matrix_version: report.prior_matrix_version,
|
|
20388
|
+
current_generated_at: report.current_generated_at,
|
|
20389
|
+
current_signer_did: report.current_signer_did,
|
|
20390
|
+
current_regulation_version: report.current_regulation_version,
|
|
20391
|
+
current_matrix_version: report.current_matrix_version,
|
|
20392
|
+
regulation_version_changed: report.regulation_version_changed ? "yes" : "no",
|
|
20393
|
+
matrix_version_changed: report.matrix_version_changed ? "yes" : "no",
|
|
20394
|
+
rows_added_count: report.rows_added.length,
|
|
20395
|
+
rows_removed_count: report.rows_removed.length,
|
|
20396
|
+
rows_changed_count: report.rows_changed.length,
|
|
20397
|
+
...sections,
|
|
20398
|
+
document_title: "Compliance Bundle Delta Report",
|
|
20399
|
+
document_subtitle: "Changes between the prior bundle and this bundle"
|
|
20400
|
+
};
|
|
20401
|
+
return finaliseDocument(
|
|
20402
|
+
DELTA_TEMPLATE,
|
|
20403
|
+
context,
|
|
20404
|
+
"08_delta.md",
|
|
20405
|
+
ds
|
|
20406
|
+
);
|
|
20407
|
+
}
|
|
20408
|
+
var ALLOWED_VERASCORE_HOSTS = /* @__PURE__ */ new Set([
|
|
20409
|
+
"verascore.ai",
|
|
20410
|
+
"www.verascore.ai",
|
|
20411
|
+
"api.verascore.ai"
|
|
20412
|
+
]);
|
|
20413
|
+
async function publishBundleManifestToVerascore(manifest, signer, encryptionKey, verascoreUrl, ds) {
|
|
20414
|
+
const result = {
|
|
20415
|
+
attempted: true,
|
|
20416
|
+
published: false,
|
|
20417
|
+
verascore_url: verascoreUrl
|
|
20418
|
+
};
|
|
20419
|
+
let parsed;
|
|
20420
|
+
try {
|
|
20421
|
+
parsed = new URL(verascoreUrl);
|
|
20422
|
+
} catch {
|
|
20423
|
+
result.error = `Verascore URL is not a valid URL: ${verascoreUrl}`;
|
|
20424
|
+
return result;
|
|
20425
|
+
}
|
|
20426
|
+
if (parsed.protocol !== "https:") {
|
|
20427
|
+
result.error = `Verascore URL must use HTTPS (got ${parsed.protocol})`;
|
|
20428
|
+
return result;
|
|
20429
|
+
}
|
|
20430
|
+
if (!ALLOWED_VERASCORE_HOSTS.has(parsed.hostname)) {
|
|
20431
|
+
result.error = `Verascore URL must point to a known Verascore host (${[...ALLOWED_VERASCORE_HOSTS].join(", ")}). Got: ${parsed.hostname}`;
|
|
20432
|
+
return result;
|
|
20433
|
+
}
|
|
20434
|
+
const canonicalManifest = canonicalJSON(manifest);
|
|
20435
|
+
const manifestBytes = stringToBytes(canonicalManifest);
|
|
20436
|
+
const manifestSha256 = ds.sha256Hex(canonicalManifest);
|
|
20437
|
+
result.published_manifest_sha256 = manifestSha256;
|
|
20438
|
+
let requestSignatureB64;
|
|
20439
|
+
try {
|
|
20440
|
+
const sigBytes = sign(
|
|
20441
|
+
manifestBytes,
|
|
20442
|
+
signer.encrypted_private_key,
|
|
20443
|
+
encryptionKey
|
|
20444
|
+
);
|
|
20445
|
+
requestSignatureB64 = toBase64url(sigBytes);
|
|
20446
|
+
} catch (e) {
|
|
20447
|
+
result.error = `Failed to sign Verascore publish payload: ${e.message}`;
|
|
20448
|
+
return result;
|
|
20449
|
+
}
|
|
20450
|
+
const agentId = manifest.agent_did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
20451
|
+
result.verascore_agent_id = agentId;
|
|
20452
|
+
const requestBody = {
|
|
20453
|
+
agentId,
|
|
20454
|
+
signature: requestSignatureB64,
|
|
20455
|
+
publicKey: signer.public_key,
|
|
20456
|
+
type: "eu-ai-act-bundle",
|
|
20457
|
+
data: manifest
|
|
20458
|
+
};
|
|
20459
|
+
try {
|
|
20460
|
+
const response = await fetch(`${verascoreUrl}/api/publish`, {
|
|
20461
|
+
method: "POST",
|
|
20462
|
+
headers: { "Content-Type": "application/json" },
|
|
20463
|
+
body: JSON.stringify(requestBody)
|
|
20464
|
+
});
|
|
20465
|
+
result.status = response.status;
|
|
20466
|
+
if (!response.ok) {
|
|
20467
|
+
result.error = `Verascore API returned ${response.status}`;
|
|
20468
|
+
return result;
|
|
20469
|
+
}
|
|
20470
|
+
result.published = true;
|
|
20471
|
+
return result;
|
|
20472
|
+
} catch (e) {
|
|
20473
|
+
result.error = `Verascore fetch failed: ${e.message}`;
|
|
20474
|
+
return result;
|
|
20475
|
+
}
|
|
20476
|
+
}
|
|
20477
|
+
async function generateEuAiActBundle(input, deps) {
|
|
20478
|
+
const signer = deps.identityManager.getDefault();
|
|
20479
|
+
if (!signer) {
|
|
20480
|
+
throw new Error(
|
|
20481
|
+
"No primary identity configured. Run identity_create and identity_set_primary before generating a compliance bundle."
|
|
20482
|
+
);
|
|
20483
|
+
}
|
|
20484
|
+
const ds = makeSigner(signer, deps.masterKey);
|
|
20485
|
+
const auditSummary = await computeAuditPeriodSummary(
|
|
20486
|
+
deps.auditLog,
|
|
20487
|
+
input.period_start,
|
|
20488
|
+
input.period_end
|
|
20489
|
+
);
|
|
20490
|
+
const generatedAt = input.generated_at_override ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
20491
|
+
const baseContext = buildCommonContext(
|
|
20492
|
+
input,
|
|
20493
|
+
deps,
|
|
20494
|
+
signer,
|
|
20495
|
+
auditSummary,
|
|
20496
|
+
generatedAt
|
|
20497
|
+
);
|
|
20498
|
+
const doc1 = renderAnnexIV(baseContext, ds);
|
|
20499
|
+
const doc2 = renderArticle26(baseContext, ds);
|
|
20500
|
+
const doc3 = renderArticle12(baseContext, ds);
|
|
20501
|
+
const doc4 = renderRiskManagement(baseContext, ds);
|
|
20502
|
+
const doc5 = renderHumanOversight(baseContext, ds);
|
|
20503
|
+
const doc7 = renderAnnexIIIClassification(
|
|
20504
|
+
baseContext,
|
|
20505
|
+
input.deployment_context.intended_purpose ?? "",
|
|
20506
|
+
ds
|
|
20507
|
+
);
|
|
20508
|
+
const partialManifest = buildManifest(
|
|
20509
|
+
input,
|
|
20510
|
+
generatedAt,
|
|
20511
|
+
signer,
|
|
20512
|
+
[doc1, doc2, doc3, doc4, doc5],
|
|
20513
|
+
ds
|
|
20514
|
+
);
|
|
20515
|
+
const partialCanonical = canonicalJSON(partialManifest);
|
|
20516
|
+
const partialSha256 = ds.sha256Hex(partialCanonical);
|
|
20517
|
+
const partialSignature = partialManifest.manifest_signature;
|
|
20518
|
+
const doc6 = renderAttestations(
|
|
20519
|
+
baseContext,
|
|
20520
|
+
[doc1, doc2, doc3, doc4, doc5],
|
|
20521
|
+
partialSha256,
|
|
20522
|
+
partialSignature,
|
|
20523
|
+
ds
|
|
20524
|
+
);
|
|
20525
|
+
let allFiles = [doc1, doc2, doc3, doc4, doc5, doc6, doc7];
|
|
20526
|
+
if (input.delta_from_bundle_path) {
|
|
20527
|
+
const deltaWarnings = [];
|
|
20528
|
+
const priorManifest = await loadPriorBundleManifest(
|
|
20529
|
+
input.delta_from_bundle_path,
|
|
20530
|
+
deltaWarnings
|
|
20531
|
+
);
|
|
20532
|
+
if (priorManifest !== null) {
|
|
20533
|
+
const currentManifestSnapshot = buildManifest(
|
|
20534
|
+
input,
|
|
20535
|
+
generatedAt,
|
|
20536
|
+
signer,
|
|
20537
|
+
allFiles,
|
|
20538
|
+
ds
|
|
20539
|
+
);
|
|
20540
|
+
const report = computeDelta(
|
|
20541
|
+
priorManifest,
|
|
20542
|
+
currentManifestSnapshot,
|
|
20543
|
+
input.delta_from_bundle_path,
|
|
20544
|
+
deltaWarnings
|
|
20545
|
+
);
|
|
20546
|
+
const doc8 = renderDelta(baseContext, report, ds);
|
|
20547
|
+
allFiles = [...allFiles, doc8];
|
|
20548
|
+
}
|
|
20549
|
+
}
|
|
20550
|
+
const finalManifest = buildManifest(
|
|
20551
|
+
input,
|
|
20552
|
+
generatedAt,
|
|
20553
|
+
signer,
|
|
20554
|
+
allFiles,
|
|
20555
|
+
ds
|
|
20556
|
+
);
|
|
20557
|
+
let publishResult = void 0;
|
|
20558
|
+
if (input.publish_to_verascore) {
|
|
20559
|
+
const encryptionKey = derivePurposeKey(
|
|
20560
|
+
deps.masterKey,
|
|
20561
|
+
"identity-encryption"
|
|
20562
|
+
);
|
|
20563
|
+
publishResult = await publishBundleManifestToVerascore(
|
|
20564
|
+
finalManifest,
|
|
20565
|
+
signer,
|
|
20566
|
+
encryptionKey,
|
|
20567
|
+
input.verascore_url ?? "https://verascore.ai",
|
|
20568
|
+
ds
|
|
20569
|
+
);
|
|
20570
|
+
}
|
|
20571
|
+
return {
|
|
20572
|
+
manifest: finalManifest,
|
|
20573
|
+
files: allFiles,
|
|
20574
|
+
publish_result: publishResult
|
|
20575
|
+
};
|
|
20576
|
+
}
|
|
20577
|
+
function createComplianceTools(deps) {
|
|
20578
|
+
const tools = [
|
|
20579
|
+
{
|
|
20580
|
+
name: "compliance_eu_ai_act_annex_iii_classify",
|
|
20581
|
+
description: "Classify a free-text agent description against the eight Annex III high-risk categories of Regulation (EU) 2024/1689. Returns zero or more candidate categories with a rule_based_confidence score (sum of matched keyword weights clamped to [0, 1]). This is a RULE-BASED classifier \u2014 NOT a machine-learning model, and NOT legal advice. Use the result to narrow the deployer's review. Tier 3 (read-only, auto-allow).",
|
|
20582
|
+
inputSchema: {
|
|
20583
|
+
type: "object",
|
|
20584
|
+
properties: {
|
|
20585
|
+
description: {
|
|
20586
|
+
type: "string",
|
|
20587
|
+
description: "Free-text description of what the agent does \u2014 its intended purpose, the users it serves, the decisions it makes, the data it processes."
|
|
20588
|
+
}
|
|
20589
|
+
},
|
|
20590
|
+
required: ["description"]
|
|
20591
|
+
},
|
|
20592
|
+
handler: async (args) => {
|
|
20593
|
+
const description = String(args.description ?? "");
|
|
20594
|
+
if (description.trim().length === 0) {
|
|
20595
|
+
return toolResult({
|
|
20596
|
+
error: "description must be a non-empty string describing the agent's intended purpose"
|
|
20597
|
+
});
|
|
20598
|
+
}
|
|
20599
|
+
const result = classifyAgentDescription(description);
|
|
20600
|
+
return toolResult(result);
|
|
20601
|
+
}
|
|
20602
|
+
},
|
|
20603
|
+
{
|
|
20604
|
+
name: "compliance_generate_eu_ai_act_bundle",
|
|
20605
|
+
description: "Generate an EU AI Act compliance bundle for a given agent and reporting period. Produces 6 Markdown documents plus a signed JSON manifest, covering Annex IV technical documentation (Art. 11), Art. 12 automatic record-keeping, Art. 13 transparency, Art. 14 human oversight, Art. 15 cybersecurity, and Art. 26 deployer obligations. Every file is individually SHA-256 hashed and signed with the provider's primary Ed25519 identity. NOT LEGAL ADVICE \u2014 consult qualified counsel before filing.",
|
|
20606
|
+
inputSchema: {
|
|
20607
|
+
type: "object",
|
|
20608
|
+
properties: {
|
|
20609
|
+
agent_did: {
|
|
20610
|
+
type: "string",
|
|
20611
|
+
description: "DID of the agent the bundle is being generated for."
|
|
20612
|
+
},
|
|
20613
|
+
deployment_context: {
|
|
20614
|
+
type: "object",
|
|
20615
|
+
description: "Enterprise-supplied deployment facts that Sanctuary cannot infer (vertical, intended purpose, provider legal name, Annex III classification, etc.).",
|
|
20616
|
+
properties: {
|
|
20617
|
+
vertical: { type: "string" },
|
|
20618
|
+
annex_iii_class: { type: "string" },
|
|
20619
|
+
intended_purpose: { type: "string" },
|
|
20620
|
+
provider_legal_name: { type: "string" },
|
|
20621
|
+
provider_contact: { type: "string" },
|
|
20622
|
+
deployer_is_public_authority: { type: "boolean" },
|
|
20623
|
+
notes: { type: "string" }
|
|
20624
|
+
}
|
|
20625
|
+
},
|
|
20626
|
+
period_start: {
|
|
20627
|
+
type: "string",
|
|
20628
|
+
description: "ISO 8601 timestamp \u2014 start of the reporting period."
|
|
20629
|
+
},
|
|
20630
|
+
period_end: {
|
|
20631
|
+
type: "string",
|
|
20632
|
+
description: "ISO 8601 timestamp \u2014 end of the reporting period."
|
|
20633
|
+
},
|
|
20634
|
+
delta_from_bundle_path: {
|
|
20635
|
+
type: "string",
|
|
20636
|
+
description: "Optional filesystem path to a prior bundle directory. When supplied, the generator compares the current bundle to the prior one and emits an additional 08_delta.md document summarising what changed (regulation_version, matrix_version, coverage rows added/removed/changed). Delta problems never fail bundle generation; the bundle always lands locally."
|
|
20637
|
+
},
|
|
20638
|
+
publish_to_verascore: {
|
|
20639
|
+
type: "boolean",
|
|
20640
|
+
description: "If true, POST the signed bundle MANIFEST (not the document bodies) to Verascore after the bundle is generated. Non-dependency principle: publish failure never fails bundle generation \u2014 the bundle is returned with a publish_result field describing the outcome. Defaults to false."
|
|
20641
|
+
},
|
|
20642
|
+
verascore_url: {
|
|
20643
|
+
type: "string",
|
|
20644
|
+
description: "Optional Verascore base URL (HTTPS, allow-listed hosts only). Defaults to https://verascore.ai."
|
|
20645
|
+
}
|
|
20646
|
+
},
|
|
20647
|
+
required: [
|
|
20648
|
+
"agent_did",
|
|
20649
|
+
"deployment_context",
|
|
20650
|
+
"period_start",
|
|
20651
|
+
"period_end"
|
|
20652
|
+
]
|
|
20653
|
+
},
|
|
20654
|
+
handler: async (args) => {
|
|
20655
|
+
const input = {
|
|
20656
|
+
agent_did: String(args.agent_did),
|
|
20657
|
+
deployment_context: args.deployment_context ?? {},
|
|
20658
|
+
period_start: String(args.period_start),
|
|
20659
|
+
period_end: String(args.period_end),
|
|
20660
|
+
delta_from_bundle_path: typeof args.delta_from_bundle_path === "string" ? args.delta_from_bundle_path : void 0,
|
|
20661
|
+
publish_to_verascore: args.publish_to_verascore === true,
|
|
20662
|
+
verascore_url: typeof args.verascore_url === "string" ? args.verascore_url : void 0
|
|
20663
|
+
};
|
|
20664
|
+
const bundle = await generateEuAiActBundle(input, deps);
|
|
20665
|
+
return toolResult({
|
|
20666
|
+
bundle_version: bundle.manifest.bundle_version,
|
|
20667
|
+
matrix_version: bundle.manifest.matrix_version,
|
|
20668
|
+
regulation_version: bundle.manifest.regulation_version,
|
|
20669
|
+
agent_did: bundle.manifest.agent_did,
|
|
20670
|
+
generated_at: bundle.manifest.generated_at,
|
|
20671
|
+
period: {
|
|
20672
|
+
start: bundle.manifest.period_start,
|
|
20673
|
+
end: bundle.manifest.period_end
|
|
20674
|
+
},
|
|
20675
|
+
signer: bundle.manifest.signer,
|
|
20676
|
+
coverage_summary: bundle.manifest.coverage_summary,
|
|
20677
|
+
file_count: bundle.files.length,
|
|
20678
|
+
files: bundle.files.map((f) => ({
|
|
20679
|
+
filename: f.filename,
|
|
20680
|
+
content_type: f.content_type,
|
|
20681
|
+
sha256: f.sha256,
|
|
20682
|
+
signature: f.signature,
|
|
20683
|
+
content_length: f.content.length
|
|
20684
|
+
})),
|
|
20685
|
+
manifest_signature: bundle.manifest.manifest_signature,
|
|
20686
|
+
disclaimer: bundle.manifest.disclaimer,
|
|
20687
|
+
publish_result: bundle.publish_result,
|
|
20688
|
+
_bundle_content: {
|
|
20689
|
+
manifest: bundle.manifest,
|
|
20690
|
+
files: bundle.files.map((f) => ({
|
|
20691
|
+
filename: f.filename,
|
|
20692
|
+
content: f.content
|
|
20693
|
+
}))
|
|
20694
|
+
}
|
|
20695
|
+
});
|
|
20696
|
+
}
|
|
20697
|
+
}
|
|
20698
|
+
];
|
|
20699
|
+
return { tools };
|
|
20700
|
+
}
|
|
20701
|
+
|
|
20702
|
+
// src/index.ts
|
|
20703
|
+
init_random();
|
|
20704
|
+
init_encoding();
|
|
20705
|
+
|
|
20706
|
+
// src/l2-operational/model-provenance.ts
|
|
20707
|
+
var InMemoryModelProvenanceStore = class {
|
|
20708
|
+
models = /* @__PURE__ */ new Map();
|
|
20709
|
+
primaryModelId = null;
|
|
20710
|
+
declare(provenance) {
|
|
20711
|
+
if (!provenance.model_id) {
|
|
20712
|
+
throw new Error("ModelProvenance requires a model_id");
|
|
20713
|
+
}
|
|
20714
|
+
if (!provenance.model_name) {
|
|
20715
|
+
throw new Error("ModelProvenance requires a model_name");
|
|
20716
|
+
}
|
|
20717
|
+
if (!provenance.provider) {
|
|
20718
|
+
throw new Error("ModelProvenance requires a provider");
|
|
20719
|
+
}
|
|
20720
|
+
this.models.set(provenance.model_id, provenance);
|
|
20721
|
+
if (this.primaryModelId === null) {
|
|
20722
|
+
this.primaryModelId = provenance.model_id;
|
|
20723
|
+
}
|
|
20724
|
+
}
|
|
20725
|
+
get(model_id) {
|
|
20726
|
+
return this.models.get(model_id);
|
|
20727
|
+
}
|
|
20728
|
+
list() {
|
|
20729
|
+
return Array.from(this.models.values());
|
|
20730
|
+
}
|
|
20731
|
+
primary() {
|
|
20732
|
+
if (!this.primaryModelId) return void 0;
|
|
20733
|
+
return this.models.get(this.primaryModelId);
|
|
20734
|
+
}
|
|
20735
|
+
setPrimary(model_id) {
|
|
20736
|
+
if (!this.models.has(model_id)) {
|
|
20737
|
+
throw new Error(`Model ${model_id} not found in store`);
|
|
20738
|
+
}
|
|
20739
|
+
this.primaryModelId = model_id;
|
|
20740
|
+
}
|
|
20741
|
+
};
|
|
20742
|
+
var MODEL_PRESETS = {
|
|
20743
|
+
/**
|
|
20744
|
+
* Claude Opus 4 via Anthropic API (cloud inference, closed weights/source)
|
|
20745
|
+
*/
|
|
20746
|
+
claudeOpus4: () => ({
|
|
20747
|
+
model_id: "claude-opus-4",
|
|
20748
|
+
model_name: "Claude Opus 4",
|
|
20749
|
+
model_version: "4.0",
|
|
20750
|
+
provider: "Anthropic",
|
|
20751
|
+
license: "proprietary",
|
|
20752
|
+
open_weights: false,
|
|
20753
|
+
open_source: false,
|
|
20754
|
+
local_inference: false,
|
|
20755
|
+
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20756
|
+
}),
|
|
20757
|
+
/**
|
|
20758
|
+
* Qwen 3.5 via local inference (open weights, proprietary training)
|
|
20759
|
+
*/
|
|
20760
|
+
qwen35Local: () => ({
|
|
20761
|
+
model_id: "qwen-3.5-35b",
|
|
20762
|
+
model_name: "Qwen 3.5 35B",
|
|
20763
|
+
model_version: "3.5",
|
|
20764
|
+
provider: "Alibaba Cloud",
|
|
20765
|
+
license: "Apache-2.0",
|
|
20766
|
+
open_weights: true,
|
|
20767
|
+
open_source: false,
|
|
20768
|
+
local_inference: true,
|
|
20769
|
+
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20770
|
+
}),
|
|
20771
|
+
/**
|
|
20772
|
+
* Llama 3.3 70B via local inference (open weights and code)
|
|
20773
|
+
*/
|
|
20774
|
+
llama33Local: () => ({
|
|
20775
|
+
model_id: "llama-3.3-70b-instruct",
|
|
20776
|
+
model_name: "Llama 3.3 70B Instruct",
|
|
20777
|
+
model_version: "3.3",
|
|
20778
|
+
provider: "Meta",
|
|
20779
|
+
license: "Apache-2.0",
|
|
20780
|
+
open_weights: true,
|
|
20781
|
+
open_source: true,
|
|
20782
|
+
local_inference: true,
|
|
20783
|
+
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20784
|
+
}),
|
|
20785
|
+
/**
|
|
20786
|
+
* Mistral 7B (open weights, open code, local inference)
|
|
20787
|
+
*/
|
|
20788
|
+
mistral7bLocal: () => ({
|
|
20789
|
+
model_id: "mistral-7b-instruct",
|
|
20790
|
+
model_name: "Mistral 7B Instruct",
|
|
20791
|
+
model_version: "7",
|
|
20792
|
+
provider: "Mistral AI",
|
|
20793
|
+
license: "Apache-2.0",
|
|
20794
|
+
open_weights: true,
|
|
20795
|
+
open_source: true,
|
|
20796
|
+
local_inference: true,
|
|
20797
|
+
declared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20798
|
+
})
|
|
20799
|
+
};
|
|
20800
|
+
|
|
20801
|
+
// src/storage/memory.ts
|
|
20802
|
+
var MemoryStorage = class {
|
|
20803
|
+
store = /* @__PURE__ */ new Map();
|
|
20804
|
+
storageKey(namespace, key) {
|
|
20805
|
+
return `${namespace}/${key}`;
|
|
20806
|
+
}
|
|
20807
|
+
async write(namespace, key, data) {
|
|
20808
|
+
this.store.set(this.storageKey(namespace, key), {
|
|
20809
|
+
data: new Uint8Array(data),
|
|
20810
|
+
// Copy to prevent external mutation
|
|
20811
|
+
modified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
20812
|
+
});
|
|
20813
|
+
}
|
|
20814
|
+
async read(namespace, key) {
|
|
20815
|
+
const entry = this.store.get(this.storageKey(namespace, key));
|
|
20816
|
+
if (!entry) return null;
|
|
20817
|
+
return new Uint8Array(entry.data);
|
|
20818
|
+
}
|
|
20819
|
+
async delete(namespace, key, _secureOverwrite) {
|
|
20820
|
+
return this.store.delete(this.storageKey(namespace, key));
|
|
20821
|
+
}
|
|
20822
|
+
async list(namespace, prefix) {
|
|
20823
|
+
const entries = [];
|
|
20824
|
+
const nsPrefix = `${namespace}/`;
|
|
20825
|
+
for (const [storeKey, entry] of this.store) {
|
|
20826
|
+
if (!storeKey.startsWith(nsPrefix)) continue;
|
|
20827
|
+
const key = storeKey.slice(nsPrefix.length);
|
|
20828
|
+
if (prefix && !key.startsWith(prefix)) continue;
|
|
20829
|
+
entries.push({
|
|
20830
|
+
key,
|
|
20831
|
+
namespace,
|
|
20832
|
+
size_bytes: entry.data.length,
|
|
20833
|
+
modified_at: entry.modified_at
|
|
20834
|
+
});
|
|
20835
|
+
}
|
|
20836
|
+
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
20837
|
+
}
|
|
20838
|
+
async exists(namespace, key) {
|
|
20839
|
+
return this.store.has(this.storageKey(namespace, key));
|
|
20840
|
+
}
|
|
20841
|
+
async totalSize() {
|
|
20842
|
+
let total = 0;
|
|
17668
20843
|
for (const entry of this.store.values()) {
|
|
17669
20844
|
total += entry.data.length;
|
|
17670
20845
|
}
|
|
@@ -18115,6 +21290,18 @@ async function createSanctuaryServer(options) {
|
|
|
18115
21290
|
policy,
|
|
18116
21291
|
keyProtection
|
|
18117
21292
|
});
|
|
21293
|
+
const { tools: memoryAttestTools } = createMemoryAttestTools(
|
|
21294
|
+
identityManager,
|
|
21295
|
+
masterKey,
|
|
21296
|
+
auditLog
|
|
21297
|
+
);
|
|
21298
|
+
const { tools: complianceTools } = createComplianceTools({
|
|
21299
|
+
config,
|
|
21300
|
+
identityManager,
|
|
21301
|
+
masterKey,
|
|
21302
|
+
auditLog,
|
|
21303
|
+
policy
|
|
21304
|
+
});
|
|
18118
21305
|
const dashboardTools = [];
|
|
18119
21306
|
if (dashboard) {
|
|
18120
21307
|
dashboardTools.push({
|
|
@@ -18158,6 +21345,8 @@ async function createSanctuaryServer(options) {
|
|
|
18158
21345
|
...profileTools,
|
|
18159
21346
|
...dashboardTools,
|
|
18160
21347
|
...sanctuaryMetaTools,
|
|
21348
|
+
...memoryAttestTools,
|
|
21349
|
+
...complianceTools,
|
|
18161
21350
|
manifestTool
|
|
18162
21351
|
];
|
|
18163
21352
|
let clientManager;
|
|
@@ -18261,7 +21450,14 @@ async function createSanctuaryServer(options) {
|
|
|
18261
21450
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`
|
|
18262
21451
|
);
|
|
18263
21452
|
}
|
|
18264
|
-
return {
|
|
21453
|
+
return {
|
|
21454
|
+
server,
|
|
21455
|
+
config,
|
|
21456
|
+
identityManager,
|
|
21457
|
+
masterKey,
|
|
21458
|
+
auditLog,
|
|
21459
|
+
policy
|
|
21460
|
+
};
|
|
18265
21461
|
}
|
|
18266
21462
|
|
|
18267
21463
|
exports.ATTESTATION_VERSION = ATTESTATION_VERSION;
|