OSCAL export from a TypeScript stack
OSCAL export from a TypeScript stack means generating NIST's machine-readable assessment documents (Security Assessment Results, POA&M, and Assessment Plan) directly from your Node codebase, no Java re-keying. Caisson's Compliance bundle authors OSCAL v1.2.2 JSON from a signed evidence-pack manifest with assembleOscalEvidenceBundle, deterministic given a pinned clock and id seam, and emits an XML sibling through NIST's own oscal-cli.
In code
// From an evidence-pack manifest — authors the OSCAL Assessment Plan, SAR, and POA&M,
// then signs the manifest. Pin the `now` + `newId` seams for byte-identical output.
const bundle = await assembleOscalEvidenceBundle(manifest, signer, {
now: runAt,
newId: seededUuid,
});
// bundle.files is a { relativePath -> canonical UTF-8 bytes } map — write it straight out:
// ./assessment-plan/soc2.json ./sar.json ./poam.json ./manifest.json ./manifest.sig
for (const [path, bytes] of Object.entries(bundle.files)) {
await writeFile(join(outDir, path), bytes);
}How it holds
Native TypeScript/JSON, no Java re-key
The Assessment Plan, SAR, and POA&M are authored in TypeScript against the NIST OSCAL v1.2.2 schema; JSON is the canonical, byte-stable output your Node build emits directly, so a GRC tool or FedRAMP reviewer ingests it without a human re-keying a PDF.
Deterministic given a pinned seam
The wall-clock now and the UUID source newId are injected; pin them and the same evidence pack canonicalizes to byte-identical OSCAL every run, so the export is safe to diff and re-verify in CI, not a fresh blob each time.
Satisfied/not-satisfied is derived, gaps recorded not guessed
Each control's OSCAL finding maps from the evidence pack's own readiness (a not-satisfied finding's remarks is the flagged evidence's recorded reason (gapReason), never an inferred explanation) and a clean pack ships the single truthful no-open-items POA&M entry the schema requires.
An XML sibling from NIST's own converter
When XML is required, it's produced by shelling out to NIST's oscal-cli converter, never a hand-rolled serializer, so the XML validates against the same conformance target (v1.2.2) the JSON does.