filegrc 0.12.1 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/model/v10.json CHANGED
@@ -7485,6 +7485,71 @@
7485
7485
  "coverage"
7486
7486
  ]
7487
7487
  },
7488
+ "reconciliation-dismissal": {
7489
+ "title": "Reconciliation dismissal",
7490
+ "pluralTitle": "Reconciliation dismissals",
7491
+ "group": "repository",
7492
+ "collection": "reconciliation-dismissals",
7493
+ "description": "A reviewed decision that one exact Git transition candidate is a false positive and does not represent a real policy event.",
7494
+ "guidance": {
7495
+ "policyBasis": "Direct file changes can resemble real operational events. This record preserves the reviewer, date, rationale, and exact candidate fingerprint when management confirms that no event occurred.",
7496
+ "cadence": "Create only from a current reconciliation candidate. A later change produces a different fingerprint and requires its own review."
7497
+ },
7498
+ "required": [
7499
+ "transitionFingerprint",
7500
+ "eventType",
7501
+ "subjectResourceId",
7502
+ "reviewedByIds",
7503
+ "reviewedOn",
7504
+ "rationale"
7505
+ ],
7506
+ "fields": {
7507
+ "transitionFingerprint": {
7508
+ "type": "string",
7509
+ "label": "Transition fingerprint"
7510
+ },
7511
+ "eventType": {
7512
+ "type": "string",
7513
+ "label": "Candidate event type",
7514
+ "filter": true
7515
+ },
7516
+ "subjectResourceId": {
7517
+ "type": "id",
7518
+ "relationGroup": "event-subject",
7519
+ "label": "Candidate subject"
7520
+ },
7521
+ "reviewedByIds": {
7522
+ "type": "array",
7523
+ "items": "id",
7524
+ "relation": [
7525
+ "person"
7526
+ ],
7527
+ "label": "Reviewed by"
7528
+ },
7529
+ "reviewedOn": {
7530
+ "type": "date",
7531
+ "label": "Reviewed on"
7532
+ },
7533
+ "rationale": {
7534
+ "type": "string",
7535
+ "search": true
7536
+ }
7537
+ },
7538
+ "listFields": [
7539
+ "title",
7540
+ "eventType",
7541
+ "subjectResourceId",
7542
+ "reviewedOn"
7543
+ ],
7544
+ "formFields": [
7545
+ "transitionFingerprint",
7546
+ "eventType",
7547
+ "subjectResourceId",
7548
+ "reviewedByIds",
7549
+ "reviewedOn",
7550
+ "rationale"
7551
+ ]
7552
+ },
7488
7553
  "collection-review": {
7489
7554
  "title": "Collection review",
7490
7555
  "pluralTitle": "Collection reviews",
@@ -9516,7 +9581,8 @@
9516
9581
  "vendor",
9517
9582
  "access-grant",
9518
9583
  "vulnerability",
9519
- "incident"
9584
+ "incident",
9585
+ "exception"
9520
9586
  ],
