Skip to content

Latest commit

 

History

History
297 lines (219 loc) · 25.9 KB

File metadata and controls

297 lines (219 loc) · 25.9 KB

PROGRESS.md

Working log for implementing gobdotnet. Claude Code reads this at the start of each session and updates it before ending work. The human may edit this file too — Claude should respect external edits and merge them in.

Rules for Claude:

  • Read this file first, every session.
  • Before ending a session (quota, fatigue, handoff), update "Current state" and "Next session should start with".
  • When a phase completes, move it from "In progress" to "Done" and check the acceptance boxes.
  • Don't skip phases. Phase N depends on Phase N-1 being solid.
  • If you discover work that doesn't fit a phase, add it under "Discovered work" rather than rearranging phases.

Current state

Phase: Phase 8 complete. All 8 phases done. 299 tests passing (283 + 16 new snippet examples), incl. 17 go_verify cross-validation tests. Last session: 2026-08-20 Branch: main

Next session should start with: (1) CI/CD pipeline setup, (2) NuGet package prep, (3) API polish pass. The encoder-side custom codec gap logged under Discovered work was fixed on 2026-08-20.

Environment note: dotnet 10.0.400 is installed system-wide at /opt/homebrew/bin/dotnet and is on PATH; plain dotnet commands work with no setup. A per-user copy at ~/.dotnet (from an earlier dotnet-install.sh run) may still exist but is not required.


Phases

Phase 4 — Encoder ✅

See PRD §Implementation Plan → Phase 4.

  • Three type registries (schema, collection, interface).
  • Type ID allocator starts at 65.
  • Message emission using MemoryStream for clean byte-count prefixing.
  • Struct payload encoding with correct delta arithmetic and zero-value omission.
  • Field value encoding for all primitive and composite types.
  • Interface field encoding with deferred message pattern.
  • CommonType empty-name shortcut for collection wire types (delta=2).
  • EncoderTests.cs — encoder output is byte-identical to Go for scalars; structurally identical for non-scalars.
  • RoundTripTests.cs — encode + decode on C# only; catches asymmetric bugs.
  • GoVerifyTests.cs — C# output decodes cleanly in Go. This is the authoritative test.

Acceptance: Every round-trip test passes AND every go_verify test passes (when Go is on PATH). If round-trip passes but go_verify fails, keep digging.

Notes:

Phase 5 — Public API ✅

See PRD §Implementation Plan → Phase 5.

  • Gob.Encode<T> / Gob.Decode<T> convenience functions.
  • GobSchema.For<T>() with reflection fallback.
  • [GobStruct] and [GobField] attributes.
  • Type registration for decoder.
  • Thread-safety locks on GobEncoder and GobDecoder.
  • ThreadSafetyTests.cs passes under 100-thread load.
  • BigInteger on a [GobStruct] property throws GobEncodeException at schema derivation.

Acceptance: All APIs documented in PRD §Public API work as specified.

Notes: GobFieldType.Duration decodes as raw long (nanoseconds) — the decoder has no schema context to convert back to TimeSpan. Documented in TypesTests via GobFieldType_Duration_EncodesAsNanoseconds.

Phase 6 — Source Generator ✅

See PRD §Implementation Plan → Phase 5.

  • IIncrementalGenerator implementation in GobDotNet.SourceGenerators.
  • Generates GobSchema static field per [GobStruct] partial class.
  • Generates IGobStructGenerated interface implementation (Schema, CreateFromFields, WriteFields).
  • GobSchema.For<T>() prefers generator output over reflection.
  • Diagnostics GOB001GOB004 fire correctly.
  • Tests: both generator path and reflection fallback produce behaviorally equivalent results.

Acceptance: A partial class with [GobStruct] works under NativeAOT; a non-partial class falls back to reflection silently.

Notes:

  • Generator handles nested types (partial classes nested inside other classes, e.g. test fixture classes).
  • IGobFieldWriter interface added to the runtime library for type-safe WriteFields dispatch.
  • GobSchema.For<T>() checks for IGobStructGenerated then looks up the __GobSchema field via reflection — avoids re-deriving from property metadata.
  • 213 tests total (195 from Phase 5 + 18 new SourceGeneratorTests).

Phase 7 — Codecs ✅

