@skillsmith/core 0.8.2 → 0.10.0
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/CHANGELOG.md +17 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +6 -0
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/client.test.d.ts +9 -0
- package/dist/src/api/client.test.d.ts.map +1 -0
- package/dist/src/api/client.test.js +81 -0
- package/dist/src/api/client.test.js.map +1 -0
- package/dist/src/config/device-identity.d.ts +113 -0
- package/dist/src/config/device-identity.d.ts.map +1 -0
- package/dist/src/config/device-identity.js +171 -0
- package/dist/src/config/device-identity.js.map +1 -0
- package/dist/src/config/device-identity.test.d.ts +10 -0
- package/dist/src/config/device-identity.test.d.ts.map +1 -0
- package/dist/src/config/device-identity.test.js +213 -0
- package/dist/src/config/device-identity.test.js.map +1 -0
- package/dist/src/config/index.d.ts +9 -0
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/exports/services.d.ts +3 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +11 -1
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/agent-config-merge.json-array.d.ts +29 -0
- package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.json-array.js +100 -0
- package/dist/src/install/agent-config-merge.json-array.js.map +1 -0
- package/dist/src/install/agent-config-merge.json.d.ts +37 -0
- package/dist/src/install/agent-config-merge.json.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.json.js +134 -0
- package/dist/src/install/agent-config-merge.json.js.map +1 -0
- package/dist/src/install/agent-config-merge.toml-block.d.ts +48 -0
- package/dist/src/install/agent-config-merge.toml-block.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.toml-block.js +107 -0
- package/dist/src/install/agent-config-merge.toml-block.js.map +1 -0
- package/dist/src/install/agent-config-merge.types.d.ts +87 -0
- package/dist/src/install/agent-config-merge.types.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.types.js +83 -0
- package/dist/src/install/agent-config-merge.types.js.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.d.ts +46 -0
- package/dist/src/install/agent-config-merge.yaml.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.js +133 -0
- package/dist/src/install/agent-config-merge.yaml.js.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.test.d.ts +2 -0
- package/dist/src/install/agent-config-merge.yaml.test.d.ts.map +1 -0
- package/dist/src/install/agent-config-merge.yaml.test.js +210 -0
- package/dist/src/install/agent-config-merge.yaml.test.js.map +1 -0
- package/dist/src/install/agent-harness-targets.d.ts +98 -0
- package/dist/src/install/agent-harness-targets.d.ts.map +1 -0
- package/dist/src/install/agent-harness-targets.js +154 -0
- package/dist/src/install/agent-harness-targets.js.map +1 -0
- package/dist/src/install/agent-home-relocate.d.ts +29 -0
- package/dist/src/install/agent-home-relocate.d.ts.map +1 -0
- package/dist/src/install/agent-home-relocate.js +38 -0
- package/dist/src/install/agent-home-relocate.js.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.d.ts +55 -0
- package/dist/src/install/agent-manifest-path-guard.d.ts.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.js +109 -0
- package/dist/src/install/agent-manifest-path-guard.js.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.test.d.ts +2 -0
- package/dist/src/install/agent-manifest-path-guard.test.d.ts.map +1 -0
- package/dist/src/install/agent-manifest-path-guard.test.js +72 -0
- package/dist/src/install/agent-manifest-path-guard.test.js.map +1 -0
- package/dist/src/install/agent-manifest.d.ts +58 -0
- package/dist/src/install/agent-manifest.d.ts.map +1 -0
- package/dist/src/install/agent-manifest.js +103 -0
- package/dist/src/install/agent-manifest.js.map +1 -0
- package/dist/src/install/agent-pack-installer.d.ts +39 -0
- package/dist/src/install/agent-pack-installer.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.entry.d.ts +55 -0
- package/dist/src/install/agent-pack-installer.entry.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.entry.js +90 -0
- package/dist/src/install/agent-pack-installer.entry.js.map +1 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.d.ts +33 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.js +59 -0
- package/dist/src/install/agent-pack-installer.fs-helpers.js.map +1 -0
- package/dist/src/install/agent-pack-installer.harness.d.ts +66 -0
- package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.harness.js +310 -0
- package/dist/src/install/agent-pack-installer.harness.js.map +1 -0
- package/dist/src/install/agent-pack-installer.js +221 -0
- package/dist/src/install/agent-pack-installer.js.map +1 -0
- package/dist/src/install/agent-pack-installer.preserve.test.d.ts +11 -0
- package/dist/src/install/agent-pack-installer.preserve.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.preserve.test.js +72 -0
- package/dist/src/install/agent-pack-installer.preserve.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.test.d.ts +2 -0
- package/dist/src/install/agent-pack-installer.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.test.js +350 -0
- package/dist/src/install/agent-pack-installer.test.js.map +1 -0
- package/dist/src/install/agent-pack-installer.types.d.ts +51 -0
- package/dist/src/install/agent-pack-installer.types.d.ts.map +1 -0
- package/dist/src/install/agent-pack-installer.types.js +16 -0
- package/dist/src/install/agent-pack-installer.types.js.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.d.ts +43 -0
- package/dist/src/install/agent-pack-uninstaller.d.ts.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.js +113 -0
- package/dist/src/install/agent-pack-uninstaller.js.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.test.d.ts +2 -0
- package/dist/src/install/agent-pack-uninstaller.test.d.ts.map +1 -0
- package/dist/src/install/agent-pack-uninstaller.test.js +212 -0
- package/dist/src/install/agent-pack-uninstaller.test.js.map +1 -0
- package/dist/src/install/index.d.ts +8 -1
- package/dist/src/install/index.d.ts.map +1 -1
- package/dist/src/install/index.js +8 -1
- package/dist/src/install/index.js.map +1 -1
- package/dist/src/install/paths.d.ts +17 -1
- package/dist/src/install/paths.d.ts.map +1 -1
- package/dist/src/install/paths.js +26 -0
- package/dist/src/install/paths.js.map +1 -1
- package/dist/src/install/paths.test.d.ts +2 -0
- package/dist/src/install/paths.test.d.ts.map +1 -0
- package/dist/src/install/paths.test.js +76 -0
- package/dist/src/install/paths.test.js.map +1 -0
- package/dist/src/journal/hash.d.ts +13 -0
- package/dist/src/journal/hash.d.ts.map +1 -0
- package/dist/src/journal/hash.js +16 -0
- package/dist/src/journal/hash.js.map +1 -0
- package/dist/src/journal/index.d.ts +15 -0
- package/dist/src/journal/index.d.ts.map +1 -0
- package/dist/src/journal/index.js +15 -0
- package/dist/src/journal/index.js.map +1 -0
- package/dist/src/journal/journal.test.d.ts +16 -0
- package/dist/src/journal/journal.test.d.ts.map +1 -0
- package/dist/src/journal/journal.test.js +171 -0
- package/dist/src/journal/journal.test.js.map +1 -0
- package/dist/src/journal/path.d.ts +44 -0
- package/dist/src/journal/path.d.ts.map +1 -0
- package/dist/src/journal/path.js +61 -0
- package/dist/src/journal/path.js.map +1 -0
- package/dist/src/journal/reader.d.ts +38 -0
- package/dist/src/journal/reader.d.ts.map +1 -0
- package/dist/src/journal/reader.js +93 -0
- package/dist/src/journal/reader.js.map +1 -0
- package/dist/src/journal/types.d.ts +87 -0
- package/dist/src/journal/types.d.ts.map +1 -0
- package/dist/src/journal/types.js +21 -0
- package/dist/src/journal/types.js.map +1 -0
- package/dist/src/journal/writer.d.ts +52 -0
- package/dist/src/journal/writer.d.ts.map +1 -0
- package/dist/src/journal/writer.js +125 -0
- package/dist/src/journal/writer.js.map +1 -0
- package/dist/src/paywall-triggers/index.d.ts +13 -0
- package/dist/src/paywall-triggers/index.d.ts.map +1 -0
- package/dist/src/paywall-triggers/index.js +13 -0
- package/dist/src/paywall-triggers/index.js.map +1 -0
- package/dist/src/paywall-triggers/path.d.ts +47 -0
- package/dist/src/paywall-triggers/path.d.ts.map +1 -0
- package/dist/src/paywall-triggers/path.js +73 -0
- package/dist/src/paywall-triggers/path.js.map +1 -0
- package/dist/src/paywall-triggers/store.d.ts +75 -0
- package/dist/src/paywall-triggers/store.d.ts.map +1 -0
- package/dist/src/paywall-triggers/store.js +122 -0
- package/dist/src/paywall-triggers/store.js.map +1 -0
- package/dist/src/paywall-triggers/store.test.d.ts +2 -0
- package/dist/src/paywall-triggers/store.test.d.ts.map +1 -0
- package/dist/src/paywall-triggers/store.test.js +121 -0
- package/dist/src/paywall-triggers/store.test.js.map +1 -0
- package/dist/src/paywall-triggers/types.d.ts +24 -0
- package/dist/src/paywall-triggers/types.d.ts.map +1 -0
- package/dist/src/paywall-triggers/types.js +13 -0
- package/dist/src/paywall-triggers/types.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts +13 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.js +127 -0
- package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +10 -1
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.pii.d.ts +23 -0
- package/dist/src/security/scanner/SecurityScanner.pii.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.pii.js +152 -0
- package/dist/src/security/scanner/SecurityScanner.pii.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts +5 -2
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.scanners.js +63 -70
- package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +2 -0
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.js +79 -11
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +2 -0
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/services/agent-pack/agent-pack.test.d.ts +18 -0
- package/dist/src/services/agent-pack/agent-pack.test.d.ts.map +1 -0
- package/dist/src/services/agent-pack/agent-pack.test.js +344 -0
- package/dist/src/services/agent-pack/agent-pack.test.js.map +1 -0
- package/dist/src/services/agent-pack/hooks.d.ts +29 -0
- package/dist/src/services/agent-pack/hooks.d.ts.map +1 -0
- package/dist/src/services/agent-pack/hooks.js +139 -0
- package/dist/src/services/agent-pack/hooks.js.map +1 -0
- package/dist/src/services/agent-pack/index.d.ts +26 -0
- package/dist/src/services/agent-pack/index.d.ts.map +1 -0
- package/dist/src/services/agent-pack/index.js +109 -0
- package/dist/src/services/agent-pack/index.js.map +1 -0
- package/dist/src/services/agent-pack/prompt-source.d.ts +47 -0
- package/dist/src/services/agent-pack/prompt-source.d.ts.map +1 -0
- package/dist/src/services/agent-pack/prompt-source.js +182 -0
- package/dist/src/services/agent-pack/prompt-source.js.map +1 -0
- package/dist/src/services/agent-pack/shims.d.ts +40 -0
- package/dist/src/services/agent-pack/shims.d.ts.map +1 -0
- package/dist/src/services/agent-pack/shims.js +96 -0
- package/dist/src/services/agent-pack/shims.js.map +1 -0
- package/dist/src/services/agent-pack/skill-md.d.ts +35 -0
- package/dist/src/services/agent-pack/skill-md.d.ts.map +1 -0
- package/dist/src/services/agent-pack/skill-md.js +89 -0
- package/dist/src/services/agent-pack/skill-md.js.map +1 -0
- package/dist/src/services/agent-pack/types.d.ts +118 -0
- package/dist/src/services/agent-pack/types.d.ts.map +1 -0
- package/dist/src/services/agent-pack/types.js +61 -0
- package/dist/src/services/agent-pack/types.js.map +1 -0
- package/dist/src/services/agent-tool-profile.d.ts +59 -0
- package/dist/src/services/agent-tool-profile.d.ts.map +1 -0
- package/dist/src/services/agent-tool-profile.js +76 -0
- package/dist/src/services/agent-tool-profile.js.map +1 -0
- package/dist/src/services/agent-tool-profile.test.d.ts +2 -0
- package/dist/src/services/agent-tool-profile.test.d.ts.map +1 -0
- package/dist/src/services/agent-tool-profile.test.js +28 -0
- package/dist/src/services/agent-tool-profile.test.js.map +1 -0
- package/dist/src/services/bundled-sibling-scan.d.ts +111 -0
- package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -0
- package/dist/src/services/bundled-sibling-scan.js +170 -0
- package/dist/src/services/bundled-sibling-scan.js.map +1 -0
- package/dist/src/services/skill-installation.io.d.ts +16 -5
- package/dist/src/services/skill-installation.io.d.ts.map +1 -1
- package/dist/src/services/skill-installation.io.js +44 -20
- package/dist/src/services/skill-installation.io.js.map +1 -1
- package/dist/src/services/skill-installation.policy.d.ts +96 -0
- package/dist/src/services/skill-installation.policy.d.ts.map +1 -0
- package/dist/src/services/skill-installation.policy.js +144 -0
- package/dist/src/services/skill-installation.policy.js.map +1 -0
- package/dist/src/services/skill-installation.service.d.ts.map +1 -1
- package/dist/src/services/skill-installation.service.js +9 -3
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/sources/index.d.ts +1 -0
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js +4 -0
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sync/index.d.ts +6 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +8 -0
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/inventory-builder.d.ts +21 -0
- package/dist/src/sync/inventory-builder.d.ts.map +1 -0
- package/dist/src/sync/inventory-builder.js +26 -0
- package/dist/src/sync/inventory-builder.js.map +1 -0
- package/dist/src/sync/inventory-client.d.ts +80 -0
- package/dist/src/sync/inventory-client.d.ts.map +1 -0
- package/dist/src/sync/inventory-client.js +204 -0
- package/dist/src/sync/inventory-client.js.map +1 -0
- package/dist/src/sync/inventory-client.test.d.ts +14 -0
- package/dist/src/sync/inventory-client.test.d.ts.map +1 -0
- package/dist/src/sync/inventory-client.test.js +212 -0
- package/dist/src/sync/inventory-client.test.js.map +1 -0
- package/dist/src/sync/inventory-collector.d.ts +35 -0
- package/dist/src/sync/inventory-collector.d.ts.map +1 -0
- package/dist/src/sync/inventory-collector.js +176 -0
- package/dist/src/sync/inventory-collector.js.map +1 -0
- package/dist/src/sync/inventory-collector.test.d.ts +14 -0
- package/dist/src/sync/inventory-collector.test.d.ts.map +1 -0
- package/dist/src/sync/inventory-collector.test.js +156 -0
- package/dist/src/sync/inventory-collector.test.js.map +1 -0
- package/dist/src/sync/inventory-device.d.ts +36 -0
- package/dist/src/sync/inventory-device.d.ts.map +1 -0
- package/dist/src/sync/inventory-device.js +58 -0
- package/dist/src/sync/inventory-device.js.map +1 -0
- package/dist/src/sync/inventory-push.d.ts +45 -0
- package/dist/src/sync/inventory-push.d.ts.map +1 -0
- package/dist/src/sync/inventory-push.js +62 -0
- package/dist/src/sync/inventory-push.js.map +1 -0
- package/dist/src/sync/inventory-push.test.d.ts +16 -0
- package/dist/src/sync/inventory-push.test.d.ts.map +1 -0
- package/dist/src/sync/inventory-push.test.js +104 -0
- package/dist/src/sync/inventory-push.test.js.map +1 -0
- package/dist/src/sync/inventory-types.d.ts +110 -0
- package/dist/src/sync/inventory-types.d.ts.map +1 -0
- package/dist/src/sync/inventory-types.js +45 -0
- package/dist/src/sync/inventory-types.js.map +1 -0
- package/dist/src/telemetry/agent-marker.d.ts +137 -0
- package/dist/src/telemetry/agent-marker.d.ts.map +1 -0
- package/dist/src/telemetry/agent-marker.js +242 -0
- package/dist/src/telemetry/agent-marker.js.map +1 -0
- package/dist/src/telemetry/agent-marker.test.d.ts +13 -0
- package/dist/src/telemetry/agent-marker.test.d.ts.map +1 -0
- package/dist/src/telemetry/agent-marker.test.js +202 -0
- package/dist/src/telemetry/agent-marker.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +2 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +6 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +10 -0
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +5 -1
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/telemetry/wrap.d.ts +28 -7
- package/dist/src/telemetry/wrap.d.ts.map +1 -1
- package/dist/src/telemetry/wrap.gate.test.d.ts +22 -0
- package/dist/src/telemetry/wrap.gate.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.gate.test.js +197 -0
- package/dist/src/telemetry/wrap.gate.test.js.map +1 -0
- package/dist/src/telemetry/wrap.js +111 -26
- package/dist/src/telemetry/wrap.js.map +1 -1
- package/dist/src/telemetry/wrap.marker.test.d.ts +16 -0
- package/dist/src/telemetry/wrap.marker.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.marker.test.js +185 -0
- package/dist/src/telemetry/wrap.marker.test.js.map +1 -0
- package/dist/src/telemetry/wrap.test.js +4 -0
- package/dist/src/telemetry/wrap.test.js.map +1 -1
- package/dist/tests/SecurityScanner.exec.test.js +60 -0
- package/dist/tests/SecurityScanner.exec.test.js.map +1 -1
- package/dist/tests/security/ContinuousSecurity.test.js +10 -4
- package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.d.ts +2 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.d.ts.map +1 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.js +126 -0
- package/dist/tests/security/SecurityScanner.chmod-evasion.test.js.map +1 -0
- package/dist/tests/security/chmod-compound.test.d.ts +2 -0
- package/dist/tests/security/chmod-compound.test.d.ts.map +1 -0
- package/dist/tests/security/chmod-compound.test.js +188 -0
- package/dist/tests/security/chmod-compound.test.js.map +1 -0
- package/dist/tests/security/pii-detection.test.js +131 -0
- package/dist/tests/security/pii-detection.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.d.ts +23 -7
- package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +27 -11
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/security/sensitive-path-fp.test.d.ts +2 -0
- package/dist/tests/security/sensitive-path-fp.test.d.ts.map +1 -0
- package/dist/tests/security/sensitive-path-fp.test.js +142 -0
- package/dist/tests/security/sensitive-path-fp.test.js.map +1 -0
- package/dist/tests/services/bundled-sibling-scan.test.d.ts +2 -0
- package/dist/tests/services/bundled-sibling-scan.test.d.ts.map +1 -0
- package/dist/tests/services/bundled-sibling-scan.test.js +139 -0
- package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.js +166 -0
- package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.policy.test.d.ts +8 -0
- package/dist/tests/unit/services/skill-installation.policy.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.policy.test.js +151 -0
- package/dist/tests/unit/services/skill-installation.policy.test.js.map +1 -0
- package/package.json +23 -3
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inventory-client tests (SMI-5392).
|
|
3
|
+
*
|
|
4
|
+
* Mocks the token-credentials module and global fetch.
|
|
5
|
+
*
|
|
6
|
+
* UC-1: no credentials -> InventoryAuthError, no fetch.
|
|
7
|
+
* UC-2: expired credentials -> refresh path runs, new creds stored, new token used.
|
|
8
|
+
* UC-3: expired credentials + failed refresh -> InventoryAuthError.
|
|
9
|
+
* UC-4: 200 body returned verbatim (incl. the consent-off no-op).
|
|
10
|
+
* UC-5: 401 / 409 / 400 / 500 -> the matching typed error.
|
|
11
|
+
* UC-6: network throw -> InventoryUploadError.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
vi.mock('../config/token-credentials.js', () => ({
|
|
15
|
+
loadCredentials: vi.fn(),
|
|
16
|
+
refreshAccessToken: vi.fn(),
|
|
17
|
+
storeCredentials: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
import { loadCredentials, refreshAccessToken, storeCredentials, } from '../config/token-credentials.js';
|
|
20
|
+
import { uploadInventory, purgeInventory, InventoryAuthError, InventoryConflictError, InventoryValidationError, InventoryUploadError, } from './inventory-client.js';
|
|
21
|
+
const fetchMock = vi.fn();
|
|
22
|
+
const payload = {
|
|
23
|
+
device: { device_id: '11111111-1111-4111-8111-111111111111' },
|
|
24
|
+
skills: [],
|
|
25
|
+
};
|
|
26
|
+
function jsonResponse(body, status) {
|
|
27
|
+
return new Response(JSON.stringify(body), {
|
|
28
|
+
status,
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const futureCreds = {
|
|
33
|
+
accessToken: 'at_valid',
|
|
34
|
+
refreshToken: 'rt_valid',
|
|
35
|
+
expiresAt: Date.now() + 3_600_000,
|
|
36
|
+
version: 2,
|
|
37
|
+
};
|
|
38
|
+
describe('inventory-client', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
42
|
+
});
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
vi.unstubAllGlobals();
|
|
45
|
+
});
|
|
46
|
+
it('UC-1: throws InventoryAuthError when no credentials are stored', async () => {
|
|
47
|
+
vi.mocked(loadCredentials).mockResolvedValue(null);
|
|
48
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryAuthError);
|
|
49
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
it('UC-2: refreshes + stores when the access token is near expiry, then uses the new token', async () => {
|
|
52
|
+
vi.mocked(loadCredentials).mockResolvedValue({
|
|
53
|
+
accessToken: 'at_expired',
|
|
54
|
+
refreshToken: 'rt_old',
|
|
55
|
+
expiresAt: Date.now() - 1_000,
|
|
56
|
+
version: 2,
|
|
57
|
+
});
|
|
58
|
+
vi.mocked(refreshAccessToken).mockResolvedValue({
|
|
59
|
+
accessToken: 'at_new',
|
|
60
|
+
refreshToken: 'rt_new',
|
|
61
|
+
expiresAt: Date.now() + 3_600_000,
|
|
62
|
+
version: 2,
|
|
63
|
+
});
|
|
64
|
+
fetchMock.mockResolvedValue(jsonResponse({ ok: true, applied: true, device_id: 'd' }, 200));
|
|
65
|
+
const result = await uploadInventory(payload);
|
|
66
|
+
expect(refreshAccessToken).toHaveBeenCalledWith('rt_old');
|
|
67
|
+
expect(storeCredentials).toHaveBeenCalledWith(expect.objectContaining({ accessToken: 'at_new', version: 2 }));
|
|
68
|
+
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/inventory-upload'), expect.objectContaining({
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: expect.objectContaining({ Authorization: 'Bearer at_new' }),
|
|
71
|
+
}));
|
|
72
|
+
expect(result.applied).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it('UC-3: throws InventoryAuthError when the refresh fails', async () => {
|
|
75
|
+
vi.mocked(loadCredentials).mockResolvedValue({
|
|
76
|
+
accessToken: 'at_expired',
|
|
77
|
+
refreshToken: 'rt_old',
|
|
78
|
+
expiresAt: Date.now() - 1_000,
|
|
79
|
+
version: 2,
|
|
80
|
+
});
|
|
81
|
+
vi.mocked(refreshAccessToken).mockResolvedValue(null);
|
|
82
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryAuthError);
|
|
83
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it('UC-4: returns the 200 body verbatim, including the consent-off no-op', async () => {
|
|
86
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
87
|
+
const consentOff = { ok: true, applied: false, reason: 'consent_disabled' };
|
|
88
|
+
fetchMock.mockResolvedValue(jsonResponse(consentOff, 200));
|
|
89
|
+
const result = await uploadInventory(payload);
|
|
90
|
+
expect(result).toEqual(consentOff);
|
|
91
|
+
expect(refreshAccessToken).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
it('UC-5a: maps 401 to InventoryAuthError', async () => {
|
|
94
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
95
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'authentication_required' }, 401));
|
|
96
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryAuthError);
|
|
97
|
+
});
|
|
98
|
+
it('UC-5b: maps 409 to InventoryConflictError', async () => {
|
|
99
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
100
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'device_conflict' }, 409));
|
|
101
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryConflictError);
|
|
102
|
+
});
|
|
103
|
+
it('UC-5c: maps 400 to InventoryValidationError carrying the server message', async () => {
|
|
104
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
105
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'too_many_skills' }, 400));
|
|
106
|
+
// Single call: the mocked Response body can only be read once.
|
|
107
|
+
const error = await uploadInventory(payload).catch((e) => e);
|
|
108
|
+
expect(error).toBeInstanceOf(InventoryValidationError);
|
|
109
|
+
expect(error.message).toBe('too_many_skills');
|
|
110
|
+
});
|
|
111
|
+
it('UC-5d: maps 500 to InventoryUploadError', async () => {
|
|
112
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
113
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'server_error' }, 500));
|
|
114
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryUploadError);
|
|
115
|
+
});
|
|
116
|
+
it('UC-6: wraps a network throw in InventoryUploadError', async () => {
|
|
117
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
118
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
119
|
+
await expect(uploadInventory(payload)).rejects.toBeInstanceOf(InventoryUploadError);
|
|
120
|
+
});
|
|
121
|
+
// Security invariant: tokens must never appear in error message strings.
|
|
122
|
+
// Covers both the non-200 response path and the network-throw path.
|
|
123
|
+
it('UC-7: error messages do not leak access tokens or refresh tokens (security invariant)', async () => {
|
|
124
|
+
const sentinelCreds = {
|
|
125
|
+
accessToken: 'at_SECRET_5392',
|
|
126
|
+
refreshToken: 'rt_SECRET_5392',
|
|
127
|
+
expiresAt: Date.now() + 3_600_000,
|
|
128
|
+
version: 2,
|
|
129
|
+
};
|
|
130
|
+
// Non-200 path: 500 → InventoryUploadError whose message must not contain tokens.
|
|
131
|
+
vi.mocked(loadCredentials).mockResolvedValue(sentinelCreds);
|
|
132
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'server_error' }, 500));
|
|
133
|
+
const err500 = await uploadInventory(payload).catch((e) => e);
|
|
134
|
+
expect(err500).toBeInstanceOf(InventoryUploadError);
|
|
135
|
+
expect(err500.message).not.toContain('at_SECRET_5392');
|
|
136
|
+
expect(err500.message).not.toContain('rt_SECRET_5392');
|
|
137
|
+
// Network-throw path: fetch rejects → InventoryUploadError whose message must not contain tokens.
|
|
138
|
+
vi.mocked(loadCredentials).mockResolvedValue(sentinelCreds);
|
|
139
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
140
|
+
const errNet = await uploadInventory(payload).catch((e) => e);
|
|
141
|
+
expect(errNet).toBeInstanceOf(InventoryUploadError);
|
|
142
|
+
expect(errNet.message).not.toContain('at_SECRET_5392');
|
|
143
|
+
expect(errNet.message).not.toContain('rt_SECRET_5392');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// purgeInventory (SMI-5510) — reuses the same auth + error-typing as upload.
|
|
148
|
+
//
|
|
149
|
+
// PC-1: 200 { ok, deleted_device_count } -> returns the count.
|
|
150
|
+
// PC-2: no credentials -> InventoryAuthError, no fetch.
|
|
151
|
+
// PC-3: expired credentials + failed refresh -> InventoryAuthError.
|
|
152
|
+
// PC-4: 401 -> InventoryAuthError.
|
|
153
|
+
// PC-5: 500 -> InventoryUploadError.
|
|
154
|
+
// PC-6: network throw -> InventoryUploadError.
|
|
155
|
+
// PC-7: unexpected 200 body shape -> InventoryUploadError.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
describe('purgeInventory', () => {
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
vi.clearAllMocks();
|
|
160
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
161
|
+
});
|
|
162
|
+
afterEach(() => {
|
|
163
|
+
vi.unstubAllGlobals();
|
|
164
|
+
});
|
|
165
|
+
it('PC-1: returns the deleted device count and POSTs to /purge-inventory with the bearer token', async () => {
|
|
166
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
167
|
+
fetchMock.mockResolvedValue(jsonResponse({ ok: true, deleted_device_count: 3 }, 200));
|
|
168
|
+
const deleted = await purgeInventory();
|
|
169
|
+
expect(deleted).toBe(3);
|
|
170
|
+
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/purge-inventory'), expect.objectContaining({
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: expect.objectContaining({ Authorization: 'Bearer at_valid' }),
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
it('PC-2: throws InventoryAuthError when no credentials are stored', async () => {
|
|
176
|
+
vi.mocked(loadCredentials).mockResolvedValue(null);
|
|
177
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryAuthError);
|
|
178
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
it('PC-3: throws InventoryAuthError when the refresh fails', async () => {
|
|
181
|
+
vi.mocked(loadCredentials).mockResolvedValue({
|
|
182
|
+
accessToken: 'at_expired',
|
|
183
|
+
refreshToken: 'rt_old',
|
|
184
|
+
expiresAt: Date.now() - 1_000,
|
|
185
|
+
version: 2,
|
|
186
|
+
});
|
|
187
|
+
vi.mocked(refreshAccessToken).mockResolvedValue(null);
|
|
188
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryAuthError);
|
|
189
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
it('PC-4: maps 401 to InventoryAuthError', async () => {
|
|
192
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
193
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'authentication_required' }, 401));
|
|
194
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryAuthError);
|
|
195
|
+
});
|
|
196
|
+
it('PC-5: maps 500 to InventoryUploadError', async () => {
|
|
197
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
198
|
+
fetchMock.mockResolvedValue(jsonResponse({ error: 'server_error' }, 500));
|
|
199
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryUploadError);
|
|
200
|
+
});
|
|
201
|
+
it('PC-6: wraps a network throw in InventoryUploadError', async () => {
|
|
202
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
203
|
+
fetchMock.mockRejectedValue(new Error('ECONNREFUSED'));
|
|
204
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryUploadError);
|
|
205
|
+
});
|
|
206
|
+
it('PC-7: throws InventoryUploadError on an unexpected 200 body shape', async () => {
|
|
207
|
+
vi.mocked(loadCredentials).mockResolvedValue(futureCreds);
|
|
208
|
+
fetchMock.mockResolvedValue(jsonResponse({ ok: true }, 200));
|
|
209
|
+
await expect(purgeInventory()).rejects.toBeInstanceOf(InventoryUploadError);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=inventory-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-client.test.js","sourceRoot":"","sources":["../../../src/sync/inventory-client.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExE,EAAE,CAAC,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;IACxB,kBAAkB,EAAE,EAAE,CAAC,EAAE,EAAE;IAC3B,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE;CAC1B,CAAC,CAAC,CAAA;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,uBAAuB,CAAA;AAG9B,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAgB,CAAA;AAEvC,MAAM,OAAO,GAA2B;IACtC,MAAM,EAAE,EAAE,SAAS,EAAE,sCAAsC,EAAE;IAC7D,MAAM,EAAE,EAAE;CACX,CAAA;AAED,SAAS,YAAY,CAAC,IAAa,EAAE,MAAc;IACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,WAAW,GAAG;IAClB,WAAW,EAAE,UAAU;IACvB,YAAY,EAAE,UAAU;IACxB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;IACjC,OAAO,EAAE,CAAU;CACpB,CAAA;AAED,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAA;QAClB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAA;IACvB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;QAElD,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACjF,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wFAAwF,EAAE,KAAK,IAAI,EAAE;QACtG,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC;YAC3C,WAAW,EAAE,YAAY;YACzB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YAC7B,OAAO,EAAE,CAAC;SACX,CAAC,CAAA;QACF,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,iBAAiB,CAAC;YAC9C,WAAW,EAAE,QAAQ;YACrB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACjC,OAAO,EAAE,CAAC;SACX,CAAC,CAAA;QACF,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAE3F,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAA;QAE7C,MAAM,CAAC,kBAAkB,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAA;QACzD,MAAM,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAC3C,MAAM,CAAC,gBAAgB,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAC/D,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,EAC5C,MAAM,CAAC,gBAAgB,CAAC;YACtB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC;SACrE,CAAC,CACH,CAAA;QACD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC;YAC3C,WAAW,EAAE,YAAY;YACzB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YAC7B,OAAO,EAAE,CAAC;SACX,CAAC,CAAA;QACF,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;QAErD,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACjF,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sEAAsE,EAAE,KAAK,IAAI,EAAE;QACpF,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAA;QAC3E,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAA;QAE1D,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAA;QAE7C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAClC,MAAM,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAEpF,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAE5E,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAE5E,+DAA+D;QAC/D,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACrE,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,wBAAwB,CAAC,CAAA;QACtD,MAAM,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAEzE,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAA;QAEtD,MAAM,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,yEAAyE;IACzE,oEAAoE;IACpE,EAAE,CAAC,uFAAuF,EAAE,KAAK,IAAI,EAAE;QACrG,MAAM,aAAa,GAAG;YACpB,WAAW,EAAE,gBAAgB;YAC7B,YAAY,EAAE,gBAAgB;YAC9B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACjC,OAAO,EAAE,CAAU;SACpB,CAAA;QAED,kFAAkF;QAClF,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAA;QAC3D,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAEzE,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACtE,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;QACnD,MAAM,CAAE,MAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;QACjE,MAAM,CAAE,MAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;QAEjE,kGAAkG;QAClG,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAA;QAC3D,SAAS,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAA;QAEtD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;QACtE,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;QACnD,MAAM,CAAE,MAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;QACjE,MAAM,CAAE,MAAgB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,6EAA6E;AAC7E,EAAE;AACF,+DAA+D;AAC/D,wDAAwD;AACxD,oEAAoE;AACpE,mCAAmC;AACnC,qCAAqC;AACrC,+CAA+C;AAC/C,2DAA2D;AAC3D,8EAA8E;AAE9E,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAA;QAClB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAA;IACvB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4FAA4F,EAAE,KAAK,IAAI,EAAE;QAC1G,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAErF,MAAM,OAAO,GAAG,MAAM,cAAc,EAAE,CAAA;QAEtC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,EAC3C,MAAM,CAAC,gBAAgB,CAAC;YACtB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC;SACvE,CAAC,CACH,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;QAElD,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACzE,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC;YAC3C,WAAW,EAAE,YAAY;YACzB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;YAC7B,OAAO,EAAE,CAAC;SACX,CAAC,CAAA;QACF,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAA;QAErD,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;QACzE,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAEpF,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAEzE,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAA;QAEtD,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAA;QACzD,SAAS,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAE5D,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-harness skill collector (SMI-5392, umbrella SMI-5382).
|
|
3
|
+
*
|
|
4
|
+
* Walks every known harness skill directory (`CLIENT_NATIVE_PATHS`) and emits
|
|
5
|
+
* one {@link InventorySkillEntry} per (harness, skill) observed on disk. This is
|
|
6
|
+
* the shared scanner that both the CLI `inventory push` command (Wave 3) and the
|
|
7
|
+
* MCP inventory tool call before handing the snapshot to {@link uploadInventory}.
|
|
8
|
+
*
|
|
9
|
+
* Design parity with the CLI scanner at
|
|
10
|
+
* `packages/cli/src/utils/skills-directory.ts`:
|
|
11
|
+
* - Uses the SAME {@link SkillParser} to resolve `skill_id` / `version`.
|
|
12
|
+
* - Realpath-deduplicates ACROSS harnesses (collapses symlink aliases), but does
|
|
13
|
+
* NOT name-deduplicate — the same skill independently installed under two
|
|
14
|
+
* harnesses is two distinct rows (the `device_skills` PK is `(harness, skill_id)`).
|
|
15
|
+
*
|
|
16
|
+
* @module @skillsmith/core/sync/inventory-collector
|
|
17
|
+
*/
|
|
18
|
+
import type { InventorySkillEntry } from './inventory-types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Collect every harness-installed skill on this device as inventory entries.
|
|
21
|
+
*
|
|
22
|
+
* Scans each harness in {@link CLIENT_IDS} order. Repo-local `./.claude/skills`
|
|
23
|
+
* is intentionally excluded — inventory tracks harness-installed skills only, and
|
|
24
|
+
* `harness` is always a {@link ClientId}, never `'local'`.
|
|
25
|
+
*
|
|
26
|
+
* The result is NOT truncated to `INVENTORY_LIMITS.MAX_SKILLS`. Exceeding
|
|
27
|
+
* the cap is a real condition the caller / edge function must enforce (returning
|
|
28
|
+
* a `too_many_skills` 400) — silently dropping skills here would corrupt the
|
|
29
|
+
* server-side reconcile by making present skills look absent.
|
|
30
|
+
*
|
|
31
|
+
* @returns One entry per (harness, skill), realpath-deduplicated across harnesses.
|
|
32
|
+
* @see SMI-5392
|
|
33
|
+
*/
|
|
34
|
+
export declare function collectDeviceSkills(): Promise<InventorySkillEntry[]>;
|
|
35
|
+
//# sourceMappingURL=inventory-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-collector.d.ts","sourceRoot":"","sources":["../../../src/sync/inventory-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAyJ/D;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAO1E"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-harness skill collector (SMI-5392, umbrella SMI-5382).
|
|
3
|
+
*
|
|
4
|
+
* Walks every known harness skill directory (`CLIENT_NATIVE_PATHS`) and emits
|
|
5
|
+
* one {@link InventorySkillEntry} per (harness, skill) observed on disk. This is
|
|
6
|
+
* the shared scanner that both the CLI `inventory push` command (Wave 3) and the
|
|
7
|
+
* MCP inventory tool call before handing the snapshot to {@link uploadInventory}.
|
|
8
|
+
*
|
|
9
|
+
* Design parity with the CLI scanner at
|
|
10
|
+
* `packages/cli/src/utils/skills-directory.ts`:
|
|
11
|
+
* - Uses the SAME {@link SkillParser} to resolve `skill_id` / `version`.
|
|
12
|
+
* - Realpath-deduplicates ACROSS harnesses (collapses symlink aliases), but does
|
|
13
|
+
* NOT name-deduplicate — the same skill independently installed under two
|
|
14
|
+
* harnesses is two distinct rows (the `device_skills` PK is `(harness, skill_id)`).
|
|
15
|
+
*
|
|
16
|
+
* @module @skillsmith/core/sync/inventory-collector
|
|
17
|
+
*/
|
|
18
|
+
import { readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
19
|
+
import { createHash } from 'node:crypto';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { SkillParser } from '../indexer/SkillParser.js';
|
|
22
|
+
import { CLIENT_IDS, CLIENT_NATIVE_PATHS } from '../install/paths.js';
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a path through `realpath` defensively. Returns the resolved path on
|
|
25
|
+
* success, or the input path unchanged when the link is broken / unreadable —
|
|
26
|
+
* dedup keying still works either way (we just can't collapse a broken alias).
|
|
27
|
+
*/
|
|
28
|
+
async function safeRealpath(path) {
|
|
29
|
+
try {
|
|
30
|
+
return await realpath(path);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return path;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Return `true` when a directory entry resolves to a directory, following
|
|
38
|
+
* symlinks. `withFileTypes` reports a symlinked directory as a symlink (not a
|
|
39
|
+
* directory), so symlinked aliases must be `stat`-resolved to be collected —
|
|
40
|
+
* which is exactly what makes the cross-harness realpath dedup observable.
|
|
41
|
+
*/
|
|
42
|
+
async function resolvesToDirectory(entryPath, isDirectory, isSymbolicLink) {
|
|
43
|
+
if (isDirectory)
|
|
44
|
+
return true;
|
|
45
|
+
if (!isSymbolicLink)
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
return (await stat(entryPath)).isDirectory();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false; // broken symlink
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read `<skillDir>/SKILL.md` and derive the inventory fields.
|
|
56
|
+
*
|
|
57
|
+
* - `content_hash` is the sha256 hex digest of the raw SKILL.md content. Wave 5
|
|
58
|
+
* validates that this hashing stays drift-aligned with the indexer's hashing.
|
|
59
|
+
* - `skill_id` is the parsed `id` front-matter field, falling back to the parsed
|
|
60
|
+
* `name`, then the directory name (same precedence as the CLI scanner).
|
|
61
|
+
* - A directory with no readable SKILL.md still counts as a skill: `skill_id` is
|
|
62
|
+
* the directory name, with `version`, `content_hash`, and provenance fields `null`.
|
|
63
|
+
* - `author`, `license`, `repository` are self-asserted values from the SKILL.md
|
|
64
|
+
* front-matter (SMI-5442 Wave 3). They are `null` when absent or unparseable.
|
|
65
|
+
*/
|
|
66
|
+
async function readSkillFields(skillDir, dirName) {
|
|
67
|
+
try {
|
|
68
|
+
const content = await readFile(join(skillDir, 'SKILL.md'), 'utf-8');
|
|
69
|
+
const contentHash = createHash('sha256').update(content, 'utf8').digest('hex');
|
|
70
|
+
const parsed = new SkillParser().parse(content);
|
|
71
|
+
if (!parsed) {
|
|
72
|
+
// SKILL.md is readable but has no valid frontmatter — hash still applies.
|
|
73
|
+
return {
|
|
74
|
+
skillId: dirName,
|
|
75
|
+
version: null,
|
|
76
|
+
contentHash,
|
|
77
|
+
author: null,
|
|
78
|
+
license: null,
|
|
79
|
+
repository: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Match the CLI scanner: read `id` off the parsed metadata, then `name`.
|
|
83
|
+
const parsedId = parsed['id'];
|
|
84
|
+
return {
|
|
85
|
+
skillId: parsedId ?? parsed.name ?? dirName,
|
|
86
|
+
version: parsed.version ?? null,
|
|
87
|
+
contentHash,
|
|
88
|
+
author: parsed.author ?? null,
|
|
89
|
+
license: parsed.license ?? null,
|
|
90
|
+
repository: parsed.repository ?? null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// No readable SKILL.md — still a skill, but version/hash/provenance are unknown.
|
|
95
|
+
return {
|
|
96
|
+
skillId: dirName,
|
|
97
|
+
version: null,
|
|
98
|
+
contentHash: null,
|
|
99
|
+
author: null,
|
|
100
|
+
license: null,
|
|
101
|
+
repository: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Scan a single harness directory and append its skills to `entries`,
|
|
107
|
+
* deduplicating by realpath via the shared `seenRealpaths` set.
|
|
108
|
+
*/
|
|
109
|
+
async function collectHarness(harness, entries, seenRealpaths) {
|
|
110
|
+
const harnessDir = CLIENT_NATIVE_PATHS[harness];
|
|
111
|
+
let dirents;
|
|
112
|
+
try {
|
|
113
|
+
dirents = await readdir(harnessDir, { withFileTypes: true });
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const code = error.code;
|
|
117
|
+
// Absent harness dir is the common case (harness not installed) — skip it.
|
|
118
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
119
|
+
return;
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
for (const dirent of dirents) {
|
|
123
|
+
// Skip dot-prefixed directories: they are harness internals, not skills.
|
|
124
|
+
// Covers .backups (created by apply_recommended_edit — SMI-5440) and any
|
|
125
|
+
// other dot-dir that must not surface as inventory rows. (SMI-5442)
|
|
126
|
+
if (dirent.name.startsWith('.'))
|
|
127
|
+
continue;
|
|
128
|
+
const entryPath = join(harnessDir, dirent.name);
|
|
129
|
+
if (!(await resolvesToDirectory(entryPath, dirent.isDirectory(), dirent.isSymbolicLink()))) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// Realpath dedup ACROSS harnesses: the first harness in CLIENT_IDS order
|
|
133
|
+
// wins, so a symlinked alias under a later harness is collapsed away.
|
|
134
|
+
const realDir = await safeRealpath(entryPath);
|
|
135
|
+
if (seenRealpaths.has(realDir))
|
|
136
|
+
continue;
|
|
137
|
+
seenRealpaths.add(realDir);
|
|
138
|
+
const { skillId, version, contentHash, author, license, repository } = await readSkillFields(entryPath, dirent.name);
|
|
139
|
+
entries.push({
|
|
140
|
+
harness,
|
|
141
|
+
skill_id: skillId,
|
|
142
|
+
version,
|
|
143
|
+
content_hash: contentHash,
|
|
144
|
+
source: null,
|
|
145
|
+
author,
|
|
146
|
+
license,
|
|
147
|
+
repository,
|
|
148
|
+
pinned_version: null,
|
|
149
|
+
update_policy: null,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Collect every harness-installed skill on this device as inventory entries.
|
|
155
|
+
*
|
|
156
|
+
* Scans each harness in {@link CLIENT_IDS} order. Repo-local `./.claude/skills`
|
|
157
|
+
* is intentionally excluded — inventory tracks harness-installed skills only, and
|
|
158
|
+
* `harness` is always a {@link ClientId}, never `'local'`.
|
|
159
|
+
*
|
|
160
|
+
* The result is NOT truncated to `INVENTORY_LIMITS.MAX_SKILLS`. Exceeding
|
|
161
|
+
* the cap is a real condition the caller / edge function must enforce (returning
|
|
162
|
+
* a `too_many_skills` 400) — silently dropping skills here would corrupt the
|
|
163
|
+
* server-side reconcile by making present skills look absent.
|
|
164
|
+
*
|
|
165
|
+
* @returns One entry per (harness, skill), realpath-deduplicated across harnesses.
|
|
166
|
+
* @see SMI-5392
|
|
167
|
+
*/
|
|
168
|
+
export async function collectDeviceSkills() {
|
|
169
|
+
const entries = [];
|
|
170
|
+
const seenRealpaths = new Set();
|
|
171
|
+
for (const harness of CLIENT_IDS) {
|
|
172
|
+
await collectHarness(harness, entries, seenRealpaths);
|
|
173
|
+
}
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=inventory-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-collector.js","sourceRoot":"","sources":["../../../src/sync/inventory-collector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inventory-collector tests (SMI-5392).
|
|
3
|
+
*
|
|
4
|
+
* Controls the harness directory table by mocking `../install/paths.js` so each
|
|
5
|
+
* `ClientId` points at a real tmp fixture. CLIENT_NATIVE_PATHS is a mutable
|
|
6
|
+
* object the collector reads at call time, so per-test fixtures are visible
|
|
7
|
+
* without re-importing the module.
|
|
8
|
+
*
|
|
9
|
+
* IC-1: same skill under two harnesses (distinct realpaths) -> two entries.
|
|
10
|
+
* IC-2: a symlinked alias across harnesses -> one entry (first ClientId wins).
|
|
11
|
+
* IC-3: readable SKILL.md -> content_hash + version; missing SKILL.md -> nulls.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=inventory-collector.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-collector.test.d.ts","sourceRoot":"","sources":["../../../src/sync/inventory-collector.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inventory-collector tests (SMI-5392).
|
|
3
|
+
*
|
|
4
|
+
* Controls the harness directory table by mocking `../install/paths.js` so each
|
|
5
|
+
* `ClientId` points at a real tmp fixture. CLIENT_NATIVE_PATHS is a mutable
|
|
6
|
+
* object the collector reads at call time, so per-test fixtures are visible
|
|
7
|
+
* without re-importing the module.
|
|
8
|
+
*
|
|
9
|
+
* IC-1: same skill under two harnesses (distinct realpaths) -> two entries.
|
|
10
|
+
* IC-2: a symlinked alias across harnesses -> one entry (first ClientId wins).
|
|
11
|
+
* IC-3: readable SKILL.md -> content_hash + version; missing SKILL.md -> nulls.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { mkdtemp, mkdir, writeFile, symlink, rm } from 'node:fs/promises';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
const mockPaths = vi.hoisted(() => ({
|
|
18
|
+
CLIENT_IDS: ['claude-code', 'cursor', 'copilot', 'windsurf', 'agents'],
|
|
19
|
+
CLIENT_NATIVE_PATHS: {},
|
|
20
|
+
}));
|
|
21
|
+
vi.mock('../install/paths.js', () => ({
|
|
22
|
+
CLIENT_IDS: mockPaths.CLIENT_IDS,
|
|
23
|
+
CLIENT_NATIVE_PATHS: mockPaths.CLIENT_NATIVE_PATHS,
|
|
24
|
+
}));
|
|
25
|
+
import { collectDeviceSkills } from './inventory-collector.js';
|
|
26
|
+
import { INVENTORY_LIMITS } from './inventory-types.js';
|
|
27
|
+
function skillMd(name, version) {
|
|
28
|
+
const versionLine = version ? `\nversion: ${version}` : '';
|
|
29
|
+
return `---\nname: ${name}${versionLine}\n---\n\n# ${name}\n`;
|
|
30
|
+
}
|
|
31
|
+
function skillMdWithProvenance(name, provenance, version) {
|
|
32
|
+
const versionLine = version ? `\nversion: ${version}` : '';
|
|
33
|
+
const authorLine = provenance.author ? `\nauthor: ${provenance.author}` : '';
|
|
34
|
+
const licenseLine = provenance.license ? `\nlicense: ${provenance.license}` : '';
|
|
35
|
+
const repoLine = provenance.repository ? `\nrepository: ${provenance.repository}` : '';
|
|
36
|
+
return `---\nname: ${name}${versionLine}${authorLine}${licenseLine}${repoLine}\n---\n\n# ${name}\n`;
|
|
37
|
+
}
|
|
38
|
+
async function createSkill(harness, name, opts = {}) {
|
|
39
|
+
const dir = join(mockPaths.CLIENT_NATIVE_PATHS[harness], name);
|
|
40
|
+
await mkdir(dir, { recursive: true });
|
|
41
|
+
if (opts.withSkillMd !== false) {
|
|
42
|
+
const content = opts.content ?? skillMd(name, opts.version);
|
|
43
|
+
await writeFile(join(dir, 'SKILL.md'), content);
|
|
44
|
+
}
|
|
45
|
+
return dir;
|
|
46
|
+
}
|
|
47
|
+
let root;
|
|
48
|
+
describe('inventory-collector', () => {
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
root = await mkdtemp(join(tmpdir(), 'inv-collector-'));
|
|
51
|
+
// Point every harness at its own (initially absent) subdir under root.
|
|
52
|
+
for (const id of mockPaths.CLIENT_IDS) {
|
|
53
|
+
mockPaths.CLIENT_NATIVE_PATHS[id] = join(root, id);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
afterEach(async () => {
|
|
57
|
+
await rm(root, { recursive: true, force: true });
|
|
58
|
+
vi.restoreAllMocks();
|
|
59
|
+
});
|
|
60
|
+
it('IC-1: emits two entries for the same skill installed under two harnesses', async () => {
|
|
61
|
+
await createSkill('claude-code', 'foo', { version: '1.0.0' });
|
|
62
|
+
await createSkill('cursor', 'foo', { version: '2.0.0' });
|
|
63
|
+
const entries = await collectDeviceSkills();
|
|
64
|
+
const foo = entries.filter((e) => e.skill_id === 'foo');
|
|
65
|
+
expect(foo).toHaveLength(2);
|
|
66
|
+
expect(foo.map((e) => e.harness).sort()).toEqual(['claude-code', 'cursor']);
|
|
67
|
+
// Distinct realpaths -> distinct rows, each carrying its own version + hash.
|
|
68
|
+
expect(foo.every((e) => typeof e.content_hash === 'string')).toBe(true);
|
|
69
|
+
// Static fields are always null at the local-agent layer.
|
|
70
|
+
expect(foo.every((e) => e.source === null && e.pinned_version === null)).toBe(true);
|
|
71
|
+
expect(foo.every((e) => e.update_policy === null)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it('IC-2: collapses a symlinked alias across harnesses to one entry (first ClientId wins)', async () => {
|
|
74
|
+
const real = await createSkill('claude-code', 'bar', { version: '1.0.0' });
|
|
75
|
+
await mkdir(mockPaths.CLIENT_NATIVE_PATHS['agents'], { recursive: true });
|
|
76
|
+
await symlink(real, join(mockPaths.CLIENT_NATIVE_PATHS['agents'], 'bar'), 'dir');
|
|
77
|
+
const entries = await collectDeviceSkills();
|
|
78
|
+
const bar = entries.filter((e) => e.skill_id === 'bar');
|
|
79
|
+
expect(bar).toHaveLength(1);
|
|
80
|
+
// claude-code precedes agents in CLIENT_IDS, so it wins the dedup.
|
|
81
|
+
expect(bar[0]?.harness).toBe('claude-code');
|
|
82
|
+
});
|
|
83
|
+
// IC-4: No truncation at MAX_SKILLS boundary.
|
|
84
|
+
//
|
|
85
|
+
// The collector is intentionally uncapped — it returns EVERY on-disk entry.
|
|
86
|
+
// The >5000 ceiling is enforced server-side via the `too_many_skills` 400 error
|
|
87
|
+
// (see inventory-client tests) and will be covered end-to-end in Wave 5. This
|
|
88
|
+
// test verifies the guarantee cheaply using a small K so the suite stays fast.
|
|
89
|
+
it('IC-4: returned entry count equals the number of on-disk skills with no local cap applied', async () => {
|
|
90
|
+
const K = 4;
|
|
91
|
+
for (let i = 0; i < K; i++) {
|
|
92
|
+
await createSkill('claude-code', `skill-ic4-${i}`, { version: `1.0.${i}` });
|
|
93
|
+
}
|
|
94
|
+
const entries = await collectDeviceSkills();
|
|
95
|
+
const ic4 = entries.filter((e) => e.skill_id.startsWith('skill-ic4-'));
|
|
96
|
+
// The collector must not truncate: returned count must equal the on-disk count.
|
|
97
|
+
expect(ic4).toHaveLength(K);
|
|
98
|
+
// K is well below the server-enforced limit — no false ceiling here.
|
|
99
|
+
expect(K).toBeLessThan(INVENTORY_LIMITS.MAX_SKILLS);
|
|
100
|
+
});
|
|
101
|
+
it('IC-3: populates content_hash + version for readable SKILL.md, nulls for missing', async () => {
|
|
102
|
+
await createSkill('claude-code', 'withmd', { version: '3.1.4' });
|
|
103
|
+
await createSkill('cursor', 'nomd', { withSkillMd: false });
|
|
104
|
+
const entries = await collectDeviceSkills();
|
|
105
|
+
const withMd = entries.find((e) => e.skill_id === 'withmd');
|
|
106
|
+
const noMd = entries.find((e) => e.skill_id === 'nomd');
|
|
107
|
+
expect(withMd?.version).toBe('3.1.4');
|
|
108
|
+
expect(withMd?.content_hash).toMatch(/^[0-9a-f]{64}$/);
|
|
109
|
+
// A subdir with no readable SKILL.md still counts, keyed by its dir name.
|
|
110
|
+
expect(noMd).toBeDefined();
|
|
111
|
+
expect(noMd?.version).toBeNull();
|
|
112
|
+
expect(noMd?.content_hash).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
// IC-5: provenance fields from SKILL.md front-matter propagate to the entry.
|
|
115
|
+
it('IC-5: captures author/license/repository from SKILL.md front-matter (SMI-5442)', async () => {
|
|
116
|
+
await createSkill('claude-code', 'provenance-skill', {
|
|
117
|
+
version: '1.0.0',
|
|
118
|
+
content: skillMdWithProvenance('provenance-skill', {
|
|
119
|
+
author: 'acme/tools',
|
|
120
|
+
license: 'MIT',
|
|
121
|
+
repository: 'https://github.com/acme/tools',
|
|
122
|
+
}, '1.0.0'),
|
|
123
|
+
});
|
|
124
|
+
const entries = await collectDeviceSkills();
|
|
125
|
+
const entry = entries.find((e) => e.skill_id === 'provenance-skill');
|
|
126
|
+
expect(entry).toBeDefined();
|
|
127
|
+
expect(entry?.author).toBe('acme/tools');
|
|
128
|
+
expect(entry?.license).toBe('MIT');
|
|
129
|
+
expect(entry?.repository).toBe('https://github.com/acme/tools');
|
|
130
|
+
});
|
|
131
|
+
// IC-6: provenance fields are null/absent when not in SKILL.md front-matter.
|
|
132
|
+
it('IC-6: provenance fields are null when absent from SKILL.md front-matter (SMI-5442)', async () => {
|
|
133
|
+
// skillMd() produces a minimal front-matter with no provenance fields.
|
|
134
|
+
await createSkill('claude-code', 'no-provenance', { version: '0.1.0' });
|
|
135
|
+
const entries = await collectDeviceSkills();
|
|
136
|
+
const entry = entries.find((e) => e.skill_id === 'no-provenance');
|
|
137
|
+
expect(entry).toBeDefined();
|
|
138
|
+
expect(entry?.author).toBeNull();
|
|
139
|
+
expect(entry?.license).toBeNull();
|
|
140
|
+
expect(entry?.repository).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
// IC-7: dot-prefixed directories (.backups and friends) are NOT enumerated.
|
|
143
|
+
it('IC-7: dot-prefixed directories are skipped and never emitted as skill entries (SMI-5440/5442)', async () => {
|
|
144
|
+
await createSkill('claude-code', 'real-skill', { version: '1.0.0' });
|
|
145
|
+
// .backups is the canonical dot-dir created by apply_recommended_edit.
|
|
146
|
+
// It contains a SKILL.md subdirectory (an EISDIR if you try to read it as a
|
|
147
|
+
// file), which previously caused it to appear as an unknown skill entry.
|
|
148
|
+
const backupsDir = join(mockPaths.CLIENT_NATIVE_PATHS['claude-code'], '.backups');
|
|
149
|
+
await mkdir(join(backupsDir, 'SKILL.md'), { recursive: true });
|
|
150
|
+
const entries = await collectDeviceSkills();
|
|
151
|
+
expect(entries.find((e) => e.skill_id === 'real-skill')).toBeDefined();
|
|
152
|
+
expect(entries.find((e) => e.skill_id === '.backups')).toBeUndefined();
|
|
153
|
+
expect(entries.some((e) => e.skill_id.startsWith('.'))).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=inventory-collector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-collector.test.js","sourceRoot":"","sources":["../../../src/sync/inventory-collector.test.ts"],"names":[],"mappings":"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"}
|