Repository navigation
test(contract): pin the shipped AP SSID format (replaces #489) - #524
Amperstrand wants to merge 8 commits into
Conversation
c7dce50 to
bcc7016
Compare
Verification — the replacement carries #489's full contract: green at head, red on all four injected drift classes, triple wiring confirmed; one mechanical CHANGELOG misplacement to fixVerified at head Green at head. Red on all four drift classes from #489's verification, each on a fresh
One scope shift vs #489 worth naming for maintainers: the band-aware public write is Wiring, exercised not just parsed. The Consistency with #531. #531 is open at review time (not yet merged), and its tree writes Contract battery at head: One repo-rule fix needed: the CHANGELOG entry is in the wrong section. Disposition: request-changes — one blocking item (move the CHANGELOG entry under |
|
Wave-3 follow-up fixup pushed ( |
c885394 to
bf25db3
Compare
|
Review at APPROVED — no change requests. The contract is real, mutation-hardened, and correctly wired into both CI workflow twins, Verified first-hand at that head:
Findings (none blocking):
|
felixfelix-bot
left a comment
There was a problem hiding this comment.
APPROVED (review at head 864aae68a03c0c2c72f606e15c6aee7e849b7848) — no change requests; the SSID-format contract is mutation-hardened (4/4 drift classes killed). Three non-blocking findings ([RISK] harness-stub fidelity in tests/contract/check-ssid-format.sh:79-83, two NITs: CHANGELOG heading, README private-AP vs open #531) — full evidence in the review comment above.
|
Technical review (evidence pass) — PR head This pins the open-AP SSID format as a checked contract and — unusually for a test-only PR — the checking machinery is genuinely good. I reproduced the full verification story on the head: Two one-line fixes should land before merge, though — both in the docs the PR itself adds. The changelog entry ate its section header. The README documents a private AP this branch doesn't ship. The new SSID-conventions section says the private management AP is One non-blocking fragility worth knowing about: the behavioural layer exercises the fallback, not the band-aware path it claims to follow. The stub's Hygiene: five well-chunked commits with genuinely useful messages (the red-evidence narrative in the first one is exemplary), no attribution footers. Overlaps are handled correctly — #489 is properly superseded, and the #531 relationship just needs the README wording above. The branch is 24 behind a main that has since restructured Disposition: request-changes — restore the |
Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section.
864aae6 to
7100ad5
Compare
|
Review follow-up + rebase onto main — new head Per finding:
Verification at the new head:
The rebased tree itself is healthy — main's own |
7100ad5 to
2d0998e
Compare
|
README bullet updated to the decided model + one new finding. The private-AP bullet no longer future-tenses #531 ("renames the default to While verifying, a real finding: |
The open AP is named `<brand>-<4 hex characters>` (`TollGate-A1B2` for the
default brand) and both radios carry that one name. Nothing enforced it: the
4-character device suffix and the single-name property were conventions in
99-tollgate-setup, so a band suffix ("TollGate-A1B2-2.4GHz") or a hardcoded
brand could come back unnoticed. The Go side keys on the `TollGate-` prefix
(discovery_log.go `hasTollGateSSID`, vendor_element_manager.go
`calculateScore`), and the SSID rides in every beacon, so the drift is both a
client-matching and a naming-honesty bug.
tests/contract/check-ssid-format.sh asserts the contract in two layers:
- behavioural: sources the real load_brand()/setup_public_wifi() out of the
script (driver stripped) and runs them against a stub `uci` that records
what they would have written, then checks the SSID actually generated —
both radios matching ^TollGate-[0-9A-F]{4}$ for the default brand, one
shared name for both radios, a per-device 4-hex suffix over 6 generations.
- structural: checks the assignment sites a checkout cannot execute — the
same-version reinstall path recovers its SSID from live config and the
whitelabel branch reads /etc/tollgate/brand — failing on a band token or on
an SSID written from anything but a variable.
Red evidence, all reverted before this commit: appending "-2.4GHz" to the
gateway name fails 12 checks; hardcoding the brand at the `.ssid` write fails
the structural layer (which the behavioural layer alone cannot see); deleting
the write fails the anti-vacuity guard.
Wired into both CI test workflows (build-purity job), hooks/pre-commit, and
the release/contribution runbooks.
Refs: t_9e5f0b8d
Public AP is BRAND-XXXX with a 4-hex uppercase suffix from /dev/urandom, shared across the 2.4GHz and 5GHz radios (band steering). Private management AP is BRAND-Private-XXXX with a WPA2 word-word-word-NN passphrase. nodogsplash gatewayname derives as '<gateway> Portal'. The public format is pinned by tests/contract/check-ssid-format.sh. Documents the post-fix private SSID (TollGate-Private-XXXX); the operator-username variant (c08r4d0r-XXXX) is a security leak being fixed on pr/private-ssid-no-operator-username.
Main's setup now resolves radios by band before assigning APs
(detect_band_radios -> setup_band_ap), so the harness bootstraps a
banded first-boot wireless state, calls detect_band_radios before
setup_public_wifi, and reads the SSIDs back from the tollgate_*_open
sections the band-aware setup writes (adopting or creating them per
radio). The asserted contract is unchanged: one shared
`^TollGate-[0-9A-F]{4}$` name on both open radios, per-device suffix,
no band tokens.
The wave-3 catch: the entry sat inside the frozen [v0.6.0-alpha4] section (the tag exists); new entries belong in [Unreleased].
…odes Codes minted by the current writers are hex, but the contract's acceptance domain is [A-Z0-9] (adopted codes from earlier writers, e.g. the pre-device-code installer's full-alphanumeric mints). Per #618's ADR clarification.
#605 changed the derivation this gate pins: the SSID is no longer a per-boot RANDOM_SUFFIX but DEVICE_SSID, built by setup_device_identity from a code minted once and stored in /etc/config/tollgate. The harness still modelled the pre-#605 world — setup_public_wifi died on 'DEVICE_SSID: unbound variable' after load_brand's brand read, and the checker was 13 checks red at head. The behavioural layer now runs the driver's own order (load_brand → setup_device_identity → detect_band_radios → setup_public_wifi) with DEVICE_CODE_STORE redirected into the per-run state (the env seam the device-code tests use), so a wiped state is a fresh device that really mints, and a new keep-mode run pins the other half of the contract: a same-store rerun adopts the stored code and never re-mints. The stub uci now renders section types bare in show output like the real uci (the band-test shim's rule): with quoted section types band detection and AP adoption were silently disabled, so the harness exercised the fallback create path while claiming to follow the banded first-boot state. The readback follows whichever sections the APs actually landed on (the adopted default_radioN today) instead of keying on tollgate_*_open, so the labels cannot lie about the path that ran. Assertion domain is the full [A-Z0-9] code alphabet (this side's mint emits hex; the installer's emits all of [A-Z0-9]; the contract is the shared alphabet), and B2 accepts the $DEVICE_SSID derivation in place of the removed RANDOM_SUFFIX/hexdump forms. Verified: green at head (18 ok / 0 fail, 5 consecutive runs); red on fresh-archive mutations — band suffix on the derivation (exit 1), hardcoded literal at GATEWAY_NAME (exit 1: distinctness + store/SSID disagreement + B2), deleted public ssid write (exit 1); band suite 22/22, device-code suite 72/72.
The README public-AP bullet still called the XXXX a '4-hex uppercase
device code' and the changelog entry still pinned ^TollGate-[0-9A-F]{4}$
and a 'per-device 4-hex suffix' — both stale against the #605 decision
(docs/architecture/one-device-code.md): the code is four characters of
[A-Z0-9] (this side's mint emits hex, the installer's emits the full
alphabet), minted once and reused from the store. The changelog entry
now also describes the harness as it works after the one-device-code
adaptation: the identity chain is sourced and run, the stub renders
uci show section types bare, and the code-reuse property is asserted.
d839b3d to
e98badb
Compare
|
One-device-code adaptation of the SSID harness + rebase onto main — new head Per finding (the 2026-09-28 blocker):
Rebased onto Verification at the new head:
|
Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section.
|
Rebased onto |
…ollGate#531's TollGate-Private-XXXX is the post-OpenTollGate#531 state and OpenTollGate#531 is still open; the shipped 99-tollgate-setup broadcasts c08r4d0r-${RANDOM_SUFFIX}. Say that, and point at OpenTollGate#531 for the rename (PR OpenTollGate#524 review follow-up).
|
Conflicts still resolved — now refreshed onto the current Replacement PR: #672 — #672 — base All 8 commits replayed onto The one file that still conflicts in a plain Verification on the refreshed head:
No force-push to your branch is needed from anyone: merging #672 lands the identical 8 commits, and this PR then closes as superseded. The alternative (force-push |
* test(contract): pin the shipped AP SSID format
The open AP is named `<brand>-<4 hex characters>` (`TollGate-A1B2` for the
default brand) and both radios carry that one name. Nothing enforced it: the
4-character device suffix and the single-name property were conventions in
99-tollgate-setup, so a band suffix ("TollGate-A1B2-2.4GHz") or a hardcoded
brand could come back unnoticed. The Go side keys on the `TollGate-` prefix
(discovery_log.go `hasTollGateSSID`, vendor_element_manager.go
`calculateScore`), and the SSID rides in every beacon, so the drift is both a
client-matching and a naming-honesty bug.
tests/contract/check-ssid-format.sh asserts the contract in two layers:
- behavioural: sources the real load_brand()/setup_public_wifi() out of the
script (driver stripped) and runs them against a stub `uci` that records
what they would have written, then checks the SSID actually generated —
both radios matching ^TollGate-[0-9A-F]{4}$ for the default brand, one
shared name for both radios, a per-device 4-hex suffix over 6 generations.
- structural: checks the assignment sites a checkout cannot execute — the
same-version reinstall path recovers its SSID from live config and the
whitelabel branch reads /etc/tollgate/brand — failing on a band token or on
an SSID written from anything but a variable.
Red evidence, all reverted before this commit: appending "-2.4GHz" to the
gateway name fails 12 checks; hardcoding the brand at the `.ssid` write fails
the structural layer (which the behavioural layer alone cannot see); deleting
the write fails the anti-vacuity guard.
Wired into both CI test workflows (build-purity job), hooks/pre-commit, and
the release/contribution runbooks.
Refs: t_9e5f0b8d
* docs(readme): document SSID format conventions
Public AP is BRAND-XXXX with a 4-hex uppercase suffix from /dev/urandom,
shared across the 2.4GHz and 5GHz radios (band steering). Private
management AP is BRAND-Private-XXXX with a WPA2 word-word-word-NN
passphrase. nodogsplash gatewayname derives as '<gateway> Portal'.
The public format is pinned by tests/contract/check-ssid-format.sh.
Documents the post-fix private SSID (TollGate-Private-XXXX); the
operator-username variant (c08r4d0r-XXXX) is a security leak being
fixed on pr/private-ssid-no-operator-username.
* test(contract): follow the band-aware setup in the SSID harness
Main's setup now resolves radios by band before assigning APs
(detect_band_radios -> setup_band_ap), so the harness bootstraps a
banded first-boot wireless state, calls detect_band_radios before
setup_public_wifi, and reads the SSIDs back from the tollgate_*_open
sections the band-aware setup writes (adopting or creating them per
radio). The asserted contract is unchanged: one shared
`^TollGate-[0-9A-F]{4}$` name on both open radios, per-device suffix,
no band tokens.
* docs(changelog): move the SSID-format contract entry to [Unreleased]
The wave-3 catch: the entry sat inside the frozen [v0.6.0-alpha4]
section (the tag exists); new entries belong in [Unreleased].
* docs(readme): the private-AP bullet documents today's SSID, not #531's
TollGate-Private-XXXX is the post-#531 state and #531 is still open;
the shipped 99-tollgate-setup broadcasts c08r4d0r-${RANDOM_SUFFIX}.
Say that, and point at #531 for the rename (PR #524 review follow-up).
* test(contract): assert the acceptance domain [A-Z0-9], not hex-only codes
Codes minted by the current writers are hex, but the contract's acceptance
domain is [A-Z0-9] (adopted codes from earlier writers, e.g. the
pre-device-code installer's full-alphanumeric mints). Per #618's ADR
clarification.
* test(contract): follow the one-device-code model in the SSID harness
#605 changed the derivation this gate pins: the SSID is no longer a
per-boot RANDOM_SUFFIX but DEVICE_SSID, built by setup_device_identity
from a code minted once and stored in /etc/config/tollgate. The harness
still modelled the pre-#605 world — setup_public_wifi died on
'DEVICE_SSID: unbound variable' after load_brand's brand read, and the
checker was 13 checks red at head.
The behavioural layer now runs the driver's own order (load_brand →
setup_device_identity → detect_band_radios → setup_public_wifi) with
DEVICE_CODE_STORE redirected into the per-run state (the env seam the
device-code tests use), so a wiped state is a fresh device that really
mints, and a new keep-mode run pins the other half of the contract: a
same-store rerun adopts the stored code and never re-mints.
The stub uci now renders section types bare in show output like the real
uci (the band-test shim's rule): with quoted section types band
detection and AP adoption were silently disabled, so the harness
exercised the fallback create path while claiming to follow the banded
first-boot state. The readback follows whichever sections the APs
actually landed on (the adopted default_radioN today) instead of keying
on tollgate_*_open, so the labels cannot lie about the path that ran.
Assertion domain is the full [A-Z0-9] code alphabet (this side's mint
emits hex; the installer's emits all of [A-Z0-9]; the contract is the
shared alphabet), and B2 accepts the $DEVICE_SSID derivation in place
of the removed RANDOM_SUFFIX/hexdump forms.
Verified: green at head (18 ok / 0 fail, 5 consecutive runs); red on
fresh-archive mutations — band suffix on the derivation (exit 1),
hardcoded literal at GATEWAY_NAME (exit 1: distinctness + store/SSID
disagreement + B2), deleted public ssid write (exit 1); band suite
22/22, device-code suite 72/72.
* docs: the SSID contract wording follows the device-code alphabet
The README public-AP bullet still called the XXXX a '4-hex uppercase
device code' and the changelog entry still pinned ^TollGate-[0-9A-F]{4}$
and a 'per-device 4-hex suffix' — both stale against the #605 decision
(docs/architecture/one-device-code.md): the code is four characters of
[A-Z0-9] (this side's mint emits hex, the installer's emits the full
alphabet), minted once and reused from the store. The changelog entry
now also describes the harness as it works after the one-device-code
adaptation: the identity chain is sourced and run, the stub renders
uci show section types bare, and the code-reuse property is asserted.
---------
Co-authored-by: felixfelix-bot <felixfelix-bot@users.noreply.github.com>
Co-authored-by: Amperstrand <amperstrand@localhost>
Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section.
Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section.
Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section.
…581) Resolving a CHANGELOG conflict with a section-level 'take ours' can resurrect an entry a branch commit had already moved or reworded — the same bullet then ships in two sections, release notes describe one change twice, and entry-counting fold scripts go wrong. This happened twice in one rebasing session (#524 and #531 both carried a resurrected stale copy past a --ours resolution until audited). check-changelog-duplicates.py fails when the same bold lead-in appears in two sections and at least one is [Unreleased] — the live edit zone. The same lead-in inside already-released sections (15 such pairs exist in history, e.g. 'gonuts re-pin.') is frozen history and deliberately tolerated: release notes are not rewritten. Wired into hooks/pre-commit next to the version-sync check; pinned by changelog-duplicates_test.sh across all three verdicts. PR #581 review follow-ups, folded into the same commit: - Wire the check and its verdict script into .github/workflows/test.yml beside the deps/import contract checks: pre-commit cannot gate sequencer commits (a bad --ours resolution during rebase --continue sails past the hook), so CI is the only place the check runs for everyone regardless of hooks. - Scope the docstring and the failure/success wording to the version-section keying actually enforced — the same lead-in in two version sections with [Unreleased] among them — instead of promising lead-in-wide uniqueness the frozen-history tolerance contradicts. - Append the PR link to the changelog entry; the rebase onto post-#577 main resolves the CHANGELOG conflict keep-both and drops the branch's inherited orphaned second [Unreleased] section. Co-authored-by: Amperstrand <amperstrand@localhost>
…nflicts stop existing (#679) * feat(changelog): fold changelog.d fragments into CHANGELOG.md Two PRs that change operator-visible behaviour both append one bullet to the same anchor of CHANGELOG.md, so the second merge conflicts every time — about a dozen hand-resolved hunks on the 0.6.0 train, and two of those resolutions (#524, #531) resurrected an entry a branch had already moved past a keep-both-sides resolution. 15 open PRs carry a CHANGELOG.md hunk at once as this lands, so the merge order was also a changelog-editing order. A fragment is one file per change — changelog.d/<pr>-<slug>.<type>.md, the body being the bullet verbatim — so unique filenames cannot conflict and a merge never touches the changelog at all. `scripts/changelog-assemble.py fold` writes the accumulated entries into the release section in one deterministic step (one wave under the release heading, newest PR first, subsections in canonical order) and deletes the folded fragments; `check` validates names and bodies; `--dry-run` prints the wave and writes nothing. The contract test went red first — 21 failing cases with the tool absent — and green after: 18 cases covering where entries land, the order they land in, what is deleted, the dry run, and each verdict of check. shellcheck clean; the fold was also run against the real 3,500-line CHANGELOG.md in a copy to see the wave it produces (folded into `## [Unreleased]`, everything else untouched). Decision and the rejected alternatives: docs/architecture/changelog-fragments-decision.md * chore(changelog): validate fragments in the pre-commit hook and CI `check` runs where the other contract checks run: the pre-commit hook refuses a malformed fragment (wrong filename, unknown type, a body that is not the bullet) before it is committed, and the `contract-lint` job runs the check plus the tool's contract test on every PR. Wired into `.github/workflows/test.yml` and its ngit port, which is the lane that actually executes. * docs(changelog): record the fragment decision and the workflow The decision record states the problem (one shared anchor, a dozen hand-resolved hunks a train, two resurrections), the decision (fragments + one fold), and the alternatives it rejects — a `merge=union` gitattributes driver (hides the shared anchor and silently keeps both sides of a bullet that was rewritten) and generating the changelog from commit subjects (loses the prose this changelog is worth reading for). CONTRIBUTING sends authors to changelog.d/ instead of CHANGELOG.md, and the release process folds the accumulated fragments in its own commit before the release commit is cut. * docs(changelog): this PR's own entry is a fragment The rule this PR adds applies to it: the changelog entry lives in changelog.d/679-changelog-fragments.added.md and is not folded here, so the first fold the maintainer runs produces it. --------- Co-authored-by: Felix <felix@opentollgate.org>
Rebased replacement for #489 (same contract; original closed — its head branch was deleted and the PR stayed pinned to the orphaned SHA).
What
The open-AP SSID format becomes a checked contract:
99-tollgate-setupnames the access point<brand>-<4 hex>(TollGate-A1B2), one shared name on both radios, per-device suffix.tests/contract/check-ssid-format.sh(CI gate in both workflow twins +hooks/pre-commit) fails when that stops being true. Rationale: the Go side keys on theTollGate-prefix (discovery_log.go,vendor_element_manager.go) — a band suffix would fork the router's network identity.Rebased onto current
mainwith the harness adapted to the band-aware setup: the bootstrap state declares banded radios,detect_band_radiosruns beforesetup_public_wifi(the driver's order), and the readback follows thetollgate_*_opensections the band-aware setup writes.Verification
ssid="$GATEWAY_NAME-2.4GHz"→ exit 1. (A section-label-only change is correctly ignored — checked during test(contract): pin the shipped AP SSID format #489's original review.)node tests/contract/js-schema-lint.mjsPASS,bash tests/contract/build-purity.sh3/3 (run on the rebased tree).