@decionis/mcp 0.1.2 → 0.1.3

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.js CHANGED
@@ -2,14 +2,15 @@
2
2
  import {
3
3
  LocalDecionisGateway,
4
4
  PolicyFileRepository,
5
+ external_exports,
5
6
  normalizeVerdict,
6
7
  sha256Hex
7
- } from "./chunk-KY5AVRL6.js";
8
+ } from "./chunk-XNC3IZW5.js";
8
9
 
9
10
  // src/Server.ts
10
11
  import { createInterface } from "readline";
11
12
  var SUPPORTED_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
12
- var MCP_SERVER_VERSION = "0.1.2";
13
+ var MCP_SERVER_VERSION = "0.1.3";
13
14
  var DEFAULT_PROTOCOL_VERSION = "2025-11-25";
14
15
  var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
15
16
  function rpcResult(id, result) {
@@ -53,7 +54,10 @@ var McpMessageHandler = class {
53
54
  title,
54
55
  description,
55
56
  inputSchema,
56
- annotations
57
+ annotations: {
58
+ title,
59
+ ...annotations
60
+ }
57
61
  })
58
62
  )
59
63
  });
@@ -164,10 +168,55 @@ rule matches, the platform holds the decision for review (outcome REVIEW).
164
168
  action you intend to take (e.g. {"action": "production-deploy", "agent_generated": true})
165
169
  and abide by the verdict: allow \u2192 proceed; block \u2192 do not proceed; review/escalate \u2192