9521
9587
  "evidence-source-record": [
9522
9588
  "appointment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filegrc",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Zero-dependency Git-native GRC engine",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -68,7 +68,7 @@ import {
68
68
  import { planProgramAmendment } from "./program-amendment.js";
69
69
  import { resolveProgram } from "./program.js";
70
70
  import { resourceReviewRevisions, retentionReviewResourceIds } from "./retention.js";
71
- import { applyReconciliation, planReconciliation } from "./reconciliation.js";
71
+ import { applyReconciliation, dismissReconciliation, planReconciliation } from "./reconciliation.js";
72
72
  import { markdownEntries } from "./resource-markdown.js";
73
73
  import { effectiveResourceStatus } from "./resource-status.js";
74
74
  import { searchResources } from "./search.js";
@@ -617,7 +617,17 @@ export async function runCli(argv = process.argv.slice(2)) {
617
617
  return result;
618
618
  }
619
619
  if (command === "reconcile") {
620
- const result = flags.apply
620
+ if (flags.apply && flags.dismiss) throw new Error("Choose either --apply or --dismiss for one reconciliation candidate.");
621
+ const result = flags.dismiss
622
+ ? await withWorkflowDelta(root, () => dismissReconciliation(root, {
623
+ candidateId: flags.candidate,
624
+ transitionFingerprint: flags.candidate,
625
+ reviewedById: flags.reviewer,
626
+ reviewedOn: flags["reviewed-on"],
627
+ rationale: flags.rationale,
628
+ confirmed: flags.yes === true
629
+ }))
630
+ : flags.apply
621
631
  ? await withWorkflowDelta(root, () => applyReconciliation(root, {
622
632
  candidateId: flags.candidate,
623
633
  transitionFingerprint: flags.candidate,
@@ -630,7 +640,9 @@ export async function runCli(argv = process.argv.slice(2)) {
630
640
  }))
631
641
  : await planReconciliation(root);
632
642
  if (flags.json) console.log(JSON.stringify(result, null, 2));
633
- else if (flags.apply) {
643
+ else if (flags.dismiss) {
644
+ console.log(`Dismissed ${result.candidate.eventType}: recorded ${result.dismissal.id}.`);
645
+ } else if (flags.apply) {
634
646
  console.log(`Reconciled ${result.candidate.eventType}: created ${result.event.id} and ${result.actions.length} linked tasks.`);
635
647
  } else if (!result.candidates.length) {
636
648
  console.log("No direct-file transitions need confirmation.");
@@ -1328,7 +1340,7 @@ Usage:
1328
1340
  filegrc evidence-map [--as-of YYYY-MM-DD] [--json]
1329
1341
  filegrc audit-readiness [audit-id] [--as-of YYYY-MM-DD] [--require-ready] [--json]
1330
1342
  filegrc prepare-audit <audit-id> [--json]
1331
- filegrc reconcile [--preview|--apply --candidate fingerprint (--occurred-on YYYY-MM-DD | --occurred-at RFC3339) --yes] [--program program-id] [--risk-level normal|high] [--json]
1343
+ filegrc reconcile [--preview|--apply --candidate fingerprint (--occurred-on YYYY-MM-DD | --occurred-at RFC3339) --yes|--dismiss --candidate fingerprint --reviewer person-id --reviewed-on YYYY-MM-DD --rationale text --yes] [--program program-id] [--risk-level normal|high] [--json]
1332
1344
  filegrc external-reviewer-setup --scaffold
1333
1345
  filegrc external-reviewer-setup <reviewer.json|-> [--preview|--yes] [--json]
1334
1346
  filegrc next-audit-cycle <prior-audit-id> [cycle.json|-] --start YYYY-MM-DD --end YYYY-MM-DD [--preview|--yes] [--json]
package/src/files.js CHANGED
@@ -22,7 +22,8 @@ export const INTERNAL_WORKFLOW_CAPABILITIES = Object.freeze({
22
22
  collectionReviewReassessment: Symbol("collection-review-reassessment"),
23
23
  reportingRouteSetProposal: Symbol("reporting-route-set-proposal"),
24
24
  reportingRouteSetApproval: Symbol("reporting-route-set-approval"),
25
- reportingRouteSetCancellation: Symbol("reporting-route-set-cancellation")
25
+ reportingRouteSetCancellation: Symbol("reporting-route-set-cancellation"),
26
+ reconciliationDismissal: Symbol("reconciliation-dismissal")
26
27
  });
27
28
 
28
29
  export async function createResource(input, record, options = {}) {
@@ -671,6 +672,9 @@ function assertImmutableWorkflowRecord(existing, next, options = {}, loaded = nu
671
672
  const preservesExisting = (allowed = []) => [...new Set([...Object.keys(existing), ...Object.keys(next)])].every((key) => (
672
673
  allowed.includes(key) || JSON.stringify(next[key]) === JSON.stringify(existing[key])
673
674
  ));
675
+ if (existing.type === "reconciliation-dismissal" && !preservesExisting()) {
676
+ throw new Error(`Reconciliation dismissal "${existing.id}" is immutable because it records a fingerprint-bound review decision.`);
677
+ }
674
678
  if (existing.type === "collection-review" && ["active", "retired"].includes(existing.status)) {
675
679
  const retirement = options.workflowCapability === INTERNAL_WORKFLOW_CAPABILITIES.collectionReviewReassessment
676
680
  && existing.status === "active"
@@ -908,6 +912,13 @@ function finalizedOccurrenceUsingProof(loaded, targetId) {
908
912
  }
909
913
 
910
914
  function assertSpecializedWorkflowCreate(record, options = {}, loaded = null) {
915
+ if (
916
+ record?.type === "reconciliation-dismissal"
917
+ && options.workflowCapability !== INTERNAL_WORKFLOW_CAPABILITIES.reconciliationDismissal
918
+ && options.lifecycleOperation !== "model-migration"
919
+ ) {
920
+ throw new Error(`Reconciliation dismissal "${record.id || ""}" is workflow-managed. Dismiss the current transition candidate instead of creating it directly.`);
921
+ }
911
922
  if (
912
923
  record?.type === "collection-review"
913
924
  && options.workflowCapability !== INTERNAL_WORKFLOW_CAPABILITIES.collectionReviewReassessment
@@ -984,7 +995,8 @@ async function deleteResourceUnlocked(input, type, id, options) {
984
995
  assertRevision(source, options.expectedRevision, "The record");
985
996
  const record = JSON.parse(source);
986
997
  if (
987
- (record.type === "obligation-occurrence" && ["reconciled", "superseded"].includes(record.status))
998
+ record.type === "reconciliation-dismissal"
999
+ || (record.type === "obligation-occurrence" && ["reconciled", "superseded"].includes(record.status))
988
1000
  || (record.type === "audit-population" && ["reconciled", "not-applicable", "superseded"].includes(record.status))
989
1001
  || (record.type === "obligation-event" && ["complete", "canceled"].includes(record.status))
990
1002
  || (record.type === "action-item" && record.obligationId && ["done", "canceled"].includes(record.status))