Some checks failed
Code Quality / SonarQube Analysis (pull_request) Failing after 23s
Code Quality / Code Quality Checks (pull_request) Failing after 11s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 4s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 5s
Architecture note steps 1, 2, 10 (data model). - types/transactionState.ts: 12 states, allowed-transition table, SoD matrix - types/plan.ts: add InstrumentTerms + 'issueInstrument' PlanStep type - services/planValidation.ts: validate SBLC step (BIC, ISO-4217, sha256, YYYY-MM-DD expiry, >0 amount) - services/stateMachine.ts: transition() enforces legality + SoD + appends to transaction_state_transitions - db/migrations/002: plans.transaction_state (CHECK) + transaction_state_transitions append-only table - tests/unit: 13 + 8 unit tests (31 total, all pass) No behaviour change yet: coordinator still uses legacy status field. PRs B-G will migrate execution paths onto the new machine.
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import { describe, it, expect } from "@jest/globals";
|
||
import {
|
||
ALLOWED_TRANSITIONS,
|
||
ROLE_FOR_TRANSITION,
|
||
SOD_REQUIRED_TRANSITIONS,
|
||
TRANSACTION_STATES,
|
||
canTransition,
|
||
} from "../../src/types/transactionState";
|
||
|
||
describe("Transaction state machine (architecture note §8–§9)", () => {
|
||
it("declares the 12 states from §8.1", () => {
|
||
expect(TRANSACTION_STATES).toEqual([
|
||
"DRAFT",
|
||
"INITIATED",
|
||
"PRECONDITIONS_PENDING",
|
||
"READY_FOR_PREPARE",
|
||
"PREPARED",
|
||
"EXECUTING",
|
||
"PARTIALLY_EXECUTED",
|
||
"VALIDATING",
|
||
"COMMITTED",
|
||
"ABORTED",
|
||
"UNWIND_PENDING",
|
||
"CLOSED",
|
||
]);
|
||
});
|
||
|
||
describe("§9.1 permitted high-level transitions", () => {
|
||
// Each of these is listed in the note; canTransition must accept them.
|
||
const legal: Array<[string, string]> = [
|
||
["DRAFT", "INITIATED"],
|
||
["INITIATED", "PRECONDITIONS_PENDING"],
|
||
["PRECONDITIONS_PENDING", "READY_FOR_PREPARE"],
|
||
["READY_FOR_PREPARE", "PREPARED"],
|
||
["PREPARED", "EXECUTING"],
|
||
["EXECUTING", "PARTIALLY_EXECUTED"],
|
||
["EXECUTING", "VALIDATING"],
|
||
["PARTIALLY_EXECUTED", "VALIDATING"],
|
||
["VALIDATING", "COMMITTED"],
|
||
["VALIDATING", "ABORTED"],
|
||
["ABORTED", "UNWIND_PENDING"],
|
||
["COMMITTED", "CLOSED"],
|
||
["UNWIND_PENDING", "CLOSED"],
|
||
];
|
||
it.each(legal)("allows %s -> %s", (from, to) => {
|
||
expect(canTransition(from as any, to as any)).toBe(true);
|
||
});
|
||
|
||
// A few illegal edges — explicitly not in §9.1.
|
||
const illegal: Array<[string, string]> = [
|
||
["DRAFT", "COMMITTED"],
|
||
["INITIATED", "EXECUTING"],
|
||
["CLOSED", "INITIATED"],
|
||
["PREPARED", "COMMITTED"],
|
||
["COMMITTED", "ABORTED"],
|
||
["ABORTED", "COMMITTED"],
|
||
];
|
||
it.each(illegal)("rejects %s -> %s", (from, to) => {
|
||
expect(canTransition(from as any, to as any)).toBe(false);
|
||
});
|
||
});
|
||
|
||
it("CLOSED is a terminal state", () => {
|
||
expect(ALLOWED_TRANSITIONS.CLOSED).toEqual([]);
|
||
});
|
||
|
||
describe("segregation-of-duties checkpoints (§13)", () => {
|
||
it("flags the four SoD-gated transitions", () => {
|
||
expect([...SOD_REQUIRED_TRANSITIONS].sort()).toEqual(
|
||
[
|
||
"ABORTED->UNWIND_PENDING",
|
||
"PREPARED->EXECUTING",
|
||
"READY_FOR_PREPARE->PREPARED",
|
||
"VALIDATING->COMMITTED",
|
||
].sort(),
|
||
);
|
||
});
|
||
|
||
it("assigns a role to every SoD-gated transition", () => {
|
||
for (const key of SOD_REQUIRED_TRANSITIONS) {
|
||
expect(ROLE_FOR_TRANSITION[key]).toBeDefined();
|
||
}
|
||
});
|
||
});
|
||
});
|