PR #27 (squash-merged via Gitea API)
Some checks failed
CI / Frontend Lint (push) Failing after 6s
CI / Frontend Type Check (push) Failing after 7s
CI / Frontend Build (push) Failing after 7s
CI / Frontend E2E Tests (push) Failing after 7s
CI / Orchestrator Build (push) Failing after 7s
CI / Orchestrator Unit Tests (push) Failing after 5s
CI / Orchestrator E2E (Testcontainers) (push) Failing after 6s
CI / Contracts Compile (push) Failing after 7s
CI / Contracts Test (push) Failing after 5s
Security Scan / Dependency Vulnerability Scan (push) Failing after 5s
Security Scan / OWASP ZAP Scan (push) Failing after 3s

This commit was merged in pull request #27.
This commit is contained in:
2026-04-22 21:12:21 +00:00
parent 7fdc9c06da
commit c1aef82ede
5 changed files with 647 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
/**
* Loader for the `DBIS/cc-compliance-controls` controls matrix.
*
* `cc-compliance-controls` ships a v0 matrix at
* `controls/matrix/v0.yaml`. When `CC_CONTROLS_MATRIX_URL` is set the
* loader fetches that remote YAML; otherwise it returns an embedded
* snapshot so the orchestrator always has a usable matrix to assert
* against in validation/obligation flows without a network hop.
*
* The embedded snapshot is a faithful copy of the upstream v0 matrix
* at recon time — if upstream evolves, re-sync by fetching and
* replacing the `EMBEDDED_V0_MATRIX` literal.
*/
import { logger } from "../../logging/logger";
import type { CcControlsMatrix } from "./types";
export interface CcControlsConfig {
url?: string;
timeoutMs?: number;
fetchImpl?: typeof fetch;
}
/**
* Embedded v0 matrix — kept small and hand-typed rather than parsed
* from YAML so the orchestrator doesn't drag in a YAML runtime.
*/
const EMBEDDED_V0_MATRIX: CcControlsMatrix = {
version: 0,
source: "embedded",
domains: [
{
id: "identity_proofing",
controls: [
{
id: "IDP-001",
title: "Identity enrollment recorded in audit ledger",
evidenceType: "audit_event",
ownerTeam: "TBD",
frequency: "continuous",
},
],
},
{
id: "payment_issuance",
controls: [
{
id: "PAY-001",
title: "No production PAN in non-production",
evidenceType: "config_scan",
ownerTeam: "TBD",
frequency: "per_release",
},
],
},
{
id: "audit_non_repudiation",
controls: [
{
id: "AUD-001",
title: "Credential state change only via workflow + immutable event",
evidenceType: "architecture_review",
ownerTeam: "TBD",
frequency: "quarterly",
},
],
},
{
id: "registry_verticals",
controls: [
{
id: "REG-001",
title: "Judicial registry data classified high sensitivity; tenant-scoped APIs only",
evidenceType: "policy_review",
ownerTeam: "TBD",
frequency: "quarterly",
},
],
},
],
};
function loadConfigFromEnv(): CcControlsConfig {
return {
url: process.env.CC_CONTROLS_MATRIX_URL,
timeoutMs: process.env.CC_CONTROLS_MATRIX_TIMEOUT_MS
? parseInt(process.env.CC_CONTROLS_MATRIX_TIMEOUT_MS, 10)
: 10_000,
};
}
/**
* Minimal JSON-or-YAML-ish adapter: upstream ships YAML today but
* could add a JSON endpoint. This loader only accepts `application/
* json` responses — if the endpoint is pure YAML, serve it via a thin
* JSON-convert proxy or extend this loader.
*/
export async function loadControlsMatrix(
cfg: CcControlsConfig = loadConfigFromEnv(),
): Promise<CcControlsMatrix> {
if (!cfg.url) {
logger.info(
{ source: "embedded" },
"[CcControls] controls matrix (no CC_CONTROLS_MATRIX_URL — embedded v0)",
);
return EMBEDDED_V0_MATRIX;
}
const fetchImpl = cfg.fetchImpl ?? fetch;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs ?? 10_000);
try {
const resp = await fetchImpl(cfg.url, {
method: "GET",
headers: { Accept: "application/json" },
signal: controller.signal,
});
if (!resp.ok) {
throw new Error(
`cc-controls matrix GET failed: HTTP ${resp.status}`,
);
}
const body = (await resp.json()) as unknown;
const parsed = normaliseMatrix(body);
logger.info(
{ source: "remote", url: cfg.url, version: parsed.version },
"[CcControls] controls matrix (remote)",
);
return parsed;
} finally {
clearTimeout(timer);
}
}
function normaliseMatrix(raw: unknown): CcControlsMatrix {
if (typeof raw !== "object" || raw === null) {
throw new Error("cc-controls matrix: response is not an object");
}
const r = raw as Record<string, unknown>;
const version = typeof r.version === "number" ? r.version : 0;
const domains = Array.isArray(r.domains) ? r.domains : [];
return {
version,
source: "remote",
domains: domains.map((d) => normaliseDomain(d)),
};
}
function normaliseDomain(raw: unknown): CcControlsMatrix["domains"][number] {
const r = (raw ?? {}) as Record<string, unknown>;
const controls = Array.isArray(r.controls) ? r.controls : [];
return {
id: String(r.id ?? ""),
controls: controls.map((c) => normaliseControl(c)),
};
}
function normaliseControl(raw: unknown): CcControlsMatrix["domains"][number]["controls"][number] {
const r = (raw ?? {}) as Record<string, unknown>;
return {
id: String(r.id ?? ""),
title: String(r.title ?? ""),
evidenceType: String(r.evidence_type ?? r.evidenceType ?? ""),
ownerTeam: String(r.owner_team ?? r.ownerTeam ?? "TBD"),
frequency: String(r.frequency ?? ""),
};
}
/**
* Convenience helper — resolve a control by id across all domains.
* Used by evaluator flows that need to attach control evidence to a
* transition.
*/
export function findControl(
matrix: CcControlsMatrix,
controlId: string,
): CcControlsMatrix["domains"][number]["controls"][number] | undefined {
for (const d of matrix.domains) {
for (const c of d.controls) {
if (c.id === controlId) return c;
}
}
return undefined;
}

View File

@@ -0,0 +1,155 @@
/**
* HTTP client adapter for `DBIS/cc-identity-core`.
*
* Provider-switched: when `CC_IDENTITY_URL` is set the client makes
* real HTTP calls to the upstream Complete Credential identity
* service; otherwise every method returns a deterministic mock so
* unit tests, local dev, and CI still work.
*
* Upstream surface (openapi.yaml + src/server.mjs at recon time):
* GET /health
* GET /ready
* POST /v1/subjects
*
* Extend as additional endpoints ship upstream.
*/
import { randomUUID } from "crypto";
import { logger } from "../../logging/logger";
import type {
CcHealthStatus,
CcSubject,
CcSubjectCreate,
} from "./types";
export interface CcIdentityConfig {
baseUrl?: string;
apiKey?: string;
timeoutMs?: number;
fetchImpl?: typeof fetch;
}
export interface CcIdentityClient {
mode: "live" | "mock";
health(): Promise<CcHealthStatus>;
ready(): Promise<CcHealthStatus>;
createSubject(req: CcSubjectCreate, correlationId?: string): Promise<CcSubject>;
}
function loadConfigFromEnv(): CcIdentityConfig {
return {
baseUrl: process.env.CC_IDENTITY_URL,
apiKey: process.env.CC_IDENTITY_API_KEY,
timeoutMs: process.env.CC_IDENTITY_TIMEOUT_MS
? parseInt(process.env.CC_IDENTITY_TIMEOUT_MS, 10)
: 10_000,
};
}
class HttpCcIdentityClient implements CcIdentityClient {
readonly mode = "live" as const;
private readonly baseUrl: string;
private readonly apiKey?: string;
private readonly timeoutMs: number;
private readonly fetchImpl: typeof fetch;
constructor(
cfg: Required<Pick<CcIdentityConfig, "baseUrl">> & CcIdentityConfig,
) {
this.baseUrl = cfg.baseUrl.replace(/\/+$/, "");
this.apiKey = cfg.apiKey;
this.timeoutMs = cfg.timeoutMs ?? 10_000;
this.fetchImpl = cfg.fetchImpl ?? fetch;
}
private async request<T>(
method: "GET" | "POST",
path: string,
body?: unknown,
correlationId?: string,
): Promise<T> {
const url = `${this.baseUrl}${path}`;
const headers: Record<string, string> = { Accept: "application/json" };
if (body !== undefined) headers["Content-Type"] = "application/json";
if (this.apiKey) headers["X-API-Key"] = this.apiKey;
if (correlationId) headers["X-Correlation-Id"] = correlationId;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const resp = await this.fetchImpl(url, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
if (!resp.ok) {
const text = await resp.text().catch(() => "");
throw new Error(
`cc-identity ${method} ${path} failed: HTTP ${resp.status} ${text.slice(0, 200)}`,
);
}
return (await resp.json()) as T;
} finally {
clearTimeout(timer);
}
}
health(): Promise<CcHealthStatus> {
return this.request<CcHealthStatus>("GET", "/health");
}
ready(): Promise<CcHealthStatus> {
return this.request<CcHealthStatus>("GET", "/ready");
}
createSubject(
req: CcSubjectCreate,
correlationId?: string,
): Promise<CcSubject> {
return this.request<CcSubject>(
"POST",
"/v1/subjects",
req,
correlationId ?? randomUUID(),
);
}
}
class MockCcIdentityClient implements CcIdentityClient {
readonly mode = "mock" as const;
async health(): Promise<CcHealthStatus> {
return { status: "ok", service: "cc-identity-core" };
}
async ready(): Promise<CcHealthStatus> {
return { status: "ok", service: "cc-identity-core", persistence: false };
}
async createSubject(req: CcSubjectCreate): Promise<CcSubject> {
return {
subjectId: randomUUID(),
tenantId: req.tenantId ?? "tenant-demo",
entityId: req.entityId ?? "entity-demo",
createdAt: new Date().toISOString(),
};
}
}
export function createCcIdentityClient(
cfg: CcIdentityConfig = loadConfigFromEnv(),
): CcIdentityClient {
if (cfg.baseUrl) {
logger.info(
{ baseUrl: cfg.baseUrl, mode: "live" },
"[CcIdentity] HTTP client",
);
return new HttpCcIdentityClient({ ...cfg, baseUrl: cfg.baseUrl });
}
logger.info(
{ mode: "mock" },
"[CcIdentity] HTTP client (no CC_IDENTITY_URL — mock mode)",
);
return new MockCcIdentityClient();
}