166
170
  stop and ask a human.`;
171
+ var DEMO_POLICY = `# Decionis demo policy
172
+
173
+ Three rules and a default: refunds under the desk limit run, refunds over it
174
+ wait for a named approver, deploys during a change freeze never run, and
175
+ anything the policy has no opinion on is held for review.
176
+
177
+ \`\`\`decionis
178
+ { "version": 1, "rules": [
179
+ { "name": "Block production deploys during a change freeze",
180
+ "priority": 100, "domain": "*",
181
+ "all": [{ "field": "context.change_freeze", "op": "eq", "value": true }],
182
+ "action": "block" },
183
+ { "name": "Escalate refunds over the desk limit",
184
+ "priority": 90, "domain": "*",
185
+ "all": [{ "field": "context.amount", "op": "gt", "value": 1000 }],
186
+ "action": "escalate" },
187
+ { "name": "Allow refunds within the desk limit",
188
+ "priority": 80, "domain": "*",
189
+ "all": [{ "field": "context.amount", "op": "lte", "value": 1000 }],
190
+ "action": "allow" } ] }
191
+ \`\`\`
192
+ `;
193
+ var DEMO_INTENTS = [
194
+ {
195
+ label: "Refund $180 \u2014 within the desk limit",
196
+ payload: { action: "issue-refund", amount: 180, agent_generated: true },
197
+ shows: "Within policy: execution continues, and the rule that allowed it is named."
198
+ },
199
+ {
200
+ label: "Refund $7,500 \u2014 over the desk limit",
201
+ payload: { action: "issue-refund", amount: 7500, agent_generated: true },
202
+ shows: "Held short of execution and routed to a named approver \u2014 not refused."
203
+ },
204
+ {
205
+ label: "Production deploy during a change freeze",
206
+ payload: { action: "production-deploy", change_freeze: true, agent_generated: true },
207
+ shows: "Refused outright: nobody is asked to approve it."
208
+ },
209
+ {
210
+ label: "Export customer data \u2014 no rule covers it",
211
+ payload: { action: "export-customer-data", destination: "personal-email" },
212
+ shows: "No rule matched, so the decision queues for review \u2014 unmatched intents fail safe."
213
+ }
214
+ ];
167
215
  var DecionisTools = class {
168
- constructor(gateway2, policyFiles = new PolicyFileRepository()) {
216
+ constructor(gateway2, policyFiles = new PolicyFileRepository(), additionalTools = []) {
169
217
  this.gateway = gateway2;
170
218
  this.policyFiles = policyFiles;
219
+ this.additionalTools = additionalTools;
171
220
  }
172
221
  build() {
173
222
  const gateway2 = this.gateway;
@@ -175,7 +224,7 @@ var DecionisTools = class {
175
224
  {
176
225
  name: "decionis_read_policy",
177
226
  title: "Read Repository Policy",
178
- description: "Read and compile the repo's DECIONIS_POLICY.md governance policy: path, sha256, and the enforceable rules (name, verdict, priority, domain) or compile errors. Use this to learn what this repository allows, blocks, restrains, or escalates.",
227
+ description: "Read and compile the repo's DECIONIS_POLICY.md execution policy: path, sha256, and the enforceable rules (name, verdict, priority, domain) or compile errors. Use this to learn what this repository allows, blocks, restrains, or escalates.",
179
228
  inputSchema: {
180
229
  type: "object",
181
230
  properties: {
@@ -297,6 +346,67 @@ var DecionisTools = class {
297
346
  };
298
347
  }
299
348
  },
349
+ {
350
+ name: "decionis_demo",
351
+ title: "Run the One-Minute Demo",
352
+ description: "See Decionis work in under a minute: runs four canned intents (refund within limit, refund over limit, deploy during a change freeze, an action no rule covers) through the real protocol evaluator with a built-in sample policy. Entirely local \u2014 no account, no API key, no network, no policy file needed, nothing recorded. Call this on first contact with Decionis; use decionis_evaluate to gate this repo's own actions afterwards.",
353
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
354
+ annotations: {
355
+ readOnlyHint: true,
356
+ destructiveHint: false,
357
+ idempotentHint: true,
358
+ openWorldHint: false
359
+ },
360
+ handler: async () => {
361
+ const published = await gateway2.publishPolicy(DEMO_POLICY, { workflowKey: null });
362
+ if (!published.ok) {
363
+ return {
364
+ content: text({ error: "demo policy failed to compile", details: published.errors }),
365
+ isError: true
366
+ };
367
+ }
368
+ const decisions = [];
369
+ for (const intent of DEMO_INTENTS) {
370
+ const evaluated = await gateway2.evaluate({
371
+ payload: intent.payload,
372
+ workflowKey: null,
373
+ mode: "shadow"
374
+ });
375
+ if (evaluated.status !== 200) {
376
+ return {
377
+ content: text({
378
+ error: `protocol evaluation failed (HTTP ${evaluated.status})`,
379
+ intent: intent.label,
380
+ body: evaluated.body
381
+ }),
382
+ isError: true
383
+ };
384
+ }
385
+ const body = evaluated.body;
386
+ const policyEvaluation = isPlainObject2(body.policy_evaluation) ? body.policy_evaluation : {};
387
+ decisions.push({
388
+ intent: intent.label,
389
+ outcome: body.outcome,
390
+ verdict: normalizeVerdict(body.outcome),
391
+ selected_rule: policyEvaluation.selected_rule_name ?? null,
392
+ what_this_shows: intent.shows
393
+ });
394
+ }
395
+ return {
396
+ content: text({
397
+ headline: "Four intents, four outcomes \u2014 each decided by policy before execution, through the same evaluator that runs in production.",
398
+ decisions,
399
+ vocabulary: "Wire outcomes are APPROVE / ESCALATE / REJECT / REVIEW \u2014 the wire spellings of the three decision classes (allow / escalate / block). ESCALATE routes to a named approver; REVIEW queues without one.",
400
+ note: "Local sandbox \u2014 the sample policy above, nothing recorded, no signed dossier minted. In production every decision leaves an Ed25519-signed Decision Dossier anyone can verify offline against the published JWKS.",
401
+ next_steps: [
402
+ "Gate this repo's own actions: decionis_read_policy, then decionis_evaluate before anything risky.",
403
+ "Watch real traffic read-only first: https://decionis.com/shadow-mode",
404
+ "First governed decision with signed proof: https://decionis.com/docs/quickstart"
405
+ ]
406
+ })
407
+ };
408
+ }
409
+ },
300
410
  {
301
411
  name: "decionis_verdict_help",
302
412
  title: "Explain Decionis Verdicts",
@@ -309,19 +419,2134 @@ var DecionisTools = class {
309
419
  openWorldHint: false
310
420
  },
311
421
  handler: async () => ({ content: text(VERDICT_HELP) })
422
+ },
423
+ ...this.additionalTools
424
+ ];
425
+ }
426
+ };
427
+ function createDecionisTools(gateway2, additionalTools = []) {
428
+ return new DecionisTools(gateway2, new PolicyFileRepository(), additionalTools).build();
429
+ }
430
+
431
+ // src/kubernetes/KubernetesActionPlanner.ts
432
+ import { randomUUID } from "crypto";
433
+
434
+ // ../../packages/sdk-node/dist/index.js
435
+ import { createHash } from "crypto";
436
+ import { createHash as createHash2 } from "crypto";
437
+ var DEFAULT_SKEW_MS = 6 * 60 * 60 * 1e3;
438
+ var authoritySelectorSchema = external_exports.discriminatedUnion("kind", [
439
+ external_exports.object({
440
+ kind: external_exports.literal("ROLE"),
441
+ role_id: external_exports.string().regex(/^[A-Z][A-Z0-9_]{1,63}$/)
442
+ }).strict(),
443
+ external_exports.object({
444
+ kind: external_exports.literal("PRINCIPAL"),
445
+ principal_id: external_exports.string().min(1).max(200)
446
+ }).strict()
447
+ ]);
448
+ var authorityVerificationRequirementsSchema = external_exports.object({
449
+ level: external_exports.enum(["STANDARD", "HIGH_CONFIDENCE"]),
450
+ methods: external_exports.array(external_exports.enum(["WEBAUTHN", "MOBILE_DEVICE", "ACTIVE_LIVENESS"])).min(1).max(3),
451
+ hardware_pki_required: external_exports.boolean(),
452
+ disallow_virtual_cameras: external_exports.boolean(),
453
+ mobile_device_policy: external_exports.enum(["WEB_OR_JIT_DEVICE", "MANAGED_DEVICE_REQUIRED"]).optional(),
454
+ trusted_ip_regions: external_exports.array(external_exports.string().trim().min(1).max(64)).min(1).max(100).optional()
455
+ }).strict().superRefine((requirements, context) => {
456
+ if (new Set(requirements.methods).size !== requirements.methods.length) {
457
+ context.addIssue({
458
+ code: external_exports.ZodIssueCode.custom,
459
+ path: ["methods"],
460
+ message: "verification methods must be unique"
461
+ });
462
+ }
463
+ if (requirements.trusted_ip_regions !== void 0 && new Set(requirements.trusted_ip_regions).size !== requirements.trusted_ip_regions.length) {
464
+ context.addIssue({
465
+ code: external_exports.ZodIssueCode.custom,
466
+ path: ["trusted_ip_regions"],
467
+ message: "trusted IP regions must be unique"
468
+ });
469
+ }
470
+ });
471
+ var authorityResolutionPolicySchema = external_exports.object({
472
+ quorum: external_exports.literal(1),
473
+ delegation: external_exports.enum(["DISALLOWED", "REQUIREMENT_REFRESH"]),
474
+ fallback_role_ids: external_exports.array(external_exports.string().regex(/^[A-Z][A-Z0-9_]{1,63}$/)).max(10).default([])
475
+ }).strict().superRefine((policy, context) => {
476
+ if (new Set(policy.fallback_role_ids).size !== policy.fallback_role_ids.length) {
477
+ context.addIssue({
478
+ code: external_exports.ZodIssueCode.custom,
479
+ path: ["fallback_role_ids"],
480
+ message: "fallback role ids must be unique"
481
+ });
482
+ }
483
+ });
484
+ var originatorSchema = external_exports.object({
485
+ organization_id: external_exports.string().min(1).max(200),
486
+ organization_name: external_exports.string().trim().min(1).max(160),
487
+ actor_id: external_exports.string().min(1).max(200).optional(),
488
+ display_name: external_exports.string().trim().min(1).max(160).optional(),
489
+ role: external_exports.string().trim().min(1).max(120).optional(),
490
+ provenance: external_exports.enum(["AUTHENTICATED_TENANT", "VERIFIED_CONNECTOR", "CALLER_DECLARED"])
491
+ }).strict();
492
+ var displayFieldSchema = external_exports.object({
493
+ key: external_exports.string().regex(/^[a-z][a-z0-9_]{0,63}$/),
494
+ label: external_exports.string().trim().min(1).max(120),
495
+ value: external_exports.string().trim().min(1).max(500)
496
+ }).strict();
497
+ var presentationSchema = external_exports.object({
498
+ locale: external_exports.string().min(2).max(35).regex(/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/),
499
+ title: external_exports.string().trim().min(1).max(160),
500
+ description: external_exports.string().trim().min(1).max(1e3),
501
+ display_fields: external_exports.array(displayFieldSchema).max(20)
502
+ }).strict().superRefine((presentation, context) => {
503
+ const keys = presentation.display_fields.map((field) => field.key);
504
+ if (new Set(keys).size !== keys.length) {
505
+ context.addIssue({
506
+ code: external_exports.ZodIssueCode.custom,
507
+ path: ["display_fields"],
508
+ message: "display field keys must be unique"
509
+ });
510
+ }
511
+ });
512
+ var decionisAuthorityRequirementSchema = external_exports.object({
513
+ requirement_version: external_exports.literal("decionis.authority_requirement/1"),
514
+ requirement_id: external_exports.string().min(8).max(200),
515
+ source_decision_id: external_exports.string().min(1).max(200),
516
+ source_dossier_id: external_exports.string().min(1).max(200),
517
+ policy_version: external_exports.string().min(1).max(120),
518
+ policy_reason_codes: external_exports.array(external_exports.string().regex(/^[A-Z][A-Z0-9_]{1,119}$/)).min(1).max(20),
519
+ action_digest: external_exports.string().regex(/^[0-9a-f]{64}$/),
520
+ selector: authoritySelectorSchema,
521
+ verification_requirements: authorityVerificationRequirementsSchema,
522
+ resolution: authorityResolutionPolicySchema,
523
+ originator: originatorSchema,
524
+ presentation: presentationSchema,
525
+ nonce: external_exports.string().regex(/^[A-Za-z0-9_-]{43,128}$/),
526
+ issued_at: external_exports.string().datetime(),
527
+ expires_at: external_exports.string().datetime()
528
+ }).strict().superRefine((requirement, context) => {
529
+ if (Date.parse(requirement.expires_at) <= Date.parse(requirement.issued_at)) {
530
+ context.addIssue({
531
+ code: external_exports.ZodIssueCode.custom,
532
+ path: ["expires_at"],
533
+ message: "expires_at must be later than issued_at"
534
+ });
535
+ }
536
+ if (requirement.selector.kind === "ROLE" && requirement.resolution.fallback_role_ids.includes(requirement.selector.role_id)) {
537
+ context.addIssue({
538
+ code: external_exports.ZodIssueCode.custom,
539
+ path: ["resolution", "fallback_role_ids"],
540
+ message: "fallback roles must not repeat the primary role"
541
+ });
542
+ }
543
+ });
544
+ var decionisEvaluateModeSchema = external_exports.enum(["SHADOW", "PARALLEL", "ENFORCEMENT"]);
545
+ var decionisEvaluateOutcomeSchema = external_exports.enum(["APPROVE", "ESCALATE", "REJECT", "REVIEW"]);
546
+ var decionisEvaluateDecisionRequestSchema = external_exports.object({
547
+ org_id: external_exports.string().uuid(),
548
+ decision_type: external_exports.string().min(1).max(120),
549
+ amount: external_exports.number().optional(),
550
+ risk_score: external_exports.number().min(0).max(1).optional(),
551
+ channel: external_exports.string().min(1).max(120).optional(),
552
+ policy_version: external_exports.string().min(1).max(120).optional(),
553
+ objective_profile: external_exports.string().min(1).max(120).optional(),
554
+ mode: decionisEvaluateModeSchema.optional(),
555
+ decision_band: external_exports.string().min(1).max(120).optional(),
556
+ transaction_type: external_exports.string().min(1).max(120).optional(),
557
+ workflow_key: external_exports.string().min(1).max(120).optional(),
558
+ vertical_pack: external_exports.string().min(1).max(120).optional(),
559
+ context: external_exports.record(external_exports.unknown()).optional(),
560
+ idempotency_key: external_exports.string().min(1).max(180).optional()
561
+ });
562
+ var governanceMetricsSchema = external_exports.object({
563
+ override_drift_rate: external_exports.number(),
564
+ governance_tension_index: external_exports.number(),
565
+ boundary_volatility_score: external_exports.number(),
566
+ decision_volume_under_governance: external_exports.number()
567
+ });
568
+ var policyGuardSchema = external_exports.object({
569
+ status: external_exports.enum(["ready", "configured", "missing"]),
570
+ requires_manual_review: external_exports.boolean(),
571
+ total_bundle_count: external_exports.number(),
572
+ active_bundle_count: external_exports.number(),
573
+ active_bundle_id: external_exports.string().uuid().nullable(),
574
+ active_bundle_version: external_exports.string().nullable(),
575
+ active_effective_from: external_exports.string().nullable(),
576
+ active_effective_to: external_exports.string().nullable(),
577
+ latest_bundle_id: external_exports.string().uuid().nullable(),
578
+ latest_bundle_version: external_exports.string().nullable(),
579
+ latest_effective_from: external_exports.string().nullable(),
580
+ latest_effective_to: external_exports.string().nullable(),
581
+ reason: external_exports.enum([
582
+ "missing_org_policy_bundle",
583
+ "no_active_org_policy_bundle",
584
+ "requested_policy_bundle_not_found"
585
+ ]).nullable(),
586
+ evaluated_at: external_exports.string()
587
+ }).optional();
588
+ var policyEvaluationSchema = external_exports.object({
589
+ resolution: external_exports.enum([
590
+ "policy_guard_blocked",
591
+ "matched_rule",
592
+ "no_matching_rule",
593
+ "missing_required_signals"
594
+ ]),
595
+ bundle_id: external_exports.string().uuid().nullable(),
596
+ bundle_version: external_exports.string().nullable(),
597
+ evaluated_at: external_exports.string(),
598
+ decision_domain: external_exports.string(),
599
+ evaluated_rule_count: external_exports.number(),
600
+ matched_rule_count: external_exports.number(),
601
+ matched_rule_ids: external_exports.array(external_exports.string()),
602
+ selected_rule_id: external_exports.string().nullable(),
603
+ selected_rule_name: external_exports.string().nullable(),
604
+ selected_rule_action: external_exports.enum(["REQUIRE_REVIEW", "AUTO_APPROVE", "AUTO_REJECT", "ESCALATE", "REQUEST_INFO"]).nullable(),
605
+ selected_rule_priority: external_exports.number().nullable(),
606
+ selected_rule_rationale: external_exports.string().nullable(),
607
+ selected_rule_severity: external_exports.enum(["routine", "elevated", "urgent"]).nullable(),
608
+ selected_rule_assignee_role: external_exports.string().nullable(),
609
+ selected_rule_sla_minutes: external_exports.number().nullable(),
610
+ selected_rule_authority: external_exports.unknown().optional(),
611
+ missing_required_signals: external_exports.array(external_exports.string()).optional()
612
+ }).optional();
613
+ var policySnapshotSchema = external_exports.object({
614
+ policy_version: external_exports.string(),
615
+ policy_bundle_id: external_exports.string().uuid().nullable(),
616
+ rules_sha256: external_exports.string().nullable(),
617
+ parameters_sha256: external_exports.string().nullable(),
618
+ effective_from: external_exports.string().nullable(),
619
+ effective_to: external_exports.string().nullable(),
620
+ evaluated_at: external_exports.string(),
621
+ source: external_exports.enum(["org_policy_bundle", "policy_guard", "request_default"])
622
+ }).optional();
623
+ var decionisEvaluateDecisionResponseSchema = external_exports.object({
624
+ outcome: decionisEvaluateOutcomeSchema,
625
+ confidence: external_exports.number(),
626
+ policy_version: external_exports.string(),
627
+ objective_profile: external_exports.string(),
628
+ dossier_id: external_exports.string().uuid(),
629
+ dossier_sha256: external_exports.string().nullable().optional(),
630
+ evaluation_id: external_exports.string().uuid(),
631
+ decision_resolution_id: external_exports.string().uuid().nullable().optional(),
632
+ ledger_entry_id: external_exports.string().uuid().nullable().optional(),
633
+ ledger_entry_hash: external_exports.string().nullable().optional(),
634
+ mode: decionisEvaluateModeSchema,
635
+ reason_codes: external_exports.array(external_exports.string().min(1)).optional(),
636
+ fallback_to_legacy: external_exports.boolean(),
637
+ fallback_reason: external_exports.string().nullable().optional(),
638
+ reconciliation_required: external_exports.boolean().optional(),
639
+ reconciliation_status: external_exports.enum(["NOT_REQUIRED", "PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]).optional(),
640
+ decision_domain: external_exports.string().optional(),
641
+ governance_metrics: governanceMetricsSchema,
642
+ policy_guard: policyGuardSchema,
643
+ policy_evaluation: policyEvaluationSchema,
644
+ policy_snapshot: policySnapshotSchema.nullable().optional(),
645
+ authority_requirement: decionisAuthorityRequirementSchema.optional(),
646
+ idempotent_replay: external_exports.boolean()
647
+ });
648
+ var authorityDecisionSchema = external_exports.object({
649
+ decision_id: external_exports.string().min(1),
650
+ chain_id: external_exports.string().nullable(),
651
+ status: external_exports.enum(["ALLOW", "BLOCK", "ESCALATE", "REVIEW_REQUIRED", "ERROR"]),
652
+ should_execute: external_exports.boolean(),
653
+ reason_codes: external_exports.array(external_exports.string()),
654
+ action_hash: external_exports.string().regex(/^sha256:[0-9a-f]{64}$/),
655
+ policy_version: external_exports.string().nullable().optional().default(null),
656
+ mode: external_exports.enum(["SHADOW", "PARALLEL", "ENFORCEMENT"]).nullable().optional().default(null),
657
+ execution_token: external_exports.string().min(1).nullable(),
658
+ execution_token_expires_at: external_exports.string().datetime().nullable(),
659
+ dossier_id: external_exports.string().nullable(),
660
+ dossier_sha256: external_exports.string().nullable().optional().default(null),
661
+ dossier_url: external_exports.string().nullable(),
662
+ approval_request_id: external_exports.string().nullable().optional().default(null),
663
+ ledger_entry_id: external_exports.string().nullable()
664
+ }).passthrough();
665
+ var DecionisExecutionClientError = class extends Error {
666
+ status;
667
+ body;
668
+ constructor(message, options) {
669
+ super(message);
670
+ this.name = "DecionisExecutionClientError";
671
+ this.status = options.status;
672
+ this.body = options.body;
673
+ }
674
+ };
675
+ async function postJson(options, route, body) {
676
+ const baseUrl = options.authorityBaseUrl.replace(/\/+$/, "");
677
+ const fetchImpl = options.fetch ?? fetch;
678
+ const controller = new AbortController();
679
+ const timeout = setTimeout(
680
+ () => controller.abort(),
681
+ Math.min(Math.max(options.timeoutMs ?? 4e3, 1), 15e3)
682
+ );
683
+ try {
684
+ const response = await fetchImpl(`${baseUrl}${route}`, {
685
+ method: "POST",
686
+ headers: {
687
+ "content-type": "application/json",
688
+ ...options.defaultHeaders ?? {}
689
+ },
690
+ body: JSON.stringify(body),
691
+ signal: controller.signal
692
+ });
693
+ const responseText = await response.text();
694
+ const responseBody = Buffer.byteLength(responseText, "utf8") <= 100 * 1024 ? (() => {
695
+ try {
696
+ return JSON.parse(responseText);
697
+ } catch {
698
+ return null;
699
+ }
700
+ })() : null;
701
+ if (!response.ok) {
702
+ throw new DecionisExecutionClientError(
703
+ `Decionis execution authority request failed with status ${response.status}`,
704
+ {
705
+ status: response.status,
706
+ body: responseBody
707
+ }
708
+ );
709
+ }
710
+ return responseBody;
711
+ } finally {
712
+ clearTimeout(timeout);
713
+ }
714
+ }
715
+ var DecionisExecutionClient = class {
716
+ options;
717
+ constructor(options) {
718
+ assertAuthorityUrl(options);
719
+ this.options = options;
720
+ }
721
+ enforceAndBind = async (request) => {
722
+ const response = await postJson(
723
+ {
724
+ ...this.options,
725
+ defaultHeaders: {
726
+ "idempotency-key": request.intent_id,
727
+ ...this.options.defaultHeaders ?? {}
728
+ }
729
+ },
730
+ "/v1/authority/enforce-and-bind",
731
+ request
732
+ );
733
+ return authorityDecisionSchema.parse(response);
734
+ };
735
+ verifyToken = (body) => {
736
+ return postJson(
737
+ this.options,
738
+ "/v1/execution/verify-token",
739
+ body
740
+ );
741
+ };
742
+ consumeToken = (body) => {
743
+ return postJson(
744
+ this.options,
745
+ "/v1/execution/consume-token",
746
+ body
747
+ );
748
+ };
749
+ claimToken = (body) => {
750
+ return postJson(
751
+ this.options,
752
+ "/v1/execution/claim-token",
753
+ body
754
+ );
755
+ };
756
+ /**
757
+ * Grant-required, fail-closed enforcement — the default path. Delegates to
758
+ * {@link enforceAndExecuteStrict}: the authority must authorize AND issue a
759
+ * single-use grant, and that grant must redeem, BEFORE `execute` runs. Policy
760
+ * block, missing grant, failed redemption, and an unreachable authority all
761
+ * resolve to `executed: false` (`decision` is `null` on authority errors)
762
+ * instead of running the action. Call the strict method directly when the
763
+ * caller needs the grant redemption or the precise non-execution reason;
764
+ * this wrapper keeps the smaller executed/decision/result shape.
765
+ */
766
+ enforceAndExecute = async (input) => {
767
+ const strict = await this.enforceAndExecuteStrict({
768
+ request: input.request,
769
+ execute: ({ decision, executionToken }) => input.execute({ decision, executionToken })
770
+ });
771
+ if (!strict.executed) {
772
+ return {
773
+ executed: false,
774
+ decision: strict.decision,
775
+ result: null
776
+ };
777
+ }
778
+ return {
779
+ executed: true,
780
+ decision: strict.decision,
781
+ result: strict.result
782
+ };
783
+ };
784
+ /**
785
+ * Redeem a single-use execution grant via `/v1/execution/consume-token`, binding
786
+ * it to the exact action. Never throws — a transport/HTTP failure returns an
787
+ * invalid redemption so the strict path fails closed rather than executing on an
788
+ * unverified grant.
789
+ */
790
+ redeemGrant = async (executionToken, request, expectedDecision, commitCorrelationId = request.intent_id) => {
791
+ const baseUrl = this.options.authorityBaseUrl.replace(/\/+$/, "");
792
+ const fetchImpl = this.options.fetch ?? fetch;
793
+ const controller = new AbortController();
794
+ const timeout = setTimeout(
795
+ () => controller.abort(),
796
+ Math.min(Math.max(this.options.timeoutMs ?? 4e3, 1), 15e3)
797
+ );
798
+ try {
799
+ const response = await fetchImpl(`${baseUrl}/v1/execution/claim-token`, {
800
+ method: "POST",
801
+ headers: { "content-type": "application/json", ...this.options.defaultHeaders ?? {} },
802
+ body: JSON.stringify({
803
+ execution_token: executionToken,
804
+ intent_hash: request.intent_hash,
805
+ intent: authorityIntentBindingOf(request),
806
+ ...request.evidence ? { evidence: request.evidence } : {},
807
+ consumed_by: request.actor.id,
808
+ commit_correlation_id: commitCorrelationId
809
+ }),
810
+ signal: controller.signal
811
+ });
812
+ const responseText = await response.text();
813
+ const body = (() => {
814
+ if (Buffer.byteLength(responseText, "utf8") > 100 * 1024) return null;
815
+ try {
816
+ return JSON.parse(responseText);
817
+ } catch {
818
+ return null;
819
+ }
820
+ })();
821
+ if (!response.ok || !body || body.valid !== true) {
822
+ const reason_codes = Array.isArray(body?.reason_codes) ? body?.reason_codes : ["DECIONIS_GRANT_REJECTED"];
823
+ return { valid: false, reason_codes, claims: body?.claims ?? null };
824
+ }
825
+ const claims = body.claims;
826
+ const binding = claims && typeof claims.binding === "object" && !Array.isArray(claims.binding) ? claims.binding : null;
827
+ if (!claims || typeof claims.jti !== "string" || claims.org_id !== request.tenant_id || claims.sub !== request.actor.id || claims.action !== request.action.type || binding?.intent_hash !== request.intent_hash || expectedDecision !== void 0 && (claims.decision_id !== expectedDecision.decision_id || claims.dossier_id !== expectedDecision.dossier_id)) {
828
+ return {
829
+ valid: false,
830
+ reason_codes: ["DECIONIS_GRANT_BINDING_MISMATCH"],
831
+ claims: null
832
+ };
833
+ }
834
+ return {
835
+ valid: true,
836
+ reason_codes: Array.isArray(body.reason_codes) ? body.reason_codes : [],
837
+ claims,
838
+ claim_token: typeof body.claim_token === "string" ? body.claim_token : null,
839
+ claim_lease_expires_at: typeof body.claim_lease_expires_at === "string" ? body.claim_lease_expires_at : null,
840
+ ...body.evidence && typeof body.evidence === "object" ? { evidence: body.evidence } : {}
841
+ };
842
+ } catch {
843
+ return { valid: false, reason_codes: ["DECIONIS_GRANT_REDEEM_ERROR"], claims: null };
844
+ } finally {
845
+ clearTimeout(timeout);
846
+ }
847
+ };
848
+ finalizeGrant = async (input) => {
849
+ try {
850
+ const response = await postJson(
851
+ this.options,
852
+ "/v1/execution/finalize-token",
853
+ {
854
+ execution_token: input.executionToken,
855
+ claim_token: input.claimToken,
856
+ outcome: input.outcome,
857
+ commit_correlation_id: input.commitCorrelationId,
858
+ ...input.downstreamEvidence ? { downstream_evidence: input.downstreamEvidence } : {}
859
+ }
860
+ );
861
+ return response.finalized === true;
862
+ } catch {
863
+ return false;
864
+ }
865
+ };
866
+ /**
867
+ * Grant-required (strict) enforcement: "no valid grant, no execution." The default
868
+ * {@link enforceAndExecute} delegates to this path. This makes the single-use
869
+ * execution grant a hard precondition: the authority must authorize AND issue a
870
+ * grant, and that grant must redeem successfully, BEFORE `execute` runs. Because
871
+ * the grant is redeemed up front, a downstream failure requires a fresh decision
872
+ * rather than replaying an ALLOW — the fail-closed posture for consequential
873
+ * actions. Every non-executing path returns `executed: false` with a reason.
874
+ */
875
+ enforceAndExecuteStrict = async (input) => {
876
+ let decision;
877
+ try {
878
+ decision = await this.enforceAndBind(input.request);
879
+ } catch {
880
+ return {
881
+ executed: false,
882
+ decision: null,
883
+ grant: null,
884
+ reason: "AUTHORITY_ERROR",
885
+ result: null
886
+ };
887
+ }
888
+ if (!decision.should_execute) {
889
+ return { executed: false, decision, grant: null, reason: "BLOCKED_BY_POLICY", result: null };
890
+ }
891
+ if (!decision.execution_token) {
892
+ return { executed: false, decision, grant: null, reason: "NO_EXECUTION_GRANT", result: null };
893
+ }
894
+ const commitCorrelationId = input.request.intent_id;
895
+ const grant = await this.redeemGrant(
896
+ decision.execution_token,
897
+ input.request,
898
+ decision,
899
+ commitCorrelationId
900
+ );
901
+ if (!grant.valid || !grant.claim_token) {
902
+ return { executed: false, decision, grant, reason: "GRANT_NOT_REDEEMABLE", result: null };
903
+ }
904
+ let result;
905
+ try {
906
+ result = await input.execute({
907
+ decision,
908
+ executionToken: decision.execution_token,
909
+ grant,
910
+ claimToken: grant.claim_token,
911
+ commitCorrelationId
912
+ });
913
+ } catch (error) {
914
+ await this.finalizeGrant({
915
+ executionToken: decision.execution_token,
916
+ claimToken: grant.claim_token,
917
+ outcome: input.classifyFailure?.(error) ?? "INDETERMINATE",
918
+ commitCorrelationId,
919
+ downstreamEvidence: { error_type: error instanceof Error ? error.name : "unknown" }
920
+ });
921
+ throw error;
922
+ }
923
+ const finalized = await this.finalizeGrant({
924
+ executionToken: decision.execution_token,
925
+ claimToken: grant.claim_token,
926
+ outcome: "COMMITTED",
927
+ commitCorrelationId
928
+ });
929
+ if (!finalized) grant.reason_codes = [...grant.reason_codes, "COMMIT_FINALIZATION_PENDING"];
930
+ return { executed: true, decision, grant, result };
931
+ };
932
+ };
933
+ function createDecionisExecutionClient(options) {
934
+ return new DecionisExecutionClient(options);
935
+ }
936
+ function authorityIntentBindingOf(request) {
937
+ return {
938
+ protocol_version: request.protocol_version,
939
+ tenant_id: request.tenant_id,
940
+ intent_id: request.intent_id,
941
+ captured_at: request.captured_at,
942
+ expires_at: request.expires_at,
943
+ actor: request.actor,
944
+ action: request.action,
945
+ context: request.context,
946
+ downstream_target: request.downstream_target
947
+ };
948
+ }
949
+ function hashAuthorityIntentBinding(binding) {
950
+ const canonical = stableJsonStringify(binding);
951
+ if (Buffer.byteLength(canonical, "utf8") > 100 * 1024) {
952
+ throw new Error("DECIONIS_INTENT_TOO_LARGE");
953
+ }
954
+ return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
955
+ }
956
+ function bindAuthorityIntent(binding, evidence, mode = "ENFORCEMENT") {
957
+ return {
958
+ ...binding,
959
+ intent_hash: hashAuthorityIntentBinding(binding),
960
+ mode,
961
+ ...evidence === void 0 ? {} : { evidence }
962
+ };
963
+ }
964
+ function stableJsonStringify(value) {
965
+ let entries = 0;
966
+ const normalize = (current, depth) => {
967
+ if (depth > 20) throw new Error("DECIONIS_INTENT_TOO_DEEP");
968
+ if (current === null || typeof current === "string" || typeof current === "boolean") {
969
+ return current;
970
+ }
971
+ if (typeof current === "number") {
972
+ if (!Number.isFinite(current)) throw new Error("DECIONIS_INTENT_NUMBER_INVALID");
973
+ return current;
974
+ }
975
+ if (Array.isArray(current)) {
976
+ if (current.length > 1e3) throw new Error("DECIONIS_INTENT_ARRAY_TOO_LARGE");
977
+ entries += current.length;
978
+ if (entries > 5e3) throw new Error("DECIONIS_INTENT_TOO_MANY_ENTRIES");
979
+ return current.map((item) => normalize(item, depth + 1));
980
+ }
981
+ if (!current || typeof current !== "object") throw new Error("DECIONIS_INTENT_VALUE_INVALID");
982
+ const source = current;
983
+ const keys = Object.keys(source).sort();
984
+ entries += keys.length;
985
+ if (entries > 5e3) throw new Error("DECIONIS_INTENT_TOO_MANY_ENTRIES");
986
+ const normalized = {};
987
+ for (const key of keys) {
988
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
989
+ throw new Error("DECIONIS_INTENT_KEY_UNSAFE");
990
+ }
991
+ normalized[key] = normalize(source[key], depth + 1);
992
+ }
993
+ return normalized;
994
+ };
995
+ return JSON.stringify(normalize(value, 0));
996
+ }
997
+ function assertAuthorityUrl(options) {
998
+ const url = new URL(options.authorityBaseUrl);
999
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
1000
+ if (url.username || url.password) throw new Error("DECIONIS_URL_MUST_NOT_CONTAIN_CREDENTIALS");
1001
+ if (url.protocol !== "https:" && !(loopback && options.allowInsecureLoopback === true)) {
1002
+ throw new Error("DECIONIS_URL_MUST_USE_HTTPS");
1003
+ }
1004
+ }
1005
+ function bindKubernetesAuthorityAction(input) {
1006
+ const group = input.apiGroup || "core";
1007
+ const actionType = `kubernetes.${input.operation.toLowerCase()}.${token(group)}.${token(input.resource)}`.slice(
1008
+ 0,
1009
+ 120
1010
+ );
1011
+ const parameters = {
1012
+ cluster_id: input.clusterId,
1013
+ operation: input.operation,
1014
+ api_group: input.apiGroup,
1015
+ api_version: input.apiVersion,
1016
+ kind: input.kind,
1017
+ resource: input.resource,
1018
+ subresource: input.subresource ?? null,
1019
+ namespace: input.namespace ?? null,
1020
+ name: input.name ?? null,
1021
+ old_object_digest: input.oldObjectDigest,
1022
+ proposed_object_digest: input.proposedObjectDigest,
1023
+ material_diff: input.materialDiff,
1024
+ material_facts: input.materialFacts,
1025
+ policy_version: input.policyVersion,
1026
+ policy_rule: input.policyRule,
1027
+ dry_run: input.dryRun ?? false
1028
+ };
1029
+ return bindAuthorityIntent(
1030
+ {
1031
+ protocol_version: "agent-safe.intent/1",
1032
+ tenant_id: input.organizationId,
1033
+ intent_id: input.intentId,
1034
+ captured_at: input.capturedAt,
1035
+ expires_at: input.expiresAt,
1036
+ actor: input.actor,
1037
+ action: {
1038
+ type: actionType,
1039
+ resource: [
1040
+ input.clusterId,
1041
+ group,
1042
+ input.apiVersion,
1043
+ input.resource,
1044
+ input.subresource ?? "-",
1045
+ input.namespace ?? "-",
1046
+ input.name ?? "-"
1047
+ ].map((part) => encodeURIComponent(part)).join(":").slice(0, 500),
1048
+ parameters
1049
+ },
1050
+ context: {
1051
+ source: input.requestSource ?? "kubernetes",
1052
+ kubernetes: {
1053
+ ...parameters,
1054
+ request_source: input.requestSource ?? null,
1055
+ ...input.additionalKubernetesContext ?? {}
1056
+ }
1057
+ },
1058
+ downstream_target: {
1059
+ system: `kubernetes:${input.clusterId}`,
1060
+ operation: input.operation,
1061
+ endpoint: [
1062
+ group,
1063
+ input.apiVersion,
1064
+ input.resource,
1065
+ input.namespace ?? "-",
1066
+ input.name ?? "-"
1067
+ ].map((part) => encodeURIComponent(part)).join("/").slice(0, 500)
1068
+ }
1069
+ },
1070
+ void 0,
1071
+ input.mode
1072
+ );
1073
+ }
1074
+ function hashKubernetesObject(input) {
1075
+ if (!input) return null;
1076
+ return `sha256:${createHash2("sha256").update(stableJson(input), "utf8").digest("hex")}`;
1077
+ }
1078
+ function summarizeKubernetesObjectDiff(oldObject, proposedObject, secret) {
1079
+ const paths = [];
1080
+ collectChangedPaths(oldObject, proposedObject, "", paths, secret);
1081
+ const unique = [...new Set(paths)].sort();
1082
+ return {
1083
+ changed_path_count: unique.length,
1084
+ changed_paths: unique.slice(0, 64),
1085
+ truncated: unique.length > 64
1086
+ };
1087
+ }
1088
+ function deriveKubernetesMaterialFacts(input) {
1089
+ const object = input.object ?? input.oldObject;
1090
+ const facts = {};
1091
+ const replicas = finiteNumber(record(input.object?.spec).replicas);
1092
+ if (replicas !== null && replicas >= 0) facts.proposed_replicas = replicas;
1093
+ const workloadSpec = podSpec(object, input.kind);
1094
+ if (workloadSpec) {
1095
+ const containers = [
1096
+ ...records(workloadSpec.containers),
1097
+ ...records(workloadSpec.initContainers),
1098
+ ...records(workloadSpec.ephemeralContainers)
1099
+ ];
1100
+ const serviceAccount = string(workloadSpec.serviceAccountName) ?? string(workloadSpec.serviceAccount);
1101
+ facts.workload_security = {
1102
+ privileged_containers: containers.filter(
1103
+ (container) => record(container.securityContext).privileged === true
1104
+ ).length,
1105
+ allow_privilege_escalation_containers: containers.filter(
1106
+ (container) => record(container.securityContext).allowPrivilegeEscalation === true
1107
+ ).length,
1108
+ host_network: workloadSpec.hostNetwork === true,
1109
+ host_pid: workloadSpec.hostPID === true,
1110
+ host_ipc: workloadSpec.hostIPC === true,
1111
+ host_path_volumes: records(workloadSpec.volumes).filter((volume) => isRecord(volume.hostPath)).length,
1112
+ host_port_bindings: containers.reduce(
1113
+ (count, container) => count + records(container.ports).filter((port) => finiteNumber(port.hostPort) !== null).length,
1114
+ 0
1115
+ ),
1116
+ gpu_count: containers.reduce((count, container) => count + gpuCount(container), 0),
1117
+ ...serviceAccount ? {
1118
+ workload_service_account_sha256: createHash2("sha256").update(serviceAccount, "utf8").digest("hex")
1119
+ } : {}
1120
+ };
1121
+ }
1122
+ if (input.apiGroup === "rbac.authorization.k8s.io" && ["roles", "clusterroles", "rolebindings", "clusterrolebindings"].includes(input.resource)) {
1123
+ const roleRef = record(object?.roleRef);
1124
+ const rules = records(object?.rules);
1125
+ facts.rbac = {
1126
+ binds_cluster_admin: string(roleRef.name) === "cluster-admin" && string(roleRef.kind) === "ClusterRole",
1127
+ wildcard_rules: rules.filter((rule) => {
1128
+ const verbs = strings(rule.verbs);
1129
+ const resources = strings(rule.resources);
1130
+ const apiGroups = strings(rule.apiGroups);
1131
+ return verbs.includes("*") || resources.includes("*") || apiGroups.includes("*");
1132
+ }).length,
1133
+ privilege_escalation_rules: rules.filter(
1134
+ (rule) => strings(rule.verbs).some((verb) => ["bind", "escalate", "impersonate"].includes(verb))
1135
+ ).length
1136
+ };
1137
+ }
1138
+ if (input.kind === "Secret") facts.sensitive_object = true;
1139
+ return facts;
1140
+ }
1141
+ function collectChangedPaths(before, after, path, paths, secret) {
1142
+ if (stableJson(before) === stableJson(after)) return;
1143
+ if (secret && (path === "/data" || path === "/stringData")) {
1144
+ paths.push(`${path}/<redacted>`);
1145
+ return;
1146
+ }
1147
+ if (isRecord(before) && isRecord(after)) {
1148
+ for (const key of [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort()) {
1149
+ collectChangedPaths(before[key], after[key], `${path}/${pointer(key)}`, paths, secret);
1150
+ }
1151
+ return;
1152
+ }
1153
+ paths.push(path || "/");
1154
+ }
1155
+ function stableJson(value) {
1156
+ if (value === void 0) return "undefined";
1157
+ const normalize = (current) => {
1158
+ if (Array.isArray(current)) return current.map(normalize);
1159
+ if (!isRecord(current)) return current;
1160
+ return Object.fromEntries(
1161
+ Object.keys(current).sort().map((key) => [key, normalize(current[key])])
1162
+ );
1163
+ };
1164
+ return JSON.stringify(normalize(value));
1165
+ }
1166
+ function podSpec(object, kind) {
1167
+ const spec = record(object?.spec);
1168
+ if (kind === "Pod") return spec;
1169
+ if (kind === "CronJob") {
1170
+ const jobSpec = record(record(spec.jobTemplate).spec);
1171
+ const cronPodSpec = record(record(jobSpec.template).spec);
1172
+ return Object.keys(cronPodSpec).length > 0 ? cronPodSpec : null;
1173
+ }
1174
+ const workloadPodSpec = record(record(spec.template).spec);
1175
+ return Object.keys(workloadPodSpec).length > 0 ? workloadPodSpec : null;
1176
+ }
1177
+ function gpuCount(container) {
1178
+ const resources = record(container.resources);
1179
+ const requests = record(resources.requests);
1180
+ const limits = record(resources.limits);
1181
+ let total = 0;
1182
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(requests), ...Object.keys(limits)])) {
1183
+ if (!/(^|[./-])gpu$/i.test(key)) continue;
1184
+ total += Math.max(quantity(requests[key]), quantity(limits[key]));
1185
+ }
1186
+ return total;
1187
+ }
1188
+ function quantity(input) {
1189
+ if (typeof input === "number" && Number.isFinite(input) && input >= 0) return input;
1190
+ if (typeof input !== "string" || !/^\d+(?:\.\d+)?$/.test(input)) return 0;
1191
+ const parsed = Number(input);
1192
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
1193
+ }
1194
+ function finiteNumber(input) {
1195
+ return typeof input === "number" && Number.isFinite(input) ? input : null;
1196
+ }
1197
+ function string(input) {
1198
+ return typeof input === "string" && input.length > 0 ? input : null;
1199
+ }
1200
+ function strings(input) {
1201
+ return Array.isArray(input) ? input.filter((value) => typeof value === "string") : [];
1202
+ }
1203
+ function records(input) {
1204
+ return Array.isArray(input) ? input.filter(isRecord) : [];
1205
+ }
1206
+ function record(input) {
1207
+ return isRecord(input) ? input : {};
1208
+ }
1209
+ function isRecord(input) {
1210
+ return !!input && typeof input === "object" && !Array.isArray(input);
1211
+ }
1212
+ function pointer(input) {
1213
+ return input.replace(/~/g, "~0").replace(/\//g, "~1");
1214
+ }
1215
+ function token(input) {
1216
+ return input.toLowerCase().replace(/[^a-z0-9._:-]+/g, "-").replace(/^[^a-z]+/, "x");
1217
+ }
1218
+ var personalDeviceSchema = external_exports.object({
1219
+ device_id: external_exports.string(),
1220
+ org_id: external_exports.string(),
1221
+ label: external_exports.string(),
1222
+ scopes: external_exports.array(external_exports.string()),
1223
+ registered_at: external_exports.string()
1224
+ });
1225
+ var personalProtocolSchema = external_exports.object({
1226
+ outcome: external_exports.string(),
1227
+ dossier_id: external_exports.string(),
1228
+ evaluation_id: external_exports.string().optional(),
1229
+ policy_version: external_exports.string().optional(),
1230
+ verification_url: external_exports.string().optional()
1231
+ }).passthrough();
1232
+ var personalCapsuleSchema = external_exports.object({
1233
+ capsule_id: external_exports.string(),
1234
+ org_id: external_exports.string(),
1235
+ title: external_exports.string(),
1236
+ state_hash: external_exports.string(),
1237
+ payload_ref: external_exports.string(),
1238
+ dossier_id: external_exports.string(),
1239
+ verification_url: external_exports.string(),
1240
+ evidence_hashes: external_exports.array(external_exports.string()),
1241
+ captured_at: external_exports.string(),
1242
+ retention_days: external_exports.number(),
1243
+ deleted_at: external_exports.string().nullable()
1244
+ });
1245
+ var registerDeviceResponseSchema = external_exports.object({
1246
+ device: personalDeviceSchema,
1247
+ token: external_exports.string()
1248
+ });
1249
+ var captureCapsuleResponseSchema = external_exports.object({
1250
+ capsule: personalCapsuleSchema,
1251
+ protocol: personalProtocolSchema
1252
+ });
1253
+ var resumeCapsuleResponseSchema = external_exports.object({
1254
+ verdict: external_exports.literal("RESTORE"),
1255
+ execution: external_exports.object({
1256
+ type: external_exports.string(),
1257
+ parameters: external_exports.record(external_exports.unknown())
1258
+ }).optional(),
1259
+ protocol: personalProtocolSchema
1260
+ });
1261
+ var recallResponseSchema = external_exports.object({
1262
+ verdict: external_exports.enum(["NO_ACTION", "PIN_RESOURCE", "LOAD_SIDEBAR", "OPEN_RESOURCE"]),
1263
+ dispatch: external_exports.object({
1264
+ type: external_exports.enum(["PIN_RESOURCE", "LOAD_SIDEBAR", "OPEN_RESOURCE"]),
1265
+ parameters: external_exports.object({
1266
+ resource_url: external_exports.string(),
1267
+ label: external_exports.string().nullable(),
1268
+ rule_id: external_exports.string()
1269
+ })
1270
+ }).nullable(),
1271
+ protocol: personalProtocolSchema.optional()
1272
+ });
1273
+ var personalConfigResponseSchema = external_exports.object({
1274
+ service: external_exports.literal("personal"),
1275
+ org_id: external_exports.string(),
1276
+ config_version: external_exports.string().nullable(),
1277
+ device: external_exports.object({
1278
+ device_id: external_exports.string().nullable(),
1279
+ auth_type: external_exports.string().nullable(),
1280
+ scopes: external_exports.array(external_exports.string())
1281
+ }),
1282
+ policy: external_exports.object({
1283
+ bundle_id: external_exports.string(),
1284
+ version: external_exports.string(),
1285
+ rules_sha256: external_exports.string(),
1286
+ parameters_sha256: external_exports.string(),
1287
+ rule_count: external_exports.number(),
1288
+ source: external_exports.string(),
1289
+ stored_at: external_exports.string()
1290
+ }).nullable(),
1291
+ realms: external_exports.record(external_exports.unknown()),
1292
+ recall_rules: external_exports.array(external_exports.record(external_exports.unknown()))
1293
+ });
1294
+
1295
+ // src/kubernetes/KubernetesActionPlanner.ts
1296
+ var KubernetesActionPlanner = class {
1297
+ constructor(config, runner, now = () => /* @__PURE__ */ new Date(), createId = randomUUID) {
1298
+ this.config = config;
1299
+ this.runner = runner;
1300
+ this.now = now;
1301
+ this.createId = createId;
1302
+ }
1303
+ discoveryCache = /* @__PURE__ */ new Map();
1304
+ async planManifest(manifestPath, namespace, source) {
1305
+ if (!manifestPath.trim() || manifestPath.length > 2e3 || /[\r\n\0]/.test(manifestPath)) {
1306
+ throw new Error("KUBERNETES_MANIFEST_PATH_INVALID");
1307
+ }
1308
+ const namespaceArgs = namespace ? ["--namespace", namespace] : [];
1309
+ const proposed = this.objects(
1310
+ await this.jsonCommand([
1311
+ "create",
1312
+ "--dry-run=client",
1313
+ "--output=json",
1314
+ "--filename",
1315
+ manifestPath,
1316
+ ...namespaceArgs
1317
+ ])
1318
+ );
1319
+ if (proposed.length === 0 || proposed.length > 100) {
1320
+ throw new Error("KUBERNETES_MANIFEST_OBJECT_COUNT_INVALID");
1321
+ }
1322
+ const current = this.objects(
1323
+ await this.jsonCommand([
1324
+ "get",
1325
+ "--ignore-not-found",
1326
+ "--output=json",
1327
+ "--filename",
1328
+ manifestPath,
1329
+ ...namespaceArgs
1330
+ ])
1331
+ );
1332
+ return Promise.all(
1333
+ proposed.map(async (object) => {
1334
+ const identity = this.identity(object, namespace);
1335
+ const oldObject = current.find((candidate) => this.sameIdentity(candidate, identity)) ?? null;
1336
+ const resource = await this.resourceName(
1337
+ identity.apiGroup,
1338
+ identity.apiVersion,
1339
+ identity.kind
1340
+ );
1341
+ return this.build({
1342
+ object,
1343
+ oldObject,
1344
+ resource,
1345
+ subresource: null,
1346
+ source,
1347
+ ...identity
1348
+ });
1349
+ })
1350
+ );
1351
+ }
1352
+ async planScale(input) {
1353
+ if (!Number.isInteger(input.replicas) || input.replicas < 0 || input.replicas > 1e6) {
1354
+ throw new Error("KUBERNETES_REPLICA_COUNT_INVALID");
1355
+ }
1356
+ const endpoint = `/apis/apps/v1/namespaces/${encodeURIComponent(input.namespace)}/deployments/${encodeURIComponent(input.name)}/scale`;
1357
+ const oldObject = this.record(await this.jsonCommand(["get", "--raw", endpoint]));
1358
+ const object = {
1359
+ ...oldObject,
1360
+ spec: { ...this.record(oldObject.spec), replicas: input.replicas }
1361
+ };
1362
+ return this.build({
1363
+ object,
1364
+ oldObject,
1365
+ apiGroup: "apps",
1366
+ apiVersion: "v1",
1367
+ kind: "Scale",
1368
+ resource: "deployments",
1369
+ subresource: "scale",
1370
+ namespace: input.namespace,
1371
+ name: input.name,
1372
+ source: "mcp:k8s_scale",
1373
+ executionEndpoint: endpoint
1374
+ });
1375
+ }
1376
+ build(input) {
1377
+ const capturedAt = this.now();
1378
+ const proposedObjectDigest = hashKubernetesObject(input.object);
1379
+ if (!proposedObjectDigest) throw new Error("KUBERNETES_OBJECT_DIGEST_MISSING");
1380
+ const oldObjectDigest = hashKubernetesObject(input.oldObject);
1381
+ const materialDiff = summarizeKubernetesObjectDiff(
1382
+ input.oldObject,
1383
+ input.object,
1384
+ input.kind === "Secret"
1385
+ );
1386
+ const materialFacts = deriveKubernetesMaterialFacts({
1387
+ object: input.object,
1388
+ oldObject: input.oldObject,
1389
+ kind: input.kind,
1390
+ apiGroup: input.apiGroup,
1391
+ resource: input.resource
1392
+ });
1393
+ const operation = input.oldObject ? "UPDATE" : "CREATE";
1394
+ const request = bindKubernetesAuthorityAction({
1395
+ organizationId: this.config.organizationId,
1396
+ clusterId: this.config.clusterId,
1397
+ policyVersion: this.config.policyVersion,
1398
+ policyRule: "mcp-client-gate",
1399
+ mode: "ENFORCEMENT",
1400
+ intentId: this.createId(),
1401
+ capturedAt: capturedAt.toISOString(),
1402
+ expiresAt: new Date(capturedAt.valueOf() + this.config.grantTtlSeconds * 1e3).toISOString(),
1403
+ actor: {
1404
+ id: this.config.actorId,
1405
+ type: "KUBERNETES_CLIENT",
1406
+ runtime: "mcp",
1407
+ trust_level: "MCP_CONFIGURED"
1408
+ },
1409
+ operation,
1410
+ apiGroup: input.apiGroup,
1411
+ apiVersion: input.apiVersion,
1412
+ kind: input.kind,
1413
+ resource: input.resource,
1414
+ subresource: input.subresource,
1415
+ namespace: input.namespace,
1416
+ name: input.name,
1417
+ oldObjectDigest,
1418
+ proposedObjectDigest,
1419
+ materialDiff,
1420
+ materialFacts,
1421
+ requestSource: input.source
1422
+ });
1423
+ return {
1424
+ request,
1425
+ operation,
1426
+ apiGroup: input.apiGroup,
1427
+ apiVersion: input.apiVersion,
1428
+ kind: input.kind,
1429
+ resource: input.resource,
1430
+ namespace: input.namespace,
1431
+ name: input.name,
1432
+ oldObjectDigest,
1433
+ proposedObjectDigest,
1434
+ materialDiff,
1435
+ materialFacts,
1436
+ executionObject: input.object,
1437
+ executionEndpoint: input.executionEndpoint ?? null
1438
+ };
1439
+ }
1440
+ async resourceName(apiGroup, apiVersion, kind) {
1441
+ const key = `${apiGroup}/${apiVersion}`;
1442
+ let resources = this.discoveryCache.get(key);
1443
+ if (!resources) {
1444
+ const endpoint = apiGroup ? `/apis/${encodeURIComponent(apiGroup)}/${encodeURIComponent(apiVersion)}` : `/api/${encodeURIComponent(apiVersion)}`;
1445
+ const discovery = this.record(await this.jsonCommand(["get", "--raw", endpoint]));
1446
+ resources = Array.isArray(discovery.resources) ? discovery.resources.flatMap((candidate) => {
1447
+ const value = this.record(candidate);
1448
+ return typeof value.name === "string" && typeof value.kind === "string" && !value.name.includes("/") ? [{ name: value.name, kind: value.kind }] : [];
1449
+ }) : [];
1450
+ this.discoveryCache.set(key, resources);
1451
+ }
1452
+ const matched = resources.find((candidate) => candidate.kind === kind);
1453
+ if (!matched) throw new Error("KUBERNETES_RESOURCE_DISCOVERY_FAILED");
1454
+ return matched.name;
1455
+ }
1456
+ identity(object, fallbackNamespace) {
1457
+ const apiVersionValue = typeof object.apiVersion === "string" ? object.apiVersion : "";
1458
+ const kind = typeof object.kind === "string" ? object.kind : "";
1459
+ const metadata = this.record(object.metadata);
1460
+ const name = typeof metadata.name === "string" ? metadata.name : "";
1461
+ if (!apiVersionValue || !kind || !name) throw new Error("KUBERNETES_OBJECT_IDENTITY_INVALID");
1462
+ const separator = apiVersionValue.indexOf("/");
1463
+ const apiGroup = separator < 0 ? "" : apiVersionValue.slice(0, separator);
1464
+ const apiVersion = separator < 0 ? apiVersionValue : apiVersionValue.slice(separator + 1);
1465
+ const objectNamespace = typeof metadata.namespace === "string" ? metadata.namespace : null;
1466
+ return { apiGroup, apiVersion, kind, namespace: objectNamespace ?? fallbackNamespace, name };
1467
+ }
1468
+ sameIdentity(object, expected) {
1469
+ const actual = this.identity(object, expected.namespace);
1470
+ return actual.apiGroup === expected.apiGroup && actual.apiVersion === expected.apiVersion && actual.kind === expected.kind && actual.namespace === expected.namespace && actual.name === expected.name;
1471
+ }
1472
+ async jsonCommand(args) {
1473
+ const output = await this.runner.run(args);
1474
+ if (Buffer.byteLength(output, "utf8") > 4 * 1024 * 1024) {
1475
+ throw new Error("KUBERNETES_COMMAND_OUTPUT_TOO_LARGE");
1476
+ }
1477
+ try {
1478
+ return JSON.parse(output);
1479
+ } catch {
1480
+ throw new Error("KUBERNETES_COMMAND_OUTPUT_INVALID");
1481
+ }
1482
+ }
1483
+ objects(input) {
1484
+ const value = this.record(input);
1485
+ if (value.kind === "List" && Array.isArray(value.items)) {
1486
+ return value.items.map((item) => this.record(item)).filter((item) => Object.keys(item).length > 0);
1487
+ }
1488
+ return Object.keys(value).length > 0 ? [value] : [];
1489
+ }
1490
+ record(input) {
1491
+ return input && typeof input === "object" && !Array.isArray(input) ? input : {};
1492
+ }
1493
+ };
1494
+
1495
+ // src/kubernetes/KubernetesCommandRunner.ts
1496
+ import { spawn } from "child_process";
1497
+ var KubectlCommandError = class extends Error {
1498
+ constructor(code) {
1499
+ super(code);
1500
+ this.code = code;
1501
+ this.name = "KubectlCommandError";
1502
+ }
1503
+ };
1504
+ var KubectlCommandRunner = class {
1505
+ constructor(context, timeoutMs = 1e4) {
1506
+ this.context = context;
1507
+ this.timeoutMs = timeoutMs;
1508
+ }
1509
+ run(args, standardInput) {
1510
+ if (standardInput && Buffer.byteLength(standardInput, "utf8") > 4 * 1024 * 1024) {
1511
+ return Promise.reject(new KubectlCommandError("KUBECTL_STANDARD_INPUT_TOO_LARGE"));
1512
+ }
1513
+ const fullArgs = this.context ? ["--context", this.context, ...args] : args;
1514
+ return new Promise((resolve, reject) => {
1515
+ const child = spawn("kubectl", fullArgs, {
1516
+ stdio: ["pipe", "pipe", "pipe"],
1517
+ windowsHide: true
1518
+ });
1519
+ const chunks = [];
1520
+ let outputBytes = 0;
1521
+ let settled = false;
1522
+ const finish = (callback) => {
1523
+ if (settled) return;
1524
+ settled = true;
1525
+ clearTimeout(timer);
1526
+ callback();
1527
+ };
1528
+ const timer = setTimeout(() => {
1529
+ child.kill("SIGKILL");
1530
+ finish(() => reject(new KubectlCommandError("KUBECTL_COMMAND_TIMEOUT")));
1531
+ }, this.timeoutMs);
1532
+ child.stdout.on("data", (chunk) => {
1533
+ outputBytes += chunk.length;
1534
+ if (outputBytes > 4 * 1024 * 1024) {
1535
+ child.kill("SIGKILL");
1536
+ finish(() => reject(new KubectlCommandError("KUBECTL_COMMAND_OUTPUT_TOO_LARGE")));
1537
+ return;
1538
+ }
1539
+ chunks.push(chunk);
1540
+ });
1541
+ child.stderr.resume();
1542
+ child.on("error", () => {
1543
+ finish(() => reject(new KubectlCommandError("KUBECTL_COMMAND_FAILED")));
1544
+ });
1545
+ child.on("close", (code) => {
1546
+ finish(() => {
1547
+ if (code !== 0) {
1548
+ reject(new KubectlCommandError("KUBECTL_COMMAND_FAILED"));
1549
+ return;
1550
+ }
1551
+ resolve(Buffer.concat(chunks).toString("utf8"));
1552
+ });
1553
+ });
1554
+ child.stdin.on("error", () => void 0);
1555
+ child.stdin.end(standardInput);
1556
+ });
1557
+ }
1558
+ };
1559
+
1560
+ // ../../packages/dossier-verify/src/canonical.ts
1561
+ function canonicalize(value) {
1562
+ if (Array.isArray(value)) {
1563
+ return value.map((item) => canonicalize(item));
1564
+ }
1565
+ if (!value || typeof value !== "object") {
1566
+ return value;
1567
+ }
1568
+ const record2 = value;
1569
+ const sortedKeys = Object.keys(record2).sort();
1570
+ const normalized = {};
1571
+ for (const key of sortedKeys) {
1572
+ normalized[key] = canonicalize(record2[key]);
1573
+ }
1574
+ return normalized;
1575
+ }
1576
+ function stableJsonStringify2(value) {
1577
+ return JSON.stringify(canonicalize(value));
1578
+ }
1579
+ function jcsCanonicalize(value) {
1580
+ if (value === null) return "null";
1581
+ if (typeof value === "boolean") return value ? "true" : "false";
1582
+ if (typeof value === "string") {
1583
+ assertUnicodeScalarString(value);
1584
+ return JSON.stringify(value);
1585
+ }
1586
+ if (typeof value === "number") {
1587
+ if (!Number.isFinite(value)) throw new TypeError("JCS_NUMBER_MUST_BE_FINITE");
1588
+ return JSON.stringify(value);
1589
+ }
1590
+ if (Array.isArray(value)) return `[${value.map((item) => jcsCanonicalize(item)).join(",")}]`;
1591
+ if (!value || typeof value !== "object") throw new TypeError("JCS_VALUE_NOT_JSON");
1592
+ const prototype = Object.getPrototypeOf(value);
1593
+ if (prototype !== Object.prototype && prototype !== null) {
1594
+ throw new TypeError("JCS_OBJECT_MUST_BE_PLAIN");
1595
+ }
1596
+ const record2 = value;
1597
+ return `{${Object.keys(record2).sort().map((key) => {
1598
+ assertUnicodeScalarString(key);
1599
+ if (record2[key] === void 0) throw new TypeError("JCS_UNDEFINED_NOT_ALLOWED");
1600
+ return `${JSON.stringify(key)}:${jcsCanonicalize(record2[key])}`;
1601
+ }).join(",")}}`;
1602
+ }
1603
+ function assertUnicodeScalarString(value) {
1604
+ for (let index = 0; index < value.length; index += 1) {
1605
+ const code = value.charCodeAt(index);
1606
+ if (code >= 55296 && code <= 56319) {
1607
+ const next = value.charCodeAt(index + 1);
1608
+ if (!(next >= 56320 && next <= 57343)) throw new TypeError("JCS_LONE_SURROGATE");
1609
+ index += 1;
1610
+ } else if (code >= 56320 && code <= 57343) {
1611
+ throw new TypeError("JCS_LONE_SURROGATE");
1612
+ }
1613
+ }
1614
+ }
1615
+
1616
+ // ../../packages/dossier-verify/src/verify.ts
1617
+ import { createHash as createHash3, createPublicKey, verify as verifyEd25519 } from "crypto";
1618
+ function asRecord(value) {
1619
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1620
+ return value;
1621
+ }
1622
+ function asString(value) {
1623
+ return typeof value === "string" ? value : null;
1624
+ }
1625
+ function resolveJsonPointer(root, pointer2) {
1626
+ if (!pointer2.startsWith("/")) return void 0;
1627
+ return pointer2.split("/").slice(1).reduce((current, rawSegment) => {
1628
+ const segment = rawSegment.replace(/~1/g, "/").replace(/~0/g, "~");
1629
+ if (current && typeof current === "object" && !Array.isArray(current)) {
1630
+ return current[segment];
1631
+ }
1632
+ return void 0;
1633
+ }, root);
1634
+ }
1635
+ function findJwk(jwks, keyIds) {
1636
+ const wanted = new Set(keyIds.filter((keyId) => keyId.trim().length > 0));
1637
+ if (!jwks || wanted.size === 0) return null;
1638
+ for (const key of jwks.keys) {
1639
+ if (asString(key.kty) === "OKP" && asString(key.crv) === "Ed25519" && wanted.has(asString(key.kid) ?? "")) {
1640
+ return key;
1641
+ }
1642
+ }
1643
+ return null;
1644
+ }
1645
+ function parseProofBundle(value) {
1646
+ const record2 = asRecord(value);
1647
+ if (asString(record2.bundle_type) !== "decionis.decision_dossier.proof_bundle" || asString(record2.version) !== "2.0" && asString(record2.version) !== "2.1" || asString(record2.issued_at) == null || asString(record2.key_id) == null) {
1648
+ return null;
1649
+ }
1650
+ const rotationPolicyRecord = asRecord(record2.rotation_policy);
1651
+ const activeKeyId = asString(rotationPolicyRecord.active_key_id);
1652
+ const publicJwksPath = asString(rotationPolicyRecord.public_jwks_path);
1653
+ if (!activeKeyId || !publicJwksPath) return null;
1654
+ const artifacts = (Array.isArray(record2.artifacts) ? record2.artifacts : []).map((entry) => {
1655
+ const item = asRecord(entry);
1656
+ const artifactKind = asString(item.artifact_kind);
1657
+ const mediaType = asString(item.media_type);
1658
+ const documentPath = asString(item.document_path);
1659
+ const canonicalDocumentSha = asString(item.canonical_document_sha256);
1660
+ const signature = asString(item.signature);
1661
+ const canonicalizationProfile = asString(item.canonicalization_profile);
1662
+ if (!artifactKind || !mediaType || !documentPath || !canonicalDocumentSha || !signature) {
1663
+ return null;
1664
+ }
1665
+ return {
1666
+ artifact_kind: artifactKind,
1667
+ media_type: mediaType,
1668
+ document_path: documentPath,
1669
+ canonical_document_sha256: canonicalDocumentSha,
1670
+ ...canonicalizationProfile === "RFC8785/JCS" || canonicalizationProfile === "DECIONIS_SORTED_JSON_V1" ? { canonicalization_profile: canonicalizationProfile } : {},
1671
+ signature
1672
+ };
1673
+ }).filter((item) => item !== null);
1674
+ if (artifacts.length === 0) return null;
1675
+ const previousKeyIds = (Array.isArray(rotationPolicyRecord.previous_key_ids) ? rotationPolicyRecord.previous_key_ids : []).map((entry) => asString(entry)).filter((entry) => entry !== null);
1676
+ const gracePeriod = Number.parseInt(
1677
+ String(rotationPolicyRecord.verification_grace_period_days ?? ""),
1678
+ 10
1679
+ );
1680
+ return {
1681
+ bundle_type: "decionis.decision_dossier.proof_bundle",
1682
+ version: asString(record2.version),
1683
+ issued_at: asString(record2.issued_at),
1684
+ algorithm: "Ed25519",
1685
+ key_id: asString(record2.key_id),
1686
+ rotation_policy: {
1687
+ strategy: asString(rotationPolicyRecord.strategy) ?? "JWKS_OVERLAP",
1688
+ active_key_id: activeKeyId,
1689
+ previous_key_ids: previousKeyIds,
1690
+ verification_grace_period_days: Number.isFinite(gracePeriod) ? gracePeriod : 0,
1691
+ rotated_at: asString(rotationPolicyRecord.rotated_at),
1692
+ public_jwks_path: publicJwksPath,
1693
+ public_jwks_url: asString(rotationPolicyRecord.public_jwks_url) ?? void 0
1694
+ },
1695
+ artifacts
1696
+ };
1697
+ }
1698
+ function verifyDossierProofBundle(input) {
1699
+ const integrity = asRecord(input.dossier_payload.integrity);
1700
+ const bundle = parseProofBundle(integrity.proof_bundle);
1701
+ if (!bundle) {
1702
+ return {
1703
+ verified: false,
1704
+ available: false,
1705
+ key_id: null,
1706
+ artifacts_checked: 0,
1707
+ checks: [
1708
+ {
1709
+ key: "proof_bundle",
1710
+ label: "Proof Bundle",
1711
+ verified: false,
1712
+ severity: "warn",
1713
+ detail: "No persisted Ed25519 proof bundle was available for verification."
1714
+ }
1715
+ ]
1716
+ };
1717
+ }
1718
+ const jwk = findJwk(input.public_jwks ?? null, [
1719
+ bundle.key_id,
1720
+ bundle.rotation_policy.active_key_id,
1721
+ ...bundle.rotation_policy.previous_key_ids
1722
+ ]);
1723
+ if (!jwk) {
1724
+ return {
1725
+ verified: false,
1726
+ available: true,
1727
+ key_id: bundle.key_id,
1728
+ artifacts_checked: 0,
1729
+ checks: [
1730
+ {
1731
+ key: "public_key",
1732
+ label: "Public Key",
1733
+ verified: false,
1734
+ severity: "fail",
1735
+ detail: `No Ed25519 public key matched proof bundle key ${bundle.key_id}.`
1736
+ }
1737
+ ]
1738
+ };
1739
+ }
1740
+ let publicKey;
1741
+ try {
1742
+ publicKey = createPublicKey({ key: jwk, format: "jwk" });
1743
+ } catch (err) {
1744
+ return {
1745
+ verified: false,
1746
+ available: true,
1747
+ key_id: bundle.key_id,
1748
+ artifacts_checked: 0,
1749
+ checks: [
1750
+ {
1751
+ key: "public_key",
1752
+ label: "Public Key",
1753
+ verified: false,
1754
+ severity: "fail",
1755
+ detail: err instanceof Error ? err.message : "Unable to import Ed25519 public key."
1756
+ }
1757
+ ]
1758
+ };
1759
+ }
1760
+ const checks = [
1761
+ {
1762
+ key: "public_key",
1763
+ label: "Public Key",
1764
+ verified: true,
1765
+ severity: "pass",
1766
+ detail: `Loaded Ed25519 public key ${String(jwk.kid ?? bundle.key_id)}.`
1767
+ }
1768
+ ];
1769
+ let artifactsChecked = 0;
1770
+ for (const artifact of bundle.artifacts) {
1771
+ const document = resolveJsonPointer(input.dossier_payload, artifact.document_path);
1772
+ if (document === void 0) {
1773
+ checks.push({
1774
+ key: `${artifact.artifact_kind}:document`,
1775
+ label: `${artifact.artifact_kind} Document`,
1776
+ verified: false,
1777
+ severity: "fail",
1778
+ detail: `Signed document path ${artifact.document_path} was not present in the dossier payload.`
1779
+ });
1780
+ continue;
1781
+ }
1782
+ const canonicalDocument = artifact.canonicalization_profile === "RFC8785/JCS" ? jcsCanonicalize(document) : stableJsonStringify2(document);
1783
+ const actualSha = createHash3("sha256").update(canonicalDocument, "utf8").digest("hex");
1784
+ const hashVerified = actualSha.toLowerCase() === artifact.canonical_document_sha256.toLowerCase();
1785
+ checks.push({
1786
+ key: `${artifact.artifact_kind}:sha256`,
1787
+ label: `${artifact.artifact_kind} SHA-256`,
1788
+ verified: hashVerified,
1789
+ severity: hashVerified ? "pass" : "fail",
1790
+ detail: hashVerified ? `Canonical digest ${actualSha}.` : `Expected ${artifact.canonical_document_sha256}, computed ${actualSha}.`
1791
+ });
1792
+ let signatureVerified = false;
1793
+ try {
1794
+ signatureVerified = verifyEd25519(
1795
+ null,
1796
+ Buffer.from(canonicalDocument, "utf8"),
1797
+ publicKey,
1798
+ Buffer.from(artifact.signature, "base64url")
1799
+ );
1800
+ } catch {
1801
+ signatureVerified = false;
1802
+ }
1803
+ checks.push({
1804
+ key: `${artifact.artifact_kind}:signature`,
1805
+ label: `${artifact.artifact_kind} Signature`,
1806
+ verified: signatureVerified,
1807
+ severity: signatureVerified ? "pass" : "fail",
1808
+ detail: signatureVerified ? "Ed25519 signature verified against the public JWKS." : "Ed25519 signature did not verify against the public JWKS."
1809
+ });
1810
+ artifactsChecked += 1;
1811
+ }
1812
+ return {
1813
+ verified: checks.every((check) => check.verified),
1814
+ available: true,
1815
+ key_id: bundle.key_id,
1816
+ artifacts_checked: artifactsChecked,
1817
+ checks
1818
+ };
1819
+ }
1820
+
1821
+ // ../../packages/dossier-verify/src/resolve.ts
1822
+ function asRecord2(value) {
1823
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1824
+ return value;
1825
+ }
1826
+ function extractDossierPayload(body) {
1827
+ const record2 = asRecord2(body);
1828
+ if (asRecord2(record2.integrity).proof_bundle !== void 0) return record2;
1829
+ for (const key of ["dossier", "dossier_payload", "data", "result"]) {
1830
+ const candidate = asRecord2(record2[key]);
1831
+ if (asRecord2(candidate.integrity).proof_bundle !== void 0) return candidate;
1832
+ }
1833
+ return record2;
1834
+ }
1835
+ function resolveJwksUrl(payload, dossierUrl) {
1836
+ const bundle = parseProofBundle(asRecord2(payload.integrity).proof_bundle);
1837
+ if (!bundle) return null;
1838
+ const absolute = bundle.rotation_policy.public_jwks_url;
1839
+ if (absolute && /^https?:\/\//i.test(absolute)) return absolute;
1840
+ try {
1841
+ return new URL(bundle.rotation_policy.public_jwks_path, dossierUrl).toString();
1842
+ } catch {
1843
+ return null;
1844
+ }
1845
+ }
1846
+ var MAX_VERIFICATION_RESPONSE_BYTES = 2 * 1024 * 1024;
1847
+ async function fetchJson(url, fetchImpl) {
1848
+ const response = await fetchImpl(url, { headers: { accept: "application/json" } });
1849
+ if (!response.ok) {
1850
+ throw new Error(`Request to ${url} failed with HTTP ${response.status}.`);
1851
+ }
1852
+ const responseText = await boundedResponseText(response, url);
1853
+ return JSON.parse(responseText);
1854
+ }
1855
+ async function boundedResponseText(response, url) {
1856
+ const declaredLength = Number(response.headers.get("content-length"));
1857
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_VERIFICATION_RESPONSE_BYTES) {
1858
+ throw new Error(`Response from ${url} exceeded the verification size limit.`);
1859
+ }
1860
+ if (!response.body) return "";
1861
+ const reader = response.body.getReader();
1862
+ const chunks = [];
1863
+ let receivedBytes = 0;
1864
+ while (true) {
1865
+ const { done, value } = await reader.read();
1866
+ if (done) break;
1867
+ receivedBytes += value.byteLength;
1868
+ if (receivedBytes > MAX_VERIFICATION_RESPONSE_BYTES) {
1869
+ await reader.cancel().catch(() => void 0);
1870
+ throw new Error(`Response from ${url} exceeded the verification size limit.`);
1871
+ }
1872
+ chunks.push(value);
1873
+ }
1874
+ return Buffer.concat(
1875
+ chunks.map((chunk) => Buffer.from(chunk)),
1876
+ receivedBytes
1877
+ ).toString("utf8");
1878
+ }
1879
+ async function verifyDossierFromUrls(input) {
1880
+ const fetchImpl = input.fetch ?? fetch;
1881
+ const body = await fetchJson(input.dossierUrl, fetchImpl);
1882
+ const payload = extractDossierPayload(body);
1883
+ const jwksUrl = input.jwksUrl ?? resolveJwksUrl(payload, input.dossierUrl);
1884
+ if (!jwksUrl) {
1885
+ return {
1886
+ result: verifyDossierProofBundle({ dossier_payload: payload, public_jwks: null }),
1887
+ jwksUrl: null,
1888
+ payload
1889
+ };
1890
+ }
1891
+ const jwks = await fetchJson(jwksUrl, fetchImpl);
1892
+ return {
1893
+ result: verifyDossierProofBundle({ dossier_payload: payload, public_jwks: jwks }),
1894
+ jwksUrl,
1895
+ payload
1896
+ };
1897
+ }
1898
+
1899
+ // src/kubernetes/KubernetesOperations.ts
1900
+ var KubernetesOperations = class _KubernetesOperations {
1901
+ constructor(planner, runner, authority, allowedVerificationOrigins) {
1902
+ this.planner = planner;
1903
+ this.runner = runner;
1904
+ this.authority = authority;
1905
+ this.allowedVerificationOrigins = allowedVerificationOrigins;
1906
+ }
1907
+ async plan(manifestPath, namespace) {
1908
+ return this.planSummary(
1909
+ await this.planner.planManifest(manifestPath, namespace, "mcp:k8s_plan")
1910
+ );
1911
+ }
1912
+ async requestAuthority(manifestPath, namespace) {
1913
+ const planned = await this.planner.planManifest(
1914
+ manifestPath,
1915
+ namespace,
1916
+ "mcp:k8s_request_authority"
1917
+ );
1918
+ const decisions = await Promise.all(
1919
+ planned.map(async (action) => this.authority.enforceAndBind(action.request))
1920
+ );
1921
+ return decisions.map((decision, index) => this.decisionSummary(decision, planned[index]));
1922
+ }
1923
+ async apply(manifestPath, namespace) {
1924
+ const planned = await this.planner.planManifest(manifestPath, namespace, "mcp:k8s_apply");
1925
+ return this.authorizeAndExecute(planned, async () => {
1926
+ const args = ["apply", "--filename", "-"];
1927
+ if (namespace) args.push("--namespace", namespace);
1928
+ return this.runner.run(args, _KubernetesOperations.executionManifest(planned));
1929
+ });
1930
+ }
1931
+ async rollback(manifestPath, namespace) {
1932
+ const planned = await this.planner.planManifest(manifestPath, namespace, "mcp:k8s_rollback");
1933
+ if (planned.some((action) => action.operation !== "UPDATE")) {
1934
+ return {
1935
+ executed: false,
1936
+ verdict: "ERROR",
1937
+ decisions: [],
1938
+ reason_code: "KUBERNETES_ROLLBACK_TARGET_NOT_FOUND"
1939
+ };
1940
+ }
1941
+ return this.authorizeAndExecute(planned, async () => {
1942
+ const args = ["apply", "--filename", "-"];
1943
+ if (namespace) args.push("--namespace", namespace);
1944
+ return this.runner.run(args, _KubernetesOperations.executionManifest(planned));
1945
+ });
1946
+ }
1947
+ async scale(input) {
1948
+ const planned = await this.planner.planScale(input);
1949
+ if (!planned.executionEndpoint) throw new Error("KUBERNETES_SCALE_ENDPOINT_MISSING");
1950
+ return this.authorizeAndExecute(
1951
+ [planned],
1952
+ () => this.runner.run(
1953
+ ["replace", "--raw", planned.executionEndpoint, "--filename", "-"],
1954
+ JSON.stringify(planned.executionObject)
1955
+ )
1956
+ );
1957
+ }
1958
+ async verifyDossier(dossierUrl, jwksUrl) {
1959
+ this.assertAllowedVerificationUrl(dossierUrl);
1960
+ this.assertAllowedVerificationUrl(jwksUrl);
1961
+ const controller = new AbortController();
1962
+ const timer = setTimeout(() => controller.abort(), 1e4);
1963
+ try {
1964
+ const verified = await verifyDossierFromUrls({
1965
+ dossierUrl,
1966
+ jwksUrl,
1967
+ fetch: (input, init) => fetch(input, { ...init, redirect: "error", signal: controller.signal })
1968
+ });
1969
+ return {
1970
+ verified: verified.result.verified,
1971
+ available: verified.result.available,
1972
+ key_id: verified.result.key_id,
1973
+ artifacts_checked: verified.result.artifacts_checked,
1974
+ checks: verified.result.checks,
1975
+ jwks_url: verified.jwksUrl
1976
+ };
1977
+ } finally {
1978
+ clearTimeout(timer);
1979
+ }
1980
+ }
1981
+ async authorizeAndExecute(planned, execute) {
1982
+ const decisions = [];
1983
+ for (const action of planned) {
1984
+ try {
1985
+ decisions.push(await this.authority.enforceAndBind(action.request));
1986
+ } catch {
1987
+ return {
1988
+ executed: false,
1989
+ verdict: "ERROR",
1990
+ decisions: decisions.map(
1991
+ (decision, index) => this.decisionSummary(decision, planned[index])
1992
+ ),
1993
+ reason_code: "DECIONIS_AUTHORITY_UNAVAILABLE"
1994
+ };
1995
+ }
1996
+ }
1997
+ const summaries = decisions.map(
1998
+ (decision, index) => this.decisionSummary(decision, planned[index])
1999
+ );
2000
+ const deniedIndex = decisions.findIndex(
2001
+ (decision, index) => decision.status !== "ALLOW" || !decision.should_execute || !decision.execution_token || decision.mode !== "ENFORCEMENT" || decision.action_hash !== planned[index]?.request.intent_hash || !decision.dossier_id || !decision.dossier_sha256
2002
+ );
2003
+ if (deniedIndex >= 0) {
2004
+ const decision = decisions[deniedIndex];
2005
+ return {
2006
+ executed: false,
2007
+ verdict: _KubernetesOperations.verdict(decision),
2008
+ decisions: summaries,
2009
+ reason_code: decision.reason_codes[0] ?? "DECIONIS_EXECUTION_NOT_AUTHORIZED"
2010
+ };
2011
+ }
2012
+ for (let index = 0; index < decisions.length; index += 1) {
2013
+ const decision = decisions[index];
2014
+ const action = planned[index];
2015
+ const redemption = await this.authority.redeemGrant(
2016
+ decision.execution_token,
2017
+ action.request,
2018
+ decision
2019
+ );
2020
+ if (!redemption.valid) {
2021
+ return {
2022
+ executed: false,
2023
+ verdict: "ERROR",
2024
+ decisions: summaries,
2025
+ reason_code: redemption.reason_codes[0] ?? "DECIONIS_EXECUTION_GRANT_INVALID"
2026
+ };
2027
+ }
2028
+ }
2029
+ try {
2030
+ return {
2031
+ executed: true,
2032
+ verdict: "ALLOW",
2033
+ decisions: summaries,
2034
+ kubernetes_result: (await execute()).trim().slice(0, 8e3)
2035
+ };
2036
+ } catch {
2037
+ return {
2038
+ executed: false,
2039
+ verdict: "ERROR",
2040
+ decisions: summaries,
2041
+ reason_code: "KUBERNETES_EXECUTION_FAILED_FRESH_AUTHORITY_REQUIRED"
2042
+ };
2043
+ }
2044
+ }
2045
+ planSummary(planned) {
2046
+ return planned.map((action) => ({
2047
+ action_hash: action.request.intent_hash,
2048
+ action_type: action.request.action.type,
2049
+ operation: action.operation,
2050
+ api_group: action.apiGroup,
2051
+ api_version: action.apiVersion,
2052
+ kind: action.kind,
2053
+ resource: action.resource,
2054
+ namespace: action.namespace,
2055
+ name: action.name,
2056
+ old_object_digest: action.oldObjectDigest,
2057
+ proposed_object_digest: action.proposedObjectDigest,
2058
+ material_diff: action.materialDiff,
2059
+ material_facts: action.materialFacts,
2060
+ note: "No raw manifest or Secret value is included in this plan."
2061
+ }));
2062
+ }
2063
+ decisionSummary(decision, action) {
2064
+ return {
2065
+ verdict: _KubernetesOperations.verdict(decision),
2066
+ decision_id: decision.decision_id,
2067
+ dossier_id: decision.dossier_id,
2068
+ dossier_sha256: decision.dossier_sha256,
2069
+ dossier_url: decision.dossier_url,
2070
+ policy_version: decision.policy_version,
2071
+ approval_request_id: decision.approval_request_id,
2072
+ reason_codes: decision.reason_codes,
2073
+ action_hash: decision.action_hash,
2074
+ expected_action_hash: action.request.intent_hash,
2075
+ resource: action.resource,
2076
+ namespace: action.namespace,
2077
+ name: action.name
2078
+ };
2079
+ }
2080
+ static verdict(decision) {
2081
+ if (decision.status === "ALLOW") return "ALLOW";
2082
+ if (decision.status === "ESCALATE" || decision.status === "REVIEW_REQUIRED") return "HOLD";
2083
+ if (decision.status === "BLOCK") return "BLOCK";
2084
+ return "ERROR";
2085
+ }
2086
+ static executionManifest(planned) {
2087
+ if (planned.length === 1) return JSON.stringify(planned[0].executionObject);
2088
+ return JSON.stringify({
2089
+ apiVersion: "v1",
2090
+ kind: "List",
2091
+ items: planned.map((action) => action.executionObject)
2092
+ });
2093
+ }
2094
+ assertAllowedVerificationUrl(input) {
2095
+ const url = new URL(input);
2096
+ if (url.protocol !== "https:" || url.username || url.password || !this.allowedVerificationOrigins.has(url.origin)) {
2097
+ throw new Error("DOSSIER_URL_ORIGIN_NOT_ALLOWED");
2098
+ }
2099
+ }
2100
+ };
2101
+ function createKubernetesAuthorityClient(input) {
2102
+ return createDecionisExecutionClient({
2103
+ authorityBaseUrl: input.baseUrl,
2104
+ timeoutMs: input.timeoutMs,
2105
+ defaultHeaders: { authorization: `Bearer ${input.apiKey}` }
2106
+ });
2107
+ }
2108
+
2109
+ // src/kubernetes/KubernetesMcpTools.ts
2110
+ var text2 = (value) => [
2111
+ { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
2112
+ ];
2113
+ var KubernetesMcpTools = class _KubernetesMcpTools {
2114
+ constructor(operations) {
2115
+ this.operations = operations;
2116
+ }
2117
+ build() {
2118
+ const manifestSchema = {
2119
+ type: "object",
2120
+ required: ["manifest_path"],
2121
+ properties: {
2122
+ manifest_path: {
2123
+ type: "string",
2124
+ description: "Local path to the exact Kubernetes manifest. Manifest content is never returned."
2125
+ },
2126
+ namespace: { type: "string", description: "Optional namespace fallback." }
2127
+ },
2128
+ additionalProperties: false
2129
+ };
2130
+ return [
2131
+ {
2132
+ name: "k8s_plan",
2133
+ title: "Plan Exact Kubernetes Actions",
2134
+ description: "Resolve an exact manifest against the current cluster and return only digests, bounded material facts, and action hashes. Does not call authority or mutate Kubernetes.",
2135
+ inputSchema: manifestSchema,
2136
+ annotations: {
2137
+ readOnlyHint: true,
2138
+ destructiveHint: false,
2139
+ idempotentHint: true,
2140
+ openWorldHint: true
2141
+ },
2142
+ handler: async (args) => ({
2143
+ content: text2(
2144
+ await this.operations.plan(
2145
+ _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
2146
+ _KubernetesMcpTools.optionalNamespace(args.namespace)
2147
+ )
2148
+ )
2149
+ })
2150
+ },
2151
+ {
2152
+ name: "k8s_request_authority",
2153
+ title: "Request Kubernetes Execution Authority",
2154
+ description: "Ask the existing Decionis authority for ALLOW, HOLD, or BLOCK on each exact manifest action and return dossier references. Does not run kubectl apply.",
2155
+ inputSchema: manifestSchema,
2156
+ annotations: {
2157
+ readOnlyHint: false,
2158
+ destructiveHint: false,
2159
+ idempotentHint: false,
2160
+ openWorldHint: true
2161
+ },
2162
+ handler: async (args) => ({
2163
+ content: text2(
2164
+ await this.operations.requestAuthority(
2165
+ _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
2166
+ _KubernetesMcpTools.optionalNamespace(args.namespace)
2167
+ )
2168
+ )
2169
+ })
2170
+ },
2171
+ {
2172
+ name: "k8s_apply",
2173
+ title: "Apply an Authorized Kubernetes Manifest",
2174
+ description: "Recompute the exact action, require and consume a Decionis ALLOW grant, then run kubectl apply. The admission webhook remains the independent cluster-side gate.",
2175
+ inputSchema: manifestSchema,
2176
+ annotations: {
2177
+ readOnlyHint: false,
2178
+ destructiveHint: true,
2179
+ idempotentHint: false,
2180
+ openWorldHint: true
2181
+ },
2182
+ handler: async (args) => ({
2183
+ content: text2(
2184
+ await this.operations.apply(
2185
+ _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
2186
+ _KubernetesMcpTools.optionalNamespace(args.namespace)
2187
+ )
2188
+ )
2189
+ })
2190
+ },
2191
+ {
2192
+ name: "k8s_scale",
2193
+ title: "Scale an Authorized Kubernetes Deployment",
2194
+ description: "Bind the exact Deployment scale object and replica count, require and consume a Decionis ALLOW grant, then run kubectl scale. Scale-to-zero is visible as a material fact.",
2195
+ inputSchema: {
2196
+ type: "object",
2197
+ required: ["namespace", "name", "replicas"],
2198
+ properties: {
2199
+ namespace: { type: "string" },
2200
+ name: { type: "string" },
2201
+ replicas: { type: "integer", minimum: 0, maximum: 1e6 }
2202
+ },
2203
+ additionalProperties: false
2204
+ },
2205
+ annotations: {
2206
+ readOnlyHint: false,
2207
+ destructiveHint: true,
2208
+ idempotentHint: false,
2209
+ openWorldHint: true
2210
+ },
2211
+ handler: async (args) => ({
2212
+ content: text2(
2213
+ await this.operations.scale({
2214
+ namespace: _KubernetesMcpTools.requiredDnsName(args.namespace, "namespace"),
2215
+ name: _KubernetesMcpTools.requiredDnsName(args.name, "name"),
2216
+ replicas: _KubernetesMcpTools.requiredInteger(args.replicas, "replicas")
2217
+ })
2218
+ )
2219
+ })
2220
+ },
2221
+ {
2222
+ name: "k8s_rollback",
2223
+ title: "Apply an Exact Authorized Rollback",
2224
+ description: "Apply an operator-supplied exact rollback manifest only when the target already exists and Decionis grants the bound UPDATE. Vague rollout undo is intentionally not used.",
2225
+ inputSchema: manifestSchema,
2226
+ annotations: {
2227
+ readOnlyHint: false,
2228
+ destructiveHint: true,
2229
+ idempotentHint: false,
2230
+ openWorldHint: true
2231
+ },
2232
+ handler: async (args) => ({
2233
+ content: text2(
2234
+ await this.operations.rollback(
2235
+ _KubernetesMcpTools.requiredString(args.manifest_path, "manifest_path"),
2236
+ _KubernetesMcpTools.optionalNamespace(args.namespace)
2237
+ )
2238
+ )
2239
+ })
2240
+ },
2241
+ {
2242
+ name: "k8s_verify_dossier",
2243
+ title: "Verify a Kubernetes Decision Dossier",
2244
+ description: "Fetch a dossier proof bundle and JWKS from configured HTTPS origins and verify SHA-256 plus Ed25519 evidence independently.",
2245
+ inputSchema: {
2246
+ type: "object",
2247
+ required: ["dossier_url", "jwks_url"],
2248
+ properties: {
2249
+ dossier_url: { type: "string", format: "uri" },
2250
+ jwks_url: { type: "string", format: "uri" }
2251
+ },
2252
+ additionalProperties: false
2253
+ },
2254
+ annotations: {
2255
+ readOnlyHint: true,
2256
+ destructiveHint: false,
2257
+ idempotentHint: true,
2258
+ openWorldHint: true
2259
+ },
2260
+ handler: async (args) => ({
2261
+ content: text2(
2262
+ await this.operations.verifyDossier(
2263
+ _KubernetesMcpTools.requiredString(args.dossier_url, "dossier_url"),
2264
+ _KubernetesMcpTools.requiredString(args.jwks_url, "jwks_url")
2265
+ )
2266
+ )
2267
+ })
312
2268
  }
313
2269
  ];
314
2270
  }
2271
+ static requiredString(input, field) {
2272
+ if (typeof input !== "string" || !input.trim())
2273
+ throw new Error(`${field.toUpperCase()}_REQUIRED`);
2274
+ return input.trim();
2275
+ }
2276
+ static optionalNamespace(input) {
2277
+ if (input === void 0) return null;
2278
+ return _KubernetesMcpTools.requiredDnsName(input, "namespace");
2279
+ }
2280
+ static requiredDnsName(input, field) {
2281
+ const value = _KubernetesMcpTools.requiredString(input, field);
2282
+ if (!/^[a-z0-9](?:[-a-z0-9.]*[a-z0-9])?$/.test(value) || value.length > 253) {
2283
+ throw new Error(`${field.toUpperCase()}_INVALID`);
2284
+ }
2285
+ return value;
2286
+ }
2287
+ static requiredInteger(input, field) {
2288
+ if (!Number.isInteger(input)) throw new Error(`${field.toUpperCase()}_INVALID`);
2289
+ return input;
2290
+ }
315
2291
  };
316
- function createDecionisTools(gateway2) {
317
- return new DecionisTools(gateway2).build();
2292
+ function createKubernetesMcpToolsFromEnvironment(environment = process.env) {
2293
+ if (environment.DECIONIS_KUBERNETES_TOOLS_ENABLED !== "true") return [];
2294
+ const baseUrl = requiredEnvironment(environment, "DECIONIS_AUTHORITY_BASE_URL");
2295
+ const apiKey = requiredEnvironment(environment, "DECIONIS_API_KEY");
2296
+ const organizationId = requiredEnvironment(environment, "DECIONIS_ORG_ID");
2297
+ const clusterId = requiredEnvironment(environment, "DECIONIS_KUBERNETES_CLUSTER_ID");
2298
+ const actorId = requiredEnvironment(environment, "DECIONIS_KUBERNETES_ACTOR");
2299
+ const policyVersion = requiredEnvironment(environment, "DECIONIS_KUBERNETES_POLICY_VERSION");
2300
+ const context = environment.DECIONIS_KUBERNETES_CONTEXT?.trim() || null;
2301
+ const timeoutMs = boundedInteger(environment.DECIONIS_KUBERNETES_TIMEOUT_MS, 4e3, 100, 1e4);
2302
+ const grantTtlSeconds = boundedInteger(
2303
+ environment.DECIONIS_KUBERNETES_GRANT_TTL_SECONDS,
2304
+ 60,
2305
+ 1,
2306
+ 300
2307
+ );
2308
+ const authorityOrigin = new URL(baseUrl).origin;
2309
+ const allowedVerificationOrigins = /* @__PURE__ */ new Set([
2310
+ authorityOrigin,
2311
+ ...(environment.DECIONIS_KUBERNETES_VERIFICATION_ORIGINS ?? "").split(",").map((value) => value.trim()).filter(Boolean).map((value) => new URL(value).origin)
2312
+ ]);
2313
+ const runner = new KubectlCommandRunner(context, timeoutMs);
2314
+ const planner = new KubernetesActionPlanner(
2315
+ { organizationId, clusterId, actorId, policyVersion, grantTtlSeconds },
2316
+ runner
2317
+ );
2318
+ const operations = new KubernetesOperations(
2319
+ planner,
2320
+ runner,
2321
+ createKubernetesAuthorityClient({ baseUrl, apiKey, timeoutMs }),
2322
+ allowedVerificationOrigins
2323
+ );
2324
+ return new KubernetesMcpTools(operations).build();
2325
+ }
2326
+ function requiredEnvironment(environment, name) {
2327
+ const value = environment[name]?.trim();
2328
+ if (!value) throw new Error(`${name}_REQUIRED`);
2329
+ return value;
2330
+ }
2331
+ function boundedInteger(input, fallback, minimum, maximum) {
2332
+ const value = input === void 0 ? fallback : Number(input);
2333
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
2334
+ throw new Error("DECIONIS_KUBERNETES_NUMERIC_CONFIGURATION_INVALID");
2335
+ }
2336
+ return value;
2337
+ }
2338
+
2339
+ // src/ShieldClient.ts
2340
+ var ShieldClient = class {
2341
+ baseUrl;
2342
+ apiKey;
2343
+ appId;
2344
+ fetchImpl;
2345
+ constructor(options = {}) {
2346
+ this.baseUrl = (options.baseUrl ?? process.env.DECIONIS_SHIELD_API_URL ?? "https://api.decionis.com").replace(/\/+$/, "");
2347
+ this.apiKey = options.apiKey ?? process.env.DECIONIS_SHIELD_API_KEY ?? "";
2348
+ this.appId = options.appId ?? process.env.DECIONIS_SHIELD_APP_ID ?? "";
2349
+ this.fetchImpl = options.fetch ?? fetch;
2350
+ }
2351
+ authorize(request) {
2352
+ return this.json("/v1/shield/authorize", "POST", request);
2353
+ }
2354
+ permission() {
2355
+ return this.json("/v1/shield/permission", "GET");
2356
+ }
2357
+ requestApproval(decisionId) {
2358
+ return this.json(`/v1/shield/decisions/${encodeURIComponent(decisionId)}/approval`, "POST", {});
2359
+ }
2360
+ decision(decisionId) {
2361
+ return this.json(`/v1/shield/decisions/${encodeURIComponent(decisionId)}`, "GET");
2362
+ }
2363
+ async json(path, method, body) {
2364
+ if (!this.apiKey || !this.appId) {
2365
+ throw new ShieldClientError(
2366
+ "SHIELD_MCP_CONFIGURATION_REQUIRED",
2367
+ "Configure DECIONIS_SHIELD_API_KEY and DECIONIS_SHIELD_APP_ID. Do not execute the transaction."
2368
+ );
2369
+ }
2370
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
2371
+ method,
2372
+ headers: {
2373
+ accept: "application/json",
2374
+ authorization: `Bearer ${this.apiKey}`,
2375
+ "content-type": "application/json",
2376
+ "x-shield-app-id": this.appId,
2377
+ "x-shield-environment": "production"
2378
+ },
2379
+ ...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {}
2380
+ });
2381
+ const payload = await response.json().catch(() => null);
2382
+ if (!response.ok) {
2383
+ const server = asRecord3(payload);
2384
+ throw new ShieldClientError(
2385
+ typeof server.error === "string" ? server.error : "SHIELD_MCP_REQUEST_FAILED",
2386
+ typeof server.message === "string" ? `${server.message} Do not execute the transaction.` : `Shield returned HTTP ${response.status}. Do not execute the transaction.`
2387
+ );
2388
+ }
2389
+ return asRecord3(payload);
2390
+ }
2391
+ };
2392
+ var ShieldClientError = class extends Error {
2393
+ constructor(code, message) {
2394
+ super(message);
2395
+ this.code = code;
2396
+ this.name = "ShieldClientError";
2397
+ }
2398
+ };
2399
+ function asRecord3(value) {
2400
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2401
+ }
2402
+
2403
+ // src/ShieldTools.ts
2404
+ var text3 = (value) => [
2405
+ { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
2406
+ ];
2407
+ var purchaseProperties = {
2408
+ amount: { type: "number", minimum: 0, description: "Amount in major currency units." },
2409
+ currency: {
2410
+ type: "string",
2411
+ pattern: "^[A-Za-z]{3}$",
2412
+ description: "ISO 4217 currency code."
2413
+ },
2414
+ purpose: {
2415
+ type: "string",
2416
+ minLength: 1,
2417
+ description: "Clear user-facing reason for spending."
2418
+ },
2419
+ merchant: { type: "string" },
2420
+ category: { type: "string" },
2421
+ agentId: {
2422
+ type: "string",
2423
+ description: "Registered agent identity, when an agent is spending."
2424
+ },
2425
+ transactionType: {
2426
+ type: "string",
2427
+ enum: ["PURCHASE", "SUBSCRIPTION", "RENEWAL", "TRANSFER", "REFUND", "OTHER"]
2428
+ },
2429
+ metadata: { type: "object" }
2430
+ };
2431
+ var networkMutation = {
2432
+ readOnlyHint: false,
2433
+ destructiveHint: false,
2434
+ idempotentHint: false,
2435
+ openWorldHint: true
2436
+ };
2437
+ var networkRead = {
2438
+ readOnlyHint: true,
2439
+ destructiveHint: false,
2440
+ idempotentHint: true,
2441
+ openWorldHint: true
2442
+ };
2443
+ function createShieldTools(shield) {
2444
+ const authorize = async (args) => {
2445
+ if (typeof args.amount !== "number" || typeof args.currency !== "string" || typeof args.purpose !== "string") {
2446
+ return {
2447
+ content: text3(
2448
+ "amount, currency, and purpose are required. Do not execute the transaction."
2449
+ ),
2450
+ isError: true
2451
+ };
2452
+ }
2453
+ return shieldResult(async () => await shield.authorize(args));
2454
+ };
2455
+ return [
2456
+ {
2457
+ name: "shield.request_purchase",
2458
+ title: "Request Shield Purchase Authorization",
2459
+ description: "Ask the user's Shield for ALLOW, ASK, or BLOCK before a purchase. Execute only on ALLOW.",
2460
+ inputSchema: {
2461
+ type: "object",
2462
+ required: ["amount", "currency", "purpose"],
2463
+ properties: purchaseProperties,
2464
+ additionalProperties: false
2465
+ },
2466
+ annotations: networkMutation,
2467
+ handler: authorize
2468
+ },
2469
+ {
2470
+ name: "shield.check_spending_authority",
2471
+ title: "Check Shield Spending Authority",
2472
+ description: "Check spending authority for an app or AI agent before taking the consequential action.",
2473
+ inputSchema: {
2474
+ type: "object",
2475
+ required: ["amount", "currency", "purpose"],
2476
+ properties: purchaseProperties,
2477
+ additionalProperties: false
2478
+ },
2479
+ annotations: networkMutation,
2480
+ handler: authorize
2481
+ },
2482
+ {
2483
+ name: "shield.get_permission",
2484
+ title: "Get Shield Permission",
2485
+ description: "Read the spender identity and user-owned limits bound to this Shield credential.",
2486
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
2487
+ annotations: networkRead,
2488
+ handler: async () => shieldResult(async () => await shield.permission())
2489
+ },
2490
+ {
2491
+ name: "shield.request_approval",
2492
+ title: "Request Shield Approval",
2493
+ description: "Start or poll Presence approval for an ASK decision. Do not execute while the verdict remains ASK.",
2494
+ inputSchema: {
2495
+ type: "object",
2496
+ required: ["decisionId"],
2497
+ properties: { decisionId: { type: "string" } },
2498
+ additionalProperties: false
2499
+ },
2500
+ annotations: networkMutation,
2501
+ handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.requestApproval(args.decisionId)) : {
2502
+ content: text3("decisionId is required. Do not execute the transaction."),
2503
+ isError: true
2504
+ }
2505
+ },
2506
+ {
2507
+ name: "shield.get_decision",
2508
+ title: "Get Shield Decision",
2509
+ description: "Retrieve the current ALLOW, ASK, or BLOCK decision and its evidence identifiers.",
2510
+ inputSchema: {
2511
+ type: "object",
2512
+ required: ["decisionId"],
2513
+ properties: { decisionId: { type: "string" } },
2514
+ additionalProperties: false
2515
+ },
2516
+ annotations: networkRead,
2517
+ handler: async (args) => typeof args.decisionId === "string" ? shieldResult(async () => await shield.decision(args.decisionId)) : {
2518
+ content: text3("decisionId is required. Do not execute the transaction."),
2519
+ isError: true
2520
+ }
2521
+ }
2522
+ ];
2523
+ }
2524
+ async function shieldResult(work) {
2525
+ try {
2526
+ return { content: text3(await work()) };
2527
+ } catch (error) {
2528
+ return {
2529
+ content: text3({
2530
+ error: error instanceof Error ? error.name : "ShieldError",
2531
+ message: error instanceof Error ? error.message : String(error),
2532
+ safeToExecute: false
2533
+ }),
2534
+ isError: true
2535
+ };
2536
+ }
318
2537
  }
319
2538
 
320
2539
  // src/index.ts
321
2540
  var gateway = new LocalDecionisGateway();
2541
+ var shieldConfigured = Boolean(
2542
+ process.env.DECIONIS_SHIELD_API_KEY && process.env.DECIONIS_SHIELD_APP_ID
2543
+ );
322
2544
  var handler = createMcpHandler({
323
2545
  serverInfo: { name: "decionis-mcp", version: MCP_SERVER_VERSION },
324
- tools: createDecionisTools(gateway)
2546
+ tools: createDecionisTools(gateway, [
2547
+ ...createKubernetesMcpToolsFromEnvironment(),
2548
+ ...shieldConfigured ? createShieldTools(new ShieldClient()) : []
2549
+ ])
325
2550
  });
326
2551
  runStdioServer(handler).then(async () => {
327
2552
  await gateway.close();