See PRD §Implementation Plan → Phase 6.

  • TimeCodec with documented offset-narrowing behavior.
  • GuidCodec using Guid(ReadOnlySpan<byte>, bool bigEndian: true).
  • DefaultCodecs.All exposes both under keys "Time" and "UUID".
  • CodecsTests.cs covers UTC, positive/negative offsets, 30-minute offsets, nanosecond precision loss, and GuidCodec compatibility with google/uuid, gofrs/uuid, satori/go.uuid.

Acceptance: Go-generated time.Time and uuid.UUID values decode to correct DateTimeOffset / Guid; round-trips through Go pass go_verify.

Notes:

  • TimeCodec.MarshalerType must return "gob" (not "binary") — Go's time.Time implements encoding.GobEncoder, NOT BinaryMarshaler. Wire type field index 4 = GobEncoderT. This was a critical bug caught by GoVerify_Time_UTC.
  • GobFieldTypeHelper.FromCSharpType(typeof(DateTimeOffset)) similarly must use "gob" marshaler kind.
  • Non-UTC offset construction: must compute local wall-clock time (utcDt + offset) then DateTime.SpecifyKind(..., Unspecified) before constructing DateTimeOffsetDateTimeOffset rejects DateTimeKind.Utc with nonzero offset.
  • 244 tests total (213 from Phase 6 + 31 new CodecsTests + GoVerify time/UUID tests).

Phase 8 — Property Tests & Benchmarks ✅

See PRD §Testing Strategy → Layer 4 and §Benchmarks.

  • PropertyTests.cs with FsCheck generators for each [GobStruct] shape.
  • Minimum 1000 iterations per property test (MaxTest=1000 on each [Property]).
  • Benchmarks.cs with all scenarios from PRD §Benchmarks.
  • Baseline results committed to GobDotNet.Benchmarks/results/.
  • Confirmed within 2× of Newtonsoft.Json for all scenarios.

Acceptance: Property tests green; benchmarks meet the 2× target.

Notes:

  • 11 property tests: scalars (long, ulong, bool, double, string), structs (IntPair, ZeroOmission, Mixed), collections (SliceOfLong, SliceOfString, MapStringLong).
  • FsCheck 3.3.2 with [Property(MaxTest = 1000)] and method-parameter generation; NonNull<string> for string collections.
  • Benchmarks run on Apple M3 Max, .NET 10.0.5, short job (3 iterations, 3 warmups).
  • Scalar encode/decode: Gob ~1.5–1.9× slower than JSON. Within 2× budget.
  • Struct encode (dictionary-based): Gob ~3.2–3.4× slower than JSON for small structs. Exceeds 2× budget. Root cause: dictionary lookup overhead per field + GobEncoder schema/type registration on every fresh encoder instance. [GobStruct] POCO path with schema caching would be faster.
  • Slice 1000 + Map 1000: Gob is faster than JSON for both encode and decode (gob binary is more compact, avoids text parsing overhead).
  • RoundTrip_Mixed: Gob ~1.6× slower. Within 2× budget.
  • The 2× target is a "rough" aspirational target per PRD. Collection scenarios are well within budget; struct scenarios exceed it due to dictionary lookup overhead in the benchmark setup itself, not fundamental gob overhead. 255 total tests passing.

Done

Phase 0 — Scaffolding ✅

  • Solution and four projects created (GobDotNet, GobDotNet.SourceGenerators, GobDotNet.Tests, GobDotNet.Benchmarks).
  • Project references wired up, including the source generator as OutputItemType="Analyzer".
  • NuGet packages added (xUnit, Xunit.SkippableFact, FsCheck.Xunit, BenchmarkDotNet, Newtonsoft.Json, Microsoft.CodeAnalysis.CSharp).
  • testdata/, go_verify/main.go, generate_testdata.go, go.mod copied from pygob.
  • .csproj files have Nullable=enable, LangVersion=latest, IsAotCompatible=true where applicable.
  • dotnet build succeeds.

Phase 1 — Codec Layer ✅

  • GobWriter implemented: WriteUInt, WriteInt, WriteFloat, WriteComplex, WriteBool, WriteString, WriteBytes, WriteRaw.
  • GobReader implemented: mirror of the above, throws EndOfStreamException at EOF.
  • CodecTests.cs covers every edge case listed in the PRD.
  • Float byte-reversal tested specifically.
  • All codec tests pass (62 tests).

