OdbDesign is a C++ library for parsing and working with ODB++ design files (PCB manufacturing format). It uses CMake for building, vcpkg for dependency management, and GoogleTest for testing.
- Default branch:
development - Integration branch:
nam20485— every feature branch is cut fromnam20485and its PR targetsnam20485as the base. - Feature branch naming:
nam/<feature>(e.g.nam/argocd,nam/unified-clang-build).
Merge flow (left to right):
nam/<feature> → nam20485 → development (default) → staging → main → release
- CI triggers (CMake multi-platform, code coverage, dependency review, Docker publish) are configured for
["development", "staging", "main", "release", "nam20485"]; pushes tonam/*feature branches are NOT built — only PRs gate those (see.github/workflows/cmake-multi-platform.yml). releaseis fed frommain; the release workflow fires onrepository_dispatch.productionis a defunct legacy branch — abandoned, absent from every CI trigger list, and not part of the flow. Do not promote to it, merge from it, or treat its contents as current.
All merges are merge commits — gh pr merge --merge. Never squash and never rebase any PR, on any branch pair (nam/<feature> → nam20485 included).
- Rebase is fine only on a private, never-pushed branch. Once commits are published, merge them; rewriting published commits desyncs the merge base for everyone downstream.
- Squash is prohibited everywhere (directive 2026-09-10; the earlier "squash + delete-branch" allowance for feature PRs is revoked). Squash leaves no patch-equivalent commits behind, so the source branch reads as permanently unmerged and downstream merge bases desync — the same failure shape as the April 2026 replay described below.
- Enforced server-side since 2026-09-10: rulesets
169324(development),169360(main/release/staging/production) and186765(nam20485) all setpull_request.allowed_merge_methods: ["merge"]— the merge-method dropdown offers nothing else on those bases. UI trap to know: GitHub's "Update branch" split-button on an out-of-date PR offers "Update with rebase" in its dropdown — that is a rebase (it rewrites the PR branch server-side and force-pushes; commits get committerGitHub <noreply@github.com>). It is disabled by the merge-only ruleset on the bases above, but never use it anywhere: always click "Update branch" (merge). If a PR branch is rewritten anyway, remote is canonical — verifygit diff origin/<branch> <local>is empty andgit cherryshows no unique+commits before resetting local to remote.
Do not re-add required_linear_history to ruleset 169360 (covers main, release, staging, production). It rejects merge commits while that same ruleset's pull_request.allowed_merge_methods permits only ["merge"] — an unsatisfiable pair that makes those four branches unmergeable through any PR, leaving hand-rebase plus admin force-push as the only route in. That is what happened in April 2026: ~270 of development's commits were replayed onto main with new SHAs, so the branches reported ~320 ahead / ~500 behind each other while git cherry showed 259 of them were patch-identical. It also dragged the merge base back to 2025-07-08, making a later real merge cost 148 conflicted files. Removed 2026-09-08, when main and release were re-baselined onto development (rollback tags: backup/main-pre-rebaseline-20260908, backup/release-pre-rebaseline-20260908).
Required checks differ per branch — check before waiting on CI:
| Branch | Ruleset | Required status checks |
|---|---|---|
nam20485 |
186765 |
Codacy Static Code Analysis, CodeQL, Analyze (actions), Analyze (c-cpp), Analyze (javascript-typescript), dependency-review — no CMake builds |
development |
169324 |
CMake-Multi-Platform-Build × 3 — (windows-2022, x64-release), (ubuntu-24.04, linux-dynamic-release), (macos-14, macos-release) — plus Generate-Submit-SBOM, Codacy Static Code Analysis, dependency-review; strict: true, 1 approval |
main, release, staging, production |
169360 |
Same three CMake-Multi-Platform-Build legs as development, plus Generate-Submit-SBOM, Codacy Static Code Analysis, dependency-review and Docker-Build-and-Publish; code-owner review and last-push approval also required |
Two requirements on 169360 were unsatisfiable and were removed on 2026-09-08 — do not reintroduce either:
required_linear_history— see above; it contradictedallowed_merge_methods: ["merge"].- Required check
CMake-Multi-Platform-Build (ubuntu-24.04, linux-release)— no workflow produces that context. The matrix switched tolinux-dynamic-releasebecause thelinux-releasepreset loads two protobuf copies and SIGABRTs (seedocs/completed/linux-dynamic-release-plan.md). Verified against the 25 most recent CMake runs on every branch: all 21 that spawned the job reportedlinux-dynamic-release, zero reportedlinux-release. Withstrict_required_status_checks_policy: truethe context stayed permanently "expected", so PRs could never satisfy required checks. Renamed to(ubuntu-24.04, linux-dynamic-release).
169360 still requires 1 approval with require_code_owner_review and require_last_push_approval, which a solo maintainer cannot satisfy on their own PR — the last pusher is always the author. Promotions into main/release/staging/production therefore still need --admin. That is deliberate and was left in place on 2026-09-08.
For docs- or workflow-YAML-only changes the C++ builds cannot detect a regression; Analyze (actions) is the applicable check for workflow edits. gh pr merge --merge --admin is the pragmatic route in that case. Note tag.gpgsign = true locally and required_signatures is on every ruleset, so commits must be signed — and scripted git tag blocks on a passphrase prompt without a TTY (use git update-ref refs/tags/... for lightweight tags).
- CMake 3.21+
- vcpkg (set
VCPKG_ROOTenvironment variable) - C++17 compiler (MSVC, GCC, or Clang)
cmake --preset linux-debug # Debug build
cmake --preset linux-dynamic-release # Release build (main Linux release; shared protobuf/gRPC)cmake --preset x64-debug # Debug build
cmake --preset x64-release # Release buildcmake --build --preset linux-debug
cmake --build --preset linux-dynamic-release
cmake --build --preset x64-releasecmake --build --preset linux-debug --clean-firstctest --preset linux-debug
ctest --preset linux-dynamic-release
ctest --preset x64-debug# Using ctest with test name pattern
ctest --preset linux-debug -R <TestName>
# Example: Run specific test
ctest --preset linux-debug -R BasicAssertions
ctest --preset linux-debug -R Test_DesignOdb
# Run tests with verbose output
ctest --preset linux-debug -R <TestName> -V
# Run test executable directly
./out/build/linux-debug/OdbDesignTests/OdbDesignTests --gtest_filter=<TestSuite>.<TestName>
# Example: Run single test directly
./out/build/linux-debug/OdbDesignTests/OdbDesignTests --gtest_filter=TestTest.BasicAssertions
./out/build/linux-debug/OdbDesignTests/OdbDesignTests --gtest_filter=FileArchiveLoadFixture.Test_SampleDesign*ctest --preset linux-debug -j$(nproc)- The project uses SonarLint integration via
compile_commands.json - Compilation database is generated automatically by CMake (
CMAKE_EXPORT_COMPILE_COMMANDS ON) - Compiler warnings enabled:
-Wall -Wextra -Wpedantic(GCC/Clang),/W4(MSVC)
| Element | Convention | Example |
|---|---|---|
| Classes/Structs | PascalCase | Design, FileArchive, NetRecord |
| Functions/Methods | PascalCase | GetNets(), ParseFileModel(), BuildNets() |
| Member variables | m_ prefix + camelCase | m_name, m_pFileModel, m_netsByName |
| Local variables | camelCase | pNetRecord, componentNumber |
| Constants | SCREAMING_SNAKE_CASE or inline static | NONE_NET_NAME, COMMENT_TOKEN |
| Namespaces | PascalCase, nested | Odb::Lib::ProductModel, Odb::Test |
| Enums | PascalCase for enum, UPPER for values | BoardSide::Top, Polarity::Positive |
| Type aliases | PascalCase + typedef | Vector, StringMap |
OdbDesign/
├── OdbDesignLib/ # Main library
│ ├── FileModel/ # File parsing models
│ │ └── Design/ # ODB++ design file parsers
│ ├── ProductModel/ # High-level design objects
│ ├── App/ # Application utilities (cache, routes)
│ └── protoc/ # Protocol buffer definitions
├── OdbDesignServer/ # gRPC server
├── OdbDesignApp/ # CLI application
├── OdbDesignTests/ # Test suite
│ └── Fixtures/ # Test fixtures
└── Utils/ # Shared utilities
#pragma once // Always use #pragma once, not include guards
// Includes: relative paths from component root
#include "../odbdesign_export.h"
#include <string>
#include <memory>
#include <vector>
namespace Odb::Lib::ProductModel
{
class ODBDESIGN_EXPORT Design : public IProtoBuffable<Protobuf::ProductModel::Design>
{
// ...
};
}#include "Design.h" // Own header first
#include "Package.h"
#include "Logger.h"
#include "../enums.h"
#include <memory>
namespace Odb::Lib::ProductModel
{
// Implementation...
}- Corresponding header (e.g.,
Design.cppincludes"Design.h"first) - Project headers (relative paths)
- System/STL headers (
<string>,<vector>,<memory>) - External library headers (
<gtest/gtest.h>)
- Use
std::shared_ptrfor shared ownership - Use
std::unique_ptrfor exclusive ownership - Use
std::make_sharedandstd::make_unique
auto pDesign = std::make_shared<Design>();
auto pMessage = std::make_unique<Protobuf::ProductModel::Design>();- Return
boolfor success/failure in parsing/building methods - Use
nullptrchecks for pointer parameters - Use custom
parse_errorexception for file parsing errors
bool Design::Build(std::shared_ptr<FileModel::Design::FileArchive> pFileModel)
{
if (pFileModel == nullptr) return false;
// ...
}
// Parsing errors
throw_parse_error(m_path, line, token, lineNumber);- Mark getters as
const - Use
const auto&for iterating over collections
const Net::Vector& GetNets() const;
const std::string& GetName() const;
for (const auto& pNet : m_nets) { /* ... */ }Define Vector and StringMap typedefs for container types:
typedef std::vector<std::shared_ptr<Design>> Vector;
typedef std::map<std::string, std::shared_ptr<Design>> StringMap;Use constexpr inline static for class constants:
constexpr inline static const char* NONE_NET_NAME = "$NONE$";
constexpr inline static bool CLIP_FILEMODEL_AFTER_BUILD = false;class TestDataFixture : public testing::Test
{
public:
TestDataFixture();
protected:
virtual void SetUp() override;
virtual void TearDown() override;
static std::filesystem::path getTestDataDir();
};
TEST_F(TestDataFixture, TestDataDirDirectoryExists)
{
EXPECT_TRUE(exists(getTestDataDir()));
}- Pattern:
<Feature>_<Scenario>_<ExpectedResult> - Example:
Test_DesignOdb_RigidFlexDesign_CanHasCorrectData
- Use
ASSERT_*for fatal assertions (stops test on failure) - Use
EXPECT_*for non-fatal assertions (continues test)
ASSERT_TRUE(success); // Fatal
EXPECT_EQ(actual, expected); // Non-fatal
ASSERT_NE(findIt, map.end()); // Fatal - must find elementTest data is located via ODB_TEST_DATA_DIR environment variable.
Test designs are in .tgz format (ODB++ archives).
See .cursor/rules/openmemory.mdc for memory-based development workflow instructions.
See .github/copilot-instructions.md for:
- Remote instruction modules at
nam20485/agent-instructions - Tool and automation protocols
- Dynamic workflow orchestration
- URL translation for raw GitHub content
- Keep OdbDesign local test env vars in
~/.bashrc(ODB_TEST_DATA_DIR,ODB_TEST_ENVIRONMENT_VARIABLE) for routinectestruns on this machine. - The vcpkg upgrade to baseline
4f6d4ae8(grpc 1.81.1,protobuf 6.33.4#2) is the accepted target; revert future baseline bumps only if they break the gRPC/protobuf build.
- Release-only SIGABRT on
GET /filemodels/<design>/matrix/matrixcomes from dual static protobuf descriptor pools inlibOdbDesign.soandOdbDesignServer; uselinux-dynamic-debug/linux-dynamic-releasepresets (VCPKG_TARGET_TRIPLET=x64-linux-dynamic) for a single shared protobuf runtime. - Target
grpcinvcpkg.jsonmust include featurecodegenso fresh triplet installs exportgRPC::grpc++_reflection(host-only codegen is insufficient). - vcpkg baseline is
4f6d4ae8247b2dcae554555a135e52bb449dd524(supersedes the oldd1ff36c/grpc 1.71.0#3pin); it resolves toprotobuf 6.33.4#2,grpc 1.81.1,zlib 1.3.2#2,libarchive 3.8.7,crow 1.3.3and compiles cleanly. The earlier059d760baseline's gRPC break (glob.ccstd::any_of) does not recur here.vcpkg.jsonoverridespin these exactversion#port-versionstrings. - The
"version": "X.Y.Z#N"form (port-version embedded in the version string) is valid inoverrides; do not split it into separateversion+port-versionfields. - Pin the exact
version#port-versionfor every override that has a non-zero port-version (e.g.zlib 1.3.2#2, not1.3.2); a missing/wrong port-version resolves to a different build and forces vcpkg to rebuild the whole dependency chain from source. - After a protobuf major bump (e.g. 29→33), stale generated
.pb.h/.pb.ccfrom the old protoc fail with "Protobuf C++ gencode is built with an incompatible version" / missingmap_field_inl.h; wipe the build dir (keepvcpkg_installed/) and reconfigure so protoc regenerates them — don't try to compile stale gencode. - Local test fixtures live in sibling repo
OdbDesignTestData: setODB_TEST_DATA_DIR=/home/nam20485/src/github/nam20485/OdbDesignTestData/TEST_DATA; design.tgzarchives atTEST_DATA/root, small file-reader fixtures underTEST_DATA/FILES/. - Also set
ODB_TEST_ENVIRONMENT_VARIABLE=ODB_TEST_ENVIRONMENT_VARIABLE_EXISTSfor CrossPlatform env tests; these vars are not baked into CMake presets. OdbDesignServerloads designs via--designs-dir, notODB_TEST_DATA_DIR.CommandLineArgstreats tokens starting with/as flags, so absolute paths after--designs-dirparse as booleantrue; use relative paths (e.g. from/home/nam20485/src/github/nam20485:OdbDesignTestData/TEST_DATA).
- OdbDesign services deploy to the single-node k3s cluster on
debian13vm(Tailscale100.118.225.119, LAN192.168.122.200). Until the GitOps migration lands,scripts/deploy.ps1remains the manual mechanism; target state is Argo CD GitOps — seedocs/plan/argocd-gitops-handoff.md(platform handoff) anddocs/plan/argocd-deployment-plan.md(execution plan). argocdCLI (v3.5.2,~/.local/bin/argocd) is logged in on this machine (contextdebian13vm.tail11ba79.ts.net/argocd); it works only from tailnet devices. Re-auth withargocd relogin, orargocd login debian13vm.tail11ba79.ts.net --username admin --grpc-web --grpc-web-root-path /argocd(both flags required — Traefik rootpath).- An Argo CD MCP server is configured in
.zcode/config.json(stdio,argocd-mcp@0.9.0,ARGOCD_BASE_URLset; identical to the platform repo's config). Authentication is environment inheritance, not config: the server process picks upARGOCD_API_TOKENfrom the ZCode process environment (~/.api-keys-export.sh, platformmcpaccount). ZCode must be launched with the var exported and restarted to load the server. - The
mcpaccount is read-only (role:readonly): MCP mutations (sync_application,create/update/delete_application,run_resource_action) are denied by design. Token policy: 1-year expiry — rotate withargocd account generate-token --account mcp --expires-in 8760h, update~/.api-keys-export.sh, restart ZCode. Platform source of truth:linux-system-agent.agents/rules/tools.md. .zcode/is gitignored — keep MCP configs credential-free; never commit tokens.- Agent rule: MCP is a read-only view; early sync-triggering via the CLI; manifest/Application changes go through git on the
nam20485deploy branch. Neverkubectl apply/argocd app create/argocd app sync --localagainst app-managed resources — Argo CD selfHeal reverts them.