diff --git a/src/Core.FSharp.ZetaId/Codec.fs b/src/Core.FSharp.ZetaId/Codec.fs index 1b474ba84c..133b7342f7 100644 --- a/src/Core.FSharp.ZetaId/Codec.fs +++ b/src/Core.FSharp.ZetaId/Codec.fs @@ -10,8 +10,10 @@ module ZetaIdCodec = let private layout = BitLayout.Default /// Timestamp is packed into a 48-bit field; valid range [0, 2^48 - 1]. - [] - let private MaxTimestamp = 281474976710655L // (1L <<< 48) - 1L + /// Typed with the `ms` measure to compose with ZetaObservation.Timestamp + /// (per Mika V9.3). `[]` removed — measure-typed values can't be + /// F# literals; this is a regular let-binding (computed once per module init). + let private MaxTimestamp : int64 = 281474976710655L // (1L <<< 48) - 1L let private setBits (id: System.UInt128) (field: BitField) (fieldValue: uint64) : System.UInt128 = let mask = (System.UInt128.One <<< field.Width) - System.UInt128.One @@ -37,10 +39,10 @@ module ZetaIdCodec = if isNull (box env) then raise (System.ArgumentNullException("env")) - if obs.Timestamp < 0L || obs.Timestamp > MaxTimestamp then + if obs.Timestamp < 0L || obs.Timestamp > MaxTimestamp then raise (System.ArgumentOutOfRangeException( "obs", obs.Timestamp :> obj, - sprintf "ZetaObservation.Timestamp must be 0..%d (48-bit field). Values outside this range would silently truncate and collide." MaxTimestamp)) + sprintf "ZetaObservation.Timestamp must be 0..%d ms (48-bit field). Values outside this range would silently truncate and collide." (int64 MaxTimestamp))) validateEnumField (byte obs.Version) 5 "Version" validateEnumField (byte obs.Chromosome) 5 "Chromosome" @@ -82,7 +84,7 @@ module ZetaIdCodec = let mutable id = System.UInt128.Zero id <- setBits id layout.Version (uint64 (byte obs.Version)) - id <- setBits id layout.Timestamp (uint64 obs.Timestamp) + id <- setBits id layout.Timestamp (uint64 (int64 obs.Timestamp)) id <- setBits id layout.Chromosome (uint64 (byte obs.Chromosome)) id <- setBits id layout.Category (uint64 (byte obs.Category)) id <- setBits id layout.Firefly (uint64 (byte obs.Firefly)) @@ -103,7 +105,7 @@ module ZetaIdCodec = let toByte field = byte (getBits id field) { Version = LanguagePrimitives.EnumOfValue (toByte layout.Version) - Timestamp = int64 (getBits id layout.Timestamp) + Timestamp = LanguagePrimitives.Int64WithMeasure (int64 (getBits id layout.Timestamp)) Chromosome = LanguagePrimitives.EnumOfValue (toByte layout.Chromosome) Category = LanguagePrimitives.EnumOfValue (toByte layout.Category) Firefly = LanguagePrimitives.EnumOfValue (toByte layout.Firefly) diff --git a/src/Core.FSharp.ZetaId/Types.fs b/src/Core.FSharp.ZetaId/Types.fs index 0f6dd8c2f4..d6261f3596 100644 --- a/src/Core.FSharp.ZetaId/Types.fs +++ b/src/Core.FSharp.ZetaId/Types.fs @@ -1,5 +1,11 @@ namespace Zeta.Core.FSharp.ZetaId +/// Milliseconds units of measure. Duplicated locally (not imported from +/// `src/Core/Units.fs`) to preserve the zero-external-dependencies discipline +/// of the production library. Per Mika V9.3 substrate (2026-05-21). +[] +type ms + /// Version field — 5 bits. Currently V1 only. /// Mirrors `src/Core.CSharp.ZetaId/IdVersion.cs`. type IdVersion = @@ -167,7 +173,7 @@ module Momentum = /// Observation record. All fields explicit; no default-init shortcuts. type ZetaObservation = { Version: IdVersion - Timestamp: int64 + Timestamp: int64 Chromosome: Chromosome Category: Category Firefly: Firefly diff --git a/tests/Tests.FSharp/ZetaId/CrossVerifyTests.fs b/tests/Tests.FSharp/ZetaId/CrossVerifyTests.fs index e2ccb547e1..0b2c501891 100644 --- a/tests/Tests.FSharp/ZetaId/CrossVerifyTests.fs +++ b/tests/Tests.FSharp/ZetaId/CrossVerifyTests.fs @@ -70,7 +70,7 @@ let private toMomentum (v: FlatVector) : Momentum = let private toObservation (v: FlatVector) : ZetaObservation = { Version = LanguagePrimitives.EnumOfValue (checkByte v.Version "Version") - Timestamp = v.Timestamp + Timestamp = LanguagePrimitives.Int64WithMeasure v.Timestamp Chromosome = LanguagePrimitives.EnumOfValue (checkByte v.Chromosome "Chromosome") Category = LanguagePrimitives.EnumOfValue (checkByte v.Category "Category") Firefly = LanguagePrimitives.EnumOfValue (checkByte v.Firefly "Firefly")