@auths-dev/sdk 0.0.1 → 0.1.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.
Files changed (60) hide show
  1. package/Cargo.toml +45 -0
  2. package/README.md +163 -4
  3. package/__test__/client.spec.ts +78 -0
  4. package/__test__/exports.spec.ts +57 -0
  5. package/__test__/integration.spec.ts +407 -0
  6. package/__test__/policy.spec.ts +202 -0
  7. package/__test__/verify.spec.ts +88 -0
  8. package/build.rs +5 -0
  9. package/index.d.ts +259 -0
  10. package/index.js +622 -1
  11. package/lib/artifacts.ts +124 -0
  12. package/lib/attestations.ts +126 -0
  13. package/lib/audit.ts +189 -0
  14. package/lib/client.ts +293 -0
  15. package/lib/commits.ts +70 -0
  16. package/lib/devices.ts +178 -0
  17. package/lib/errors.ts +306 -0
  18. package/lib/identity.ts +280 -0
  19. package/lib/index.ts +125 -0
  20. package/lib/native.ts +255 -0
  21. package/lib/org.ts +235 -0
  22. package/lib/pairing.ts +271 -0
  23. package/lib/policy.ts +669 -0
  24. package/lib/signing.ts +204 -0
  25. package/lib/trust.ts +152 -0
  26. package/lib/types.ts +179 -0
  27. package/lib/verify.ts +241 -0
  28. package/lib/witness.ts +91 -0
  29. package/npm/darwin-arm64/README.md +3 -0
  30. package/npm/darwin-arm64/package.json +23 -0
  31. package/npm/linux-arm64-gnu/README.md +3 -0
  32. package/npm/linux-arm64-gnu/package.json +26 -0
  33. package/npm/linux-x64-gnu/README.md +3 -0
  34. package/npm/linux-x64-gnu/package.json +26 -0
  35. package/npm/win32-arm64-msvc/README.md +3 -0
  36. package/npm/win32-arm64-msvc/package.json +23 -0
  37. package/npm/win32-x64-msvc/README.md +3 -0
  38. package/npm/win32-x64-msvc/package.json +23 -0
  39. package/package.json +51 -16
  40. package/src/artifact.rs +217 -0
  41. package/src/attestation_query.rs +104 -0
  42. package/src/audit.rs +128 -0
  43. package/src/commit_sign.rs +63 -0
  44. package/src/device.rs +212 -0
  45. package/src/diagnostics.rs +106 -0
  46. package/src/error.rs +5 -0
  47. package/src/helpers.rs +60 -0
  48. package/src/identity.rs +467 -0
  49. package/src/lib.rs +26 -0
  50. package/src/org.rs +430 -0
  51. package/src/pairing.rs +454 -0
  52. package/src/policy.rs +147 -0
  53. package/src/sign.rs +215 -0
  54. package/src/trust.rs +189 -0
  55. package/src/types.rs +205 -0
  56. package/src/verify.rs +447 -0
  57. package/src/witness.rs +138 -0
  58. package/tsconfig.json +19 -0
  59. package/typedoc.json +18 -0
  60. package/vitest.config.ts +12 -0
