Decionis
Atlassian Forge

Decionis Forge Starter Kit

This is the private Atlassian distribution wrapper for Decionis: a Forge issue panel, admin configuration page, and resolver that calls the Decionis Atlassian Forge Action Gate and portable Decision Dossier APIs.

Download Integrity
19f1d5806386fe6aa302264b581936d1004a3cee1c0a004ecbd792064dc0d980 decionis-atlassian-forge-starter-kit.zip
Live Validation
Tenant
https://decionis.atlassian.net
Forge app ID
ari:cloud:ecosystem::app/ea9afd35-b0b4-403d-bd60-b19f9f9e8ad3
Installation ID
11ecc0a9-99e2-4e74-95de-c37bba82c619
Status
Installed in Jira development environment

Runtime wiring values now come from the Decionis internal Atlassian console, where an operator can copy the live bundle and generate a least-privilege Forge API key without turning Jira into a policy-authoring surface.

Forge Manifest

Private-distribution manifest for the Jira issue panel and admin page.

manifest.yml
Resolver

Thin wrapper that calls the Decionis Atlassian Forge Action Gate and portable dossier APIs.

src/index.js
Issue Panel

Jira-side Decision Dossier panel that stays lightweight and defers policy to Decionis.

src/issue-panel.jsx
Admin Wiring

Forge configuration page for orgId, endpointBaseUrl, API key, and optional policyGraphKey.

src/admin-page.jsx
Installation Flow
Step 1
Deploy the Forge app with a private install-link flow from the Atlassian developer console.
Step 2
Configure orgId, endpointBaseUrl, API key, and optional policyGraphKey in the Forge admin page.
Step 3
Add the issue panel to Jira projects where governed execution should be visible.
Step 4
Call the Atlassian Forge Action Gate before advancing Jira-side finance, risk, or change workflows.
Step 5
Keep policy in the Decionis Policy Decision Graph instead of encoding it in Forge functions or UI.
Forge is the distribution wrapper. Decionis remains the institutional policy engine and produces the portable Decision Dossier.
Manifest

Forge app modules for the Jira issue panel and admin surface.

app:
  runtime:
    name: nodejs22.x
  id: ari:cloud:ecosystem::app/ea9afd35-b0b4-403d-bd60-b19f9f9e8ad3

modules:
  jira:issuePanel:
    - key: decionis-decision-dossier-panel
      title: Decionis Decision Dossier
      icon: https://decionis.com/favicon.ico
      resource: issue-panel
      render: native
      resolver:
        function: forge-resolver
  jira:adminPage:
    - key: decionis-forge-admin
      title: Decionis Forge Configuration
      resource: admin-page
      render: native
      resolver:
        function: forge-resolver
  function:
    - key: forge-resolver
      handler: index.handler

resources:
  - key: issue-panel
    path: src/issue-panel.jsx
  - key: admin-page
    path: src/admin-page.jsx

permissions:
  scopes:
    - read:jira-work
    - storage:app
    - write:jira-work
  external:
    fetch:
      backend:
        - https://api.decionis.com
        - https://decionis.com
      client:
        - https://api.decionis.com
        - https://decionis.com
Resolver

The runtime wrapper that routes Jira context into Decionis without selecting policy in Forge.

import Resolver from "@forge/resolver";
import { fetch, storage } from "@forge/api";

const resolver = new Resolver();

async function readRuntimeConfig() {
  const config = (await storage.get("decionis.runtime")) ?? {};
  return {
    orgId: typeof config.orgId === "string" ? config.orgId.trim() : "",
    endpointBaseUrl:
      typeof config.endpointBaseUrl === "string" ? config.endpointBaseUrl.trim() : "",
    apiKey: typeof config.apiKey === "string" ? config.apiKey.trim() : "",
    policyGraphKey: typeof config.policyGraphKey === "string" ? config.policyGraphKey.trim() : "",
  };
}

async function decionisFetch(path, init) {
  const config = await readRuntimeConfig();
  if (!config.orgId || !config.endpointBaseUrl || !config.apiKey) {
    throw new Error(
      "Missing Decionis runtime wiring. Configure orgId, endpointBaseUrl, and apiKey.",
    );
  }
  const base = config.endpointBaseUrl.replace(/\/+$/, "");
  const response = await fetch(`${base}${path}`, {
    ...init,
    headers: {
      "content-type": "application/json",
      authorization: `Bearer ${config.apiKey}`,
      ...(init?.headers ?? {}),
    },
  });
  if (!response.ok) {
    throw new Error(`Decionis API ${response.status}: ${await response.text()}`);
  }
  return await response.json();
}

resolver.define("getRuntimeConfig", async () => {
  const config = await readRuntimeConfig();
  return {
    ...config,
    apiKeyConfigured: Boolean(config.apiKey),
  };
});

resolver.define("saveRuntimeConfig", async ({ payload }) => {
  const next = {
    orgId: String(payload?.orgId ?? "").trim(),
    endpointBaseUrl: String(payload?.endpointBaseUrl ?? "").trim(),
    apiKey: String(payload?.apiKey ?? "").trim(),
    policyGraphKey: String(payload?.policyGraphKey ?? "").trim(),
  };
  await storage.set("decionis.runtime", next);
  return {
    ok: true,
    ...next,
    apiKeyConfigured: Boolean(next.apiKey),
  };
});

resolver.define("evaluateActionGate", async ({ payload, context }) => {
  const config = await readRuntimeConfig();
  const issueKey = payload?.issueKey ?? context?.extension?.issue?.key ?? null;
  return await decionisFetch("/v1/atlassian/forge/action-gate", {
    method: "POST",
    body: JSON.stringify({
      org_id: config.orgId,
      ...(config.policyGraphKey ? { policy_graph_key: config.policyGraphKey } : {}),
      product: "jira",
      issue_key: issueKey,
      record_name: payload?.recordName ?? null,
      requested_action: payload?.requestedAction ?? "advance_issue_workflow",
      amount: payload?.amount ?? undefined,
      risk_score: payload?.riskScore ?? undefined,
      context: {
        issue_type: payload?.issueType ?? null,
        project_key: context?.extension?.project?.key ?? null,
        issue_key: issueKey,
      },
    }),
  });
});

resolver.define("getPortableDossier", async ({ payload }) => {
  const config = await readRuntimeConfig();
  return await decionisFetch(
    `/v1/orgs/${encodeURIComponent(config.orgId)}/decision-dossiers/${encodeURIComponent(payload.dossierId)}/portable`,
    {
      method: "GET",
      headers: {},
    },
  );
});

export const handler = resolver.getDefinitions();
Issue Panel

A minimal Jira-side Decision Dossier panel that triggers the governed action evaluation.

import React, { useState } from "react";
import { invoke } from "@forge/bridge";

export default function IssuePanel() {
  const [result, setResult] = useState(null);
  const [state, setState] = useState("Run the Action Gate to attach a governed Decision Dossier.");

  async function evaluate() {
    setState("Evaluating...");
    const response = await invoke("evaluateActionGate", {
      requestedAction: "advance_issue_workflow",
    });
    setResult(response);
    setState(response.summary ?? "Decision complete.");
  }

  return (
    <div>
      <h3>Decionis Decision Dossier</h3>
      <p>{state}</p>
      <button onClick={() => void evaluate()}>Evaluate with Decionis</button>
      {result ? (
        <div>
          <p>Verdict: {result.verdict}</p>
          <p>Policy: {result.policy_version}</p>
          <a href={result.verification_url} target="_blank" rel="noreferrer">
            Verify Decision Dossier
          </a>
        </div>
      ) : null}
    </div>
  );
}