@humanagencyp/hap-core 0.5.2 → 0.6.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/dist/index.d.mts +78 -1
- package/dist/index.d.ts +78 -1
- package/dist/index.js +41 -0
- package/dist/index.mjs +39 -0
- package/package.json +1 -1
- package/src/identity.ts +77 -0
- package/src/index.ts +1 -0
- package/src/types.ts +39 -0
package/dist/index.d.mts
CHANGED
|
@@ -46,6 +46,14 @@ interface AttestationPayload {
|
|
|
46
46
|
* without invalidating the attestation signature (companion invariant C2).
|
|
47
47
|
*/
|
|
48
48
|
intent_disclosure_hash?: string;
|
|
49
|
+
/**
|
|
50
|
+
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
51
|
+
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
52
|
+
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
53
|
+
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
54
|
+
* {@link deriveIdentityLine}.
|
|
55
|
+
*/
|
|
56
|
+
subjects?: Subject[];
|
|
49
57
|
/**
|
|
50
58
|
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
51
59
|
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
@@ -59,6 +67,38 @@ interface AttestationPayload {
|
|
|
59
67
|
issued_at: number;
|
|
60
68
|
expires_at: number;
|
|
61
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* v0.6 Identity Assurance — a signed overlay binding a Decision Owner's verified
|
|
72
|
+
* real-world identity to the attestation, gated by HOW the identity was verified.
|
|
73
|
+
*
|
|
74
|
+
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
75
|
+
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
76
|
+
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
77
|
+
* owner's own signature, AS-independent). See review.md → "Identity Assurance".
|
|
78
|
+
*/
|
|
79
|
+
interface Subject {
|
|
80
|
+
/** The Decision Owner DID this subject describes (matches an entry in resolved_owners). */
|
|
81
|
+
did: string;
|
|
82
|
+
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
83
|
+
assurance: 'low' | 'high';
|
|
84
|
+
/** How identity was established. */
|
|
85
|
+
method: 'self_declared' | 'as_vouched' | 'eudi';
|
|
86
|
+
/** Who vouches: `self` (owner's claim), `as` (operator), `external` (eID scheme). */
|
|
87
|
+
trust_root: 'self' | 'as' | 'external';
|
|
88
|
+
/** Verifier id — the AS operator (as_vouched) or the eID scheme (eudi). Required at `high`. */
|
|
89
|
+
verifier?: string;
|
|
90
|
+
/** Disclosed attributes. `name` present ONLY at `assurance:"high"` and when disclosure is on. */
|
|
91
|
+
disclose?: {
|
|
92
|
+
name: string;
|
|
93
|
+
};
|
|
94
|
+
/** When the underlying verification was performed (unix seconds). */
|
|
95
|
+
verified_at?: number;
|
|
96
|
+
/**
|
|
97
|
+
* eudi only (Phase 2) — the owner's per-event wallet signature, making the
|
|
98
|
+
* identity claim non-repudiable independent of the AS. Null/absent otherwise.
|
|
99
|
+
*/
|
|
100
|
+
owner_signature?: string | null;
|
|
101
|
+
}
|
|
62
102
|
interface Attestation {
|
|
63
103
|
header: AttestationHeader;
|
|
64
104
|
payload: AttestationPayload;
|
|
@@ -585,6 +625,43 @@ declare function intentDisclosureCanonicalBytes(intentCiphertext: string, approv
|
|
|
585
625
|
*/
|
|
586
626
|
declare function computeIntentDisclosureHash(intentCiphertext: string, approversFrozen: string[]): string;
|
|
587
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Identity Assurance (HAP v0.6) — validation + footer derivation for the signed
|
|
630
|
+
* {@link Subject} overlay. See review.md → "Identity Assurance".
|
|
631
|
+
*
|
|
632
|
+
* Two pure helpers, the single source of truth for both the gateway footer and
|
|
633
|
+
* the AS verify page:
|
|
634
|
+
* - {@link validateSubject} — enforces the level/method/trust-root invariants.
|
|
635
|
+
* - {@link deriveIdentityLine} — the human-readable footer line for a subject.
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
interface SubjectValidation {
|
|
639
|
+
valid: boolean;
|
|
640
|
+
errors: string[];
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Enforce the Identity-Assurance invariants on a single subject:
|
|
644
|
+
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
645
|
+
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
646
|
+
* - `eudi` ⇒ `high` / `external`, `verifier` + `owner_signature` required.
|
|
647
|
+
* - `disclose.name` only at `assurance:"high"`.
|
|
648
|
+
*/
|
|
649
|
+
declare function validateSubject(subject: Subject): SubjectValidation;
|
|
650
|
+
/**
|
|
651
|
+
* The footer line for a subject — the ONE place this wording lives.
|
|
652
|
+
* The name appears only at `high` with a disclosed name; otherwise no name.
|
|
653
|
+
*
|
|
654
|
+
* - low / no disclosure → "Sent by an AI agent via «operator»"
|
|
655
|
+
* - high / as_vouched → "Sent by an AI agent of «name» — verified by «operator»"
|
|
656
|
+
* - high / eudi → "Sent by an AI agent of «name» — identity verified (EUDI)"
|
|
657
|
+
*
|
|
658
|
+
* `operatorName` is the rendered AS operator (e.g. "Suveren") — never hardcode it
|
|
659
|
+
* upstream; a different operator self-vouches under its own name.
|
|
660
|
+
*/
|
|
661
|
+
declare function deriveIdentityLine(subject: Subject | undefined | null, { operatorName }: {
|
|
662
|
+
operatorName: string;
|
|
663
|
+
}): string;
|
|
664
|
+
|
|
588
665
|
/**
|
|
589
666
|
* Frame Canonicalization for Agent Profiles
|
|
590
667
|
*
|
|
@@ -782,4 +859,4 @@ declare function listProfiles(): string[];
|
|
|
782
859
|
declare function getAllProfiles(): AgentProfile[];
|
|
783
860
|
declare function clearProfiles(): void;
|
|
784
861
|
|
|
785
|
-
export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
|
|
862
|
+
export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,14 @@ interface AttestationPayload {
|
|
|
46
46
|
* without invalidating the attestation signature (companion invariant C2).
|
|
47
47
|
*/
|
|
48
48
|
intent_disclosure_hash?: string;
|
|
49
|
+
/**
|
|
50
|
+
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
51
|
+
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
52
|
+
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
53
|
+
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
54
|
+
* {@link deriveIdentityLine}.
|
|
55
|
+
*/
|
|
56
|
+
subjects?: Subject[];
|
|
49
57
|
/**
|
|
50
58
|
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
51
59
|
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
@@ -59,6 +67,38 @@ interface AttestationPayload {
|
|
|
59
67
|
issued_at: number;
|
|
60
68
|
expires_at: number;
|
|
61
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* v0.6 Identity Assurance — a signed overlay binding a Decision Owner's verified
|
|
72
|
+
* real-world identity to the attestation, gated by HOW the identity was verified.
|
|
73
|
+
*
|
|
74
|
+
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
75
|
+
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
76
|
+
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
77
|
+
* owner's own signature, AS-independent). See review.md → "Identity Assurance".
|
|
78
|
+
*/
|
|
79
|
+
interface Subject {
|
|
80
|
+
/** The Decision Owner DID this subject describes (matches an entry in resolved_owners). */
|
|
81
|
+
did: string;
|
|
82
|
+
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
83
|
+
assurance: 'low' | 'high';
|
|
84
|
+
/** How identity was established. */
|
|
85
|
+
method: 'self_declared' | 'as_vouched' | 'eudi';
|
|
86
|
+
/** Who vouches: `self` (owner's claim), `as` (operator), `external` (eID scheme). */
|
|
87
|
+
trust_root: 'self' | 'as' | 'external';
|
|
88
|
+
/** Verifier id — the AS operator (as_vouched) or the eID scheme (eudi). Required at `high`. */
|
|
89
|
+
verifier?: string;
|
|
90
|
+
/** Disclosed attributes. `name` present ONLY at `assurance:"high"` and when disclosure is on. */
|
|
91
|
+
disclose?: {
|
|
92
|
+
name: string;
|
|
93
|
+
};
|
|
94
|
+
/** When the underlying verification was performed (unix seconds). */
|
|
95
|
+
verified_at?: number;
|
|
96
|
+
/**
|
|
97
|
+
* eudi only (Phase 2) — the owner's per-event wallet signature, making the
|
|
98
|
+
* identity claim non-repudiable independent of the AS. Null/absent otherwise.
|
|
99
|
+
*/
|
|
100
|
+
owner_signature?: string | null;
|
|
101
|
+
}
|
|
62
102
|
interface Attestation {
|
|
63
103
|
header: AttestationHeader;
|
|
64
104
|
payload: AttestationPayload;
|
|
@@ -585,6 +625,43 @@ declare function intentDisclosureCanonicalBytes(intentCiphertext: string, approv
|
|
|
585
625
|
*/
|
|
586
626
|
declare function computeIntentDisclosureHash(intentCiphertext: string, approversFrozen: string[]): string;
|
|
587
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Identity Assurance (HAP v0.6) — validation + footer derivation for the signed
|
|
630
|
+
* {@link Subject} overlay. See review.md → "Identity Assurance".
|
|
631
|
+
*
|
|
632
|
+
* Two pure helpers, the single source of truth for both the gateway footer and
|
|
633
|
+
* the AS verify page:
|
|
634
|
+
* - {@link validateSubject} — enforces the level/method/trust-root invariants.
|
|
635
|
+
* - {@link deriveIdentityLine} — the human-readable footer line for a subject.
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
interface SubjectValidation {
|
|
639
|
+
valid: boolean;
|
|
640
|
+
errors: string[];
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Enforce the Identity-Assurance invariants on a single subject:
|
|
644
|
+
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
645
|
+
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
646
|
+
* - `eudi` ⇒ `high` / `external`, `verifier` + `owner_signature` required.
|
|
647
|
+
* - `disclose.name` only at `assurance:"high"`.
|
|
648
|
+
*/
|
|
649
|
+
declare function validateSubject(subject: Subject): SubjectValidation;
|
|
650
|
+
/**
|
|
651
|
+
* The footer line for a subject — the ONE place this wording lives.
|
|
652
|
+
* The name appears only at `high` with a disclosed name; otherwise no name.
|
|
653
|
+
*
|
|
654
|
+
* - low / no disclosure → "Sent by an AI agent via «operator»"
|
|
655
|
+
* - high / as_vouched → "Sent by an AI agent of «name» — verified by «operator»"
|
|
656
|
+
* - high / eudi → "Sent by an AI agent of «name» — identity verified (EUDI)"
|
|
657
|
+
*
|
|
658
|
+
* `operatorName` is the rendered AS operator (e.g. "Suveren") — never hardcode it
|
|
659
|
+
* upstream; a different operator self-vouches under its own name.
|
|
660
|
+
*/
|
|
661
|
+
declare function deriveIdentityLine(subject: Subject | undefined | null, { operatorName }: {
|
|
662
|
+
operatorName: string;
|
|
663
|
+
}): string;
|
|
664
|
+
|
|
588
665
|
/**
|
|
589
666
|
* Frame Canonicalization for Agent Profiles
|
|
590
667
|
*
|
|
@@ -782,4 +859,4 @@ declare function listProfiles(): string[];
|
|
|
782
859
|
declare function getAllProfiles(): AgentProfile[];
|
|
783
860
|
declare function clearProfiles(): void;
|
|
784
861
|
|
|
785
|
-
export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
|
|
862
|
+
export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, type ContentBinding, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, type Subject, type SubjectValidation, attestationId, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, deriveIdentityLine, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ __export(index_exports, {
|
|
|
46
46
|
computeIntentHash: () => computeIntentHash,
|
|
47
47
|
contentCanonicalBytes: () => contentCanonicalBytes,
|
|
48
48
|
decodeAttestationBlob: () => decodeAttestationBlob,
|
|
49
|
+
deriveIdentityLine: () => deriveIdentityLine,
|
|
49
50
|
encodeAttestationBlob: () => encodeAttestationBlob,
|
|
50
51
|
frameHash: () => frameHash,
|
|
51
52
|
getAllProfiles: () => getAllProfiles,
|
|
@@ -57,6 +58,7 @@ __export(index_exports, {
|
|
|
57
58
|
validateBoundsParams: () => validateBoundsParams,
|
|
58
59
|
validateContextParams: () => validateContextParams,
|
|
59
60
|
validateFrameParams: () => validateFrameParams,
|
|
61
|
+
validateSubject: () => validateSubject,
|
|
60
62
|
verify: () => verify,
|
|
61
63
|
verifyAttestation: () => verifyAttestation,
|
|
62
64
|
verifyAttestationSignature: () => verifyAttestationSignature,
|
|
@@ -136,6 +138,43 @@ function computeIntentDisclosureHash(intentCiphertext, approversFrozen) {
|
|
|
136
138
|
return `sha256:${sha256Hex2(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
// src/identity.ts
|
|
142
|
+
function validateSubject(subject) {
|
|
143
|
+
const errors = [];
|
|
144
|
+
const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
|
|
145
|
+
if (!did) errors.push("subject.did is required");
|
|
146
|
+
if (assurance === "low") {
|
|
147
|
+
if (method !== "self_declared") errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
148
|
+
if (trust_root !== "self") errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
|
|
149
|
+
if (disclose) errors.push("low assurance MUST NOT disclose a name");
|
|
150
|
+
} else if (assurance === "high") {
|
|
151
|
+
if (method === "as_vouched") {
|
|
152
|
+
if (trust_root !== "as") errors.push('as_vouched requires trust_root "as"');
|
|
153
|
+
if (!verifier) errors.push("as_vouched requires a verifier");
|
|
154
|
+
} else if (method === "eudi") {
|
|
155
|
+
if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
|
|
156
|
+
if (!verifier) errors.push("eudi requires a verifier");
|
|
157
|
+
if (!owner_signature) errors.push("eudi requires an owner_signature");
|
|
158
|
+
} else {
|
|
159
|
+
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
160
|
+
}
|
|
161
|
+
if (disclose && typeof disclose.name !== "string") errors.push("disclose.name must be a string");
|
|
162
|
+
} else {
|
|
163
|
+
errors.push(`assurance must be "low" or "high", got "${assurance}"`);
|
|
164
|
+
}
|
|
165
|
+
return { valid: errors.length === 0, errors };
|
|
166
|
+
}
|
|
167
|
+
function deriveIdentityLine(subject, { operatorName }) {
|
|
168
|
+
const name = subject?.assurance === "high" ? subject.disclose?.name : void 0;
|
|
169
|
+
if (!name) {
|
|
170
|
+
return `Sent by an AI agent via ${operatorName}`;
|
|
171
|
+
}
|
|
172
|
+
if (subject.method === "eudi") {
|
|
173
|
+
return `Sent by an AI agent of ${name} \u2014 identity verified (EUDI)`;
|
|
174
|
+
}
|
|
175
|
+
return `Sent by an AI agent of ${name} \u2014 verified by ${operatorName}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
139
178
|
// src/frame.ts
|
|
140
179
|
var import_crypto3 = require("crypto");
|
|
141
180
|
function validateFrameParams(params, profile) {
|
|
@@ -807,6 +846,7 @@ function resolveCumulativeFields(request, profile, executionLog, now) {
|
|
|
807
846
|
computeIntentHash,
|
|
808
847
|
contentCanonicalBytes,
|
|
809
848
|
decodeAttestationBlob,
|
|
849
|
+
deriveIdentityLine,
|
|
810
850
|
encodeAttestationBlob,
|
|
811
851
|
frameHash,
|
|
812
852
|
getAllProfiles,
|
|
@@ -818,6 +858,7 @@ function resolveCumulativeFields(request, profile, executionLog, now) {
|
|
|
818
858
|
validateBoundsParams,
|
|
819
859
|
validateContextParams,
|
|
820
860
|
validateFrameParams,
|
|
861
|
+
validateSubject,
|
|
821
862
|
verify,
|
|
822
863
|
verifyAttestation,
|
|
823
864
|
verifyAttestationSignature,
|
package/dist/index.mjs
CHANGED
|
@@ -67,6 +67,43 @@ function computeIntentDisclosureHash(intentCiphertext, approversFrozen) {
|
|
|
67
67
|
return `sha256:${sha256Hex2(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/identity.ts
|
|
71
|
+
function validateSubject(subject) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
|
|
74
|
+
if (!did) errors.push("subject.did is required");
|
|
75
|
+
if (assurance === "low") {
|
|
76
|
+
if (method !== "self_declared") errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
77
|
+
if (trust_root !== "self") errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
|
|
78
|
+
if (disclose) errors.push("low assurance MUST NOT disclose a name");
|
|
79
|
+
} else if (assurance === "high") {
|
|
80
|
+
if (method === "as_vouched") {
|
|
81
|
+
if (trust_root !== "as") errors.push('as_vouched requires trust_root "as"');
|
|
82
|
+
if (!verifier) errors.push("as_vouched requires a verifier");
|
|
83
|
+
} else if (method === "eudi") {
|
|
84
|
+
if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
|
|
85
|
+
if (!verifier) errors.push("eudi requires a verifier");
|
|
86
|
+
if (!owner_signature) errors.push("eudi requires an owner_signature");
|
|
87
|
+
} else {
|
|
88
|
+
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
89
|
+
}
|
|
90
|
+
if (disclose && typeof disclose.name !== "string") errors.push("disclose.name must be a string");
|
|
91
|
+
} else {
|
|
92
|
+
errors.push(`assurance must be "low" or "high", got "${assurance}"`);
|
|
93
|
+
}
|
|
94
|
+
return { valid: errors.length === 0, errors };
|
|
95
|
+
}
|
|
96
|
+
function deriveIdentityLine(subject, { operatorName }) {
|
|
97
|
+
const name = subject?.assurance === "high" ? subject.disclose?.name : void 0;
|
|
98
|
+
if (!name) {
|
|
99
|
+
return `Sent by an AI agent via ${operatorName}`;
|
|
100
|
+
}
|
|
101
|
+
if (subject.method === "eudi") {
|
|
102
|
+
return `Sent by an AI agent of ${name} \u2014 identity verified (EUDI)`;
|
|
103
|
+
}
|
|
104
|
+
return `Sent by an AI agent of ${name} \u2014 verified by ${operatorName}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
// src/frame.ts
|
|
71
108
|
import { createHash as createHash3 } from "crypto";
|
|
72
109
|
function validateFrameParams(params, profile) {
|
|
@@ -737,6 +774,7 @@ export {
|
|
|
737
774
|
computeIntentHash,
|
|
738
775
|
contentCanonicalBytes,
|
|
739
776
|
decodeAttestationBlob,
|
|
777
|
+
deriveIdentityLine,
|
|
740
778
|
encodeAttestationBlob,
|
|
741
779
|
frameHash,
|
|
742
780
|
getAllProfiles,
|
|
@@ -748,6 +786,7 @@ export {
|
|
|
748
786
|
validateBoundsParams,
|
|
749
787
|
validateContextParams,
|
|
750
788
|
validateFrameParams,
|
|
789
|
+
validateSubject,
|
|
751
790
|
verify,
|
|
752
791
|
verifyAttestation,
|
|
753
792
|
verifyAttestationSignature,
|
package/package.json
CHANGED
package/src/identity.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Assurance (HAP v0.6) — validation + footer derivation for the signed
|
|
3
|
+
* {@link Subject} overlay. See review.md → "Identity Assurance".
|
|
4
|
+
*
|
|
5
|
+
* Two pure helpers, the single source of truth for both the gateway footer and
|
|
6
|
+
* the AS verify page:
|
|
7
|
+
* - {@link validateSubject} — enforces the level/method/trust-root invariants.
|
|
8
|
+
* - {@link deriveIdentityLine} — the human-readable footer line for a subject.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Subject } from './types';
|
|
12
|
+
|
|
13
|
+
export interface SubjectValidation {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enforce the Identity-Assurance invariants on a single subject:
|
|
20
|
+
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
21
|
+
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
22
|
+
* - `eudi` ⇒ `high` / `external`, `verifier` + `owner_signature` required.
|
|
23
|
+
* - `disclose.name` only at `assurance:"high"`.
|
|
24
|
+
*/
|
|
25
|
+
export function validateSubject(subject: Subject): SubjectValidation {
|
|
26
|
+
const errors: string[] = [];
|
|
27
|
+
const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
|
|
28
|
+
|
|
29
|
+
if (!did) errors.push('subject.did is required');
|
|
30
|
+
|
|
31
|
+
if (assurance === 'low') {
|
|
32
|
+
if (method !== 'self_declared') errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
33
|
+
if (trust_root !== 'self') errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
|
|
34
|
+
if (disclose) errors.push('low assurance MUST NOT disclose a name');
|
|
35
|
+
} else if (assurance === 'high') {
|
|
36
|
+
if (method === 'as_vouched') {
|
|
37
|
+
if (trust_root !== 'as') errors.push('as_vouched requires trust_root "as"');
|
|
38
|
+
if (!verifier) errors.push('as_vouched requires a verifier');
|
|
39
|
+
} else if (method === 'eudi') {
|
|
40
|
+
if (trust_root !== 'external') errors.push('eudi requires trust_root "external"');
|
|
41
|
+
if (!verifier) errors.push('eudi requires a verifier');
|
|
42
|
+
if (!owner_signature) errors.push('eudi requires an owner_signature');
|
|
43
|
+
} else {
|
|
44
|
+
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
45
|
+
}
|
|
46
|
+
if (disclose && typeof disclose.name !== 'string') errors.push('disclose.name must be a string');
|
|
47
|
+
} else {
|
|
48
|
+
errors.push(`assurance must be "low" or "high", got "${assurance as string}"`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { valid: errors.length === 0, errors };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The footer line for a subject — the ONE place this wording lives.
|
|
56
|
+
* The name appears only at `high` with a disclosed name; otherwise no name.
|
|
57
|
+
*
|
|
58
|
+
* - low / no disclosure → "Sent by an AI agent via «operator»"
|
|
59
|
+
* - high / as_vouched → "Sent by an AI agent of «name» — verified by «operator»"
|
|
60
|
+
* - high / eudi → "Sent by an AI agent of «name» — identity verified (EUDI)"
|
|
61
|
+
*
|
|
62
|
+
* `operatorName` is the rendered AS operator (e.g. "Suveren") — never hardcode it
|
|
63
|
+
* upstream; a different operator self-vouches under its own name.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveIdentityLine(
|
|
66
|
+
subject: Subject | undefined | null,
|
|
67
|
+
{ operatorName }: { operatorName: string },
|
|
68
|
+
): string {
|
|
69
|
+
const name = subject?.assurance === 'high' ? subject.disclose?.name : undefined;
|
|
70
|
+
if (!name) {
|
|
71
|
+
return `Sent by an AI agent via ${operatorName}`;
|
|
72
|
+
}
|
|
73
|
+
if (subject!.method === 'eudi') {
|
|
74
|
+
return `Sent by an AI agent of ${name} — identity verified (EUDI)`;
|
|
75
|
+
}
|
|
76
|
+
return `Sent by an AI agent of ${name} — verified by ${operatorName}`;
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -51,6 +51,14 @@ export interface AttestationPayload {
|
|
|
51
51
|
* without invalidating the attestation signature (companion invariant C2).
|
|
52
52
|
*/
|
|
53
53
|
intent_disclosure_hash?: string;
|
|
54
|
+
/**
|
|
55
|
+
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
56
|
+
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
57
|
+
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
58
|
+
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
59
|
+
* {@link deriveIdentityLine}.
|
|
60
|
+
*/
|
|
61
|
+
subjects?: Subject[];
|
|
54
62
|
/**
|
|
55
63
|
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
56
64
|
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
@@ -65,6 +73,37 @@ export interface AttestationPayload {
|
|
|
65
73
|
expires_at: number;
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
/**
|
|
77
|
+
* v0.6 Identity Assurance — a signed overlay binding a Decision Owner's verified
|
|
78
|
+
* real-world identity to the attestation, gated by HOW the identity was verified.
|
|
79
|
+
*
|
|
80
|
+
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
81
|
+
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
82
|
+
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
83
|
+
* owner's own signature, AS-independent). See review.md → "Identity Assurance".
|
|
84
|
+
*/
|
|
85
|
+
export interface Subject {
|
|
86
|
+
/** The Decision Owner DID this subject describes (matches an entry in resolved_owners). */
|
|
87
|
+
did: string;
|
|
88
|
+
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
89
|
+
assurance: 'low' | 'high';
|
|
90
|
+
/** How identity was established. */
|
|
91
|
+
method: 'self_declared' | 'as_vouched' | 'eudi';
|
|
92
|
+
/** Who vouches: `self` (owner's claim), `as` (operator), `external` (eID scheme). */
|
|
93
|
+
trust_root: 'self' | 'as' | 'external';
|
|
94
|
+
/** Verifier id — the AS operator (as_vouched) or the eID scheme (eudi). Required at `high`. */
|
|
95
|
+
verifier?: string;
|
|
96
|
+
/** Disclosed attributes. `name` present ONLY at `assurance:"high"` and when disclosure is on. */
|
|
97
|
+
disclose?: { name: string };
|
|
98
|
+
/** When the underlying verification was performed (unix seconds). */
|
|
99
|
+
verified_at?: number;
|
|
100
|
+
/**
|
|
101
|
+
* eudi only (Phase 2) — the owner's per-event wallet signature, making the
|
|
102
|
+
* identity claim non-repudiable independent of the AS. Null/absent otherwise.
|
|
103
|
+
*/
|
|
104
|
+
owner_signature?: string | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
68
107
|
export interface Attestation {
|
|
69
108
|
header: AttestationHeader;
|
|
70
109
|
payload: AttestationPayload;
|