docs(agents): redact private email from Git author identity section#44
Closed
leodido wants to merge 84 commits into
Closed
docs(agents): redact private email from Git author identity section#44leodido wants to merge 84 commits into
leodido wants to merge 84 commits into
Conversation
feat(api): unify `Check()` requirements model
feat(check): align enum gate features with existing probes
feat(check): promote bpf stats gate
refactor(api): normalize FS naming and record decisions
docs(api): codify check/probe review checklist
…ediation fix(check): add remediation text for program-type feature gates
…diation fix(check): use deterministic remediation for lsm probe failures
…mediation fix(check): improve remediation for parameterized requirements
docs(probe): mention BPF stats in `WithCapabilities`
Add an empty .githooks/prepare-commit-msg so tools that try to append trailers/signatures via this hook have no effect, and point core.hooksPath at .githooks from the dev container so the opt-out applies on container creation.
Use the official devcontainers/go:1.24-bookworm image so the toolchain matches go.mod and 'make build' / 'make test' work out of the box.
The probe behind FeatureBPFFS and FeatureTraceFS used os.Stat + IsDir, returning Supported=true on any host where the directory existed. On modern systemd distros /sys/fs/bpf and /sys/kernel/tracing exist by default whether or not bpffs/tracefs is actually mounted, so callers gating on these features (e.g. before bpf_obj_pin) silently got a false positive and failed at I/O time. Switch the probe to Statfs + superblock magic comparison (BPF_FS_MAGIC, TRACEFS_MAGIC). Introduce a single internal checkMount helper with an injectable statfs implementation so unit tests can exercise all branches without privileged operations. Diagnostic-only fields SystemFeatures.DebugFS and .SecurityFS keep their previous presence-only semantics (no gated Feature* exists for them today).
Introduces MountRequirement and RequireMount(path, magic) for gating on arbitrary filesystem mounts (path + superblock magic). Fills the gap where FeatureBPFFS / FeatureTraceFS are too restrictive: bpffs at a non-default path, cgroupv2, debugfs, tmpfs in tests, etc. Backed by the same internal checkMount helper introduced for the bug fix in #32, so there is a single Statfs implementation for both the preset features and the parameterized requirement. Tests cover three layers: Layer 1 (always-on, unprivileged): unit tests with the injected statfs fake exercise matching magic, mismatch, missing path, errno wrapping, dedup of identical {path, magic} pairs, and distinct pairs. Layer 2 (Linux integration job, root): a //go:build linux && integration test mounts a real tmpfs in t.TempDir() and asserts RequireMount agrees with the kernel's actual f_type. Skips politely when not root or sandboxed. Layer 3 (Linux integration job, root): a bats suite cross-checks CLI exit codes for 'check --require bpf-fs' / 'trace-fs' against the runner's real mount state via stat -f. A new 'integration' job in ci.yml runs the Layer 2 + Layer 3 tests on ubuntu-latest with sudo (runneradmin has CAP_SYS_ADMIN). The existing 'test' job is unchanged - the integration build tag is opt-in.
Reject the two misuse-on-the-API-edge cases the constructor cannot
service:
- empty path: cannot be statfs'd; would surface a confusing ENOENT
or EINVAL deep inside Check rather than at the call site.
- zero magic: does not correspond to any known filesystem type and
would silently mismatch every real mount.
Both failure modes panic at construction time, matching Go's
convention for misuse-of-API errors (see regexp.MustCompile,
template.Must). MountRequirement remains a plain struct, so callers
who need to assemble one programmatically can still do so via direct
field assignment and document their own validation discipline.
…gPath The Statfs assertion on a nonexistent path needs no privilege, and the //go:build integration tag already scopes execution to the dedicated CI job. The os.Geteuid() guard only added noise.
- Guard mktemp -d failure so a missing TMPFS_DIR cannot trip teardown. - Run rmdir unconditionally (gated on directory existence) so a failed mount still cleans up the empty tempdir mktemp left behind. - Only umount when the path actually became a mountpoint.
Captures two recurring footguns that have bitten automated commits in this repo: - Pushes using the maintainer's private gmail address are rejected by GitHub (GH007). All commits and tags must use the noreply identity 120051+leodido@users.noreply.github.com. - README.md is user-facing; CONTRIBUTING.md owns API governance (Check vs ProbeWith, addition checklist, FromELF contract, gated-vs-probe-only classification). Agents should not regress this split when editing docs. Also documents the tag-driven release flow and the no-commit/push- without-approval rule.
Reorders the README so the first thing readers see after the pitch is how to use the library. Adds runnable snippets for the features that were claimed but never demonstrated: Diagnose, FromELF, and FeatureGroup. Adds a RequireMount example, a stability statement, and links to pkg.go.dev / CHANGELOG / LICENSE. The API model, feature-addition checklist, FromELF contract, and classification snapshot move to a new CONTRIBUTING.md. They are API governance content for contributors, not end-user documentation; the README is no longer the right home for them. Comparison and detect tables updated to cover IMA active measurement, RequireMount, and FromELF — capabilities that already existed but were not surfaced.
- Use shields.io flat-square style for all badges (replaces the default rounded look and the upstream pkg.go.dev/CI badges that did not honor the style param). - Replace ✅/❌ in the comparison table with ✓/✗ — same semantics, lighter visual weight, no emoji rendering quirks across terminals and grayscale renderers.
Two cells were stretching every column far wider than the symbol grid
needed:
- The kfeatures cell on the map/helper row carried '(as parameterized
requirements in Check(...))'.
- The Selective probing row used prose ('Per-function',
'All-or-nothing') instead of marks.
Replace both with footnote markers (\u2020 and \u2021/\u00a7) and inline the prose
under the table as <sup> footnotes. Promote the existing BTF asterisk
to the same scheme. All non-Capability columns are now uniformly the
width of a single \u2713 / \u2717.
Pure dependency bump from v0.11.0. The CLI compiles and tests pass unchanged; no API in our codebase moved. This is intentionally behavior-neutral so adoption of the new capabilities can be evaluated and landed individually in follow-up PRs (see PR description).
Wires structcli.SetupJSONSchema onto the root command so any subcommand (or root itself) can dump its JSON Schema with --jsonschema, and the full command tree with --jsonschema=tree. Output is a JSON Schema describing every flag (type, default, description, enum constraints) and structcli annotations covering shorthand, env-var bindings, and field paths — letting agents and automation tooling discover the CLI surface without scraping --help. Root needs an explicit RunE (deferring to Help()) so cobra does not short-circuit before structcli's PreRunE interceptor can fire on a bare 'kfeatures --jsonschema' invocation. Bare 'kfeatures' still prints help and exits 0; observable behavior is unchanged. Bats coverage added for the four user-visible modes: - kfeatures --jsonschema (root, single) - kfeatures \u003csub\u003e --jsonschema (subcommand, single) - kfeatures --jsonschema=tree (full subtree) - kfeatures --jsonschema=xml (rejects unknown values)
Pure dependency bump from v0.16.1. The CLI compiles and tests pass unchanged; no source files outside go.mod / go.sum / CHANGELOG.md moved. Behavior-neutral. v0.17.0 introduces Bind / Setup / ExecuteC ergonomics and AI-native capabilities (WithMCP, WithFlagErrors, structured errors, semantic exit codes). Adoption is intentionally split into follow-up PRs so each change can be reviewed on its own merits.
structcli v0.17.0 collapses the per-subcommand boilerplate (Attach method, PreRunE-with-Unmarshal, explicit Attach call after AddCommand) into a single Bind call. Setup orchestrates the optional capabilities (today: --jsonschema; PR 3: WithMCP, WithFlagErrors). ExecuteC runs the auto-bind pipeline (config -> unmarshal -> validate) for every Bind-registered command and is required when Bind is used. Concretely: - Drop ProbeOptions.Attach, CheckOptions.Attach, ConfigOptions.Attach. - Drop the three PreRunE closures that called structcli.Unmarshal. - Replace the three opts.Attach(cmd) calls with structcli.Bind(cmd, opts). - Replace SetupJSONSchema(...) with Setup(root, WithJSONSchema(...)). - Replace root.Execute() with structcli.ExecuteC(root). CheckOptions still uses the custom-flag triad (DefineRequire / DecodeRequire / CompleteRequire). Bind discovers them by reflection on *CheckOptions, so the require-flag completion and parsing keep working without an explicit Attach. Behaviour-neutral: ExecuteC silences cobra's own error output, so the caller renders 'Error: <msg>' itself to match the pre-refactor format. PR 3 swaps this in for ExecuteOrExit (structured JSON errors plus semantic exit codes).
Wire structcli.WithFlagErrors() and replace the post-refactor bridge
(ExecuteC + plain 'Error: …' + os.Exit(1)) with structcli.ExecuteOrExit.
Invocation errors (missing required flag, unknown flag, invalid flag
value, unknown subcommand) now emit a single JSON line on stderr and
exit with a semantic code from structcli/exitcode (input 10–19,
config/env 20–29, runtime 1–9) instead of cobra's plain string +
exit 1. The shape (error, exit_code, message, flag, got, command,
available, …) is documented by structcli's StructuredError and lets
agents self-correct without scraping --help.
Business outcomes from `kfeatures check` (FeatureError) keep their
existing contract: --json prints {ok,feature,reason} on stdout, the
human path prints 'FAIL: feature — reason' on stderr, both exit 1.
The check RunE now bypasses structcli's envelope explicitly with a
comment so reviewers know the carve-out is deliberate.
bats: added 4 structured-error assertions in cli_common.bats locking
exit codes 10/11/12/14 and the JSON shape; updated the legacy-alias
test to match the JSON-escaped substring.
Co-authored-by: Ona <no-reply@ona.com>
Add --mcp persistent flag turning kfeatures into a Model Context
Protocol server over stdio. Each runnable leaf command becomes an
MCP tool whose input schema mirrors the cobra flag set; agents
introspect via tools/list and invoke via tools/call without scraping
--help. Pure stdlib JSON-RPC inside structcli — no extra heavy SDK.
Tools exposed: probe, check, config. Excluded: version (build
metadata is in MCP serverInfo) and completion-* (no agent value).
mcpServerVersion() mirrors the human-facing version output so MCP
clients can correlate tool output with build metadata, falling back
to 'dev' when built without ldflags.
Stream routing: every command handler now writes through
cmd.OutOrStdout() / cmd.ErrOrStderr() instead of bare os.Stdout /
os.Stderr / fmt.Print*. Required so structcli's per-call buffer
capture works; non-MCP behaviour is bit-for-bit identical because
cobra's OutOrStdout() resolves to os.Stdout when no SetOut was
called. printJSON gained an io.Writer parameter.
Session survival: added inMCPMode(c) helper that detects MCP
execution (Out swapped from os.Stdout) and replaces os.Exit(1) with
return err for FeatureError (in check) and 'kernel config not
available' (in config). os.Exit would terminate the MCP server
mid-session and kill subsequent tools/call requests on the same
stdio connection.
Under MCP, the check FeatureError carve-out is collapsed (skip the
--json {ok,feature,reason} payload and the FAIL: … print) because
the MCP layer discards command stdout/stderr on execErr and emits
the structcli error envelope as isError=true content. The agent
gets the verbatim message in the envelope.
bats: new test/cli_mcp.bats covers flag presence, initialize,
tools/list exclude semantics, inputSchema shape, structcli envelope
routing for invocation errors, JSON-RPC error for excluded tools,
multi-call session survival across a FeatureError, and a stdout
leakage guard.
Co-authored-by: Ona <no-reply@ona.com>
README CLI section expanded into three explicit audiences:
* CI/CD gating — semantic exit codes (0/1/10/11/12/14) with a
table mapping codes to categories (input/runtime), example
JSON envelopes for both verdicts (FeatureError carve-out) and
invocation errors (structcli envelope), link to the
structcli/exitcode package documentation.
* AI agents — --jsonschema and --jsonschema=tree examples
(with a jq filter that hides the cobra-generated help and
completion leaves), Claude Desktop client config snippet for
--mcp, list of exposed tools, session-survival note, link to
the structcli/mcp package.
* One-line pointer near the top of the README so the new
capabilities are discoverable without scrolling to the CLI
section.
CONTRIBUTING gained a 'CLI conventions' section codifying the
invariants introduced by the structcli v0.17.0 adoption:
* Construction: Bind + Setup + ExecuteOrExit; no manual bridges.
* Stream routing through cmd.OutOrStdout / cmd.ErrOrStderr
(MCP-safety, with explanation that non-MCP behaviour is
bit-for-bit identical).
* os.Exit discipline: the inMCPMode carve-out and why os.Exit
from a RunE would kill the MCP server mid-session.
* The two coexisting error contracts (invocation envelope vs
business-outcome verdict) and how to decide which bucket a
new failure belongs to.
* MCP tool-exposure policy: default to expose; document any
exclusion with intent.
All README examples were verified against the live binary; the
--jsonschema=tree snippet was corrected after seeing real output
includes the help and completion subtrees.
Co-authored-by: Ona <no-reply@ona.com>
Add a 'CLI conventions (cmd/kfeatures)' section to AGENTS.md that
distils the rules from CONTRIBUTING.md's matching section into an
agent-actionable do/don't voice with code snippets. Same content,
different audience: humans get the prose in CONTRIBUTING; agents
get the recipes here.
Sections:
* Construction — always Bind + Setup + ExecuteOrExit; lists
explicit anti-patterns (manual Execute bridge, manual Unmarshal
in PreRunE, calling SetupX directly when a WithX exists);
includes a complete subcommand template.
* Stream routing — forbidden vs required side-by-side code blocks
for os.Stdout / os.Stderr / fmt.Print* vs cmd.OutOrStdout() /
cmd.ErrOrStderr(). Notes the only legal os.Stdout reference in
cmd/kfeatures/ is inside inMCPMode(c). Suggests grep as a
self-check.
* os.Exit discipline — shows the inMCPMode(c) gate pattern;
distinguishes os.Exit inside RunE (forbidden without gate) from
os.Exit in main() setup paths (fine).
* Two error contracts — invocation envelope (structcli) vs
business-outcome verdict (FeatureError); forbids inventing a
third shape.
* MCP tool exposure — default to expose; exclusion requires
intent; clarifies leaf-name vs parent-path matching.
* Bats coverage — short index of which test file owns which
surface; behaviour changes require same-commit bats updates.
Documentation split section gets one new paragraph explaining
AGENTS.md's role as the agent-actionable distillation of
CONTRIBUTING.md and the co-update obligation when CLI invariants
change.
Co-authored-by: Ona <no-reply@ona.com>
The previous wording quoted the maintainer's private address verbatim as a teaching example for the GH007 rejection mechanism. Remove that quotation. The noreply identity, the GH007 error message, and the recovery procedure are unchanged. Add an explicit instruction not to quote the rejected private address anywhere in the repo, commit messages, or PR descriptions, so future contributors and agents do not reintroduce it. Note: this only affects future content. The address remains in the git history from the commit that originally added AGENTS.md and is likely already cached by GitHub search and external mirrors. A history rewrite was considered and rejected (it does not unpublish the address from external caches and would invalidate every commit hash from PR #35 onward, breaking open PRs and local clones). Co-authored-by: Ona <no-reply@ona.com>
5bc3dd5 to
91cee4e
Compare
Owner
Author
|
Closing unmerged. The The backup ref |
This was referenced May 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #43. Final PR in the cli-track stack (#38 → #39 → #40 → #41 → #42 → #43 → this). The base will retarget to
mainautomatically when #43 merges. Docs-only — no code changes.Why
The "Git author identity" block in
AGENTS.mdquoted the maintainer's underlying private address verbatim as a teaching example for the GH007 push-rejection mechanism. The address is sensitive (that's the entire reason GH007 exists to protect it), and quoting it in a top-level documentation file means it's been crawled by GitHub's search index sinceAGENTS.mdoriginally landed.This PR removes the quotation. The noreply identity, the GH007 error message, and the recovery procedure are kept — they are the actually useful parts of the section.
What changes
AGENTS.mdThe "Git author identity" section is rewritten:
leodidonato@gmail.com) is removed.The diff is +13/-7 in
AGENTS.md.CHANGELOG.mdOne line under
[Unreleased]→ Changed noting the redaction and the new "do not quote the rejected address" rule.What this does not do (intentional)
AGENTS.md(and from every subsequent edit to that file before this PR). A force-rewrite was considered and rejected:If GitHub Support is willing to purge their cached copy (they are usually not, for content the account owner originally committed themselves), a separate request can follow. The right defense going forward is what this PR does: stop re-publishing the address.
.githooks/; an option to extend it with anauthor-email-must-be-noreplycheck is sensible follow-up work but out of scope here. Filing as a follow-up if you want it.Notes
git config user.emailto find your noreply" line, because this repo has exactly one maintainer and the noreply form is hard-coded everywhere already (commit trailers, the.git/configsample in CONTRIBUTING.md, etc.). If the project ever gains co-maintainers, the section will need a different rewrite anyway.