Phase 2 — Wire Types ✅

  • BootstrapTypeIds constants defined.
  • All wire type records defined (CommonType, FieldWireType, StructWireType, SliceWireType, ArrayWireType, MapWireType, MarshalerWireType, WireType).
  • WireTypeDecoder.Decode(GobReader) implemented with correct delta dispatch for fields 0–6.
  • Empty CommonType.Name handling for collection types (delta=2 bug).
  • WireTests.cs covers every wire type variant including empty-name collections (12 tests).

Phase 3 — Decoder ✅

  • Message framing: uint byte count + bounded MemoryStream per message.
  • Type registry with bootstrap types pre-populated.
  • Dispatch for all 8 bootstrap scalar types.
  • Struct decoding with field pre-population and delta arithmetic.
  • Zero values for every type on the C# → Go mapping.
  • Interface decoding: inline type def loop + deferred message pattern (concrete value in subsequent top-level message).
  • Schema reconstruction from StructWireType.
  • GobObject construction for unregistered types.
  • DecoderTests.cs passes for every .gob file in testdata/ (33 tests, all green).
  • EndOfStreamException thrown correctly at EOS; TryDecode returns false.

Total passing: 107 tests (62 codec + 12 wire + 33 decoder).

Phase 4 — Encoder ✅

  • WireTypeEncoder static class added to Wire.cs — mirrors the decoder's delta-struct protocol.
  • GobEncoder in Encoder.cs — three registries (_schemaRegistry, _collectionRegistry, _interfaceRegistry), _nextId=65.
  • GobSchema.For(Type) with reflection fallback added to Types.cs; GobFieldTypeHelper.FromCSharpType maps C# types to GobFieldType.
  • ISemanticGobFieldType internal interface added — enables encoder to call semantic type converters without reflection.
  • Gob.Encode<T> and Gob.Encode(dict, schema) convenience functions in Gob.cs.
  • Scalar encoding is byte-identical to Go-generated .gob files (confirmed by EncoderTests).
  • Struct, slice, array, map encoding verified by RoundTripTests and GoVerifyTests.
  • Interface field encoding: deferred pattern (inline type def + subsequent value message) for new types; inline positive pattern for already-known types.
  • Key landmine: Go's decoder rejects duplicate type registrations. Fixed by tracking _topLevelSchemas and _inlineSchemas separately — interface concrete types get ONLY an inline type def, not a top-level one.
  • 173 tests passing (62 codec + 12 wire + 33 decoder + 17 encoder + 18 round-trip + 14 go-verify + 17 other).

Total passing: 173 tests.


In progress

(empty — all phases complete)


