Add microsecond precision to created_at and entry mtimes #9
Labels
No labels
agent
blocked
agent
new
agent
review
agent
working
complexity
high
complexity
low
priority
high
priority
low
priority
medium
risk
high
risk
low
risk
medium
type
bug
type
chore
type
feature
type
security
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
tfks/dropbear#9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Manifest created_at, manifest object basenames, and per-entry mtime are all UTC truncated to one second. Two
devices publishing in the same wall-clock second produce manifests with identical created_at, so the
staleness guard in download.Decide cannot determine ordering when shas differ. Lab repro: d1 and d2 both
published hello.mom (different content) at 2026-05-27T21:54:06Z; the guard fell through to ActionDownload and
d1 was about to overwrite local content with d2's stale blob. The just-shipped guard fix turns these
collisions into .conflict-* files needing manual resolution — fine as a safety net, but at 1s daemon
intervals collisions are routine.
Proposal
Move all timestamps used for ordering to microsecond precision. Apply consistently to:
Validation in internal/manifest/validate.go switches from regex (TimestampPattern) to
time.Parse(time.RFC3339Nano, s) + UTC check — simpler, correct, no fractional-digit count to encode in a
pattern. Drop TimestampPattern and the format constraint in the JSON Schema files under
internal/manifest/schema/ (JSON Schema's built-in format: date-time already accepts fractional seconds).
20260527T215406.123456Z_d1.json. Microsecond field is fixed-width, so lexical sort still matches
chronological. Update the writer in internal/upload/manifest.go:40 and any reader that parses the basename.
Truncate(time.Microsecond). APFS has nanosecond resolution; ext4 has nanosecond; macOS HFS+ is one-second but
you're not running on HFS+. Cross-filesystem mtime shifts are an existing concern unchanged by this.
safety net for the now-rare microsecond collision.
Test plan
classify clean when no other field changed.
ordered determinately by microsecond — no spurious conflict file.
Out of scope
Brief - Developer
Phase: simple. Run
fjx dev prompt simplefor the cycle protocol.Context
Ledger for: agent-pm
Status: closed @ 2026-06-25T04:24:06.845Z
Briefs:
Activity
(PM manages this ledger; agent section unused)
Ledger for: agent-dev
Status: review @ 2026-06-25T01:51:48.610Z
Refs: #16
Action summary
ManifestTime.UnmarshalJSONparsed RFC3339Nano and truncated before validation.ManifestTime.UnmarshalJSONto require the original JSON string to exactly match the canonical UTC microsecond layout before storing it.created_at, tombstonecreated_at, and tombstone entrydeleted_atwith nanosecond precision.Tasks
created_at, tombstonecreated_at, and tombstone entrydeleted_at.53aadb4addsTestDecodeRejectsNonCanonicalTypedTimestampPrecision.53aadb4makesManifestTime.UnmarshalJSONreject non-canonical precision.just validatepassed after commit53aadb4.fjx dev done 9.Validation
GOCACHE=$PWD/.cache/go-build GOMODCACHE=$PWD/.cache/gomod go vet ./...passed.GOCACHE=$PWD/.cache/go-build GOMODCACHE=$PWD/.cache/gomod go test ./internal/manifest -count=1passed.just validatepassed: vet, lint (0 issues.), race tests with coverage, gitleaks (no leaks found), govulncheck (0 vulnerabilitiesaffecting code), and go.mod/go.sum drift check.Notes
go vet ./...failed because the default module cache path/usr/local/gopath/pkgis not writable in this environment; reran with workspace-local Go caches successfully.@agent-pm, fjx issues are fixed. Have @agent-dev try again.
Brief - QA
Phase: default. Run
fjx qa prompt defaultfor the cycle protocol.Context
just checkpassed andGOTOOLCHAIN=go1.26.4+auto just validatepassed twice; plainjust validatefailed only because the host Go toolchain wasgo1.26.3with stdlib findings fixed ingo1.26.4.created_at, manifest/tombstone validation, and per-entry mtime precision. The current implementation uses the single-manifest layout atdevices/<device>/manifest.json, so the issue's timestamped manifest-basename item may not have an active code path.Ledger for: agent-qa
Status: fail @ 2026-06-25T01:34:00.138Z
Action summary
668c7d8(Use canonical mtimes in manifest fixtures).fjx qa checks 16:validate.yamlrun #25 failed for head668c7d8; run URLhttps://git.tfks.net/tfks/dropbear/actions/runs/25.fjx qa checks --helpshows this CLI version only lists runs (Usage: fjx qa checks <id>), with no per-step log reader. I did not use the Forgejo API directly.GOCACHE=$PWD/.cache/go-build GOMODCACHE=$PWD/.cache/gomod go test ./internal/manifest -count=1passed.GOCACHE=$PWD/.cache/go-build GOMODCACHE=$PWD/.cache/gomod just checkpassed (go vet ./...,golangci-lint run ./...,0 issues.).coverage/summary.jsonis present in this worktree.Gap analysis
mtimeremains a raw*string, sovalidateTimestampsees the original JSON and rejects non-canonical precision. Manifestcreated_at, tombstonecreated_at, and tombstone entrydeleted_atdecode intoManifestTimefirst.ManifestTime.UnmarshalJSONparses RFC3339Nano and normalizes viaNewManifestTime, which truncates to microseconds. LaterValidate()calls.String()for those typed fields, so validation sees the canonicalized value rather than the wire value.download.Decidecompare timestamp strings. Fixed-width canonical microsecond strings preserve lexical ordering, but accepting and silently truncating non-canonical wire timestamps means peers can publish over-precise values that decode to a different ordering key than the bytes they sent.Confirmed issues
internal/manifest/time.go:75parses RFC3339Nano, theninternal/manifest/time.go:82truncates throughNewManifestTime.m.CreatedAt.String()atinternal/manifest/validate.go:79, not the original JSON string.created_atatinternal/manifest/validate.go:176anddeleted_atatinternal/manifest/validate.go:200.Untested assumptions
just validatebecause the PR already has a failed remotevalidate.yamlrun and the confirmed decode bug is sufficient to return the cycle.devices/<device>/manifest.json, so that issue item is not on an active code path.Verdict
fail