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
105 changes: 102 additions & 3 deletions .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -580,10 +580,10 @@
{
"id": "REQ-MASTER-010",
"level": "LLR",
"title": "SetSchedule rejects empty schedule",
"description": "SetSchedule shall return an error when called with nil or a zero-length slice.",
"title": "SetSchedule accepts an empty schedule",
"description": "SetSchedule shall accept nil or a zero-length slice without error, matching the lin.MasterBus.SetSchedule contract (an empty schedule disables scheduled transmission). The busy-loop hazard of running an empty schedule remains independently mitigated by REQ-MASTER-009, which makes Run fail fast when the active schedule is empty.",
"asil": "ASIL-B",
"rationale": "REQ-MASTER-009 precondition enforced at configuration time.",
"rationale": "REQ-MASTER-009 already enforces the safety-relevant precondition at Run time; rejecting an empty slice here as well contradicted the documented lin.MasterBus.SetSchedule contract without adding safety value.",
"tags": ["master", "validation"]
},
{
Expand Down Expand Up @@ -613,6 +613,33 @@
"rationale": "A temporarily absent slave must not halt the entire LIN schedule.",
"tags": ["master", "resilience"]
},
{
"id": "REQ-MASTER-014",
"level": "LLR",
"title": "Diagnostics drives a master-request/slave-response exchange",
"description": "Diagnostics shall publish req on LINDiagRequestID, trigger its header, then trigger LINDiagResponseID and return the parsed SlaveResponseFrame. It shall return an error without transmitting the response header when publishing or sending the request fails.",
"asil": "ASIL-B",
"rationale": "LIN diagnostic services (LIN 2.x §4.2.3) are the standard path for node configuration and fault reporting; a malformed or partially-sent request must not be followed by a response header that the target never saw.",
"tags": ["master", "diagnostics"]
},
{
"id": "REQ-MASTER-015",
"level": "LLR",
"title": "SetPending marks a frame ID eligible for sporadic selection",
"description": "SetPending shall record id as pending; the next Run pass over a sporadic slot (SetSporadicGroup) whose candidates include id shall select it if it is the highest-priority pending candidate.",
"asil": "ASIL-B",
"rationale": "LIN 2.x §2.3.2.4: sporadic frame transmission is driven by the application's pending-update state, not a fixed schedule.",
"tags": ["master", "sporadic"]
},
{
"id": "REQ-MASTER-016",
"level": "LLR",
"title": "Run selects and skips sporadic slots correctly",
"description": "For a schedule slot whose ID is registered via SetSporadicGroup, Run shall transmit the header of the highest-priority pending candidate (clearing its pending flag) or, if no candidate is pending, skip the slot entirely without transmitting a header or invoking OnFrame/OnError.",
"asil": "ASIL-B",
"rationale": "LIN 2.x §2.3.2.4: an empty sporadic slot must remain silent on the bus, and OnError must not report a false failure for a slot that was correctly skipped.",
"tags": ["master", "sporadic"]
},
{
"id": "REQ-SLAVE-001",
"level": "LLR",
Expand Down Expand Up @@ -1008,6 +1035,78 @@
"asil": "ASIL-B",
"rationale": "ISO 21434 / TARA: the convert driver processes untrusted JSON on stdin; invalid input must fail closed (CWE-20) and not leak a partial conversion.",
"tags": ["security", "cyber", "validation", "cli"]
},
{
"id": "REQ-STATS-001",
"level": "LLR",
"title": "New returns a ready Collector",
"description": "stats.New shall return a non-nil Collector with zeroed counters, ready to Observe frames immediately.",
"asil": "ASIL-A",
"rationale": "SG-03-class observability path: stats is a passive, out-of-band consumer of already-validated frames and has no authority over bus behaviour, so it is rated ASIL-A rather than the ASIL-B rating of the frame-validation/transmission path it observes.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-002",
"level": "LLR",
"title": "Observe updates total and per-ID counters",
"description": "Observe shall increment the total frame count and the counter for f.ID by exactly one per call.",
"asil": "ASIL-A",
"rationale": "Correct counters are required for the observability data to be trustworthy, but a miscount has no effect on bus safety.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-003",
"level": "LLR",
"title": "FrameRate reports a non-negative, finite rate",
"description": "FrameRate shall return 0, not NaN or Inf, when called before any measurable time has elapsed since New or Reset.",
"asil": "ASIL-A",
"rationale": "A NaN/Inf result propagating into a caller's logging or alerting path is a robustness defect, not a safety hazard.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-004",
"level": "LLR",
"title": "BusLoad returns 0 when the bus speed is unknown",
"description": "BusLoad shall return 0 when the Collector was constructed with baudKbps <= 0, instead of dividing by zero.",
"asil": "ASIL-A",
"rationale": "Bus speed is not always known to an observer; BusLoad must degrade gracefully rather than panic or return a meaningless value.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-005",
"level": "LLR",
"title": "PerID returns a defensive copy",
"description": "PerID shall return a copy of the internal per-ID counter map; mutating the returned map shall not affect the Collector's internal state.",
"asil": "ASIL-A",
"rationale": "Matches the defensive-copy convention used throughout the codebase (e.g. ldf.DB.Frames, master.Node.SetSchedule) to prevent aliasing bugs.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-006",
"level": "LLR",
"title": "TotalCount reports the cumulative Observe count",
"description": "TotalCount shall return the number of Observe calls since New or the last Reset.",
"asil": "ASIL-A",
"rationale": "Direct consequence of REQ-STATS-002's counting contract.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-007",
"level": "LLR",
"title": "Reset clears all counters and the rate window",
"description": "Reset shall zero the total count, per-ID counters, and bit-time accumulator, and restart the elapsed-time window used by FrameRate and BusLoad.",
"asil": "ASIL-A",
"rationale": "A caller must be able to start a fresh measurement interval without recreating the Collector.",
"tags": ["stats", "observability"]
},
{
"id": "REQ-STATS-008",
"level": "LLR",
"title": "Watch observes every frame until the channel closes",
"description": "Watch shall call Observe once per frame received from ch and return when ch is closed.",
"asil": "ASIL-A",
"rationale": "Watch is a convenience wrapper; its only safety-relevant property is that it terminates cleanly on channel close rather than leaking a goroutine.",
"tags": ["stats", "observability"]
}
]
}
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ jobs:
go-version: "1.25"

- name: Install relay CLI
run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.10.0
run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.11.0

- name: Build CLI
run: go build -o /tmp/go-lin ./cmd/go-lin
Expand All @@ -158,7 +158,7 @@ jobs:
go-version: "1.25"

- name: Install relay CLI
run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.10.0
run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.11.0

- name: Build CLI
run: go build -o /tmp/go-lin ./cmd/go-lin
Expand All @@ -167,14 +167,14 @@ jobs:
run: relay interop --protocol LIN /tmp/go-lin

# ── go-FuSa full safety lifecycle (spec §20.1.2) ──────────────────────────
# Pinned to v0.30.0. §20 makes the full lifecycle normative: every change must
# Pinned to v0.33.3. §20 makes the full lifecycle normative: every change must
# pass check (ERROR gate), 100% requirement traceability, cybersecurity
# analysis, dependency vulnerability scan, and tool qualification. The
# remaining steps generate evidence artifacts (verify/fmea/release) and are
# non-gating. To upgrade: install the new version locally, run the gating
# commands, fix all findings, then bump the @version pin below.
gofusa:
name: go-FuSa full lifecycle (v0.30.0)
name: go-FuSa full lifecycle (v0.33.3)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -184,7 +184,7 @@ jobs:
go-version: "1.25"

- name: Install go-FuSa
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.30.0
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.33.3

- name: gofusa check (gate on ERROR findings)
run: gofusa check ./...
Expand Down Expand Up @@ -272,7 +272,7 @@ jobs:
go-version: "1.25"

- name: Install go-FuSa
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.30.0
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.33.3

- name: Generate evidence
run: |
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
target: [quickstart, lintool]
target: [go-lin, quickstart, lintool]

