Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "filegrc-monorepo",
"version": "0.18.15",
"version": "0.18.16",
"private": true,
"workspaces": [
"packages/filegrc",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-filegrc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-filegrc",
"version": "0.18.15",
"version": "0.18.16",
"description": "Create a filegrc workspace for a SOC 2 program",
"license": "MIT",
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions packages/create-filegrc/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,13 @@ async function runCombinedSetup(target, input) {
async function writeMinimalLockfile(target, name, versionRange) {
const lock = {
name,
version: "0.18.15",
version: "0.18.16",
lockfileVersion: 3,
requires: true,
packages: {
"": {
name,
version: "0.18.15",
version: "0.18.16",
dependencies: { filegrc: versionRange }
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/create-filegrc/template/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "{{project_name}}",
"version": "0.18.15",
"version": "0.18.16",
"private": true,
"description": "filegrc workspace for a SOC 2 program",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/filegrc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "filegrc",
"version": "0.18.15",
"version": "0.18.16",
"description": "Zero-dependency Git-native GRC engine",
"license": "MIT",
"repository": {
Expand Down
12 changes: 10 additions & 2 deletions packages/filegrc/src/obligations.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export function planObligations(resources, options = {}) {
throw new Error(`The obligation query must be narrowed; it includes more than ${MAX_PLANNED_ITEMS.toLocaleString("en-US")} active obligations.`);
}
const calendarItems = [];
const cycleStartsByOccurrenceId = new Map();
const plannedOccurrenceKeys = new Set();
const triggerGroups = new Map();
let scannedCalendarOccurrences = 0;
Expand Down Expand Up @@ -206,20 +207,25 @@ export function planObligations(resources, options = {}) {
for (; ; index += 1) {
const window = scanCalendarWindow(recurrence, schedule.window, index);
if (!window || window.dueWindowStart > through) break;
const cycleWindowStart = window.dueWindowStart;
if (schedule.endsOn && window.dueWindowStart > schedule.endsOn) break;
if (rule && activationDate && window.dueWindowStart < activationDate) {
if (!modelSupports(model, "operating-preparation")) continue;
// A frozen full or partial cycle remains authoritative after later
// content activation. The historical pass preserves its original key.
if (records.some((record) => record.type === "obligation-occurrence"
const frozen = records.find((record) => record.type === "obligation-occurrence"
&& record.programId === (program?.id || workspace?.id || "workspace")
&& record.obligationId === obligation.id && record.ruleId === rule.id
&& record.status !== "superseded" && record.coverage?.kind === "range"
&& parseCalendarDate(record.coverage.startsOn)
&& record.coverage.startsOn >= window.dueWindowStart && record.coverage.startsOn <= activationDate
&& record.coverage.startsOn <= window.dueWindowEnd
&& record.occurrenceKey === `${program?.id || workspace?.id || "workspace"}:${obligation.id}:${record.coverage.startsOn}`
&& record.coverage.endsOn === window.dueWindowEnd)) continue;
&& record.coverage.endsOn === window.dueWindowEnd);
if (frozen) {
cycleStartsByOccurrenceId.set(frozen.id, cycleWindowStart);
continue;
}
// Preserve the eligible remainder of an initial cycle at cutover.
// Never allow pre-activation proof to satisfy that partial cycle.
if (window.dueWindowEnd < activationDate) continue;
Expand Down Expand Up @@ -280,6 +286,7 @@ export function planObligations(resources, options = {}) {
key: `${obligation.id}:${window.dueWindowStart}`,
kind: "calendar",
activationOn: activationDate || null,
cycleWindowStart,
configuredAnchor: configuredAnchor || null,
initialCycleNotice: activationDate && window.dueWindowStart > activationDate
? `The first eligible cycle starts ${window.dueWindowStart}, after activation ${activationDate}. Check whether the approved rule needs a separate initial deadline; do not silently treat earlier cycles as completed.`
Expand Down Expand Up @@ -364,6 +371,7 @@ export function planObligations(resources, options = {}) {
calendarItems.push({
key: `${obligation.id}:${dueWindowStart}`,
kind: "calendar",
cycleWindowStart: cycleStartsByOccurrenceId.get(occurrence.id) || dueWindowStart,
obligationId: obligation.id,
ruleId: rule.id,
ruleStatus: rule.status,
Expand Down
13 changes: 11 additions & 2 deletions packages/filegrc/src/operating-baseline.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,17 @@ export async function assessProgramBaseline(loaded, program, controls, asOf, mak
if (!selected.length) continue;
const controlIds = selected.map(({ id }) => id);
const notBefore = selected.flatMap((control) => [control.effectiveOn, control.procedureEffectiveOn]).filter(Boolean).sort().at(-1);
const windows = schedules.map((schedule) => calendar.filter((task) => task.obligationId === schedule.id && task.dueWindowStart <= asOf)
.sort((a, b) => b.dueWindowStart.localeCompare(a.dueWindowStart))[0]).filter(Boolean);
const windows = schedules.map((schedule) => {
const task = calendar.filter((task) => task.obligationId === schedule.id && task.dueWindowStart <= asOf)
.sort((a, b) => b.dueWindowStart.localeCompare(a.dueWindowStart))[0];
if (!task) return null;
// Setup proof may precede content cutover within the adopted cycle.
// Use the planner's exact rule, anchor and configured offsets instead of
// reconstructing a cycle from its shifted or clipped operating start.
return task.cycleWindowStart && task.cycleWindowStart < task.dueWindowStart
? { ...task, dueWindowStart: task.cycleWindowStart }
: task;
}).filter(Boolean);
const currentDate = (date) => Boolean(date && date <= asOf && (!notBefore || date >= notBefore));
const permittedDate = (date, relevantWindows = windows) => currentDate(date)
&& relevantWindows.every((window) => date >= window.dueWindowStart && date <= window.dueWindowEnd);
Expand Down
101 changes: 101 additions & 0 deletions packages/filegrc/test/operating-baseline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { assessProgramReadiness } from "../src/program-readiness.js";
import { runCli } from "../src/cli.js";
import { captureCli } from "./helpers.js";
import { planModelMigration } from "../src/model-migration.js";
import { planObligations } from "../src/obligations.js";

async function fixture(context, code) {
const root = await mkdtemp(join(tmpdir(), "filegrc-full-baseline-"));
Expand Down Expand Up @@ -345,3 +346,103 @@ test("provider reconciliation guide and scaffold expose nested model facts witho
assert.equal(scaffold.record.populationCount, null);
assert.notEqual(scaffold.record.status, "complete");
});


test("later content cutover preserves risk setup proof within its adopted cycle", async (context) => {
const { loaded, program, controls, get, baseline } = await fixture(context, "RSK-01");
const assessment = get("risk-assessment");
const initial = await baseline("program-risk");
for (const [field, ids] of Object.entries(initial.scope)) assessment[field] = ids;
Object.assign(assessment, { assessmentKind: "enterprise-risk", completedOn: "2026-10-05", approvedOn: "2026-10-06",
assessorIds: ["person-example"], reviewerIds: ["person-independent-approver-example"] });
get("obligation").activityType = "risk-assessment";
Object.assign(get("obligation-rule"), { recurrence: { mode: "calendar", unit: "month", interval: 3, anchorMode: "explicit", anchorDate: "2026-07-01" },
effectiveAt: "2026-07-01T00:00:00Z", window: { precision: "date", startsAfter: 2, dueAfter: 89 } });
assert.equal((await baseline("program-risk")).complete, true);
get("policy").effectiveOn = "2026-10-12";
let result = await baseline("program-risk");
assert.equal(result.complete, true, JSON.stringify(result.candidateResults));
assert.equal(result.completionWindows[0].startsOn, "2026-10-03", "The configured cycle offset still applies");
const task = planObligations(loaded.resources, { model: loaded.model, programId: program.id, asOf: "2026-10-13", through: "2026-10-13", includeComplete: true }).calendarItems[0];
assert.equal(task.dueWindowStart, "2026-10-12", "Actual occurrence completion is still bounded by cutover");
const frozen = { ...get("obligation-occurrence"), id: "obligation-occurrence-frozen-risk", type: "obligation-occurrence",
programId: program.id, obligationId: task.obligationId, ruleId: task.ruleId, occurrenceKey: task.occurrenceKey,
status: "open", coverage: { kind: "range", startsOn: task.dueWindowStart, endsOn: task.dueWindowEnd }, members: [] };
loaded.resources.push(frozen);
assert.equal((await baseline("program-risk")).complete, true, "Recording the partial occurrence preserves setup proof eligibility");
const frozenTask = planObligations(loaded.resources, { model: loaded.model, programId: program.id, asOf: "2026-10-13", through: "2026-10-13", includeComplete: true }).calendarItems.find((item) => item.occurrenceId === frozen.id);
assert.equal(frozenTask.dueWindowStart, "2026-10-12", "Frozen occurrence dates stay authoritative");
assert.equal(frozenTask.cycleWindowStart, "2026-10-03");
assessment.completedOn = "2026-10-02";
assert.equal((await baseline("program-risk")).complete, false, "Proof before the configured offset stays ineligible");
assessment.completedOn = "2026-09-30";
assert.equal((await baseline("program-risk")).complete, false, "Previous-cycle work stays ineligible");
assessment.completedOn = "2026-10-05";
controls[0].procedureEffectiveOn = "2026-10-06";
assert.equal((await baseline("program-risk")).complete, false, "A changed Control procedure still needs current proof");
});

test("later content cutover preserves access setup exports without accepting stale or unsupported populations", async (context) => {
const { get, baseline } = await fixture(context, "IAM-01");
const review = get("access-review"), grant = get("access-grant"), proof = get("evidence");
get("obligation").activityType = "access-review";
Object.assign(get("obligation-rule"), { recurrence: { mode: "calendar", unit: "month", interval: 3, anchorMode: "explicit", anchorDate: "2026-07-01" },
effectiveAt: "2026-07-01T00:00:00Z", window: { precision: "date", startsAfter: 0, dueAfter: 89 } });
Object.assign(review, { completedOn: "2026-10-05", approvedOn: "2026-10-06", populationCount: 1, exceptionCount: 0,
grantDecisions: [{ accessGrantId: grant.id, decision: "retain" }] });
Object.assign(proof, { artifactKind: "population-export", sourceKind: "component", sourceComponentId: get("component").id,
collectedOn: "2026-10-05", verifiedOn: "2026-10-05", generatedAt: "2026-10-05T10:00:00Z", timezone: "UTC",
queryDescription: "All scoped provider accounts", populationCount: 1,
completenessValidation: "Compared all account classes with provider totals", accuracyValidation: "Checked account identifiers and permissions",
coverage: { kind: "as-of", on: "2026-10-05" } });
delete proof.filePaths;
assert.equal((await baseline("provider-access")).complete, true);
get("policy").effectiveOn = "2026-10-12";
assert.equal((await baseline("provider-access")).complete, true);
proof.generatedAt = "2026-09-30T10:00:00Z";
assert.equal((await baseline("provider-access")).complete, false, "A prior-cycle export cannot support the current population");
proof.generatedAt = "2026-10-05T10:00:00Z";
proof.populationCount = 2;
assert.equal((await baseline("provider-access")).complete, false, "Unreconciled accounts still block readiness");
});

test("setup eligibility retains the planner's rule, activation anchor and offset cycle", async (context) => {
for (const scenario of ["activation-anchor", "future-rule", "negative-offset", "cross-cycle-offset"]) {
await context.test(scenario, async (child) => {
const { loaded, get, baseline } = await fixture(child, "RSK-01");
const assessment = get("risk-assessment");
const initial = await baseline("program-risk");
for (const [field, ids] of Object.entries(initial.scope)) assessment[field] = ids;
Object.assign(assessment, { assessmentKind: "enterprise-risk", completedOn: "2026-10-05", approvedOn: "2026-10-06",
assessorIds: ["person-example"], reviewerIds: ["person-independent-approver-example"] });
const obligation = get("obligation"), rule = get("obligation-rule");
obligation.activityType = "risk-assessment";
Object.assign(rule, { recurrence: { mode: "calendar", unit: "month", interval: 3, anchorMode: "explicit", anchorDate: "2026-07-01" },
effectiveAt: "2026-07-01T12:00:00Z", window: { precision: "date", startsAfter: 0, dueAfter: 89 } });
get("policy").effectiveOn = "2026-10-12";
let expected = false;
if (scenario === "activation-anchor") rule.recurrence.anchorMode = "activation";
if (scenario === "future-rule") {
assessment.completedOn = "2026-09-20"; assessment.approvedOn = "2026-09-21";
const successor = { ...rule, id: "obligation-rule-future-risk", supersedesId: rule.id,
effectiveAt: "2026-11-01T12:00:00Z", recurrence: { ...rule.recurrence, anchorDate: "2026-09-01" } };
rule.status = "retired"; loaded.resources.push(successor);
obligation.ruleIds = [rule.id, successor.id]; obligation.activeRuleId = successor.id;
}
if (scenario === "negative-offset") {
get("policy").effectiveOn = "2026-07-01";
rule.window = { precision: "date", startsAfter: -10, dueAfter: 80 };
assessment.completedOn = "2026-08-20"; assessment.approvedOn = "2026-08-21";
}
if (scenario === "cross-cycle-offset") {
rule.recurrence = { mode: "calendar", unit: "week", interval: 1, anchorMode: "explicit", anchorDate: "2026-10-01" };
rule.effectiveAt = "2026-10-01T12:00:00Z";
rule.window = { precision: "date", startsAfter: 10, dueAfter: 13 };
assessment.completedOn = "2026-10-11"; assessment.approvedOn = "2026-10-12";
expected = true;
}
const result = await baseline("program-risk");
assert.equal(result.complete, expected, JSON.stringify(result.candidateResults));
});
}
});
Loading