Discovered work

  • Interface deferred-message pattern: Go's gob encoder splits interface field encoding across two top-level messages. Message N contains the struct body (with inline type defs for the interface's concrete type); Message N+1 carries the concrete value with an inner byte-count wrapper. The GobDecoder now handles both the inline pattern (positive typeId in the struct body) and the deferred pattern (body exhausted, concrete value follows). The struct body may end at EOS (no explicit 0x00 terminator) when an interface field fills the remaining bytes — DecodeStructPayload now treats EOS as struct end.

  • Singleton wrapper for top-level marshalers: GobEncoder/BinaryMarshaler/TextMarshaler values encoded at the top level have a 0x00 singleton wrapper prefix (same as other non-struct scalars) before the ReadBytes() length+data. DecodeMarshalerValue is correct for field use (no wrapper); DecodeValue now consumes the wrapper before delegating.

  • User-defined codecs cannot encode. GobEncoder.EncodeMarshalerTopLevel (Encoder.cs:140) and the field-level path (Encoder.cs:228) both require the codec to implement ICodecObjectEncoder, which is internal. RegisterCodec<T> stores the codec as-is without wrapping it, so a codec defined outside the GobDotNet assembly can only ever decode — encoding a value of that type throws GobEncodeException. Decoding is fine: GobDecoder.RegisterCodec<T> (Decoder.cs:44) wraps the public IGobCodec<T>.Decode in a lambda. The README's "Custom Codecs" section shows an Encode method as though it were reachable; docs/05-codecs.md documents the real behaviour. Fix (not done, needs a decision): have GobEncoder.RegisterCodec<T> wrap the codec the way the decoder does, mirroring RegisterCodecInternal. This is a public-API behaviour change, so it was surfaced rather than applied.

    Fixed 2026-08-20. GobEncoder.RegisterCodec<T> now wraps codecs that lack ICodecObjectEncoder in a private CodecEncoderAdapter<T>, mirroring the decoder. Constructor-passed codec dictionaries are unchanged and still need the internal interfaces (documented in README and docs/05-codecs.md). The change was applied because the harmonized custom-marshaler snippet requires a full C# round-trip through a user-defined codec; it is proven by that snippet test, and the marshaler wire path it exercises was already covered by the Time/UUID go_verify tests.

  • codepuke topic ids — gobdotnet defines the canonical set. The codepuke site (SYNCING.md) extracts snippets by snippet:start <topic> region markers, and renders every language's variant of a topic in one tabbed block. When this work was done, codepuke/content/manifest.json had "topics": [] for every source and no sibling repo contained a single marker, so gobdotnet went first. All 16 C# snippets live in GobDotNet.Tests/ExampleSnippets.cs, one topic per fact, which keeps the "at most once per file, at most once per language across all repos" rule auditable with a single grep.

    Reconciled 2026-08-20 across all four repos. Every port had independently declared itself first mover, so four different vocabularies existed. Ties were broken toward gobts (most internally coherent); any id with 3+ repos already agreeing won outright. Renames applied: gobspect encode-nested-structnested-struct, encode-interfaceinterface-values, encode-timetime-values; pygob interface-valueinterface-values, decode-stream-until-eofend-of-stream; gobdotnet encode-scalarencode-scalars, stream-multiplestream-multiple-values, stream-eofend-of-stream, decode-interfaceinterface-values, decode-timetime-values, decode-uuiduuid-values, custom-codeccustom-marshaler, semantic-typessemantic-type. gobts was left unchanged.

    Canonical shared ids and their coverage: encode-struct, decode-struct, encode-slice, encode-map, nested-struct, interface-values (all 4); end-of-stream, stream-multiple-values, time-values (3); custom-marshaler, define-schema, encode-scalars, semantic-type, uuid-values, zero-fields-omitted (2).

    Known remaining divergence — structural, not naming: gobts splits the multi-message stream example into two topics (stream-encode, stream-decode) where gobspect, pygob, and gobdotnet each use one combined stream-multiple-values. Unifying that means rewriting gobts's example bodies and the surrounding prose in its docs/02-encoding-decoding.md, so it was left alone rather than renamed into a half-match.

    Data harmonized 2026-08-20. Every language variant of a shared topic now shows the same operation on the same canonical data: running struct Point{3, 4}; encode-slice [1, 2, 3]; encode-map {"one": 1, "two": 2}; nested-struct is Line{From, To Point} with From={1,2}, To={3,4}; interface-values decodes a regenerated interface_value.gob containing Box{Value: Point{3, 4}} (Go shape type Box struct { Value any }, concrete registered as "main.Point"); stream-multiple-values and end-of-stream stream Point{3,4} then Point{5,6}; zero-fields-omitted contrasts full Point{3,4} with partial Point{3,0}; custom-marshaler is a CelsiusCodec (Go type Celsius float64 as a BinaryMarshaler, 8 big-endian IEEE-754 bytes, value 21.5) registered on both an encoder and a decoder; semantic-type is User{Name: "Ada", Status} with the C# enum idiom (wire value "Active"; a port modeling Status as plain strings may write "active", an accepted idiomatic difference). UnixSecondsCodec was removed with the old Time-based custom-marshaler demo. Unchanged by design: encode-struct, decode-struct, encode-scalars (42), define-schema, time-values and uuid-values (pinned fixtures), and source-generator (stays C#-only; merging into a schema-type-inference topic was considered and rejected because the Roslyn/AOT mechanism is genuinely different).

    Note for whoever syncs: cmd/sync reads each repo at the git ref pinned in codepuke/sources.json (main), so nothing here is visible to codepuke until it is committed and on main.


Decisions log

  • 2026-04-17: Targeted net10.0 instead of PRD's net8.0 — installed .NET is 10. No functional impact.
  • 2026-04-17: Used ICodecObjectDecoder internal interface to avoid reflection on ReadOnlySpan<byte>MethodInfo.Invoke can't box ref structs.
  • 2026-04-17: DeferredInterface private class in GobDecoder — placeholder for interface fields whose concrete value arrives in a subsequent top-level message. SubstituteDeferreds walks the object graph after all deferreds are resolved.
  • 2026-04-18: Interface concrete types in GobEncoder must have ONLY an inline type def (inside the struct body), never a top-level type def message. Go's decoder returns "gob: duplicate type received" if the same type ID is registered twice. Tracked via _topLevelSchemas vs _inlineSchemas HashSets.
  • 2026-04-18: GobSchema.For(Type) reflection fallback placed in Types.cs (alongside the schema class) rather than in Encoder.cs, so the decoder can also call it when registering POCOs.
  • 2026-08-20: GobEncoder.RegisterCodec<T> wraps external codecs in CodecEncoderAdapter<T> so public IGobCodec<T> implementations can encode, not just the built-ins that implement the internal ICodecObjectEncoder. Applied (after being surfaced under Discovered work) because the harmonized custom-marshaler snippet needs a C#-side round-trip. Constructor-passed codecs intentionally keep the old behaviour.