steps:
- uses: actions/checkout@v5
Expand All @@ -40,11 +40,23 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# go-lin is the RELAY-conformant CLI (spec §13.5): it publishes under
# the bare tool name (ghcr.io/soundmatt/go-lin), not a "-go-lin" suffix.
# quickstart/lintool are example/legacy images and keep their suffix.
- name: Resolve image name
id: image
run: |
if [ "${{ matrix.target }}" = "go-lin" ]; then
echo "name=${{ env.IMAGE_BASE }}" >> "$GITHUB_OUTPUT"
else
echo "name=${{ env.IMAGE_BASE }}-${{ matrix.target }}" >> "$GITHUB_OUTPUT"
fi

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_BASE }}-${{ matrix.target }}
images: ${{ steps.image.outputs.name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
go-version: "1.25"

- name: Install go-FuSa
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.30.0
run: go install github.com/SoundMatt/go-FuSa/cmd/gofusa@v0.33.3

- name: Regenerate dFMEA table
run: gofusa fmea -cyber
Expand Down
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Changelog

All notable changes to go-LIN are documented here. Versions correspond to
git tags; see `gh release list` / `git tag --sort=-creatordate` for the
canonical list. Dates are release dates (UTC-7, matching tag creation).

## [Unreleased]

- fix: `virtual.Bus.CloseWithDrain` now returns `lin.ErrTimeout` (and counts
undelivered frames into `DropCount`) when the context expires before
draining completes, instead of silently returning `nil` (#37)
- fix: `lin.FromMessage` now wraps `lin.ErrInvalidFrame` for an out-of-range
or unparseable message ID, so `errors.Is(err, lin.ErrInvalidFrame)` works (#38)
- fix: `master.Node.SetSchedule` now accepts an empty schedule, matching the
documented `lin.MasterBus.SetSchedule` contract; `master.Node.Run` still
fails fast on an empty schedule (#43)
- feat: `cmd/go-lin`'s optional `send`/`subscribe` commands now implement the
spec §11.2 flag signatures — `send --id <uint> --data <hex>` and
`subscribe --format json --count N` (#41)
- feat: `ldf` package gains write-direction signal encoding, `db.Encode(id,
signals) ([]byte, error)` (#6)
- feat: new `stats` package — a `Collector` tracking frames/sec, per-ID
counters, and bus load percentage for observability (#10)
- feat: `lin` package gains typed diagnostic-frame helpers,
`MasterRequestFrame`/`SlaveResponseFrame`, and a `master.Node.Diagnostics`
handler for the master request (0x3C) / slave response (0x3D) exchange (#2)
- feat: `master.Node` gains sporadic-frame slot support — a schedule slot can
now select from a group of candidate frame IDs based on pending-update
flags set by the application (#4)
- docs: add runnable `Example*` functions for the core `lin`, `virtual`, and
`master` API surface (#11)
- docs: README documents `cmd/go-lin` (the RELAY-conformant CLI) as the
primary CLI, with `cmd/lintool` clearly marked legacy (#40)
- docker: add a `go-lin` image target/stage with the spec §13.5 `io.relay.*`
labels, published as `ghcr.io/soundmatt/go-lin` (#39)
- chore: bump `github.com/SoundMatt/RELAY` to v1.11.0 (go.mod, CI) — closes
the `relay versions` ALIGNED=false drift (#42)
- chore: bump the pinned `go-FuSa` CLI from v0.30.0 to v0.33.3 in CI and the
release-artifact-regeneration workflow (#9)
- docs: replace the stale pre-RELAY roadmap with an up-to-date release
history and re-baseline "planned" work against the current issue tracker;
add this CHANGELOG (#44)

## [1.2.0] — 2026-06-19

Full ISO/IEC/DO compliance evidence pack + max coverage (#35).

## [1.1.0] — 2026-06-19

Adopt RELAY spec v1.10: §13.7 cross-language library architecture convention,
§20 continuous conformance (#34).

## [1.0.0] — 2026-06-17

Adopt RELAY spec v1.0 (stable) conformance (#27, #28).

## [0.4.0] — 2026-06-17

Adopt RELAY spec v0.3 conformance (#25, #26); CI fix making short fuzz runs
iteration-based to stop flaky failures (#24).

## [0.3.0] — 2026-06-16

RELAY spec v0.2 conformance: `Subscribe` slice signature and LIN-prefixed
constants (#16, #20); full RELAY v0.2 conformance (#17, #21); optional
interfaces — `HealthProvider`, `MetricsProvider`, `Drainer` (#18, #23).

## [0.2.0] — 2026-06-13

Expand to 100 atomic ASIL-B SEOOC requirements (#15).

## [0.1.0] — 2026-06-13

Initial release: core `lin.Bus`/`MasterBus` interfaces, virtual bus, LDF
parser, master/slave nodes, E2E safety, `cmd/lintool` CLI, Docker quickstart.
Loading
Loading