From 72d1bafb455d713dfac294edb8df641252a1b482 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:17:19 +0600 Subject: [PATCH 1/8] fix(helper): bundle interposer slices in the app The installed helper carried no interposer slices: build-menubar-app.sh sealed the bundle without them, so the slice locator found nothing in an installed .app and arming skipped every simulator. Build the ios and watchos sim slices and copy them, codesigned, into Resources. --- Makefile | 6 +++++- scripts/build-menubar-app.sh | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6d8749b..a2d4592 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ WATCH_TARGET := arm64-apple-watchos10.0-simulator SWIFT_PKGS := packages/host-core packages/protocol/swift apps/helper tools/simblectl C_FILES := $(shell find packages -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) -.PHONY: help bootstrap configure build app test test-portable fence docs lint format clean \ +.PHONY: help bootstrap configure build app dylib test test-portable fence docs lint format clean \ mechanism-ios mechanism-watchos mechanism-peripheral-ios help: ## Show targets @@ -42,6 +42,10 @@ build: configure ## Build C targets and Swift packages @if [ -d build-watchsim ]; then cmake --build build-watchsim -j; fi @for p in $(SWIFT_PKGS); do echo "== swift build: $$p =="; ( cd $$p && xcrun swift build ) || exit 1; done +dylib: configure ## Build the interposer simulator slices (ios and watchos) + @if [ -d build-sim ]; then cmake --build build-sim -j; fi + @if [ -d build-watchsim ]; then cmake --build build-watchsim -j; fi + app: ## Build the menubar SimBLE.app bundle into dist/ (ad-hoc signed) bash scripts/build-menubar-app.sh diff --git a/scripts/build-menubar-app.sh b/scripts/build-menubar-app.sh index 32c23e1..e7aa279 100755 --- a/scripts/build-menubar-app.sh +++ b/scripts/build-menubar-app.sh @@ -46,6 +46,21 @@ cat > "$APP/Contents/Info.plist" < PLIST +# Bundle the interposers the helper injects, one per simulator platform. Each is a simulator-slice +# binary the fence keeps out of any shipped app; the helper carries them and arms a booted simulator +# with the slice matching its platform. Without these the installed helper finds no slice and arms +# nothing. Build, copy, and sign each into Resources before the bundle is sealed. +command -v cmake >/dev/null || { echo "cmake is required to build the interposers: brew install cmake"; exit 1; } +echo "building the interposers (ios and watchos sim slices)..." +( cd "$REPO" && make dylib ) || { echo "failed to build the interposers"; exit 1; } +for slice in build-sim/bin/simble-interpose.dylib build-watchsim/bin/simble-interpose-watchos.dylib; do + [ -f "$REPO/$slice" ] || { echo "missing interposer slice: $slice (is the simulator SDK installed?)"; exit 1; } + name="$(basename "$slice")" + cp "$REPO/$slice" "$APP/Contents/Resources/$name" + codesign -s "$SIGN_ID" --force --timestamp=none "$APP/Contents/Resources/$name" >/dev/null 2>&1 \ + || { echo "codesign failed for $name"; exit 1; } +done + # Ad-hoc sign the bundle so the Bluetooth grant attributes to a stable identity across runs. # --deep covers any nested code; --force replaces an existing signature. codesign --force --deep --sign "$SIGN_ID" "$APP" >/dev/null 2>&1 \ From 01a827f495e5acda80f8a7c000e0f113f0e7528c Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:17:28 +0600 Subject: [PATCH 2/8] fix(helper): assert bundled slices in the fence The release fence --helper check was a placeholder, so a sliceless bundle shipped. It now reads each interposer's Mach-O build platform and fails closed on a missing slice or a non-simulator platform. The architecture doc drops the placeholder note. --- docs/architecture/simble-architecture.qd | 8 ++++---- scripts/fence-check.sh | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/architecture/simble-architecture.qd b/docs/architecture/simble-architecture.qd index 08ceaf0..beb2371 100644 --- a/docs/architecture/simble-architecture.qd +++ b/docs/architecture/simble-architecture.qd @@ -327,10 +327,10 @@ $$$ CI runs the fence on every change. It asserts the static naming and wiring rules: that any scheme carrying the variable is Debug-only, that no Xcode project links the dylib into a build, and that the -variable appears only in a reviewed allowlist. The fence also defines a binary bundle check, that -every interposer the helper carries is a simulator slice and fails closed on any device platform; the -release workflow runs it against the built `.app`, where it is a placeholder pending the full -slice-platform assertion. +variable appears only in a reviewed allowlist. The fence also runs a binary bundle check, that the +helper carries exactly the interposers it injects and each is a simulator slice, reading the Mach-O +build platform and failing closed on a missing slice or any device platform; the release workflow +runs it against the built `.app` before publishing. .mermaid caption:{The fence as a decision: a load survives only the Simulator-and-debug path.} flowchart TD diff --git a/scripts/fence-check.sh b/scripts/fence-check.sh index e6e9cfc..24ab323 100755 --- a/scripts/fence-check.sh +++ b/scripts/fence-check.sh @@ -32,8 +32,24 @@ fi if [ "${1:-}" = "--helper" ]; then BUNDLE="${2:-}" [ -d "$BUNDLE" ] || { echo "usage: fence-check.sh --helper " >&2; exit 2; } - echo "FENCE (helper): placeholder ok" - exit 0 + RES="$BUNDLE/Contents/Resources" + # The helper must carry exactly the interposers it injects, and each must be a simulator slice: + # a missing slice means the helper arms nothing; a device slice would defeat the fence. Read the + # Mach-O build platform and fail closed on anything that is not a *SIMULATOR platform. + for name in "${DYLIB_NAME}.dylib" "${DYLIB_NAME}-watchos.dylib"; do + slice="$RES/$name" + if [ ! -f "$slice" ]; then + fail "helper bundle carries no $name; it would arm nothing" + continue + fi + plat="$(vtool -show-build "$slice" 2>/dev/null | awk '/platform/ {print $2; exit}')" + case "$plat" in + *SIMULATOR) ;; + *) fail "$name is not a simulator slice (platform: ${plat:-unknown})" ;; + esac + done + [ "$FAIL" -eq 0 ] && echo "FENCE (helper): ok ($(basename "$BUNDLE") carries simulator-only interposers)" + exit "$FAIL" fi git ls-files '*.xcscheme' | while read -r scheme; do From 792a21598e22554f9907584df5d1c102a678d7e0 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:23 +0600 Subject: [PATCH 3/8] chore(repo): wire lefthook git hooks lefthook was a dependency with no config, so the hooks were inert. Wire pre-commit biome, commit-msg commitlint, and a no-direct-main pre-push guard. --- lefthook.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lefthook.yml diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..7dfbbbe --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Nirapod Labs + +pre-commit: + parallel: true + commands: + biome: + glob: "*.{js,ts,tsx,json,jsonc}" + run: pnpm exec biome check --write --no-errors-on-unmatched {staged_files} + stage_fixed: true + +commit-msg: + commands: + commitlint: + run: pnpm exec commitlint --edit {1} + +# Branch protection also lives on the remote, but the gate is enforced locally +# too. To bypass in a genuine emergency: git push --no-verify. +pre-push: + commands: + no-direct-main: + run: | + branch="$(git symbolic-ref --short HEAD 2>/dev/null || echo detached)" + if [ "$branch" = "main" ]; then + echo "Direct push to main is blocked. Branch, open a PR, let CI run." + exit 1 + fi From ab655fccec65e1dc5a21879df7c2b5015c3d31d3 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:42 +0600 Subject: [PATCH 4/8] docs(repo): add a code of conduct Adopt Contributor Covenant 2.1. Conduct reports go privately to the Nirapod Labs maintainers. --- CODE_OF_CONDUCT.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a7ea41c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,82 @@ + + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the maintainers responsible for enforcement, privately, through the [Nirapod Labs](https://github.com/nirapod-labs) organization. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at . + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . From 861eec48fd26871ff559461c79ff286c776cd378 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:42 +0600 Subject: [PATCH 5/8] docs(repo): expand contributing and pr template Add the lefthook install step, the PR-title squash-subject rule, and the no-verify policy to CONTRIBUTING; add a checklist to the PR template, including the fence and custody invariant. --- .github/PULL_REQUEST_TEMPLATE.md | 7 ++++++ CONTRIBUTING.md | 43 ++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ee23460..9a90283 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,3 +10,10 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs ## How ## Testing + +## Checklist + +- [ ] The change is scoped and small enough to review in one sitting. +- [ ] Conventional commits, and CI is green (lint, build, tests, fence). +- [ ] A change to the mechanism, protocol, or security model was designed first. +- [ ] Nothing here lets the interposer reach a production build or cross the custody boundary. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d053608..ff377c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,29 +6,34 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs # Contributing SimBLE is PR-driven. Everything lands through a pull request that a maintainer -reviews. Nothing goes straight to `main` after the repository seed. +reviews. Nothing goes straight to `main`. -## Basics +## The basics -- Branch off `main` and keep the branch focused. -- Use conventional commit subjects. The allowed types and scopes are in - `.commitlintrc.json`. -- Keep PRs small enough to review in one sitting. -- CI must be green before merge. -- The release is cut from a version tag on merged `main`. +- Branch off `main` and keep the branch focused on one thing. +- Conventional commits, lowercase subject after the type and scope. The allowed + types and scopes are in `.commitlintrc.json`, and the commit-msg hook checks + them, so a bad message will not commit. +- The PR title becomes the squash subject on `main`, so it has to be a valid + conventional subject too. GitHub appends ` (#N)` and `subject-max-length` is + 50, so keep the title around 45 characters. commitlint runs on the PR. +- Small PRs. If a reviewer cannot hold the whole change in their head, split it. +- CI has to be green before merge: lint, build, and the relevant tests. -## Building +## Hooks + +Run `pnpm install` and `pnpm exec lefthook install` once. After that, formatting +and the commit-message check run on commit, and the no-direct-main guard runs on +push. If a hook blocks you and you genuinely need around it, that is a +conversation with a maintainer, not a quiet `--no-verify`. -Run: +## Building -```sh -make bootstrap -make build -make test -``` +`make bootstrap` from a fresh clone, then `make build` and `make test`. The +toolchain and every `make` target are in [docs/development.md](docs/development.md). -## Design changes +## Design before code -If your PR changes the BLE routing mechanism, wire protocol, security model, or -platform limits, describe the design in the PR before the code, so a reviewer can -check the approach first. +If a change touches the BLE routing mechanism, the wire protocol, the security +model, or the platform limits, describe the design in the PR before the code, so +a reviewer can check the approach first. Not the other way around. From a3224ac9417f654e010f736301c547e51b921608 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:53 +0600 Subject: [PATCH 6/8] chore(repo): add issue templates Bug and feature templates, plus a config that disables blank issues and points security reports to the private advisory channel. --- .github/ISSUE_TEMPLATE/bug_report.md | 26 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++++++ .github/ISSUE_TEMPLATE/feature_request.md | 22 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..5e8f2e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Something in SimBLE behaves incorrectly +title: "" +labels: bug +--- + + + +## What happened + +## What you expected + +## Steps to reproduce + +## Environment + +- SimBLE version (`simblectl version` or the release tag): +- macOS version: +- Xcode and Simulator runtime: +- Role: central or peripheral + +## Logs or screenshots diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f8417c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Nirapod Labs + +blank_issues_enabled: false +contact_links: + - name: Security report + url: https://github.com/nirapod-labs/simble/security/advisories/new + about: Report a security vulnerability privately, not as a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..925b0e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest a capability for SimBLE +title: "" +labels: enhancement +--- + + + +## The problem + +## The capability you want + +## Alternatives you considered + +## Scope + +SimBLE is a faithful dev bridge to the Mac's Bluetooth radio, simulator-only and behind a fence. +Note anything the request would touch in the fence or the custody boundary. From 5042131c7623da13edaaf641b15afe653c4029d3 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:53 +0600 Subject: [PATCH 7/8] ci: publish the architecture doc to pages Build simble-architecture.qd with a pinned, checksum-verified Quarkdown and deploy it to GitHub Pages on docs changes, and add the docs badge. The repo's Pages source must be set to GitHub Actions once for the first deploy. --- .github/workflows/pages.yml | 58 +++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..1534475 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Nirapod Labs +# +# Build the architecture document with Quarkdown and publish it to GitHub Pages as the project site. +# Quarkdown ships a self-contained Linux build (bundled JRE), pinned by version and checksum here. +name: pages + +on: + push: + branches: [main] + paths: + - "docs/architecture/**" + - ".github/workflows/pages.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Serialize deploys; a newer push supersedes an in-flight one. +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + env: + QD_VERSION: "2.2.0" + QD_SHA256: "810b885087cdb41e07279311c0b0fb141d86ac89adf1b18533623d3c4eb97836" + steps: + - uses: actions/checkout@v4 + + - name: Fetch Quarkdown (pinned, checksum-verified) + run: | + url="https://github.com/iamgio/quarkdown/releases/download/v${QD_VERSION}/quarkdown-linux-x64.zip" + curl -fsSL "$url" -o quarkdown.zip + echo "${QD_SHA256} quarkdown.zip" | sha256sum -c - + unzip -q quarkdown.zip -d qd + + - name: Build the document + run: ./qd/quarkdown/bin/quarkdown c docs/architecture/simble-architecture.qd --strict --out build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: build/SimBLE-Architecture + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - id: deploy + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 4dda317..6470c7b 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs C Platforms: iOS and watchOS Simulators, macOS Latest release + Documentation