package/src/sign.rs ADDED
@@ -0,0 +1,215 @@
1
+ use auths_core::signing::{PrefilledPassphraseProvider, SecureSigner, StorageSigner};
2
+ use auths_core::storage::keychain::{KeyAlias, get_platform_keychain_with_config};
3
+ use auths_verifier::core::MAX_ATTESTATION_JSON_SIZE;
4
+ use auths_verifier::types::IdentityDID;
5
+ use napi_derive::napi;
6
+
7
+ use crate::error::format_error;
8
+ use crate::helpers::{make_env_config, resolve_passphrase};
9
+ use crate::types::{NapiActionEnvelope, NapiCommitSignResult};
10
+
11
+ fn make_signer(
12
+ passphrase: &str,
13
+ repo_path: &str,
14
+ ) -> napi::Result<(
15
+ StorageSigner<Box<dyn auths_core::storage::keychain::KeyStorage + Send + Sync>>,
16
+ PrefilledPassphraseProvider,
17
+ )> {
18
+ let env_config = make_env_config(passphrase, repo_path);
19
+ let keychain = get_platform_keychain_with_config(&env_config)
20
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
21
+ let signer = StorageSigner::new(keychain);
22
+ let provider = PrefilledPassphraseProvider::new(passphrase);
23
+ Ok((signer, provider))
24
+ }
25
+
26
+ #[napi]
27
+ pub fn sign_as_identity(
28
+ message: napi::bindgen_prelude::Buffer,
29
+ identity_did: String,
30
+ repo_path: String,
31
+ passphrase: Option<String>,
32
+ ) -> napi::Result<NapiCommitSignResult> {
33
+ let passphrase_str = resolve_passphrase(passphrase);
34
+ let (signer, provider) = make_signer(&passphrase_str, &repo_path)?;
35
+ let did =
36
+ IdentityDID::parse(&identity_did).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
37
+
38
+ let sig_bytes = signer
39
+ .sign_for_identity(&did, &provider, message.as_ref())
40
+ .map_err(|e| format_error("AUTHS_SIGNING_FAILED", format!("Signing failed: {e}")))?;
41
+
42
+ Ok(NapiCommitSignResult {
43
+ signature: hex::encode(sig_bytes),
44
+ signer_did: identity_did,
45
+ })
46
+ }
47
+
48
+ #[napi]
49
+ pub fn sign_action_as_identity(
50
+ action_type: String,
51
+ payload_json: String,
52
+ identity_did: String,
53
+ repo_path: String,
54
+ passphrase: Option<String>,
55
+ ) -> napi::Result<NapiActionEnvelope> {
56
+ if payload_json.len() > MAX_ATTESTATION_JSON_SIZE {
57
+ return Err(format_error(
58
+ "AUTHS_INVALID_INPUT",
59
+ format!(
60
+ "Payload JSON too large: {} bytes, max {MAX_ATTESTATION_JSON_SIZE}",
61
+ payload_json.len()
62
+ ),
63
+ ));
64
+ }
65
+
66
+ let payload: serde_json::Value = serde_json::from_str(&payload_json)
67
+ .map_err(|e| format_error("AUTHS_INVALID_INPUT", format!("Invalid payload JSON: {e}")))?;
68
+
69
+ #[allow(clippy::disallowed_methods)] // Presentation boundary
70
+ let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
71
+
72
+ let signing_data = serde_json::json!({
73
+ "version": "1.0",
74
+ "type": action_type,
75
+ "identity": identity_did,
76
+ "payload": payload,
77
+ "timestamp": &timestamp,
78
+ });
79
+
80
+ let canonical = json_canon::to_string(&signing_data).map_err(|e| {
81
+ format_error(
82
+ "AUTHS_SERIALIZATION_ERROR",
83
+ format!("Canonicalization failed: {e}"),
84
+ )
85
+ })?;
86
+
87
+ let passphrase_str = resolve_passphrase(passphrase);
88
+ let (signer, provider) = make_signer(&passphrase_str, &repo_path)?;
89
+ let did =
90
+ IdentityDID::parse(&identity_did).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
91
+
92
+ let sig_bytes = signer
93
+ .sign_for_identity(&did, &provider, canonical.as_bytes())
94
+ .map_err(|e| format_error("AUTHS_SIGNING_FAILED", format!("Signing failed: {e}")))?;
95
+
96
+ let sig_hex = hex::encode(sig_bytes);
97
+
98
+ let envelope = serde_json::json!({
99
+ "version": "1.0",
100
+ "type": action_type,
101
+ "identity": identity_did,
102
+ "payload": payload,
103
+ "timestamp": timestamp,
104
+ "signature": sig_hex,
105
+ });
106
+
107
+ let envelope_json = serde_json::to_string(&envelope).map_err(|e| {
108
+ format_error(
109
+ "AUTHS_SERIALIZATION_ERROR",
110
+ format!("Failed to serialize envelope: {e}"),
111
+ )
112
+ })?;
113
+
114
+ Ok(NapiActionEnvelope {
115
+ envelope_json,
116
+ signature_hex: sig_hex,
117
+ signer_did: identity_did,
118
+ })
119
+ }
120
+
121
+ #[napi]
122
+ pub fn sign_as_agent(
123
+ message: napi::bindgen_prelude::Buffer,
124
+ key_alias: String,
125
+ repo_path: String,
126
+ passphrase: Option<String>,
127
+ ) -> napi::Result<NapiCommitSignResult> {
128
+ let passphrase_str = resolve_passphrase(passphrase);
129
+ let (signer, provider) = make_signer(&passphrase_str, &repo_path)?;
130
+ let alias = KeyAlias::new(&key_alias)
131
+ .map_err(|e| format_error("AUTHS_KEY_NOT_FOUND", format!("Invalid key alias: {e}")))?;
132
+
133
+ let sig_bytes = signer
134
+ .sign_with_alias(&alias, &provider, message.as_ref())
135
+ .map_err(|e| format_error("AUTHS_SIGNING_FAILED", format!("Signing failed: {e}")))?;
136
+
137
+ Ok(NapiCommitSignResult {
138
+ signature: hex::encode(sig_bytes),
139
+ signer_did: key_alias,
140
+ })
141
+ }
142
+
143
+ #[napi]
144
+ pub fn sign_action_as_agent(
145
+ action_type: String,
146
+ payload_json: String,
147
+ key_alias: String,
148
+ agent_did: String,
149
+ repo_path: String,
150
+ passphrase: Option<String>,
151
+ ) -> napi::Result<NapiActionEnvelope> {
152
+ if payload_json.len() > MAX_ATTESTATION_JSON_SIZE {
153
+ return Err(format_error(
154
+ "AUTHS_INVALID_INPUT",
155
+ format!(
156
+ "Payload JSON too large: {} bytes, max {MAX_ATTESTATION_JSON_SIZE}",
157
+ payload_json.len()
158
+ ),
159
+ ));
160
+ }
161
+
162
+ let payload: serde_json::Value = serde_json::from_str(&payload_json)
163
+ .map_err(|e| format_error("AUTHS_INVALID_INPUT", format!("Invalid payload JSON: {e}")))?;
164
+
165
+ #[allow(clippy::disallowed_methods)]
166
+ let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
167
+
168
+ let signing_data = serde_json::json!({
169
+ "version": "1.0",
170
+ "type": action_type,
171
+ "identity": agent_did,
172
+ "payload": payload,
173
+ "timestamp": &timestamp,
174
+ });
175
+
176
+ let canonical = json_canon::to_string(&signing_data).map_err(|e| {
177
+ format_error(
178
+ "AUTHS_SERIALIZATION_ERROR",
179
+ format!("Canonicalization failed: {e}"),
180
+ )
181
+ })?;
182
+
183
+ let passphrase_str = resolve_passphrase(passphrase);
184
+ let (signer, provider) = make_signer(&passphrase_str, &repo_path)?;
185
+ let alias = KeyAlias::new(&key_alias)
186
+ .map_err(|e| format_error("AUTHS_KEY_NOT_FOUND", format!("Invalid key alias: {e}")))?;
187
+
188
+ let sig_bytes = signer
189
+ .sign_with_alias(&alias, &provider, canonical.as_bytes())
190
+ .map_err(|e| format_error("AUTHS_SIGNING_FAILED", format!("Signing failed: {e}")))?;
191
+
192
+ let sig_hex = hex::encode(sig_bytes);
193
+
194
+ let envelope = serde_json::json!({
195
+ "version": "1.0",
196
+ "type": action_type,
197
+ "identity": agent_did,
198
+ "payload": payload,
199
+ "timestamp": timestamp,
200
+ "signature": sig_hex,
201
+ });
202
+
203
+ let envelope_json = serde_json::to_string(&envelope).map_err(|e| {
204
+ format_error(
205
+ "AUTHS_SERIALIZATION_ERROR",
206
+ format!("Failed to serialize envelope: {e}"),
207
+ )
208
+ })?;
209
+
210
+ Ok(NapiActionEnvelope {
211
+ envelope_json,
212
+ signature_hex: sig_hex,
213
+ signer_did: agent_did,
214
+ })
215
+ }
package/src/trust.rs ADDED
@@ -0,0 +1,189 @@
1
+ use std::path::PathBuf;
2
+ use std::sync::Arc;
3
+
4
+ use auths_core::trust::pinned::{PinnedIdentity, PinnedIdentityStore, TrustLevel};
5
+ use auths_id::identity::resolve::{DefaultDidResolver, DidResolver, RegistryDidResolver};
6
+ use auths_storage::git::{GitRegistryBackend, RegistryConfig};
7
+ use auths_verifier::PublicKeyHex;
8
+ use napi_derive::napi;
9
+
10
+ use crate::error::format_error;
11
+
12
+ fn resolve_repo(repo_path: &str) -> PathBuf {
13
+ PathBuf::from(shellexpand::tilde(repo_path).as_ref())
14
+ }
15
+
16
+ fn store_path(repo_path: &str) -> PathBuf {
17
+ resolve_repo(repo_path).join("known_identities.json")
18
+ }
19
+
20
+ fn parse_trust_level(s: &str) -> napi::Result<TrustLevel> {
21
+ match s {
22
+ "tofu" => Ok(TrustLevel::Tofu),
23
+ "manual" => Ok(TrustLevel::Manual),
24
+ "org_policy" => Ok(TrustLevel::OrgPolicy),
25
+ _ => Err(format_error(
26
+ "AUTHS_INVALID_INPUT",
27
+ format!(
28
+ "Invalid trust_level '{}': must be one of 'tofu', 'manual', 'org_policy'",
29
+ s
30
+ ),
31
+ )),
32
+ }
33
+ }
34
+
35
+ fn trust_level_str(tl: &TrustLevel) -> &'static str {
36
+ match tl {
37
+ TrustLevel::Tofu => "tofu",
38
+ TrustLevel::Manual => "manual",
39
+ TrustLevel::OrgPolicy => "org_policy",
40
+ }
41
+ }
42
+
43
+ #[napi(object)]
44
+ #[derive(Clone)]
45
+ pub struct NapiPinnedIdentity {
46
+ pub did: String,
47
+ pub label: Option<String>,
48
+ pub trust_level: String,
49
+ pub first_seen: String,
50
+ pub kel_sequence: Option<u32>,
51
+ pub pinned_at: String,
52
+ }
53
+
54
+ #[napi]
55
+ pub fn pin_identity(
56
+ did: String,
57
+ repo_path: String,
58
+ label: Option<String>,
59
+ trust_level: Option<String>,
60
+ ) -> napi::Result<NapiPinnedIdentity> {
61
+ let tl = parse_trust_level(&trust_level.unwrap_or_else(|| "manual".to_string()))?;
62
+ let store = PinnedIdentityStore::new(store_path(&repo_path));
63
+ let repo = resolve_repo(&repo_path);
64
+
65
+ let resolved = DefaultDidResolver::with_repo(&repo)
66
+ .resolve(&did)
67
+ .or_else(|_| {
68
+ let backend: Arc<dyn auths_id::ports::registry::RegistryBackend + Send + Sync> =
69
+ Arc::new(GitRegistryBackend::from_config_unchecked(
70
+ RegistryConfig::single_tenant(&repo),
71
+ ));
72
+ RegistryDidResolver::new(backend).resolve(&did)
73
+ })
74
+ .map_err(|e| {
75
+ format_error(
76
+ "AUTHS_TRUST_ERROR",
77
+ format!("Cannot resolve public key for {did}: {e}"),
78
+ )
79
+ })?;
80
+ #[allow(clippy::disallowed_methods)] // INVARIANT: hex::encode always produces valid hex
81
+ let public_key_hex = PublicKeyHex::new_unchecked(hex::encode(resolved.public_key().as_bytes()));
82
+
83
+ #[allow(clippy::disallowed_methods)]
84
+ let now = chrono::Utc::now();
85
+
86
+ if let Ok(Some(existing)) = store.lookup(&did) {
87
+ let _ = store.remove(&did);
88
+ let pin = PinnedIdentity {
89
+ did: did.clone(),
90
+ public_key_hex: if public_key_hex.as_ref().is_empty() {
91
+ existing.public_key_hex
92
+ } else {
93
+ public_key_hex
94
+ },
95
+ kel_tip_said: existing.kel_tip_said,
96
+ kel_sequence: existing.kel_sequence,
97
+ first_seen: existing.first_seen,
98
+ origin: label.clone().unwrap_or(existing.origin),
99
+ trust_level: tl.clone(),
100
+ };
101
+ store
102
+ .pin(pin.clone())
103
+ .map_err(|e| format_error("AUTHS_TRUST_ERROR", e))?;
104
+ return Ok(NapiPinnedIdentity {
105
+ did: pin.did,
106
+ label,
107
+ trust_level: trust_level_str(&pin.trust_level).to_string(),
108
+ first_seen: pin.first_seen.to_rfc3339(),
109
+ kel_sequence: pin.kel_sequence.map(|s| s as u32),
110
+ pinned_at: now.to_rfc3339(),
111
+ });
112
+ }
113
+
114
+ let pin = PinnedIdentity {
115
+ did: did.clone(),
116
+ public_key_hex,
117
+ kel_tip_said: None,
118
+ kel_sequence: None,
119
+ first_seen: now,
120
+ origin: label.clone().unwrap_or_else(|| "manual".to_string()),
121
+ trust_level: tl.clone(),
122
+ };
123
+
124
+ store
125
+ .pin(pin)
126
+ .map_err(|e| format_error("AUTHS_TRUST_ERROR", e))?;
127
+
128
+ Ok(NapiPinnedIdentity {
129
+ did,
130
+ label,
131
+ trust_level: trust_level_str(&tl).to_string(),
132
+ first_seen: now.to_rfc3339(),
133
+ kel_sequence: None,
134
+ pinned_at: now.to_rfc3339(),
135
+ })
136
+ }
137
+
138
+ #[napi]
139
+ pub fn remove_pinned_identity(did: String, repo_path: String) -> napi::Result<()> {
140
+ let store = PinnedIdentityStore::new(store_path(&repo_path));
141
+ store
142
+ .remove(&did)
143
+ .map_err(|e| format_error("AUTHS_TRUST_ERROR", e))?;
144
+ Ok(())
145
+ }
146
+
147
+ #[napi]
148
+ pub fn list_pinned_identities(repo_path: String) -> napi::Result<String> {
149
+ let store = PinnedIdentityStore::new(store_path(&repo_path));
150
+ let entries = store
151
+ .list()
152
+ .map_err(|e| format_error("AUTHS_TRUST_ERROR", e))?;
153
+
154
+ let json_entries: Vec<serde_json::Value> = entries
155
+ .iter()
156
+ .map(|e| {
157
+ serde_json::json!({
158
+ "did": e.did,
159
+ "label": e.origin,
160
+ "trust_level": trust_level_str(&e.trust_level),
161
+ "first_seen": e.first_seen.to_rfc3339(),
162
+ "kel_sequence": e.kel_sequence,
163
+ "pinned_at": e.first_seen.to_rfc3339(),
164
+ })
165
+ })
166
+ .collect();
167
+
168
+ serde_json::to_string(&json_entries).map_err(|e| format_error("AUTHS_TRUST_ERROR", e))
169
+ }
170
+
171
+ #[napi]
172
+ pub fn get_pinned_identity(
173
+ did: String,
174
+ repo_path: String,
175
+ ) -> napi::Result<Option<NapiPinnedIdentity>> {
176
+ let store = PinnedIdentityStore::new(store_path(&repo_path));
177
+ let entry = store
178
+ .lookup(&did)
179
+ .map_err(|e| format_error("AUTHS_TRUST_ERROR", e))?;
180
+
181
+ Ok(entry.map(|e| NapiPinnedIdentity {
182
+ did: e.did,
183
+ label: Some(e.origin),
184
+ trust_level: trust_level_str(&e.trust_level).to_string(),
185
+ first_seen: e.first_seen.to_rfc3339(),
186
+ kel_sequence: e.kel_sequence.map(|s| s as u32),
187
+ pinned_at: e.first_seen.to_rfc3339(),
188
+ }))
189
+ }
package/src/types.rs ADDED
@@ -0,0 +1,205 @@
1
+ use napi_derive::napi;
2
+
3
+ use auths_verifier::types::{
4
+ ChainLink as RustChainLink, VerificationReport as RustVerificationReport,
5
+ VerificationStatus as RustVerificationStatus,
6
+ };
7
+
8
+ #[napi(object)]
9
+ #[derive(Clone)]
10
+ pub struct NapiVerificationResult {
11
+ pub valid: bool,
12
+ pub error: Option<String>,
13
+ pub error_code: Option<String>,
14
+ }
15
+
16
+ #[napi(object)]
17
+ #[derive(Clone)]
18
+ pub struct NapiVerificationStatus {
19
+ pub status_type: String,
20
+ pub at: Option<String>,
21
+ pub step: Option<u32>,
22
+ pub missing_link: Option<String>,
23
+ pub required: Option<u32>,
24
+ pub verified: Option<u32>,
25
+ }
26
+
27
+ impl NapiVerificationStatus {
28
+ pub fn is_valid(&self) -> bool {
29
+ self.status_type == "Valid"
30
+ }
31
+ }
32
+
33
+ impl From<RustVerificationStatus> for NapiVerificationStatus {
34
+ fn from(status: RustVerificationStatus) -> Self {
35
+ match status {
36
+ RustVerificationStatus::Valid => NapiVerificationStatus {
37
+ status_type: "Valid".to_string(),
38
+ at: None,
39
+ step: None,
40
+ missing_link: None,
41
+ required: None,
42
+ verified: None,
43
+ },
44
+ RustVerificationStatus::Expired { at } => NapiVerificationStatus {
45
+ status_type: "Expired".to_string(),
46
+ at: Some(at.to_rfc3339()),
47
+ step: None,
48
+ missing_link: None,
49
+ required: None,
50
+ verified: None,
51
+ },
52
+ RustVerificationStatus::Revoked { at } => NapiVerificationStatus {
53
+ status_type: "Revoked".to_string(),
54
+ at: at.map(|t| t.to_rfc3339()),
55
+ step: None,
56
+ missing_link: None,
57
+ required: None,
58
+ verified: None,
59
+ },
60
+ RustVerificationStatus::InvalidSignature { step } => NapiVerificationStatus {
61
+ status_type: "InvalidSignature".to_string(),
62
+ at: None,
63
+ step: Some(step as u32),
64
+ missing_link: None,
65
+ required: None,
66
+ verified: None,
67
+ },
68
+ RustVerificationStatus::BrokenChain { missing_link } => NapiVerificationStatus {
69
+ status_type: "BrokenChain".to_string(),
70
+ at: None,
71
+ step: None,
72
+ missing_link: Some(missing_link),
73
+ required: None,
74
+ verified: None,
75
+ },
76
+ RustVerificationStatus::InsufficientWitnesses { required, verified } => {
77
+ NapiVerificationStatus {
78
+ status_type: "InsufficientWitnesses".to_string(),
79
+ at: None,
80
+ step: None,
81
+ missing_link: None,
82
+ required: Some(required as u32),
83
+ verified: Some(verified as u32),
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ #[napi(object)]
91
+ #[derive(Clone)]
92
+ pub struct NapiChainLink {
93
+ pub issuer: String,
94
+ pub subject: String,
95
+ pub valid: bool,
96
+ pub error: Option<String>,
97
+ }
98
+
99
+ impl From<RustChainLink> for NapiChainLink {
100
+ fn from(link: RustChainLink) -> Self {
101
+ NapiChainLink {
102
+ issuer: link.issuer,
103
+ subject: link.subject,
104
+ valid: link.valid,
105
+ error: link.error,
106
+ }
107
+ }
108
+ }
109
+
110
+ #[napi(object)]
111
+ #[derive(Clone)]
112
+ pub struct NapiVerificationReport {
113
+ pub status: NapiVerificationStatus,
114
+ pub chain: Vec<NapiChainLink>,
115
+ pub warnings: Vec<String>,
116
+ }
117
+
118
+ impl NapiVerificationReport {
119
+ pub fn is_valid(&self) -> bool {
120
+ self.status.is_valid()
121
+ }
122
+ }
123
+
124
+ impl From<RustVerificationReport> for NapiVerificationReport {
125
+ fn from(report: RustVerificationReport) -> Self {
126
+ NapiVerificationReport {
127
+ status: report.status.into(),
128
+ chain: report.chain.into_iter().map(|l| l.into()).collect(),
129
+ warnings: report.warnings,
130
+ }
131
+ }
132
+ }
133
+
134
+ // Identity types
135
+
136
+ #[napi(object)]
137
+ #[derive(Clone)]
138
+ pub struct NapiIdentityResult {
139
+ pub did: String,
140
+ pub key_alias: String,
141
+ pub public_key_hex: String,
142
+ }
143
+
144
+ #[napi(object)]
145
+ #[derive(Clone)]
146
+ pub struct NapiAgentIdentityBundle {
147
+ pub agent_did: String,
148
+ pub key_alias: String,
149
+ pub attestation_json: String,
150
+ pub public_key_hex: String,
151
+ pub repo_path: Option<String>,
152
+ }
153
+
154
+ #[napi(object)]
155
+ #[derive(Clone)]
156
+ pub struct NapiDelegatedAgentBundle {
157
+ pub agent_did: String,
158
+ pub key_alias: String,
159
+ pub attestation_json: String,
160
+ pub public_key_hex: String,
161
+ pub repo_path: Option<String>,
162
+ }
163
+
164
+ #[napi(object)]
165
+ #[derive(Clone)]
166
+ pub struct NapiRotationResult {
167
+ pub controller_did: String,
168
+ pub new_key_fingerprint: String,
169
+ pub previous_key_fingerprint: String,
170
+ pub sequence: i64,
171
+ }
172
+
173
+ // Device types
174
+
175
+ #[napi(object)]
176
+ #[derive(Clone)]
177
+ pub struct NapiLinkResult {
178
+ pub device_did: String,
179
+ pub attestation_id: String,
180
+ }
181
+
182
+ #[napi(object)]
183
+ #[derive(Clone)]
184
+ pub struct NapiExtensionResult {
185
+ pub device_did: String,
186
+ pub new_expires_at: String,
187
+ pub previous_expires_at: Option<String>,
188
+ }
189
+
190
+ // Signing types
191
+
192
+ #[napi(object)]
193
+ #[derive(Clone)]
194
+ pub struct NapiCommitSignResult {
195
+ pub signature: String,
196
+ pub signer_did: String,
197
+ }
198
+
199
+ #[napi(object)]
200
+ #[derive(Clone)]
201
+ pub struct NapiActionEnvelope {
202
+ pub envelope_json: String,
203
+ pub signature_hex: String,
204
+ pub signer_did: String,
205
+ }