fix(provider): accept fractional-second created_at in model manifests - #676
fix(provider): accept fractional-second created_at in model manifests#676owizdom wants to merge 1 commit into
Conversation
JSONDecoder's .iso8601 strategy resolves against whichever Foundation the host ships, so what it accepts is a property of the provider's macOS version rather than of the wire format. The coordinator marshals a manifest's created_at from a Go time.Time, which RFC3339Nano-encodes with 1-9 fractional digits; on macOS 15.x that fraction is rejected and every model download aborted at the manifest hop before transferring a byte. All five live manifests carry one. Parse the two shapes that are actually on the wire instead. The fraction is not always present: Go strips trailing zeros, and darkbloom-publish writes the CDN manifest.json through JSONEncoder.iso8601, which always truncates to whole seconds. Pinning the decoder to .withFractionalSeconds would therefore trade a constant failure for an intermittent one. Uses Date.ISO8601FormatStyle rather than ISO8601DateFormatter: it is a Sendable value type, which the shared static decoder needs, and it keeps the microseconds the formatter truncates to milliseconds. Closes Layr-Labs#673
|
@owizdom is attempting to deploy a commit to the EigenLabs Team on Vercel. A member of the Team first needs to authorize it. |
|
Closing to keep my open PRs down to one. Leaving the finding here so #673 isn't lost:
The obvious fix regresses the other direction: Happy to reopen if it's wanted. |
|
Reopening — issue #673 is still open and this fixes it. Sorry for the churn. |
Summary
ModelCatalogClient.manifestDecoderdecodedcreated_atwithJSONDecoder's.iso8601strategy, which resolves against whatever Foundation the host ships. On macOS 15.x that rejects fractional seconds, and the coordinator marshalscreated_atfrom a Gotime.Time, so every live manifest carries them. Result: every model download failed at the manifest hop before a byte transferred. This parses the wire format explicitly instead, accepting both whole-second and fractional-second RFC 3339.Linked issue
Closes #673
Before / after
Behavior:
flowchart TB subgraph Before["Before: every download aborts at the manifest hop"] direction TB A1["darkbloom start, pick a model"] --> B1["GET /v1/models/catalog"] B1 -->|"int epoch, decodes"| C1["GET /v1/models/catalog/manifest/:id"] C1 --> D1{"JSONDecoder .iso8601 meets<br/>created_at 2026-05-25T22:46:27.580497Z"} D1 -->|"macOS 15.x"| E1["reject: could not decode catalog response<br/>download aborts, 0 bytes transferred"] D1 -->|"macOS 26.x"| F1["accept: download proceeds"] end subgraph After["After: the parse no longer depends on the host"] direction TB A2["darkbloom start, pick a model"] --> C2["GET /v1/models/catalog/manifest/:id"] C2 --> D2{"explicit RFC 3339 parse"} D2 -->|"fractional or whole seconds"| F2["download proceeds on every macOS"] D2 -->|"anything else"| E2["DecodingError naming the value"] end Before ~~~ AfterCode:
flowchart TB subgraph CodeBefore["Before: ModelCatalogClient.manifestDecoder"] direction TB X1["dateDecodingStrategy = .iso8601"] --> Y1["host Foundation decides which shapes parse"] Y1 --> Z1["macOS 15.x: whole seconds only"] Y1 --> Z2["macOS 26.x: whole + fractional"] end subgraph CodeAfter["After: ModelCatalogClient.manifestDecoder"] direction TB X2["dateDecodingStrategy = .custom"] --> P1["ISO8601FormatStyle(includingFractionalSeconds: true)"] P1 -->|"no match"| P2["ISO8601FormatStyle(includingFractionalSeconds: false)"] P1 -->|"match"| OK["Date"] P2 -->|"match"| OK P2 -->|"no match"| P3["DecodingError.dataCorrupted"] end CodeBefore ~~~ CodeAfterAll four call sites of the shared decoder pick this up:
fetchManifest(coordinator manifest),ModelDownloader+Download.swift:77(CDNmanifest.json),SpecDecStore.swift:86andSpecDecResolver.swift:346(drafter manifests).Test plan
swift test --filter ModelCatalogTests— 24 tests in the suite pass, 0 failures (6.271s). That is the 20 already on master plus the 4 added here.swift build --target ProviderCorebuilds clean (147.7s), no warnings or errors in the changed file.Date.ISO8601FormatStylesatisfiesSendable.How the test run was obtained, since it is not the plain command: this machine has Command Line Tools without full Xcode, so
swift testneeds the swift-testing framework pointed at explicitly, and two test targets that are built entirely onXCTest(ProviderCoreFoundationTests,DarkbloomPublishTests) plus one XCTest file inProviderCoreTests(JinjaSanitizationTests.swift) cannot link at all. Those were excluded locally and reverted afterwards; none is touched by this PR, and the suite under test ran unmodified:Four new tests in
ModelCatalogTests.swift:fetchManifestDecodesFractionalSecondswalks the real HTTP path viaRegistryURLProtocol, using the literalcreated_atproduction serves forgpt-oss-20bmanifestDecoderAcceptsEveryRFC3339NanoShapecovers whole seconds and 1-9 fractional digits, bothZand+00:00manifestDecoderPreservesSubMillisecondPrecisionpins the microsecondsmanifestDecoderRejectsNonRFC3339CreatedAtkeeps the decoder from getting permissiveConfirmed against the live coordinator that all five manifests carry microsecond fractions (
gpt-oss-20bat.580497Z,qwen3.6-35b-a3b-vl-mtp-mxfp8at.184276Z, the threegemma-4-26bvariants likewise), so on an affected host this is a total download outage rather than a flake.Worth stating plainly: the regression test fails without the fix on macOS 15.4.1 and passes without it on macOS 26.2, because the pre-fix decoder already accepts both shapes there. That split is the defect. CI runs
macos-latest, so it could not have caught this, and it will not fail on this PR's absence either.Components touched
Protocol / interface changes
The wire format is untouched. This makes the client accept what the coordinator already emits.
Notes for reviewers
ISO8601DateFormatter. [bug] Every model download fails to decode the manifest: coordinator emits fractional-second timestamps, client uses .iso8601 which rejects them (0.8.10) #673 suggests[.withInternetDateTime, .withFractionalSeconds], but that combination rejects input with no fraction, so it would swap a constant failure for an intermittent one. Go's RFC3339Nano strips trailing zeros, anddarkbloom-publishwrites CDNmanifest.jsonthroughJSONEncoder.iso8601, which always truncates to whole seconds. Both shapes are live, so the decoder has to take both.Date.ISO8601FormatStyleis also aSendablevalue type, which matters for a static decoder shared across concurrent downloads, and it keeps microseconds the formatter truncates to milliseconds (1779749187.580497vs1779749187.58).fetchManifestDecodesRegistryRoutebuilds its fixture withJSONEncoder.iso8601andDate(timeIntervalSince1970: 0), so it round-trips through the same library and only ever exercises the one shape that already worked. The new tests use literal wire bytes instead.