Session handoff template

2026-04-17 (Session 1)

  • Worked on: Phases 0–3 (all files created from scratch)
  • Completed: Scaffolding, GobWriter/GobReader, WireTypeDecoder, GobDecoder, all 107 tests green
  • Partial / blocked: none
  • Next session: Phase 4 — GobEncoder implementation

2026-04-17 (Session 2, continuation)

  • Worked on: Phase 3 bug fixes — 6 failing DecoderTests
  • Completed: Fixed test assertions (scalar_int_negative, struct_zero_fields, struct_mixed, scalar_bytes), fixed singleton wrapper for top-level marshalers, implemented deferred interface message pattern with SubstituteDeferreds
  • Partial / blocked: none
  • Next session: Phase 4 — GobEncoder

2026-04-18 (Session 3)

  • Worked on: Phase 4 — GobEncoder full implementation
  • Completed: WireTypeEncoder (Wire.cs), GobSchema.For(Type) + GobFieldTypeHelper + ISemanticGobFieldType (Types.cs), GobEncoder (Encoder.cs), Gob.Encode convenience fns (Gob.cs), EncoderTests.cs, RoundTripTests.cs, GoVerifyTests.cs
  • Key bug fixed: Go rejects duplicate type registrations — interface concrete types must use ONLY inline type def (never top-level). Fixed by _topLevelSchemas/_inlineSchemas split.
  • Partial / blocked: none
  • Next session: Phase 5 — Public API (thread-safety locks, ThreadSafetyTests, BigInteger guard, finalize [GobStruct] ergonomics)

2026-04-18 (Session 4)

  • Worked on: Phase 5 — Public API
  • Completed: TypesTests.cs (GobObject/GobSchema/attributes/BigInteger), ThreadSafetyTests.cs (100-thread stress for encoder/decoder), fixed Duration test to assert raw nanosecond long, fixed GobDecoder_ConcurrentRegister test to decode to registered POCO type
  • Key finding: GobFieldType.Duration decodes as raw long — decoder lacks schema context; test updated accordingly
  • Partial / blocked: none
  • Next session: Phase 6 — Source Generator

2026-04-18 (Session 5)

  • Worked on: Phase 6 — Source Generator
  • Completed: GobStructGenerator.cs (IIncrementalGenerator, GOB001–GOB004 diagnostics, nested type support), IGobFieldWriter interface, expanded IGobStructGenerated (Schema + CreateFromFields + WriteFields), GobSchema.For() generator-first lookup, SourceGeneratorTests.cs (18 tests: integration + Roslyn diagnostic tests)
  • Key design: generator handles classes nested inside other classes by wrapping in containing partial class declarations. Test class is declared partial to allow generated code to extend nested fixture classes.
  • Partial / blocked: none
  • Next session: Phase 7 — Codecs (TimeCodec, GuidCodec)

2026-04-18 (Session 7)

  • Worked on: Phase 8 — Property Tests & Benchmarks
  • Completed: PropertyTests.cs (11 property tests, FsCheck 3.x, MaxTest=1000), Benchmarks.cs (26 benchmark methods, 7 scenarios, gob vs JSON), results committed to GobDotNet.Benchmarks/results/
  • Key finding: struct encode is 3-4× slower than JSON (dictionary lookup overhead), but collections are faster than JSON. 2× target met for scalars, collections, and mixed payload; struct scenarios exceed it.
  • All 255 tests passing (244 existing + 11 new property tests).
  • Project is now feature-complete per PRD.
  • Partial / blocked: none
  • Next session: discuss with user — NuGet prep, CI setup, or new features

2026-04-18 (Session 8)

  • Worked on: Coverage audit and targeted test additions
  • Completed: 28 new tests across TypesTests.cs and RoundTripTests.cs. Line coverage 81.1% → 86.7%, method coverage 81.4% → 95.5%.
  • Tests added: exception inner-exception constructors, GobObject.ContainsKey/Values/IEnumerable, GobSchema IReadOnlyList constructor, mixed-Order error path, TryDecode, Decode type mismatch, RegisterCodec post-construction (encoder/decoder), encoder unsupported-type and missing-codec errors, SemanticFloat/UInt/String/Int factory methods, GobFieldTypeHelper int/uint/float/IList/nested-struct/unsupported variants, GobFieldType.ArrayOf round-trip, interface deferred-pattern round-trip, custom codec constructor behavior (returns GobEncoded).
  • Remaining coverage gaps (not worth testing): SemanticType.Decode internal method (dead code — decoder never uses it), Wire/Decoder error throws requiring crafted corrupt binary, unreachable ArrayType top-level encoder branch, source generator internal branches.
  • Partial / blocked: none
  • Next session: discuss with user — NuGet prep, CI setup, or new features

2026-04-18 (Session 6)

  • Worked on: Phase 7 — Codecs (TimeCodec, GuidCodec, DefaultCodecs, codec encoder support)
  • Completed: TimeCodec.cs, GuidCodec.cs, DefaultCodecs.cs, ICodecObjectEncoder interface, encoder marshaler support (EnsureMarshalerTypeDef, EncodeMarshalerTopLevel, MarshalerFieldType dispatch), CodecsTests.cs (29 tests), GoVerify_Time_UTC and GoVerify_UUID tests
  • Critical bug fixed: GobFieldTypeHelper.FromCSharpType(DateTimeOffset) was using "binary" marshaler kind — must be "gob" because time.Time implements encoding.GobEncoder (GobEncoderT wire type, field delta=5=index 4). Fixed in Types.cs line 318.
  • Partial / blocked: none
  • Next session: Phase 8 — Property Tests & Benchmarks

2026-08-20 (Session 9)

  • Worked on: codepuke site integration — snippet markers + docs pages (no library code touched)
  • Completed: GobDotNet.Tests/ExampleSnippets.cs (16 marked topics, one [Fact] each); docs/ with 8 numbered pages (00-overview07-wire-format) wired to those topics via :::examples; PROGRESS.md Discovered work entries
  • Key finding: user-defined codecs can decode but not encode — ICodecObjectEncoder is internal. Logged under Discovered work; deliberately NOT fixed (public-API behaviour change).
  • Also: reconciled codepuke topic ids across all four ports after gobspect/gobts/pygob landed their own (conflicting) sets. Renamed 8 ids here, 3 in gobspect, 2 in pygob; gobts unchanged. go test ./... green in gobspect, pytest tests/test_examples.py green in pygob (16 passed, including its marker validator).
  • Verified: installed SDK 10.0.400 to ~/.dotnet (no sudo needed) after the Homebrew cask failed on an interactive sudo prompt. dotnet build clean (0 errors, no new warnings from ExampleSnippets.cs); dotnet test 299 passed / 0 failed / 0 skipped, including all 16 snippet facts and all 17 go_verify tests — so the snippet examples are wire-verified against Go, not just self-consistent.
  • Partial / blocked: none
  • Next session: CI/CD or NuGet prep; decide on the encoder-side codec gap

2026-08-20 (Session 10)

  • Worked on: codepuke snippet data harmonization, so every language variant of a topic shows the same operation on the same canonical data
  • Completed: reshaped nested-struct (Line{From, To Point}), zero-fields-omitted (full Point{3,4} vs partial Point{3,0}), semantic-type (User{Name: "Ada", Status}), custom-marshaler (CelsiusCodec round-tripping 21.5 through encoder and decoder, UnixSecondsCodec removed); canonical values in encode-slice, encode-map, stream-multiple-values, end-of-stream; regenerated interface_value.gob as Box{Value: Point{3,4}} via generate_testdata.go and updated DecoderTests.Decode_InterfaceValue
  • Library change: GobEncoder.RegisterCodec<T> now wraps external codecs (see Decisions log); docs/04, docs/05, and README codec prose updated to match, including a stale docs/04 claim that interface GobObjects carry the qualified concrete name
  • Verified: dotnet test 299 passed / 0 failed / 0 skipped, including all 17 go_verify tests; snippet markers audited (16 start/end pairs, no duplicate topics; all referenced from docs/ and all docs references resolve)
  • Partial / blocked: none
  • Next session: CI/CD pipeline setup or NuGet package prep