View File

@@ -0,0 +1,28 @@
/**
* Public surface for the DBIS Complete Credential (cc-*) adapters.
*
* Covers the upstream bounded-context repos the orchestrator needs:
* - cc-identity-core → identityClient (HTTP, provider-switched)
* - cc-compliance-controls → controlsMatrix (embedded v0 with
* optional remote JSON override)
*
* cc-payment-adapters / cc-audit-ledger / cc-shared-events are still
* template scaffolds upstream at recon time; when those services
* ship, add sibling clients here following the same pattern.
*/
export * from "./types";
export {
createCcIdentityClient,
} from "./identityClient";
export type {
CcIdentityClient,
CcIdentityConfig,
} from "./identityClient";
export {
loadControlsMatrix,
findControl,
} from "./controlsMatrix";
export type {
CcControlsConfig,
} from "./controlsMatrix";

View File

@@ -0,0 +1,49 @@
/**
* Types shared across the Complete Credential (DBIS cc-*) adapters.
*
* Shapes mirror the relevant upstream repos:
* - cc-identity-core (openapi/openapi.yaml + src/server.mjs)
* - cc-compliance-controls (controls/matrix/v0.yaml)
*
* Only the fields the orchestrator actually consumes are typed —
* extend as needed when more of the CC surface is wired.
*/
export interface CcHealthStatus {
status: "ok" | "unready";
service: string;
persistence?: boolean;
error?: string;
}
export interface CcSubjectCreate {
tenantId?: string;
entityId?: string;
metadata?: Record<string, string | number | boolean>;
}
export interface CcSubject {
subjectId: string;
tenantId: string;
entityId: string;
createdAt: string;
}
export interface CcControl {
id: string;
title: string;
evidenceType: string;
ownerTeam: string;
frequency: string;
}
export interface CcControlDomain {
id: string;
controls: CcControl[];
}
export interface CcControlsMatrix {
version: number;
source: "embedded" | "remote";
domains: CcControlDomain[];
}