Skip to content

test: guard src/ against platform, store and package names (RFC #406 enforcement) - #417

Merged
apotema merged 6 commits into
developmentfrom
feat/agnostic-guard
Sep 26, 2026
Merged

apotema merged 6 commits into
developmentfrom
feat/agnostic-guard

Conversation

@apotema

@apotema apotema commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

What

PR 3 of the RFC #406 follow-up: the agnosticism guard the RFC's "Enforcement" section promises, modelled on labelle-bgfx's src/heap_guard_test.zig.

Mechanism

src/agnostic_guard_test.zig walks every .zig file under src/ at test time (cwd pinned to the repo root by build.zig), splits each line into [A-Za-z0-9]+ runs, lowercases them and compares against the forbidden table: android ios wasm emsdk emscripten steam itch xcode gradle apk aab ndk raylib sokol sdl sdl2 bgfx wgpu. Everything else is a word boundary, so ios_cmd and cli/android/ flag while std.Io and biosphere do not. Comments count (the mandate is textual). Host OS names (macos windows linux darwin win32) are permanently allowed; a comptime check keeps the two tables disjoint.

A finding prints as:

src/<path>:<line>: '<word>' (platform/store/package names belong in providers; see docs/rfc-package-commands.md#enforcement)

The test also fails when fewer than 60 files were scanned (a wrong cwd must not pass vacuously).

Allowlist: 43 entries, shrink-only

Files that still carry legacy platform code sit on allowed_files: 42 files plus the cli/android/ directory prefix (covering 8 files), recomputed by running the scanner on this branch rather than copied from the plan. Compared with the plan's list, three more files are dirty here: cli/linux_desktop.zig, cli/progress.zig, cli/status.zig (all name bgfx/raylib/sokol).

The list can only shrink: an allowlisted file that no longer contains any forbidden word fails the test with src/<f> is on the migration allowlist but is clean; remove it. New files (PR 1's provider_hooks.zig, PR 2's provider_targets.zig) are not on it and must stay clean.

Self-tests (mechanism, not just value)

  • tokenizer: std.Io ios_cmd biosphere Android cli/android/run.zig SDL2_image yields exactly ios android android sdl2; wasmtime bgfxdebug libsdl yields nothing
  • host words never flag, in either case
  • allowlist matches Windows-style walker paths (cli\android\run.zig) and directory prefixes, and rejects cli/androidx/… and the provider files
  • an in-memory Scan produces the documented finding lines for a non-allowlisted file and marks a clean allowlisted entry stale

Build wiring

addAgnosticGuard(b, test_step, optimize) mirrors bgfx's addHeapGuard (host target, setCwd(b.path("."))): part of zig build test on all three CI hosts, plus a standalone zig build test-guard.

Verification

  • zig build test-guard: exit 0 (5/5)
  • zig build test: exit 0 (13/13 steps, 964 passed, 5 skipped)
  • bite check: appending // … Android … to the clean src/cli/provider_manifest.zig makes zig build test-guard exit 1 with src/cli/provider_manifest.zig:156: 'android' (…); reverted

Docs: docs/rfc-package-commands.md "Enforcement" now points at the file and states the shrink rule. The RFC's acceptance check "guard passes with only host OS names" stays open until extraction.

https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…enforcement)

Add src/agnostic_guard_test.zig, modelled on labelle-bgfx's heap guard:
it walks every .zig file under src/, splits each into [A-Za-z0-9]+ runs
and flags platform/store/package/backend names (android, ios, wasm,
emsdk, emscripten, steam, itch, xcode, gradle, apk, aab, ndk, raylib,
sokol, sdl, sdl2, bgfx, wgpu) case-insensitively. Host OS names (macos,
windows, linux, darwin, win32) are permanently allowed.

Files that still carry legacy platform code sit on a migration allowlist
(43 entries: 42 files plus the cli/android/ prefix, recomputed on this
branch) that can only shrink: the test also fails when an allowlisted
file is clean, so the entry must be removed in the same change.

build.zig wires it into `zig build test` via addAgnosticGuard (host
target, cwd = repo root) and adds a standalone `zig build test-guard`.
The RFC's Enforcement section points at the file and the shrink rule.

Claude-Session: https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 82d76c08-6709-49a5-9b83-a4100ed31118

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T14:34:30.949916Z 01a39b3 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32d8c27cdc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agnostic_guard_test.zig Outdated
var scan: Scan = .{ .gpa = gpa };
defer scan.deinit();
while (try walker.next(io)) |entry| {
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan non-Zig source files for forbidden names

The .zig filter leaves compiled C sources and headers completely outside the guard, despite the RFC requiring forbidden names not to appear under src/. This already produces a false clean result: src/cli/stb_image_impl.c:6 contains bgfx, while src/cli/stb_image.h:270 contains iOS and Android, but neither file is explicitly allowlisted or reported. Scan applicable C/header files as well and place existing exceptions on the migration allowlist.

Useful? React with 👍 / 👎.

Comment thread src/agnostic_guard_test.zig Outdated
"cli.zig",
"cli/add.zig",
"cli/android.zig",
"cli/android/",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enumerate legacy Android files instead of exempting the directory

Using a directory-prefix allowlist means every future .zig file under src/cli/android/ is automatically exempt, even though it was never part of the legacy baseline; the self-test explicitly confirms this with not_yet_written.zig. As long as any one file in the directory contains a forbidden token, the single dirty bit also prevents the prefix from becoming stale, so new platform-specific code can accumulate while the documented “file allowlist can only shrink” check remains green. List the eight existing files individually instead.

Useful? React with 👍 / 👎.

Comment thread src/agnostic_guard_test.zig Outdated
Comment on lines +129 to +130
while (self.pos < self.text.len and isWordByte(self.text[self.pos])) self.pos += 1;
if (classify(self.text[start..self.pos])) |word| return word;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Tokenize CamelCase identifiers before classifying them

Treating the entire alphanumeric run as one token misses the idiomatic Zig names most likely to carry these identities, such as AndroidProvider, WasmConfig, or IosConfig. This already lets src/cli/config.zig:78 mention the non-allowlisted IosConfig while the guard reports no offender, and an allowlisted file can similarly become “clean” while retaining platform-specific types. Recognize case transitions/acronym boundaries before comparing against the forbidden names.

Useful? React with 👍 / 👎.

Comment thread src/agnostic_guard_test.zig Outdated
for (scan.offenders.items) |o| std.debug.print("{s}\n", .{o});
for (stale.items) |s| std.debug.print("src/{s} is on the migration allowlist but is clean; remove it\n", .{s});
// The walk really covered the tree (a wrong cwd would scan nothing and pass).
try std.testing.expect(scan.scanned > 60);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the source-count threshold with a stable sentinel

The guard fails every otherwise-clean checkout once src/ contains 60 or fewer Zig files, even though that is a valid outcome of the RFC's planned extraction of platform code into provider packages. The current tree has only 79 such files, so moving or consolidating enough legacy modules will make zig build test fail solely because of this magic number. Verify a stable repository sentinel instead; opening src already prevents the ordinary wrong-working-directory case from passing vacuously.

Useful? React with 👍 / 👎.

Four Codex findings, all valid against the tree merged with development:

1. Scan C sources and headers too. The walk now reads `.zig`, `.c` and
   `.h` under src/, so the vendored stb files are covered; the JSON wire
   fixtures under cli/provider_contract/ stay out of scope as data, not
   source (documented in the header). New allowlist entries:
   cli/stb_image.h, cli/stb_image_impl.c.

2. Enumerate the eight cli/android/*.zig files individually. Directory
   prefix support is removed from the allowlist matcher, so a new file
   under that directory is no longer exempt; the self-test now asserts
   `not_yet_written.zig` is NOT covered.

3. Tokenize CamelCase. Alphanumeric runs split at lowercase->uppercase
   and digit->uppercase transitions and at acronym boundaries, digits
   staying attached (IosConfig -> ios; SDL2Provision -> sdl2, provision;
   getSDLPath -> get, sdl, path), while std.Io, Iostream, biosphere and
   wasmtime do not flag. Self-tests cover exactly these. Newly dirty:
   cli/config.zig (IosConfig).

4. Replace the `scanned > 60` threshold with a stable sentinel: the walk
   must see src/cli.zig (the exe root in build.zig) or the test fails
   with a clear message instead of passing vacuously.

Allowlist recomputed: 53 file entries, shrink-only rule and message
formats unchanged.

Claude-Session: https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4e9d09080

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +207 to +210
fn file(self: *Scan, path: []const u8, bytes: []const u8) !void {
if (std.mem.eql(u8, path, cli_root)) self.saw_cli_root = true;
const allowed = allowedIndex(path);
var line_no: usize = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan forbidden words in source paths

Tokenization is applied only to bytes, while path is used solely for allowlist lookup. Consequently, a clean file such as src/cli/android/run.zig is considered stale and, after its allowlist entry is removed, passes despite retaining a forbidden platform name in the core tree; a newly added src/cli/steam/upload.zig can pass for the same reason. Scan the relative path as well as the contents so moving or renaming platform-specific files remains part of the migration.

Useful? React with 👍 / 👎.

Comment on lines +153 to +155
if (!std.ascii.isUpper(cur)) return false;
if (std.ascii.isLower(prev) or std.ascii.isDigit(prev)) return true;
return i + 1 < text.len and std.ascii.isLower(text[i + 1]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the conventional iOS spelling during tokenization

When a source contains the conventional spelling iOS (or an identifier such as iOSConfig), the lowercase-to-uppercase rule splits it into i and OS, so neither piece matches the forbidden ios entry. This lets new, non-allowlisted code name the iOS platform while the guard stays green; classify exact runs or otherwise handle lowercase-leading acronyms before applying CamelCase boundaries.

Useful? React with 👍 / 👎.

Comment thread src/agnostic_guard_test.zig Outdated
Comment on lines +28 to +30
"android", "ios", "wasm", "emsdk", "emscripten", "steam",
"itch", "xcode", "gradle", "apk", "aab", "ndk",
"raylib", "sokol", "sdl", "sdl2", "bgfx", "wgpu",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the web provider name to the forbidden set

The RFC explicitly moves the web implementation into the labelle-web provider and says only host OS names should remain after migration, but neither web nor labelle-web can match this table. A new non-allowlisted core file can therefore contain web, WebProvider, or labelle-web and still pass the guard, even though the equivalent Android/provider identities are rejected.

Useful? React with 👍 / 👎.

Three Codex findings on b4e9d09:

- Scan the relative path too. Directory and file-name segments go through
  the same tokenizer and report as `src/<path>: '<word>' in path (...)`,
  so a clean-bodied `src/cli/steam/upload.zig` is a finding and the
  `cli/android/*.zig` and `cli/ios.zig` entries stay dirty until the file
  is moved or renamed, not merely emptied of platform words.
- Lowercase-leading acronyms. Each `[A-Za-z0-9]+` run is classified whole
  before it is split into CamelCase pieces, and a run that opens with one
  lowercase letter followed by two or more uppercase ones keeps its
  acronym as one piece: `iOS` -> `ios`, `iOSConfig` -> `iOS|Config`,
  `iosConfig` -> `ios|Config`, while `iostream`, `std.Io` and `IoReader`
  still pass.
- `web` joins the forbidden set (RFC #406 moves it to the `labelle-web`
  provider); `wasm`/`emsdk` stay.

The allowlist was recomputed against the tree merged with development
(#415): still 53 entries, every file the new rules reach was already
listed. Self-tests cover the path hit, the acronym cases, `web`, and the
shrink-only rule under the path scan.

Claude-Session: https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e8d7c6119

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +147 to +151
if (run.len > max_word_len) return null;
var buf: [max_word_len]u8 = undefined;
const lower = std.ascii.lowerString(buf[0..run.len], run);
for (allowed_words) |a| if (std.mem.eql(u8, a, lower)) return null;
for (forbidden) |f| if (std.mem.eql(u8, f, lower)) return f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize forbidden names with numeric suffixes

Because classification requires an exact table match and digits remain attached to the preceding token, common platform identities such as wasm32, android14, and sdl3 all return clean. A new non-allowlisted source file can therefore hard-code wasm32—the architecture used by the web provider—without tripping this guard; preserve exact allowed forms such as win32, but classify a forbidden root before an all-numeric suffix.

Useful? React with 👍 / 👎.

Comment thread src/agnostic_guard_test.zig Outdated
Comment on lines +33 to +36
const forbidden = [_][]const u8{
"android", "ios", "web", "wasm", "emsdk", "emscripten", "steam",
"itch", "xcode", "gradle", "apk", "aab", "ndk", "raylib",
"sokol", "sdl", "sdl2", "bgfx", "wgpu",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the remaining Apple target platforms to the denylist

The forbidden table includes ios but omits the related target platforms tvos, watchos, visionos, and maccatalyst, even though these exact spellings already occur in src/astc/cmd.zig. A new non-allowlisted core file can therefore branch on any of those provider-specific targets while the guard remains green; unlike macos, these are not supported host OS names covered by the permanent exception.

Useful? React with 👍 / 👎.

Round-3 review on #417 (two Codex findings):

- A token spelled `<letters><digits>` (`wasm32`, `android14`, `sdl3`,
  `bgfx2`, the CamelCase piece `Wasm32` of `Wasm32Target`) returned clean
  because classification was an exact table match. `classify` now tries the
  exact spelling first and then the letter root before an all-digit suffix.
  The permanent host allowlist keeps its exact forms: `win32` is allowed as
  spelled and `win` is not forbidden, so `win32`, `x86_64`, `utf8`,
  `base64` and `sha256` stay clean; a self-test asserts both sides.
- `tvos`, `watchos`, `visionos` and `maccatalyst` join `ios` on the
  forbidden table; `macos` stays a permanently allowed host.

Allowlist recomputed: unchanged (53 entries); every file the new rules
reach (`astc/cmd.zig`, `cli/doctor.zig`, `cli/sdl_provision.zig`, ...) was
already listed, and no entry went stale.

Claude-Session: https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01a39b3a39

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

test "the tokenizer flags whole alphanumeric runs, case-insensitively" {
try expectWords("std.Io ios_cmd biosphere Android cli/android/run.zig SDL2_image", &.{ "ios", "android", "android", "sdl2" });
// Substrings inside a longer lowercase run never match: `wasm` is not a word here.
try expectWords("wasmtime bgfxdebug libsdl", &.{});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect provider names inside conventional compound words

When a new core file uses conventional provider-specific names such as Steamworks or the Linux package libsdl2-dev, the tokenizer sees the runs Steamworks and libsdl2; CamelCase splitting does not extract steam, and numeric-root classification reduces the latter only to libsdl. Both therefore pass even though the RFC assigns Steamworks and SDL provisioning to providers. Add these known compound/package forms or recognize their conventional affixes rather than treating every libsdl run as clean.

Useful? React with 👍 / 👎.

var scan: Scan = .{ .gpa = gpa };
defer scan.deinit();
while (try walker.next(io)) |entry| {
if (entry.kind != .file or !isSource(entry.path)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect or reject source symlinks

On platforms where the directory walker reports symlinks as .sym_link, a committed link such as src/cli/steam.zig is skipped by this condition even though Zig can import it and read its target. Consequently both the forbidden path and any platform-specific contents behind a source symlink bypass the guard; follow source links safely or fail when a source-shaped symlink is encountered.

Useful? React with 👍 / 👎.

"android", "ios", "web", "wasm", "emsdk", "emscripten", "steam",
"itch", "xcode", "gradle", "apk", "aab", "ndk", "raylib",
"sokol", "sdl", "sdl2", "bgfx", "wgpu", "tvos", "watchos",
"visionos", "maccatalyst",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deny the provider tools named by the RFC

The RFC explicitly assigns the butler executable to the itch provider and UIKit glue to the iOS provider, but neither butler nor uikit appears in this table. A new non-allowlisted core file can therefore invoke butler or reference UIKit without using the words itch or ios, and the guard reports it as clean; add the provider-specific tools and SDK identities already enumerated by the RFC.

Useful? React with 👍 / 👎.

@apotema
apotema merged commit 26d93c7 into development Sep 26, 2026
14 checks passed
@apotema

apotema commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Merged on green after three review-fix rounds. The three remaining P2 findings from the last pass are tracked in #419.

https://claude.ai/code/session_01R2K5sJDL5Msn1uRTkybiDP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant