@casimesh/sdk 0.1.0 → 0.2.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/index.d.ts +1490 -30
- package/index.js +300 -15
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -27,7 +27,10 @@ var ID_PREFIX = {
|
|
|
27
27
|
principal: "prn",
|
|
28
28
|
roleBinding: "rol",
|
|
29
29
|
apiKey: "key",
|
|
30
|
-
task: "tsk"
|
|
30
|
+
task: "tsk",
|
|
31
|
+
token: "tok",
|
|
32
|
+
audit: "aud",
|
|
33
|
+
revocation: "rev"
|
|
31
34
|
};
|
|
32
35
|
var ULID_PATTERN = "[0-9A-HJKMNP-TV-Z]{26}";
|
|
33
36
|
var prefixedId = (kind) => z.string().regex(new RegExp(`^${ID_PREFIX[kind]}_${ULID_PATTERN}$`), `Expected a ${kind} id (${ID_PREFIX[kind]}_<ULID>)`).meta({ description: `${kind} identifier`, example: `${ID_PREFIX[kind]}_01J9Z3K7Q8R5T2V4W6X8Y0A1B2` });
|
|
@@ -40,6 +43,10 @@ var CommandId = prefixedId("command");
|
|
|
40
43
|
var PrincipalId = prefixedId("principal");
|
|
41
44
|
var RoleBindingId = prefixedId("roleBinding");
|
|
42
45
|
var TaskId = prefixedId("task");
|
|
46
|
+
var TokenId = prefixedId("token");
|
|
47
|
+
var AuditId = prefixedId("audit");
|
|
48
|
+
var RevocationId = prefixedId("revocation");
|
|
49
|
+
var ApiKeyId = prefixedId("apiKey");
|
|
43
50
|
|
|
44
51
|
// ../contracts/src/agent.ts
|
|
45
52
|
var AgentKind = z.enum(["standard", "mesh"]);
|
|
@@ -70,6 +77,52 @@ z.object({
|
|
|
70
77
|
model: z.string().max(100).nullable().optional(),
|
|
71
78
|
metadata: Metadata.optional()
|
|
72
79
|
}).refine((body) => Object.keys(body).length > 0, "At least one field must be provided").meta({ id: "UpdateAgentRequest" });
|
|
80
|
+
var AuditKind = z.enum([
|
|
81
|
+
"authorization",
|
|
82
|
+
"authentication",
|
|
83
|
+
"credential",
|
|
84
|
+
"principal",
|
|
85
|
+
"policy",
|
|
86
|
+
"replication"
|
|
87
|
+
]);
|
|
88
|
+
var AuditOutcome = z.enum(["allowed", "denied", "done"]);
|
|
89
|
+
var AuditTarget = z.object({
|
|
90
|
+
nodeId: z.string().optional(),
|
|
91
|
+
organizationId: z.string().optional(),
|
|
92
|
+
/** The entity, credential or event concerned, when there is one. */
|
|
93
|
+
id: z.string().optional()
|
|
94
|
+
});
|
|
95
|
+
z.object({
|
|
96
|
+
/** 1, 2, 3… with no gaps: a missing number is a removed entry. */
|
|
97
|
+
seq: z.int().min(1),
|
|
98
|
+
id: AuditId,
|
|
99
|
+
at: Timestamp,
|
|
100
|
+
kind: AuditKind,
|
|
101
|
+
/** What was attempted or done, e.g. `agent.create`, `token.issue`, `event.refused`. */
|
|
102
|
+
action: z.string().max(64),
|
|
103
|
+
outcome: AuditOutcome,
|
|
104
|
+
principalId: PrincipalId.nullable(),
|
|
105
|
+
credentialId: z.string().nullable(),
|
|
106
|
+
target: AuditTarget,
|
|
107
|
+
detail: z.string().max(500),
|
|
108
|
+
/** SHA-256 (hex) of the previous entry; 64 zeros for the first. */
|
|
109
|
+
prevHash: z.string().regex(/^[0-9a-f]{64}$/),
|
|
110
|
+
/** SHA-256 (hex) of this entry's canonical JSON without `hash` and `sig`. */
|
|
111
|
+
hash: z.string().regex(/^[0-9a-f]{64}$/),
|
|
112
|
+
/** The node key's Ed25519 signature over `hash`. */
|
|
113
|
+
sig: z.string(),
|
|
114
|
+
/** The node key that signed (keys rotate; ADR-0025). Not part of `hash`; null for entries from before rotation. */
|
|
115
|
+
signer: z.string().nullable()
|
|
116
|
+
}).meta({ id: "AuditEntry", description: "One signed, hash-chained record of the node's audit log." });
|
|
117
|
+
z.object({
|
|
118
|
+
valid: z.boolean(),
|
|
119
|
+
entries: z.int().min(0),
|
|
120
|
+
/** The first entry that failed, or null when the whole chain holds. */
|
|
121
|
+
brokenAt: z.int().nullable(),
|
|
122
|
+
reason: z.string().nullable(),
|
|
123
|
+
/** The node's current key; entries may also be signed by its earlier keys. */
|
|
124
|
+
publicKey: z.string()
|
|
125
|
+
}).meta({ id: "AuditVerification" });
|
|
73
126
|
var PrincipalKind = z.enum(["user", "service"]);
|
|
74
127
|
var Principal = z.object({
|
|
75
128
|
id: PrincipalId,
|
|
@@ -78,7 +131,9 @@ var Principal = z.object({
|
|
|
78
131
|
publicKey: z.string().nullable(),
|
|
79
132
|
createdAt: Timestamp
|
|
80
133
|
}).meta({ id: "Principal", description: "A user or service that can be authenticated and granted roles." });
|
|
81
|
-
var Role = z.enum(["node_owner", "org_owner"]);
|
|
134
|
+
var Role = z.enum(["node_owner", "org_owner", "viewer", "auditor", "agent_operator"]);
|
|
135
|
+
var NODE_ROLES = ["node_owner", "viewer", "auditor"];
|
|
136
|
+
var TokenScope = z.enum(["read", "write", "admin"]);
|
|
82
137
|
var RoleScope = z.discriminatedUnion("type", [
|
|
83
138
|
z.object({ type: z.literal("node"), nodeId: NodeId }),
|
|
84
139
|
z.object({ type: z.literal("organization"), organizationId: OrganizationId, nodeId: NodeId })
|
|
@@ -90,10 +145,15 @@ var RoleBinding = z.object({
|
|
|
90
145
|
scope: RoleScope,
|
|
91
146
|
createdAt: Timestamp
|
|
92
147
|
}).refine(
|
|
93
|
-
(b) => b.role
|
|
94
|
-
"node_owner
|
|
148
|
+
(b) => NODE_ROLES.includes(b.role) ? b.scope.type === "node" : b.scope.type === "organization",
|
|
149
|
+
"node_owner, viewer and auditor are node-scoped; org_owner and agent_operator organization-scoped"
|
|
95
150
|
).meta({ id: "RoleBinding" });
|
|
96
151
|
z.object({ principalId: PrincipalId }).meta({ id: "GrantOwnerRequest" });
|
|
152
|
+
var RoleTarget = z.discriminatedUnion("type", [
|
|
153
|
+
z.object({ type: z.literal("node"), nodeId: NodeId }),
|
|
154
|
+
z.object({ type: z.literal("organization"), organizationId: OrganizationId })
|
|
155
|
+
]);
|
|
156
|
+
z.object({ principalId: PrincipalId, role: Role, target: RoleTarget }).meta({ id: "GrantRoleRequest" });
|
|
97
157
|
var TransportKind = z.enum(["memory", "ws-peer", "tcp", "mqtt", "meshtastic", "meshcore"]);
|
|
98
158
|
var LinkState = z.enum(["requested", "up", "down"]);
|
|
99
159
|
var LinkQuality = z.object({
|
|
@@ -111,6 +171,29 @@ var Link = z.object({
|
|
|
111
171
|
...EntityAudit
|
|
112
172
|
}).refine((link) => link.aNodeId !== link.bNodeId, "A link must connect two different nodes").meta({ id: "Link", description: "Transport-level adjacency between two nodes \u2014 the edges of the topology." });
|
|
113
173
|
z.object({ aNodeId: NodeId, bNodeId: NodeId, transport: TransportKind }).refine((body) => body.aNodeId !== body.bNodeId, "A link must connect two different nodes").meta({ id: "CreateLinkRequest" });
|
|
174
|
+
var Ed25519Key = z.string().startsWith("ed25519:");
|
|
175
|
+
var NodeCertificate = z.object({
|
|
176
|
+
v: z.literal(1),
|
|
177
|
+
nodeId: NodeId,
|
|
178
|
+
publicKey: Ed25519Key,
|
|
179
|
+
issuedAt: Timestamp,
|
|
180
|
+
expiresAt: Timestamp,
|
|
181
|
+
/** The mesh root's public key. */
|
|
182
|
+
rootKey: Ed25519Key,
|
|
183
|
+
/** The root's signature over the canonical JSON of every other field. */
|
|
184
|
+
sig: z.string()
|
|
185
|
+
}).meta({ id: "NodeCertificate", description: "A node key certified by the mesh root (ADR-0025)." });
|
|
186
|
+
var KeyRevocation = z.object({
|
|
187
|
+
v: z.literal(1),
|
|
188
|
+
publicKey: Ed25519Key,
|
|
189
|
+
nodeId: NodeId,
|
|
190
|
+
revokedAt: Timestamp,
|
|
191
|
+
reason: z.string().max(500),
|
|
192
|
+
rootKey: Ed25519Key,
|
|
193
|
+
sig: z.string()
|
|
194
|
+
}).meta({ id: "KeyRevocation", description: "A node key revoked by the mesh root (ADR-0025)." });
|
|
195
|
+
|
|
196
|
+
// ../contracts/src/node.ts
|
|
114
197
|
var NodeStatus = z.enum(["online", "degraded", "offline"]);
|
|
115
198
|
var Node = z.object({
|
|
116
199
|
id: NodeId,
|
|
@@ -121,6 +204,8 @@ var Node = z.object({
|
|
|
121
204
|
status: NodeStatus,
|
|
122
205
|
metadata: Metadata,
|
|
123
206
|
lastSeenAt: Timestamp.nullable(),
|
|
207
|
+
/** The mesh root's certificate for `publicKey`, in a rooted mesh (ADR-0025). */
|
|
208
|
+
certificate: NodeCertificate.nullable().default(null),
|
|
124
209
|
...EntityAudit
|
|
125
210
|
}).meta({ id: "Node", description: "A physical or virtual endpoint connected to the mesh." });
|
|
126
211
|
z.object({
|
|
@@ -128,12 +213,75 @@ z.object({
|
|
|
128
213
|
endpoint: z.url().nullable().optional(),
|
|
129
214
|
metadata: Metadata.optional()
|
|
130
215
|
}).refine((body) => Object.keys(body).length > 0, "At least one field must be provided").meta({ id: "UpdateNodeRequest" });
|
|
216
|
+
var Effect = z.enum(["allow", "deny"]);
|
|
217
|
+
var NamePattern = z.string().regex(/^(\*|[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*)*(?:\.\*)?)$/i, "An exact name, `prefix.*` or `*`").max(100);
|
|
218
|
+
var CapabilityRule = z.object({
|
|
219
|
+
effect: Effect,
|
|
220
|
+
capability: NamePattern,
|
|
221
|
+
organizationId: OrganizationId.optional(),
|
|
222
|
+
nodeId: NodeId.optional()
|
|
223
|
+
});
|
|
224
|
+
var ToolRule = z.object({ effect: Effect, tool: NamePattern, agentId: AgentId.optional() });
|
|
225
|
+
var section = (rule) => z.object({ default: Effect.default("allow"), rules: z.array(rule).max(100).default([]) });
|
|
226
|
+
var OrganizationPolicy = z.object({
|
|
227
|
+
inbound: section(CapabilityRule).default({ default: "allow", rules: [] }),
|
|
228
|
+
outbound: section(CapabilityRule).default({ default: "allow", rules: [] }),
|
|
229
|
+
tools: section(ToolRule).default({ default: "allow", rules: [] })
|
|
230
|
+
}).meta({
|
|
231
|
+
id: "OrganizationPolicy",
|
|
232
|
+
description: "Who may request which capabilities, whom its agents may ask, and which tools they may use."
|
|
233
|
+
});
|
|
234
|
+
var OPEN_POLICY = {
|
|
235
|
+
inbound: { default: "allow", rules: [] },
|
|
236
|
+
outbound: { default: "allow", rules: [] },
|
|
237
|
+
tools: { default: "allow", rules: [] }
|
|
238
|
+
};
|
|
239
|
+
function matchesPattern(pattern, name) {
|
|
240
|
+
if (pattern === "*") return true;
|
|
241
|
+
if (pattern.endsWith(".*")) return name.startsWith(pattern.slice(0, -1));
|
|
242
|
+
return pattern === name;
|
|
243
|
+
}
|
|
244
|
+
function decide(policySection, matches) {
|
|
245
|
+
const matching = policySection.rules.filter(matches);
|
|
246
|
+
const deny = matching.find((r) => r.effect === "deny");
|
|
247
|
+
if (deny) return { allowed: false, rule: deny };
|
|
248
|
+
const allow = matching.find((r) => r.effect === "allow");
|
|
249
|
+
if (allow) return { allowed: true, rule: allow };
|
|
250
|
+
return { allowed: policySection.default === "allow", rule: void 0 };
|
|
251
|
+
}
|
|
252
|
+
var party = (rule, other) => (rule.organizationId === void 0 || rule.organizationId === other.organizationId) && (rule.nodeId === void 0 || rule.nodeId === other.nodeId);
|
|
253
|
+
function mayRequest(requester, provider, capability) {
|
|
254
|
+
if (requester.id === provider.id) return { allowed: true, rule: void 0 };
|
|
255
|
+
const outbound = decide(
|
|
256
|
+
requester.policy.outbound,
|
|
257
|
+
(r) => matchesPattern(r.capability, capability) && party(r, { organizationId: provider.id, nodeId: provider.nodeId })
|
|
258
|
+
);
|
|
259
|
+
if (!outbound.allowed) return outbound;
|
|
260
|
+
return decide(
|
|
261
|
+
provider.policy.inbound,
|
|
262
|
+
(r) => matchesPattern(r.capability, capability) && party(r, { organizationId: requester.id, nodeId: requester.nodeId })
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
function inboundAllows(provider, requester, capability) {
|
|
266
|
+
if (requester.organizationId === provider.id) return { allowed: true, rule: void 0 };
|
|
267
|
+
return decide(provider.policy.inbound, (r) => matchesPattern(r.capability, capability) && party(r, requester));
|
|
268
|
+
}
|
|
269
|
+
function mayUseTool(organization, agentId, tool) {
|
|
270
|
+
return decide(
|
|
271
|
+
organization.policy.tools,
|
|
272
|
+
(r) => matchesPattern(r.tool, tool) && (r.agentId === void 0 || r.agentId === agentId)
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ../contracts/src/organization.ts
|
|
131
277
|
var Organization = z.object({
|
|
132
278
|
id: OrganizationId,
|
|
133
279
|
nodeId: NodeId,
|
|
134
280
|
name: DisplayName,
|
|
135
281
|
slug: Slug,
|
|
136
282
|
metadata: Metadata,
|
|
283
|
+
/** Capability and tool policy (ADR-0023). Organizations from before it have the open policy. */
|
|
284
|
+
policy: OrganizationPolicy.default(OPEN_POLICY),
|
|
137
285
|
...EntityAudit
|
|
138
286
|
}).meta({ id: "Organization", description: "A logical boundary on a node that owns policies, identities and agents." });
|
|
139
287
|
z.object({
|
|
@@ -146,7 +294,8 @@ z.object({
|
|
|
146
294
|
}).meta({ id: "CreateOrganizationRequest" });
|
|
147
295
|
z.object({
|
|
148
296
|
name: DisplayName.optional(),
|
|
149
|
-
metadata: Metadata.optional()
|
|
297
|
+
metadata: Metadata.optional(),
|
|
298
|
+
policy: OrganizationPolicy.optional()
|
|
150
299
|
}).refine((body) => Object.keys(body).length > 0, "At least one field must be provided").meta({ id: "UpdateOrganizationRequest" });
|
|
151
300
|
var TaskStatus = z.enum(["requested", "accepted", "completed", "failed"]);
|
|
152
301
|
var TaskParty = z.object({ agentId: AgentId, organizationId: OrganizationId, nodeId: NodeId });
|
|
@@ -191,7 +340,9 @@ z.object({
|
|
|
191
340
|
organizationName: z.string(),
|
|
192
341
|
nodeId: NodeId,
|
|
193
342
|
/** Running, on a node that is not offline — i.e. a task could be routed to it now. */
|
|
194
|
-
available: z.boolean()
|
|
343
|
+
available: z.boolean(),
|
|
344
|
+
/** Whether both organizations' policies let the agent named by `forAgent` ask this provider (ADR-0023); null without it. */
|
|
345
|
+
permitted: z.boolean().nullable()
|
|
195
346
|
}).meta({ id: "CapabilityProvider" });
|
|
196
347
|
|
|
197
348
|
// ../contracts/src/events.ts
|
|
@@ -202,6 +353,7 @@ var AgentSubject = subject("agent", AgentId);
|
|
|
202
353
|
var LinkSubject = subject("link", LinkId);
|
|
203
354
|
var RoleBindingSubject = subject("roleBinding", RoleBindingId);
|
|
204
355
|
var TaskSubject = subject("task", TaskId);
|
|
356
|
+
var RevocationSubject = subject("revocation", RevocationId);
|
|
205
357
|
var event = (type, subjectSchema, payload) => z.object({ type: z.literal(type), subject: subjectSchema, payload });
|
|
206
358
|
var AgentRegistration = z.object({
|
|
207
359
|
meshAgentId: AgentId,
|
|
@@ -258,6 +410,7 @@ var TaskRequested = event("task.requested", TaskSubject, z.object({ task: Task }
|
|
|
258
410
|
var TaskAccepted = event("task.accepted", TaskSubject, z.object({ task: Task }));
|
|
259
411
|
var TaskCompleted = event("task.completed", TaskSubject, z.object({ task: Task }));
|
|
260
412
|
var TaskFailed = event("task.failed", TaskSubject, z.object({ task: Task }));
|
|
413
|
+
var MeshKeyRevoked = event("mesh.key_revoked", RevocationSubject, z.object({ revocation: KeyRevocation }));
|
|
261
414
|
var DomainEvent = z.discriminatedUnion("type", [
|
|
262
415
|
NodeCreated,
|
|
263
416
|
NodeUpdated,
|
|
@@ -283,7 +436,8 @@ var DomainEvent = z.discriminatedUnion("type", [
|
|
|
283
436
|
TaskRequested,
|
|
284
437
|
TaskAccepted,
|
|
285
438
|
TaskCompleted,
|
|
286
|
-
TaskFailed
|
|
439
|
+
TaskFailed,
|
|
440
|
+
MeshKeyRevoked
|
|
287
441
|
]).meta({ id: "DomainEvent" });
|
|
288
442
|
DomainEvent.options.map((o) => o.shape.type.value);
|
|
289
443
|
var ENVELOPE_VERSION = 1;
|
|
@@ -309,13 +463,37 @@ z.object({
|
|
|
309
463
|
uptimeSeconds: z.number().nonnegative(),
|
|
310
464
|
timestamp: z.iso.datetime()
|
|
311
465
|
}).meta({ id: "HealthResponse", description: "Liveness and version information for a Node Server." });
|
|
312
|
-
z.object({
|
|
466
|
+
z.object({
|
|
467
|
+
principal: Principal,
|
|
468
|
+
roles: z.array(RoleBinding),
|
|
469
|
+
/** What the credential used for this request may do (ADR-0022). */
|
|
470
|
+
scopes: z.array(TokenScope)
|
|
471
|
+
}).meta({ id: "Me", description: "The authenticated principal and its role bindings." });
|
|
313
472
|
z.object({ name: DisplayName, kind: PrincipalKind.default("user") }).meta({ id: "CreatePrincipalRequest" });
|
|
314
473
|
var Credential = z.discriminatedUnion("type", [
|
|
315
474
|
z.object({ type: z.literal("token"), token: z.string(), expiresAt: Timestamp }),
|
|
316
475
|
z.object({ type: z.literal("apiKey"), apiKey: z.string() })
|
|
317
476
|
]).meta({ id: "Credential" });
|
|
318
477
|
z.object({ principal: Principal, credential: Credential }).meta({ id: "CreatePrincipalResponse" });
|
|
478
|
+
var MAX_TOKEN_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
479
|
+
z.object({
|
|
480
|
+
/** Defaults to all scopes the caller's own credential has. */
|
|
481
|
+
scopes: z.array(TokenScope).min(1).optional(),
|
|
482
|
+
ttlSeconds: z.int().min(60).max(MAX_TOKEN_TTL_SECONDS).default(30 * 24 * 60 * 60),
|
|
483
|
+
label: z.string().trim().max(120).optional()
|
|
484
|
+
}).meta({ id: "IssueTokenRequest" });
|
|
485
|
+
z.object({ id: TokenId, token: z.string(), scopes: z.array(TokenScope), expiresAt: Timestamp }).meta({ id: "IssuedToken" });
|
|
486
|
+
z.object({
|
|
487
|
+
id: z.union([TokenId, ApiKeyId]),
|
|
488
|
+
type: z.enum(["token", "apiKey"]),
|
|
489
|
+
principalId: PrincipalId,
|
|
490
|
+
scopes: z.array(TokenScope),
|
|
491
|
+
label: z.string().nullable(),
|
|
492
|
+
createdAt: Timestamp,
|
|
493
|
+
expiresAt: Timestamp.nullable(),
|
|
494
|
+
revokedAt: Timestamp.nullable(),
|
|
495
|
+
lastUsedAt: Timestamp.nullable()
|
|
496
|
+
}).meta({ id: "CredentialInfo" });
|
|
319
497
|
var MeshCursor = z.record(NodeId, z.int().min(0)).meta({ id: "MeshCursor" });
|
|
320
498
|
z.object({
|
|
321
499
|
nodes: z.array(Node),
|
|
@@ -393,6 +571,8 @@ function project(event2) {
|
|
|
393
571
|
case "task.completed":
|
|
394
572
|
case "task.failed":
|
|
395
573
|
return { op: "upsert", kind: "task", entity: event2.payload.task };
|
|
574
|
+
case "mesh.key_revoked":
|
|
575
|
+
return { op: "upsert", kind: "revocation", entity: { ...event2.payload.revocation, id: event2.subject.id } };
|
|
396
576
|
// Registration is implied by organization membership; heartbeats are handled by the presence detector.
|
|
397
577
|
case "mesh_agent.agent_registered":
|
|
398
578
|
case "mesh_agent.agent_deregistered":
|
|
@@ -401,6 +581,55 @@ function project(event2) {
|
|
|
401
581
|
}
|
|
402
582
|
}
|
|
403
583
|
|
|
584
|
+
// ../contracts/src/writers.ts
|
|
585
|
+
var owner = (...nodeIds) => ({ kind: "owner", nodeIds });
|
|
586
|
+
var ANY = { kind: "any" };
|
|
587
|
+
function writersOf(event2) {
|
|
588
|
+
switch (event2.type) {
|
|
589
|
+
case "node.created":
|
|
590
|
+
case "node.updated":
|
|
591
|
+
return owner(event2.payload.node.id);
|
|
592
|
+
case "node.heartbeat":
|
|
593
|
+
return owner(event2.payload.nodeId);
|
|
594
|
+
case "organization.created":
|
|
595
|
+
case "organization.updated":
|
|
596
|
+
return owner(event2.payload.organization.nodeId);
|
|
597
|
+
case "agent.created":
|
|
598
|
+
case "agent.updated":
|
|
599
|
+
case "agent.status_changed":
|
|
600
|
+
return owner(event2.payload.agent.nodeId);
|
|
601
|
+
case "mesh_agent.agent_registered":
|
|
602
|
+
return owner(event2.payload.nodeId);
|
|
603
|
+
case "link.up":
|
|
604
|
+
case "link.updated":
|
|
605
|
+
case "link.down":
|
|
606
|
+
return owner(event2.payload.link.aNodeId);
|
|
607
|
+
case "link.requested":
|
|
608
|
+
return owner(event2.payload.link.aNodeId, event2.payload.link.bNodeId);
|
|
609
|
+
case "role.granted":
|
|
610
|
+
return owner(event2.payload.binding.scope.nodeId);
|
|
611
|
+
case "task.requested":
|
|
612
|
+
return owner(event2.payload.task.requester.nodeId);
|
|
613
|
+
case "task.accepted":
|
|
614
|
+
case "task.completed":
|
|
615
|
+
case "task.failed":
|
|
616
|
+
return owner(event2.payload.task.assignee.nodeId);
|
|
617
|
+
case "node.removed":
|
|
618
|
+
case "node.status_changed":
|
|
619
|
+
case "organization.removed":
|
|
620
|
+
case "agent.removed":
|
|
621
|
+
case "mesh_agent.agent_deregistered":
|
|
622
|
+
case "link.removed":
|
|
623
|
+
case "role.revoked":
|
|
624
|
+
case "mesh.key_revoked":
|
|
625
|
+
return ANY;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function isAuthorizedWriter(event2, originNodeId) {
|
|
629
|
+
const writers = writersOf(event2);
|
|
630
|
+
return writers.kind === "any" || writers.nodeIds.includes(originNodeId);
|
|
631
|
+
}
|
|
632
|
+
|
|
404
633
|
// ../contracts/src/mesh-state.ts
|
|
405
634
|
var byId = (items) => Object.fromEntries(items.map((item) => [item.id, item]));
|
|
406
635
|
function meshStateFromSnapshot(snapshot) {
|
|
@@ -418,6 +647,7 @@ function applyMeshEvent(state, event2) {
|
|
|
418
647
|
const seen = state.cursor[event2.origin.nodeId] ?? 0;
|
|
419
648
|
if (event2.seq <= seen) return state;
|
|
420
649
|
const cursor = { ...state.cursor, [event2.origin.nodeId]: event2.seq };
|
|
650
|
+
if (!isAuthorizedWriter(event2, event2.origin.nodeId)) return { ...state, cursor };
|
|
421
651
|
const op = project(event2);
|
|
422
652
|
switch (op.op) {
|
|
423
653
|
case "upsert":
|
|
@@ -431,6 +661,7 @@ function applyMeshEvent(state, event2) {
|
|
|
431
661
|
case "link":
|
|
432
662
|
return { ...state, cursor, links: { ...state.links, [op.entity.id]: op.entity } };
|
|
433
663
|
case "roleBinding":
|
|
664
|
+
case "revocation":
|
|
434
665
|
return { ...state, cursor };
|
|
435
666
|
case "task": {
|
|
436
667
|
const current = state.tasks[op.entity.id];
|
|
@@ -472,7 +703,7 @@ z.object({
|
|
|
472
703
|
code: z.string().meta({ description: "Stable machine-readable error code" }),
|
|
473
704
|
errors: z.array(z.object({ path: z.string(), message: z.string() })).optional()
|
|
474
705
|
}).meta({ id: "ProblemDetails" });
|
|
475
|
-
var Topic = z.enum(["node", "organization", "agent", "link", "roleBinding", "task"]);
|
|
706
|
+
var Topic = z.enum(["node", "organization", "agent", "link", "roleBinding", "task", "revocation"]);
|
|
476
707
|
z.discriminatedUnion("type", [
|
|
477
708
|
z.object({
|
|
478
709
|
type: z.literal("hello"),
|
|
@@ -493,6 +724,8 @@ z.discriminatedUnion("type", [
|
|
|
493
724
|
var RealtimeCloseCode = {
|
|
494
725
|
normal: 1e3,
|
|
495
726
|
unauthenticated: 4401,
|
|
727
|
+
/** Authenticated, but without a role on this node (ADR-0022). */
|
|
728
|
+
forbidden: 4403,
|
|
496
729
|
resyncRequired: 4409
|
|
497
730
|
};
|
|
498
731
|
|
|
@@ -712,7 +945,12 @@ var MeshRealtime = class {
|
|
|
712
945
|
this.needsSnapshot = true;
|
|
713
946
|
break;
|
|
714
947
|
case "error":
|
|
715
|
-
this.emitError(
|
|
948
|
+
this.emitError(
|
|
949
|
+
new RealtimeError(
|
|
950
|
+
`${message.code}: ${message.message}`,
|
|
951
|
+
message.code === "unauthenticated" || message.code === "forbidden"
|
|
952
|
+
)
|
|
953
|
+
);
|
|
716
954
|
break;
|
|
717
955
|
}
|
|
718
956
|
}
|
|
@@ -727,8 +965,13 @@ var MeshRealtime = class {
|
|
|
727
965
|
onClose(code) {
|
|
728
966
|
this.socket = void 0;
|
|
729
967
|
if (this.stopped) return;
|
|
730
|
-
if (code === RealtimeCloseCode.unauthenticated) {
|
|
731
|
-
this.emitError(
|
|
968
|
+
if (code === RealtimeCloseCode.unauthenticated || code === RealtimeCloseCode.forbidden) {
|
|
969
|
+
this.emitError(
|
|
970
|
+
new RealtimeError(
|
|
971
|
+
code === RealtimeCloseCode.forbidden ? "The credential has no role on this node" : "The node rejected the credential",
|
|
972
|
+
true
|
|
973
|
+
)
|
|
974
|
+
);
|
|
732
975
|
this.close();
|
|
733
976
|
return;
|
|
734
977
|
}
|
|
@@ -792,10 +1035,52 @@ function createMeshClient(options) {
|
|
|
792
1035
|
stats: () => http.request("GET", "/v1/mesh/stats"),
|
|
793
1036
|
capabilities: async (search) => (await http.request("GET", "/v1/capabilities", { query: { q: search } })).items,
|
|
794
1037
|
/** Agents offering a capability, available ones first (ADR-0020). */
|
|
795
|
-
|
|
1038
|
+
revocations: {
|
|
1039
|
+
/** Revocations published to the mesh, each with whether it holds under the node's root (ADR-0025). */
|
|
1040
|
+
list: async () => (await http.request(
|
|
1041
|
+
"GET",
|
|
1042
|
+
"/v1/mesh/revocations"
|
|
1043
|
+
)).items,
|
|
1044
|
+
/** Publishes a root-signed revocation through this node (node owners). */
|
|
1045
|
+
publish: (revocation) => http.request("POST", "/v1/mesh/revocations", { body: revocation })
|
|
1046
|
+
},
|
|
1047
|
+
/** With `forAgent`, each provider also says whether policy lets that agent ask it (ADR-0023). */
|
|
1048
|
+
capabilityProviders: async (name, options2 = {}) => (await http.request("GET", `/v1/capabilities/${id(name)}/providers`, {
|
|
1049
|
+
query: { ...options2 }
|
|
1050
|
+
})).items,
|
|
796
1051
|
principals: {
|
|
797
1052
|
/** Node owners only. The credential is returned exactly once. */
|
|
798
|
-
create: (input, options2 = {}) => http.request("POST", "/v1/principals", { body: input, ...options2 })
|
|
1053
|
+
create: (input, options2 = {}) => http.request("POST", "/v1/principals", { body: input, ...options2 }),
|
|
1054
|
+
/** This node's principals (node owners only). */
|
|
1055
|
+
list: async () => (await http.request("GET", "/v1/principals")).items,
|
|
1056
|
+
/** Deletes a principal and its credentials; revoke its roles first (node owners only). */
|
|
1057
|
+
remove: (principalId) => http.request("DELETE", `/v1/principals/${id(principalId)}`),
|
|
1058
|
+
/**
|
|
1059
|
+
* Issues a bearer token with scopes and an expiry (ADR-0022): for yourself, never with more scopes than the
|
|
1060
|
+
* credential you use, or — node owners — for anyone. The token is returned exactly once.
|
|
1061
|
+
*/
|
|
1062
|
+
issueToken: (principalId, input = {}, options2 = {}) => http.request("POST", `/v1/principals/${id(principalId)}/tokens`, { body: input, ...options2 }),
|
|
1063
|
+
/** A principal's tokens and API keys, metadata only (yourself, or node owners for anyone). */
|
|
1064
|
+
credentials: async (principalId) => (await http.request("GET", `/v1/principals/${id(principalId)}/credentials`)).items,
|
|
1065
|
+
/** Revokes every credential of the principal, including the one this client may be using. */
|
|
1066
|
+
revokeAllCredentials: (principalId) => http.request("DELETE", `/v1/principals/${id(principalId)}/credentials`)
|
|
1067
|
+
},
|
|
1068
|
+
credentials: {
|
|
1069
|
+
/** Revokes one token (`tok_…`) or API key (`key_…`). */
|
|
1070
|
+
revoke: (credentialId) => http.request("DELETE", `/v1/credentials/${id(credentialId)}`)
|
|
1071
|
+
},
|
|
1072
|
+
audit: {
|
|
1073
|
+
/** Newest first; page backwards with `before` (node owners and auditors; ADR-0024). */
|
|
1074
|
+
list: (filter = {}) => http.request("GET", "/v1/audit", { query: { ...filter } }),
|
|
1075
|
+
/** Recomputes the whole chain on the node: gaps, hashes and signatures. */
|
|
1076
|
+
verify: () => http.request("GET", "/v1/audit/verify")
|
|
1077
|
+
},
|
|
1078
|
+
roles: {
|
|
1079
|
+
/** A principal's bindings, or those of a node or organization; by default this node's and its organizations'. */
|
|
1080
|
+
list: async (filter = {}) => (await http.request("GET", "/v1/role-bindings", { query: { ...filter } })).items,
|
|
1081
|
+
/** Grants node_owner, viewer or auditor on a node, or org_owner or agent_operator on an organization. */
|
|
1082
|
+
grant: (request, options2 = {}) => http.request("POST", "/v1/role-bindings", { body: request, ...options2 }),
|
|
1083
|
+
revoke: (bindingId) => http.request("DELETE", `/v1/role-bindings/${id(bindingId)}`)
|
|
799
1084
|
},
|
|
800
1085
|
nodes: {
|
|
801
1086
|
list: (page = {}) => http.request("GET", "/v1/nodes", { query: { ...page } }),
|
|
@@ -879,6 +1164,6 @@ function createMeshClient(options) {
|
|
|
879
1164
|
return client;
|
|
880
1165
|
}
|
|
881
1166
|
|
|
882
|
-
export { MeshApiError, MeshRealtime, RealtimeError, applyMeshEvent, createMeshClient, decodeCursor, encodeCursor, meshStateFromSnapshot };
|
|
1167
|
+
export { MeshApiError, MeshRealtime, OPEN_POLICY, RealtimeError, applyMeshEvent, createMeshClient, decodeCursor, encodeCursor, inboundAllows, isAuthorizedWriter, matchesPattern, mayRequest, mayUseTool, meshStateFromSnapshot };
|
|
883
1168
|
//# sourceMappingURL=index.js.map
|
|
884
1169
|
//# sourceMappingURL=index.js.map
|