Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c67bbf0
Bump Sodium dependency from 2.2.2 to 2.2.5
MariusStorhaug Jul 23, 2026
ab921a5
Add performance benchmark script for crypto operations
MariusStorhaug Jul 23, 2026
8fcff7b
Add cross-version compatibility test for Sodium 2.2.2 to 2.2.5
MariusStorhaug Jul 23, 2026
8ca5cc6
Resolve analyzer warnings in new test scripts
MariusStorhaug Jul 25, 2026
3e9384e
Lock Pester test dependency to the 6.x major version
MariusStorhaug Jul 7, 2026
c4bf50e
Use the 6.* wildcard for the Pester major-version ceiling
MariusStorhaug Jul 7, 2026
192b50e
Drop the GUID from the Pester requirement
MariusStorhaug Jul 7, 2026
289f2ad
Adopt Process-PSModule v6.1.4
MariusStorhaug Jul 12, 2026
22b4648
Speed recursive context conversion
MariusStorhaug Jul 23, 2026
100925d
Accelerate indexed context operations
MariusStorhaug Jul 23, 2026
56cee0e
Add heavy performance and path safety tests
MariusStorhaug Jul 23, 2026
32ec01b
Align performance helpers with analyzer rules
MariusStorhaug Jul 23, 2026
43542ef
Align CI to Process-PSModule v6.1.13
MariusStorhaug Jul 23, 2026
4d06755
Add zensical.toml to replace mkdocs.yml for docs site
MariusStorhaug Jul 23, 2026
00fa360
Refresh exact-ID cache and harden vault names
MariusStorhaug Jul 25, 2026
af45059
Pin compatibility helpers to explicit module targets
MariusStorhaug Jul 25, 2026
2e9ca98
Escape child-process arguments in compatibility script
MariusStorhaug Jul 25, 2026
46b4835
Bump tests to Pester 6.0.1
MariusStorhaug Jul 25, 2026
d0f8b9a
Use named assertions in compat script
MariusStorhaug Jul 25, 2026
62d8015
Cover cache and vault regressions
MariusStorhaug Jul 25, 2026
9928c1f
Report uncovered coverage paths
MariusStorhaug Jul 26, 2026
f5c2560
Use upstream coverage gap reporting
MariusStorhaug Jul 26, 2026
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
5 changes: 3 additions & 2 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ permissions:

jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@205d193f34cbbaf9992955c21d842bcf98a1859f # v5.4.6
secrets: inherit
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@c89cae01301e45cdcdf60e37fa3b093b051f5106 # coverage missed-path reporting
secrets:
APIKey: ${{ secrets.APIKey }}
69 changes: 69 additions & 0 deletions .github/zensical.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[project]
site_name = "Context"
repo_name = "PSModule/Context"
repo_url = "https://github.com/PSModule/Context"

[project.theme]
variant = "classic"
language = "en"
logo = "Assets/icon.png"
favicon = "Assets/icon.png"
features = [
"navigation.instant",
"navigation.instant.progress",
"navigation.indexes",
"navigation.top",
"navigation.tracking",
"navigation.expand",
"search.suggest",
"search.highlight",
"content.code.copy"
]

[[project.theme.palette]]
media = "(prefers-color-scheme)"
toggle.icon = "lucide/sun-moon"
toggle.name = "Switch to dark mode"

[[project.theme.palette]]
media = "(prefers-color-scheme: dark)"
scheme = "slate"
primary = "black"
accent = "green"
toggle.icon = "lucide/moon"
toggle.name = "Switch to light mode"

[[project.theme.palette]]
media = "(prefers-color-scheme: light)"
scheme = "default"
primary = "indigo"
accent = "green"
toggle.icon = "lucide/sun"
toggle.name = "Switch to system preference"

[project.theme.icon]
repo = "fontawesome/brands/github"

[project.markdown_extensions.toc]
permalink = true

[project.markdown_extensions.attr_list]
[project.markdown_extensions.admonition]
[project.markdown_extensions.md_in_html]
[project.markdown_extensions.pymdownx.details]
[project.markdown_extensions.pymdownx.superfences]

[[project.extra.social]]
icon = "fontawesome/brands/discord"
link = "https://discord.gg/jedJWCPAhD"
name = "PSModule on Discord"

[[project.extra.social]]
icon = "fontawesome/brands/github"
link = "https://github.com/PSModule/"
name = "PSModule on GitHub"

[project.extra.consent]
title = "Cookie consent"
description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better."
actions = ["accept", "reject"]
59 changes: 59 additions & 0 deletions src/functions/private/Assert-ContextSodiumModule.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function Assert-ContextSodiumModule {
<#
.SYNOPSIS
Ensures the required Sodium module version is available for context cryptography.

.DESCRIPTION
Validates that Sodium v2.2.5 or newer is loaded in the current session.
Imports Sodium v2.2.5 if needed, and verifies required commands exist.

.EXAMPLE
Assert-ContextSodiumModule

Ensures Sodium cryptography commands are available before encryption or decryption.
#>
[CmdletBinding()]
param()

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
if ($script:ContextSodiumModuleReady) {
return
}

$minimumVersion = [version]'2.2.5'
$loadedSodium = Get-Module -Name Sodium | Sort-Object Version -Descending | Select-Object -First 1

if ($loadedSodium -and $loadedSodium.Version -lt $minimumVersion) {
$message = "Loaded Sodium version [$($loadedSodium.Version)] is older than required version [$minimumVersion]. "
$message += "Start a new PowerShell session and import Sodium $minimumVersion."
throw $message
}

if (-not $loadedSodium) {
Import-Module -Name Sodium -RequiredVersion $minimumVersion -ErrorAction Stop
}

$requiredCommands = @(
'New-SodiumKeyPair',
'ConvertTo-SodiumSealedBox',
'ConvertFrom-SodiumSealedBox'
)

foreach ($commandName in $requiredCommands) {
if (-not (Get-Command -Name $commandName -ErrorAction SilentlyContinue)) {
throw "Required Sodium command '$commandName' is not available after loading the module."
}
}

$script:ContextSodiumModuleReady = $true
}

end {
Write-Debug "[$stackPath] - End"
}
}
39 changes: 39 additions & 0 deletions src/functions/private/Clear-ContextFileIndex.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function Clear-ContextFileIndex {
<#
.SYNOPSIS
Clears in-memory vault index entries.

.DESCRIPTION
Removes one or more vault entries from the in-memory context file index cache.

.EXAMPLE
Clear-ContextFileIndex -Vault 'MyVault'

Clears the in-memory index for 'MyVault'.
#>
[CmdletBinding()]
param(
# One or more vault names to clear from cache.
[Parameter(Mandatory)]
[string[]] $Vault
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
if ($null -eq $script:ContextFileIndexCache) {
return
}

foreach ($vaultName in $Vault) {
$null = $script:ContextFileIndexCache.Remove($vaultName)
}
}

end {
Write-Debug "[$stackPath] - End"
}
}
66 changes: 66 additions & 0 deletions src/functions/private/Get-ContextFileIndex.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
function Get-ContextFileIndex {
<#
.SYNOPSIS
Gets a cached context file index for a vault.

.DESCRIPTION
Builds (or returns) an in-memory index of context IDs to metadata file paths for a vault.
This avoids repeated full vault scans for exact-ID lookups in hot paths.

.EXAMPLE
Get-ContextFileIndex -Vault 'MyVault' -VaultPath 'C:\Users\Jane\.contextvaults\MyVault'

Returns a dictionary where keys are context IDs and values are metadata file paths.

.OUTPUTS
[System.Collections.Generic.Dictionary[string,string]]
#>
[OutputType([System.Collections.Generic.Dictionary[string, string]])]
[CmdletBinding()]
param(
# The vault name.
[Parameter(Mandatory)]
[string] $Vault,

# The full path to the vault folder.
[Parameter(Mandatory)]
[string] $VaultPath,

# Rebuilds the index from disk even if cached.
[Parameter()]
[switch] $Refresh
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
if ($null -eq $script:ContextFileIndexCache) {
$script:ContextFileIndexCache = @{}
}
}

process {
if (-not $Refresh -and $script:ContextFileIndexCache.ContainsKey($Vault)) {
return $script:ContextFileIndexCache[$Vault]
}

$index = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::OrdinalIgnoreCase)
$files = Get-ChildItem -Path $VaultPath -Filter *.json -File -ErrorAction SilentlyContinue

foreach ($file in $files) {
try {
$contextInfo = Get-ContextInfoFromFile -Path $file.FullName -Vault $Vault -ErrorAction Stop
$index[$contextInfo.ID] = $contextInfo.Path
} catch {
Write-Warning "[$stackPath] - Failed to index context file '$($file.FullName)': $($_.Exception.Message)"
}
}

$script:ContextFileIndexCache[$Vault] = $index
return $index
}

end {
Write-Debug "[$stackPath] - End"
}
}
54 changes: 54 additions & 0 deletions src/functions/private/Get-ContextInfoFromFile.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
function Get-ContextInfoFromFile {
<#
.SYNOPSIS
Reads and parses a context metadata file.

.DESCRIPTION
Reads a context metadata file from disk and returns a ContextInfo object.
The returned Path always points to the actual file on disk, not the metadata payload.

.EXAMPLE
Get-ContextInfoFromFile -Path 'C:\Users\Jane\.contextvaults\MyVault\abc.json' -Vault 'MyVault'

Parses the context metadata file and returns a ContextInfo instance.

.OUTPUTS
[ContextInfo]
#>
[OutputType([ContextInfo])]
[CmdletBinding()]
param(
# Full path to the context metadata file.
[Parameter(Mandatory)]
[string] $Path,

# The vault name the file belongs to.
[Parameter(Mandatory)]
[string] $Vault
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Begin"
}

process {
$content = Get-ContentNonLocking -Path $Path
$rawContextInfo = $content | ConvertFrom-Json -ErrorAction Stop

if ([string]::IsNullOrWhiteSpace($rawContextInfo.ID)) {
throw "Context metadata file '$Path' does not contain a valid ID."
}

[ContextInfo]::new([pscustomobject]@{
ID = [string]$rawContextInfo.ID
Path = $Path
Vault = $Vault
Context = [string]$rawContextInfo.Context
})
}

end {
Write-Debug "[$stackPath] - End"
}
}
17 changes: 16 additions & 1 deletion src/functions/private/Get-ContextVaultKeyPair.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Start"
Assert-ContextSodiumModule
if ($null -eq $script:ContextVaultKeyPairCache) {
$script:ContextVaultKeyPairCache = @{}
}
}

process {
Expand All @@ -42,12 +46,23 @@
throw "[$stackPath] - Unable to read shard file '$shardPath': $($_.Exception.Message)"
}

if ($script:ContextVaultKeyPairCache.ContainsKey($vaultObject.Name)) {
$cachedEntry = $script:ContextVaultKeyPairCache[$vaultObject.Name]
if ($cachedEntry.Shard -eq $fileShard) {
return $cachedEntry.Keys
}
}

$machineShard = [System.Environment]::MachineName
$userShard = [System.Environment]::UserName
#$userInputShard = Read-Host -Prompt 'Enter a seed shard' # Eventually 4 shards. +1 for user input.
$seed = $machineShard + $userShard + $fileShard # + $userInputShard
$keys = New-SodiumKeyPair -Seed $seed
$keys
$script:ContextVaultKeyPairCache[$vaultObject.Name] = [pscustomobject]@{
Shard = $fileShard
Keys = $keys
}
return $keys
}

end {
Expand Down
Loading
Loading