@optimuslabs/harness-map-staging 1.5.24 → 1.5.26

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.
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { executeBody } from '../../endpoint_client/http_transport.js';
5
5
  import { parseJsonWithJsoncFallback, parseTomlText, stringifyToml } from '../readers/file_readers.js';
6
6
  import { complianceRunnerDiag, hookRunLog, logRemediationApplyFailure } from './hook_logger.js';
7
- import { atomicWriteJson, getDeferredVscdbApplyPath, getFileCollectionVscdbContractPath, getRemediationInstructionsPath, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
7
+ import { atomicWriteJson, getDeferredVscdbApplyPath, getFileCollectionVscdbContractPath, getRemediationInstructionsPath, readRemediationInstructionsFile, writeApprovedTenants, writeUnapprovedEmailDomains, writeAllowedEmailDomains, writeApprovedAgents, writeRemediationInstructionsFile, } from './management_storage.js';
8
8
  import { readStoredAuthKey } from '../auth/auth_key_store.js';
9
9
  import { ensureAuthentication } from '../auth/auth_flow.js';
10
10
  import { createSignature } from '../sender/signing.js';
@@ -1600,123 +1600,172 @@ export function reportAutofixApplied(remediationUuid, result, details) {
1600
1600
  // ---------------------------------------------------------------------------
1601
1601
  // Main entry point
1602
1602
  // ---------------------------------------------------------------------------
1603
- export async function syncRemediations(endpointBase, machineUuid) {
1604
- let remediations = readInstructions().remediations;
1605
- const activeUuids = remediations.map((r) => r.uuid);
1606
- complianceRunnerDiag(`remediation_sync: begin endpoint=${endpointBase} machine_uuid=${machineUuid} local_rows=${remediations.length}`);
1603
+ const errorText = (err) => (err instanceof Error ? err.message : String(err));
1604
+ /** The strings in a synced list, lowercased; anything else in it is dropped. */
1605
+ function lowercaseStrings(list) {
1606
+ return list.filter((v) => typeof v === 'string').map((v) => v.toLowerCase());
1607
+ }
1608
+ async function ensureVscdbContract(endpointBase) {
1607
1609
  try {
1608
- if (!existsSync(getFileCollectionVscdbContractPath())) {
1609
- const pr = await getFileCollectionPatterns(endpointBase);
1610
- if (pr)
1611
- persistVscdbComposerContractFromPatternsResponse(pr);
1612
- }
1610
+ if (existsSync(getFileCollectionVscdbContractPath()))
1611
+ return;
1612
+ const pr = await getFileCollectionPatterns(endpointBase);
1613
+ if (pr)
1614
+ persistVscdbComposerContractFromPatternsResponse(pr);
1613
1615
  }
1614
1616
  catch {
1615
1617
  /* contract also written on full log-config run */
1616
1618
  }
1619
+ }
1620
+ /** The server's sync answer, or null (logged) when the request failed or came back empty. */
1621
+ async function fetchSyncResult(endpointBase, machineUuid, activeUuids) {
1617
1622
  let syncResult;
1618
1623
  try {
1619
1624
  syncResult = await fetchSync(endpointBase, machineUuid, activeUuids);
1620
1625
  }
1621
1626
  catch (err) {
1622
- const msg = `remediation_sync_error: ${err instanceof Error ? err.message : String(err)}`;
1627
+ const msg = `remediation_sync_error: ${errorText(err)}`;
1623
1628
  hookRunLog(msg);
1624
1629
  complianceRunnerDiag(msg);
1625
- return;
1630
+ return null;
1626
1631
  }
1627
1632
  if (!syncResult) {
1628
1633
  hookRunLog(`remediation_sync: no response from server, skipping`);
1629
1634
  complianceRunnerDiag('remediation_sync: no response from server (non-200 or empty body), skipping');
1630
- return;
1631
1635
  }
1632
- applyClearApplyTrackingUuids(syncResult.clear_apply_tracking_uuids);
1633
- if (syncResult.status === 'unchanged') {
1634
- hookRunLog(`remediation_sync: unchanged enforced=${activeUuids.length}`);
1635
- complianceRunnerDiag(`remediation_sync: server status=unchanged local_uuids=${activeUuids.length} — no new rows to fetch; if a deploy should exist, verify Finding.machine matches this hardware_uuid`);
1636
- const want = new Set(activeUuids);
1637
- const have = new Map(remediations.map((r) => [r.uuid, r]));
1638
- const needBackfill = activeUuids.some((u) => !have.has(u));
1639
- const haveIncomplete = remediations.some((r) => want.has(r.uuid) && needsManifestRefetch(r));
1640
- const haveOrphan = remediations.some((r) => !want.has(r.uuid));
1641
- const path = getRemediationInstructionsPath();
1642
- if (activeUuids.length > 0) {
1643
- if (needBackfill || haveIncomplete || haveOrphan || !existsSync(path)) {
1644
- try {
1645
- await ensureInstructionsForUuids(endpointBase, machineUuid, want, null);
1646
- }
1647
- catch (err) {
1648
- hookRunLog(`remediation_instructions_reconcile_error: ${err instanceof Error ? err.message : String(err)}`);
1649
- }
1650
- }
1651
- }
1652
- else if (remediations.length > 0) {
1653
- try {
1654
- await ensureInstructionsForUuids(endpointBase, machineUuid, new Set(), null);
1655
- }
1656
- catch (err) {
1657
- hookRunLog(`remediation_instructions_reconcile_error: ${err instanceof Error ? err.message : String(err)}`);
1658
- }
1659
- }
1636
+ return syncResult;
1637
+ }
1638
+ /**
1639
+ * Kept locally so the prompt gate checks the signed-in tenant without a round trip; a failed
1640
+ * sync never gets here and leaves the last lists in place.
1641
+ */
1642
+ function storeApprovals(syncResult) {
1643
+ const approvedTenants = syncResult.approved_tenants;
1644
+ if (approvedTenants && typeof approvedTenants === 'object' && !Array.isArray(approvedTenants)) {
1645
+ writeApprovedTenants(approvedTenants);
1646
+ }
1647
+ const unapprovedDomains = syncResult.unapproved_email_domains;
1648
+ if (Array.isArray(unapprovedDomains))
1649
+ writeUnapprovedEmailDomains(lowercaseStrings(unapprovedDomains));
1650
+ const approvedAgents = syncResult.approved_agents;
1651
+ if (Array.isArray(approvedAgents))
1652
+ writeApprovedAgents(lowercaseStrings(approvedAgents));
1653
+ const allowedDomains = syncResult.allowed_email_domains;
1654
+ if (Array.isArray(allowedDomains))
1655
+ writeAllowedEmailDomains(lowercaseStrings(allowedDomains));
1656
+ }
1657
+ async function reconcileInstructions(endpointBase, machineUuid, want) {
1658
+ try {
1659
+ await ensureInstructionsForUuids(endpointBase, machineUuid, want, null);
1660
+ }
1661
+ catch (err) {
1662
+ hookRunLog(`remediation_instructions_reconcile_error: ${errorText(err)}`);
1663
+ }
1664
+ }
1665
+ /** Server says nothing changed: still repair a local file that is missing rows, incomplete or stale. */
1666
+ async function reconcileUnchanged(endpointBase, machineUuid, remediations, activeUuids) {
1667
+ hookRunLog(`remediation_sync: unchanged enforced=${activeUuids.length}`);
1668
+ complianceRunnerDiag(`remediation_sync: server status=unchanged local_uuids=${activeUuids.length} — no new rows to fetch; if a deploy should exist, verify Finding.machine matches this hardware_uuid`);
1669
+ const want = new Set(activeUuids);
1670
+ if (activeUuids.length === 0) {
1671
+ if (remediations.length > 0)
1672
+ await reconcileInstructions(endpointBase, machineUuid, new Set());
1660
1673
  return;
1661
1674
  }
1662
- const { added, removed } = syncResult;
1663
- hookRunLog(`remediation_sync: status=updated added=${added.length} removed=${removed.length}`);
1664
- complianceRunnerDiag(`remediation_sync: server status=updated added=${JSON.stringify(added)} removed=${JSON.stringify(removed)}`);
1665
- const removedSet = new Set(removed);
1666
- const retiredInstructions = remediations.filter((r) => removedSet.has(r.uuid));
1667
- let removedCount = 0;
1668
- remediations = remediations.filter((r) => {
1669
- if (removedSet.has(r.uuid)) {
1670
- removedCount++;
1671
- hookRunLog(`remediation_remove: uuid=${r.uuid} path=${r.config_file_path}`);
1672
- return false;
1673
- }
1674
- return true;
1675
- });
1676
- let manifest = null;
1677
- if (added.length > 0) {
1678
- try {
1679
- manifest = await fetchManifest(endpointBase, machineUuid, added);
1680
- }
1681
- catch (err) {
1682
- const msg = `remediation_manifest_error: ${err instanceof Error ? err.message : String(err)}`;
1683
- hookRunLog(msg);
1684
- complianceRunnerDiag(msg);
1685
- if (removedCount > 0) {
1686
- remediations.sort((a, b) => a.uuid.localeCompare(b.uuid));
1687
- writeInstructions({ remediations });
1688
- }
1689
- return;
1690
- }
1691
- if (!manifest) {
1692
- hookRunLog(`remediation_sync: manifest fetch failed, skipping`);
1693
- complianceRunnerDiag('remediation_sync: manifest fetch failed (null), skipping — remediation_instructions.json not updated');
1694
- if (removedCount > 0) {
1695
- remediations.sort((a, b) => a.uuid.localeCompare(b.uuid));
1696
- writeInstructions({ remediations });
1697
- }
1698
- return;
1699
- }
1675
+ const have = new Map(remediations.map((r) => [r.uuid, r]));
1676
+ const needBackfill = activeUuids.some((u) => !have.has(u));
1677
+ const haveIncomplete = remediations.some((r) => want.has(r.uuid) && needsManifestRefetch(r));
1678
+ const haveOrphan = remediations.some((r) => !want.has(r.uuid));
1679
+ if (needBackfill || haveIncomplete || haveOrphan || !existsSync(getRemediationInstructionsPath())) {
1680
+ await reconcileInstructions(endpointBase, machineUuid, want);
1681
+ }
1682
+ }
1683
+ function writeSortedInstructions(remediations) {
1684
+ remediations.sort((a, b) => a.uuid.localeCompare(b.uuid));
1685
+ writeInstructions({ remediations });
1686
+ }
1687
+ /** The manifest rows for the added UUIDs; null (logged) when it could not be fetched. */
1688
+ async function fetchAddedManifest(endpointBase, machineUuid, added) {
1689
+ let manifest;
1690
+ try {
1691
+ manifest = await fetchManifest(endpointBase, machineUuid, added);
1692
+ }
1693
+ catch (err) {
1694
+ const msg = `remediation_manifest_error: ${errorText(err)}`;
1695
+ hookRunLog(msg);
1696
+ complianceRunnerDiag(msg);
1697
+ return null;
1698
+ }
1699
+ if (!manifest) {
1700
+ hookRunLog(`remediation_sync: manifest fetch failed, skipping`);
1701
+ complianceRunnerDiag('remediation_sync: manifest fetch failed (null), skipping — remediation_instructions.json not updated');
1702
+ }
1703
+ return manifest;
1704
+ }
1705
+ function upsertInstruction(remediations, existingUuids, instruction) {
1706
+ if (!existingUuids.has(instruction.uuid)) {
1707
+ remediations.push(instruction);
1708
+ existingUuids.add(instruction.uuid);
1709
+ return;
1700
1710
  }
1711
+ // Enforced remediations are always in `added`; overwrite local copy with fresh server data.
1712
+ const idx = remediations.findIndex((r) => r.uuid === instruction.uuid);
1713
+ if (idx >= 0)
1714
+ remediations[idx] = instruction;
1715
+ }
1716
+ /** Put the manifest's added rows into `remediations` (replacing a local copy); returns the rows put in. */
1717
+ function mergeAddedInstructions(remediations, manifest, added) {
1701
1718
  const existingUuids = new Set(remediations.map((r) => r.uuid));
1702
1719
  const addedSet = new Set(added);
1703
1720
  const overlay = [];
1704
- for (const instruction of manifest ?? []) {
1721
+ for (const instruction of manifest) {
1705
1722
  if (!addedSet.has(instruction.uuid))
1706
1723
  continue;
1707
- if (existingUuids.has(instruction.uuid)) {
1708
- // Enforced remediations are always in `added`; overwrite local copy with fresh server data.
1709
- const idx = remediations.findIndex((r) => r.uuid === instruction.uuid);
1710
- if (idx >= 0)
1711
- remediations[idx] = instruction;
1712
- }
1713
- else {
1714
- remediations.push(instruction);
1715
- existingUuids.add(instruction.uuid);
1716
- }
1724
+ upsertInstruction(remediations, existingUuids, instruction);
1717
1725
  overlay.push(instruction);
1718
1726
  hookRunLog(`remediation_instructions_saved: uuid=${instruction.uuid} path=${instruction.config_file_path}`);
1719
1727
  }
1728
+ return overlay;
1729
+ }
1730
+ /** Write the merged instructions, fetch anything still missing, and report the new UUID set. */
1731
+ async function persistSyncedInstructions(endpointBase, machineUuid, remediations, overlay, removedCount) {
1732
+ writeSortedInstructions(remediations);
1733
+ complianceRunnerDiag(`remediation_sync: wrote ${getRemediationInstructionsPath()} rows=${remediations.length} (added_from_manifest=${overlay.length} removed=${removedCount})`);
1734
+ const finalWant = new Set(remediations.map((r) => r.uuid));
1735
+ try {
1736
+ await ensureInstructionsForUuids(endpointBase, machineUuid, finalWant, overlay.length > 0 ? overlay : null);
1737
+ }
1738
+ catch (err) {
1739
+ hookRunLog(`remediation_instructions_persist_error: ${errorText(err)}`);
1740
+ }
1741
+ // Post-sync heartbeat: report the updated UUID set so the server has a before/after pair.
1742
+ const finalUuids = remediations.map((r) => r.uuid);
1743
+ try {
1744
+ await fetchSync(endpointBase, machineUuid, finalUuids);
1745
+ hookRunLog(`remediation_sync: post-sync heartbeat reported uuids=${finalUuids.length}`);
1746
+ }
1747
+ catch {
1748
+ // Non-critical — skip silently.
1749
+ }
1750
+ }
1751
+ /** Server sent added/removed UUIDs: drop the removed, fetch and merge the added, then persist. */
1752
+ async function applySyncUpdate(endpointBase, machineUuid, local, syncResult) {
1753
+ const { added, removed } = syncResult;
1754
+ hookRunLog(`remediation_sync: status=updated added=${added.length} removed=${removed.length}`);
1755
+ complianceRunnerDiag(`remediation_sync: server status=updated added=${JSON.stringify(added)} removed=${JSON.stringify(removed)}`);
1756
+ const removedSet = new Set(removed);
1757
+ const retiredInstructions = local.filter((r) => removedSet.has(r.uuid));
1758
+ for (const r of retiredInstructions)
1759
+ hookRunLog(`remediation_remove: uuid=${r.uuid} path=${r.config_file_path}`);
1760
+ const removedCount = retiredInstructions.length;
1761
+ const remediations = local.filter((r) => !removedSet.has(r.uuid));
1762
+ const manifest = added.length > 0 ? await fetchAddedManifest(endpointBase, machineUuid, added) : [];
1763
+ if (!manifest) {
1764
+ if (removedCount > 0)
1765
+ writeSortedInstructions(remediations);
1766
+ return;
1767
+ }
1768
+ const overlay = mergeAddedInstructions(remediations, manifest, added);
1720
1769
  transferQuarantineForRetiredUuids(removed, added, retiredInstructions, overlay);
1721
1770
  const addedCount = overlay.length;
1722
1771
  if (added.length > 0 && addedCount === 0) {
@@ -1725,26 +1774,24 @@ export async function syncRemediations(endpointBase, machineUuid) {
1725
1774
  complianceRunnerDiag(`${msg} — remediation_instructions.json not written`);
1726
1775
  }
1727
1776
  if (removedCount > 0 || addedCount > 0) {
1728
- remediations.sort((a, b) => a.uuid.localeCompare(b.uuid));
1729
- writeInstructions({ remediations });
1730
- complianceRunnerDiag(`remediation_sync: wrote ${getRemediationInstructionsPath()} rows=${remediations.length} (added_from_manifest=${addedCount} removed=${removedCount})`);
1731
- const finalWant = new Set(remediations.map((r) => r.uuid));
1732
- try {
1733
- await ensureInstructionsForUuids(endpointBase, machineUuid, finalWant, overlay.length > 0 ? overlay : null);
1734
- }
1735
- catch (err) {
1736
- hookRunLog(`remediation_instructions_persist_error: ${err instanceof Error ? err.message : String(err)}`);
1737
- }
1738
- // Post-sync heartbeat: report the updated UUID set so the server has a before/after pair.
1739
- const finalUuids = remediations.map((r) => r.uuid);
1740
- try {
1741
- await fetchSync(endpointBase, machineUuid, finalUuids);
1742
- hookRunLog(`remediation_sync: post-sync heartbeat reported uuids=${finalUuids.length}`);
1743
- }
1744
- catch {
1745
- // Non-critical — skip silently.
1746
- }
1777
+ await persistSyncedInstructions(endpointBase, machineUuid, remediations, overlay, removedCount);
1747
1778
  }
1748
1779
  hookRunLog(`remediation_sync: saved=${addedCount} removed=${removedCount}`);
1749
1780
  complianceRunnerDiag(`remediation_sync: done saved=${addedCount} removed=${removedCount}`);
1750
1781
  }
1782
+ export async function syncRemediations(endpointBase, machineUuid) {
1783
+ const remediations = readInstructions().remediations;
1784
+ const activeUuids = remediations.map((r) => r.uuid);
1785
+ complianceRunnerDiag(`remediation_sync: begin endpoint=${endpointBase} machine_uuid=${machineUuid} local_rows=${remediations.length}`);
1786
+ await ensureVscdbContract(endpointBase);
1787
+ const syncResult = await fetchSyncResult(endpointBase, machineUuid, activeUuids);
1788
+ if (!syncResult)
1789
+ return;
1790
+ applyClearApplyTrackingUuids(syncResult.clear_apply_tracking_uuids);
1791
+ storeApprovals(syncResult);
1792
+ if (syncResult.status === 'unchanged') {
1793
+ await reconcileUnchanged(endpointBase, machineUuid, remediations, activeUuids);
1794
+ return;
1795
+ }
1796
+ await applySyncUpdate(endpointBase, machineUuid, remediations, syncResult);
1797
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optimuslabs/harness-map-staging",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {