Skip to content

docs(agents): redact private email from Git author identity section#44

Closed
leodido wants to merge 84 commits into
docs/agents-md-cli-conventionsfrom
docs/agents-redact-private-email
Closed

docs(agents): redact private email from Git author identity section#44
leodido wants to merge 84 commits into
docs/agents-md-cli-conventionsfrom
docs/agents-redact-private-email

Conversation

@leodido
Copy link
Copy Markdown
Owner

@leodido leodido commented May 3, 2026

Stacked on #43. Final PR in the cli-track stack (#38#39#40#41#42#43 → this). The base will retarget to main automatically when #43 merges. Docs-only — no code changes.

Why

The "Git author identity" block in AGENTS.md quoted 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 since AGENTS.md originally 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.md

The "Git author identity" section is rewritten:

  • The private address (leodidonato@gmail.com) is removed.
  • The GH007 error message is hoisted into a fenced code block so it's still copy-pasteable for grep'ing in error logs.
  • The noreply form remains as the canonical identity (it's already public — it's the visible author of every commit on this account).
  • A new explicit instruction tells future contributors and agents not to look up or quote the rejected private address anywhere in the repo, commit messages, or PR descriptions.

The diff is +13/-7 in AGENTS.md.

CHANGELOG.md

One line under [Unreleased]Changed noting the redaction and the new "do not quote the rejected address" rule.

What this does not do (intentional)

  • No history rewrite. The address remains in the git history from the commit that originally added 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.

  • No git pre-commit hook enforcement. The repo already has .githooks/; an option to extend it with an author-email-must-be-noreply check is sensible follow-up work but out of scope here. Filing as a follow-up if you want it.

Notes

  • I deliberately did not delete the GH007 error string — it has genuine teaching value (an agent that doesn't know the symptom will waste time debugging the wrong thing). It's the quoted private address that needed to go, not the explanation of the mechanism.
  • I did not add a generic "use git config user.email to find your noreply" line, because this repo has exactly one maintainer and the noreply form is hard-coded everywhere already (commit trailers, the .git/config sample in CONTRIBUTING.md, etc.). If the project ever gains co-maintainers, the section will need a different rewrite anyway.

feat(api): unify `Check()` requirements model
feat(check): align enum gate features with existing probes
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`
leodido and others added 23 commits April 23, 2026 14:06
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>
@leodido leodido force-pushed the docs/agents-md-cli-conventions branch from 5bc3dd5 to 91cee4e Compare May 3, 2026 20:29
@leodido
Copy link
Copy Markdown
Owner Author

leodido commented May 3, 2026

Closing unmerged. The main branch was rewritten to remove the entire "Git author identity" section from the commit that originally introduced AGENTS.md (see comments on #38#43 for the force-push details). The redaction this PR performs no longer has a target — the section is gone from history.

The backup ref refs/backup/main-pre-rewrite-2026-05-03 on origin still points at the pre-rewrite main (3fb587f) for ~90 days as a recovery anchor.

@leodido leodido closed this May 3, 2026
@leodido leodido deleted the docs/agents-redact-private-email branch May 3, 2026 20:30
@leodido leodido added the documentation Improvements or additions to documentation label May 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants