217 lines
11 KiB
PowerShell
217 lines
11 KiB
PowerShell
# Fixture construction only. This does not parse/evaluate the proposed language.
|
|
$ErrorActionPreference = 'Stop'
|
|
$taskDir = $PSScriptRoot
|
|
$ids = @{
|
|
project = '4210b9f2-7046-47f0-aa3b-153f626a579d'
|
|
duration = '188b911c-5f8b-431c-9eea-2d41f3953767'
|
|
second = '534f4672-1ba5-4157-8b0a-b9cf41d0a3ce'
|
|
millisecond = '39053f37-fec9-4708-9f0a-3959d223f891'
|
|
combine = 'fd7ce7ab-f789-40bc-b699-0c37f1b6902f'
|
|
input_a = '28ff40b0-1b8f-4735-8536-5a6ab21b56cb'
|
|
input_b = '7a47cc1e-8ae5-4551-b2d8-8a68108e0976'
|
|
respond = 'cabec6a1-7d5d-48f3-8d2d-a7fc8cf78f59'
|
|
receive = '4ab9a8a7-7552-488d-9862-e14b89e7b353'
|
|
dispatch = '6df991ba-4c5e-4535-99e2-524ae274ee8b'
|
|
receive_time = '63a931c2-675a-4fce-89e5-04361cb99450'
|
|
dispatch_time = 'c33fc772-2a60-4044-9af9-73c61c563ace'
|
|
total_time = 'bd5b68e8-65fc-4e35-9071-a73061316a4b'
|
|
limit = '437bedbb-2cf4-44ee-a139-6563cc5086ec'
|
|
degraded = '38036c65-16d4-449c-837b-91eb141c1d93'
|
|
unmeasured = '99d3e09a-0ec3-4194-9dd4-9f3de6625cde'
|
|
budget = 'bac0de99-9d2f-4e53-9158-d424ecac61bf'
|
|
}
|
|
function Exact([string]$n, [string]$d = '1') {
|
|
return @{ numerator = $n; denominator = $d }
|
|
}
|
|
function Local([string]$name) { return @{ Local = $ids[$name] } }
|
|
function ReadProperty([string]$name) { return @{ Property = $ids[$name] } }
|
|
function Literal([string]$n, [string]$unit) {
|
|
return @{ Quantity = @{ magnitude = (Exact $n); unit = (Local $unit) } }
|
|
}
|
|
function Bound($expr) { return @{ Expression = $expr } }
|
|
function Record([string]$key, [string]$name, $body) {
|
|
return @{
|
|
id = $ids[$key]; alias = $key; name = $name; description = ''; body = $body
|
|
}
|
|
}
|
|
function Element([string]$key, [string]$name, $body, $parent = $null) {
|
|
$r = Record $key $name $body
|
|
$r.parent = $parent
|
|
return $r
|
|
}
|
|
function Property([string]$key, [string]$name, [string]$subject, [string]$unit, $binding) {
|
|
return Element $key $name @{ Oa = @{ Shared = @{ EngineeringProperty = @{
|
|
subject = @{ Element = $ids[$subject] }
|
|
value_type = $quantityType
|
|
display_unit = (Local $unit)
|
|
base = $binding
|
|
} } } }
|
|
}
|
|
function OrderedCopy($value) {
|
|
if ($null -eq $value) { return $null }
|
|
if ($value -is [Collections.IDictionary]) {
|
|
$copy = [ordered]@{}
|
|
[string[]]$keys = @($value.Keys)
|
|
[Array]::Sort($keys, [StringComparer]::Ordinal)
|
|
foreach ($key in $keys) { $copy[$key] = OrderedCopy $value[$key] }
|
|
return $copy
|
|
}
|
|
if ($value -is [Collections.IEnumerable] -and $value -isnot [string]) {
|
|
$copy = @()
|
|
foreach ($item in $value) { $copy += ,(OrderedCopy $item) }
|
|
return ,$copy
|
|
}
|
|
return $value
|
|
}
|
|
function JsonBytes($value) {
|
|
$json = (OrderedCopy $value) | ConvertTo-Json -Depth 100
|
|
$json = $json.Replace([string][char]13 + [string][char]10, [string][char]10)
|
|
return [Text.UTF8Encoding]::new($false).GetBytes($json + [string][char]10)
|
|
}
|
|
function SaveBytes([string]$name, [byte[]]$bytes) {
|
|
[IO.File]::WriteAllBytes((Join-Path $taskDir $name), $bytes)
|
|
}
|
|
$quantityType = @{ Scalar = @{ Quantity = (Local 'duration') } }
|
|
$definitions = @{}
|
|
$definitions[$ids.duration] = Record 'duration' 'Duration' @{ QuantityKind = @{
|
|
dimension = @(0, 0, 1, 0, 0, 0, 0); algebra = 'Linear'
|
|
} }
|
|
$definitions[$ids.second] = Record 'second' 'Second' @{ Unit = @{
|
|
kind = (Local 'duration'); scale = (Exact '1'); offset = (Exact '0'); symbol = 's'
|
|
} }
|
|
$definitions[$ids.millisecond] = Record 'millisecond' 'Millisecond' @{ Unit = @{
|
|
kind = (Local 'duration'); scale = (Exact '1' '1000'); offset = (Exact '0'); symbol = 'ms'
|
|
} }
|
|
$definitions[$ids.combine] = Record 'combine' 'Combine durations' @{ Calculation = @{
|
|
parameters = @(
|
|
@{ id = $ids.input_a; name = 'a'; value_type = $quantityType },
|
|
@{ id = $ids.input_b; name = 'b'; value_type = $quantityType }
|
|
)
|
|
result = $quantityType
|
|
body = (Bound @{ Binary = @{
|
|
op = 'Add'; left = @{ Parameter = $ids.input_a }; right = @{ Parameter = $ids.input_b }
|
|
} })
|
|
} }
|
|
$receiveBase = Bound (Literal '750' 'millisecond')
|
|
$dispatchBase = Bound (Literal '1' 'second')
|
|
$totalBase = Bound @{ Call = @{
|
|
definition = (Local 'combine')
|
|
arguments = @{
|
|
$ids.input_a = (ReadProperty 'receive_time')
|
|
$ids.input_b = (ReadProperty 'dispatch_time')
|
|
}
|
|
} }
|
|
$limitBase = Bound (Literal '2' 'second')
|
|
$degradedBinding = Bound (Literal '1250' 'millisecond')
|
|
$predicate = Bound @{ Binary = @{
|
|
op = 'LessOrEqual'; left = (ReadProperty 'total_time'); right = (ReadProperty 'limit')
|
|
} }
|
|
$elements = @{}
|
|
$elements[$ids.respond] = Element 'respond' 'Respond' @{ Oa = 'Activity' }
|
|
$elements[$ids.receive] = Element 'receive' 'Receive' @{ Oa = 'Activity' } $ids.respond
|
|
$elements[$ids.dispatch] = Element 'dispatch' 'Dispatch' @{ Oa = 'Activity' } $ids.respond
|
|
$elements[$ids.receive_time] = Property 'receive_time' 'Receive time' 'receive' 'millisecond' $receiveBase
|
|
$elements[$ids.dispatch_time] = Property 'dispatch_time' 'Dispatch time' 'dispatch' 'second' $dispatchBase
|
|
$elements[$ids.total_time] = Property 'total_time' 'Total time' 'respond' 'second' $totalBase
|
|
$elements[$ids.limit] = Property 'limit' 'Allowed duration' 'respond' 'second' $limitBase
|
|
$elements[$ids.budget] = Element 'budget' 'Response budget' @{ Oa = @{ Shared = @{ AnalysisCheck = @{
|
|
subject = $null; documented_constraint = $null; predicate = $predicate
|
|
} } } }
|
|
$elements[$ids.degraded] = Element 'degraded' 'Degraded radio' @{ Oa = @{ Shared = @{ AnalysisCase = @{
|
|
subject = $null; base = 'ModelBaseline'; overrides = @{ $ids.receive_time = $degradedBinding }
|
|
} } } }
|
|
$elements[$ids.unmeasured] = Element 'unmeasured' 'Unmeasured radio' @{ Oa = @{ Shared = @{ AnalysisCase = @{
|
|
subject = $null; base = 'ModelBaseline'; overrides = @{ $ids.receive_time = 'Unbound' }
|
|
} } } }
|
|
$sourceBytes = [IO.File]::ReadAllBytes((Join-Path $taskDir 'active.arcadia.proposed'))
|
|
$source = [Text.UTF8Encoding]::new($false, $true).GetString($sourceBytes)
|
|
$project = @{
|
|
schema_version = 8; id = $ids.project; alias = 'response_budget'; name = 'Response budget'
|
|
description = 'A complete proposed analysis model. Schema 8 and the new declarations are not implemented source7 syntax.'
|
|
elements = $elements; relations = @{}; diagrams = @{}; artifact_links = @{}; traces = @{}
|
|
definitions = $definitions; imports = @{}; definition_mounts = @{}; library_instances = @{}
|
|
}
|
|
$document = @{
|
|
model = $project
|
|
source = @{ entry = 'active.arcadia'; files = @{ 'active.arcadia' = $source } }
|
|
packages = @{}
|
|
}
|
|
[byte[]]$documentBytes = JsonBytes $document
|
|
SaveBytes 'portable-document.json.proposed' $documentBytes
|
|
$documentDigest = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($documentBytes)).ToLowerInvariant()
|
|
$manifest = @(
|
|
@{ property = $ids.receive_time; binding = $degradedBinding; origin = @{ CaseOverride = $ids.degraded } },
|
|
@{ property = $ids.dispatch_time; binding = $dispatchBase; origin = 'ModelBaseline' },
|
|
@{ property = $ids.total_time; binding = $totalBase; origin = 'ModelBaseline' },
|
|
@{ property = $ids.limit; binding = $limitBase; origin = 'ModelBaseline' }
|
|
) | Sort-Object { $_.property }
|
|
$freeze = @{
|
|
format = 'arcadia-analysis-freeze-1-proposal'
|
|
captured_document = @{
|
|
media_type = 'application/vnd.arcadia.portable-document+json'
|
|
sha256 = $documentDigest
|
|
bytes_base64 = [Convert]::ToBase64String($documentBytes)
|
|
}
|
|
origin = @{ Local = @{
|
|
document_instance = 'd911a3c0-8adb-4203-850c-b16e9b4e4d0a'; accepted_revision = 1
|
|
} }
|
|
model_id = $ids.project
|
|
context = @{ Case = $ids.degraded }
|
|
targets = @{ properties = @($ids.total_time); checks = @($ids.budget) }
|
|
numeric_profile = 'Q1-exact-1'
|
|
effective_bindings = @($manifest)
|
|
check_predicates = @(@{ check = $ids.budget; predicate = $predicate })
|
|
definition_capture = @{
|
|
document_sha256 = $documentDigest
|
|
local_definitions = @($ids.duration, $ids.second, $ids.millisecond, $ids.combine | Sort-Object)
|
|
imports = @()
|
|
}
|
|
}
|
|
SaveBytes 'freeze-degraded.json.proposed' (JsonBytes $freeze)
|
|
$expected = @{
|
|
status = 'Hand-derived expectations, not evaluator output'
|
|
baseline = @{ total_canonical_seconds = (Exact '7' '4'); check = 'Satisfied' }
|
|
degraded = @{ total_canonical_seconds = (Exact '9' '4'); check = 'Violated' }
|
|
unmeasured = @{ total = 'Missing'; input = $ids.receive_time; check = 'Unresolved' }
|
|
after_clear_unmeasured_override = @{ total_canonical_seconds = (Exact '7' '4'); check = 'Satisfied' }
|
|
after_baseline_dispatch_changes_to_two_seconds = @{
|
|
active_degraded_total = (Exact '13' '4'); frozen_degraded_total = (Exact '9' '4')
|
|
}
|
|
}
|
|
SaveBytes 'expected-observations.json' (JsonBytes $expected)
|
|
$largeInteger = [Numerics.BigInteger]::Pow([Numerics.BigInteger]10, 300).ToString()
|
|
$smallDenominator = [Numerics.BigInteger]::Pow([Numerics.BigInteger]2, 4095).ToString()
|
|
$boundInteger = ([Numerics.BigInteger]::Pow([Numerics.BigInteger]2, 4096) - 1).ToString()
|
|
$literalFixtures = @(
|
|
@{ name = 'ten_to_300'; expression = @{ Number = (Exact $largeInteger) }; canonical_source = $largeInteger },
|
|
@{ name = 'one_over_two_to_4095'; expression = @{ Number = (Exact '1' $smallDenominator) }; canonical_source = ('ratio(1, ' + $smallDenominator + ')') },
|
|
@{ name = 'negative_4096_bit_bound'; expression = @{ Number = (Exact ('-' + $boundInteger)) }; canonical_source = ('ratio(-' + $boundInteger + ', 1)') }
|
|
)
|
|
SaveBytes 'r7-literal-boundaries.json' (JsonBytes $literalFixtures)
|
|
# Verify only JSON transport, UUID completeness, raw source retention and hashes.
|
|
$parsed = Get-Content -LiteralPath (Join-Path $taskDir 'freeze-degraded.json.proposed') -Raw | ConvertFrom-Json
|
|
$decoded = [Convert]::FromBase64String($parsed.captured_document.bytes_base64)
|
|
if ([Convert]::ToBase64String($decoded) -cne [Convert]::ToBase64String($documentBytes)) { throw 'Document capsule differs' }
|
|
$parsedDoc = [Text.Encoding]::UTF8.GetString($decoded) | ConvertFrom-Json
|
|
if ($parsedDoc.source.files.'active.arcadia' -cne $source) { throw 'Source changed' }
|
|
if ($ids.Count -ne (@($ids.Values | Sort-Object -Unique)).Count) { throw 'Duplicate fixture UUID' }
|
|
foreach ($value in $ids.Values) {
|
|
[void][guid]::ParseExact($value, 'D')
|
|
if (-not $source.Contains('id "' + $value + '"')) { throw "Missing source identity $value" }
|
|
}
|
|
$stats = @{
|
|
source_lines = ($source -split [string][char]10).Count - 1
|
|
source_utf8_bytes = $sourceBytes.Length
|
|
explicit_source_uuids = $ids.Count
|
|
project_elements = $elements.Count
|
|
local_definitions = $definitions.Count
|
|
scoped_parameters = 2
|
|
base_bindings = 4
|
|
override_bindings = 2
|
|
shared_checks = 1
|
|
portable_document_sha256 = $documentDigest
|
|
verified = 'JSON transport, 17 syntactically valid distinct UUIDs, exact source bytes, capsule equality. No proposed parser/evaluator execution.'
|
|
}
|
|
SaveBytes 'fixture-integrity.json' (JsonBytes $stats)
|
|
$stats | ConvertTo-Json
|