Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 8 additions & 6 deletions src/Core.FSharp.ZetaId/Codec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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].
[<Literal>]
let private MaxTimestamp = 281474976710655L // (1L <<< 48) - 1L
/// Typed with the `ms` measure to compose with ZetaObservation.Timestamp
/// (per Mika V9.3). `[<Literal>]` removed — measure-typed values can't be
/// F# literals; this is a regular let-binding (computed once per module init).
let private MaxTimestamp : int64<ms> = 281474976710655L<ms> // (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
Expand All @@ -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<ms> || 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"
Expand Down Expand Up @@ -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))
Expand All @@ -103,7 +105,7 @@ module ZetaIdCodec =
let toByte field = byte (getBits id field)
{
Version = LanguagePrimitives.EnumOfValue<byte, IdVersion> (toByte layout.Version)
Timestamp = int64 (getBits id layout.Timestamp)
Timestamp = LanguagePrimitives.Int64WithMeasure<ms> (int64 (getBits id layout.Timestamp))
Chromosome = LanguagePrimitives.EnumOfValue<byte, Chromosome> (toByte layout.Chromosome)
Category = LanguagePrimitives.EnumOfValue<byte, Category> (toByte layout.Category)
Firefly = LanguagePrimitives.EnumOfValue<byte, Firefly> (toByte layout.Firefly)
Expand Down
8 changes: 7 additions & 1 deletion src/Core.FSharp.ZetaId/Types.fs
Original file line number Diff line number Diff line change
@@ -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).
[<Measure>]
type ms

/// Version field — 5 bits. Currently V1 only.
/// Mirrors `src/Core.CSharp.ZetaId/IdVersion.cs`.
type IdVersion =
Expand Down Expand Up @@ -167,7 +173,7 @@ module Momentum =
/// Observation record. All fields explicit; no default-init shortcuts.
type ZetaObservation = {
Version: IdVersion
Timestamp: int64
Timestamp: int64<ms>
Chromosome: Chromosome
Category: Category
Firefly: Firefly
Expand Down
2 changes: 1 addition & 1 deletion tests/Tests.FSharp/ZetaId/CrossVerifyTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ let private toMomentum (v: FlatVector) : Momentum =
let private toObservation (v: FlatVector) : ZetaObservation =
{
Version = LanguagePrimitives.EnumOfValue<byte, IdVersion> (checkByte v.Version "Version")
Timestamp = v.Timestamp
Timestamp = LanguagePrimitives.Int64WithMeasure<ms> v.Timestamp
Chromosome = LanguagePrimitives.EnumOfValue<byte, Chromosome> (checkByte v.Chromosome "Chromosome")
Category = LanguagePrimitives.EnumOfValue<byte, Category> (checkByte v.Category "Category")
Firefly = LanguagePrimitives.EnumOfValue<byte, Firefly> (checkByte v.Firefly "Firefly")
Expand Down
Loading