# SimBLE From f5d65ef5a2aa03ba1f9511fe53ecc9f3dc585009 Mon Sep 17 00:00:00 2001 From: athexweb3 Date: Tue, 30 Jun 2026 00:37:53 +0600 Subject: [PATCH 8/8] chore(release): v1.0.0 VERSION, package.json, and the simblectl version constant move to the stable 1.0.0 release. --- VERSION | 2 +- package.json | 2 +- tools/simblectl/Sources/SimBLECTLKit/Version.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 896b9bc..3eefcb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0-beta +1.0.0 diff --git a/package.json b/package.json index 40507b8..f36b32a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simble", "private": true, - "version": "1.0.0-beta", + "version": "1.0.0", "description": "Real Bluetooth Low Energy for the iOS and watchOS Simulators.", "author": "athexweb3", "license": "Apache-2.0", diff --git a/tools/simblectl/Sources/SimBLECTLKit/Version.swift b/tools/simblectl/Sources/SimBLECTLKit/Version.swift index 0cd8041..5238584 100644 --- a/tools/simblectl/Sources/SimBLECTLKit/Version.swift +++ b/tools/simblectl/Sources/SimBLECTLKit/Version.swift @@ -3,4 +3,4 @@ /// The simblectl marketing version. `release.sh` rewrites this; the CLI cannot read the VERSION /// file at runtime, so the constant carries it. -public let simblectlVersion = "1.0.0-beta" +public let simblectlVersion = "1.0.0"