Skip to content

fields: add {def.NAME} namespace reading a defines map - #58

Merged
helly25 merged 1 commit into
mainfrom
feat/field-def-namespace
Jun 21, 2026
Merged

fields: add {def.NAME} namespace reading a defines map#58
helly25 merged 1 commit into
mainfrom
feat/field-def-namespace

Conversation

@helly25

@helly25 helly25 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Second namespaced field (#53, fields side): {def.NAME} renders a value from RenderContext::defines (the --define map, last-wins), empty when undefined or no map is present.

  • DefField looks up key in ctx.defines.
  • ResolveName routes the def. prefix to it (alongside env. and {0}..{N}).

Fields-only — the --define=NAME=VALUE CLI global that populates the map and wires it onto the render/eval contexts is the follow-up PR (mirrors how {N} captures were split from their -regex/-exec wiring). Until then {def.*} resolves empty (no map set), so no behaviour change to existing output.

Test plan

  • fields_test.DefNamespaceReadsDefines: a defines map via RenderContext{def.greeting}/{def.n} resolve; undefined key → empty; no-map → empty.
  • Green on both toolchains: bazel test //... (default) and --config=clang --config=asan.

Second namespaced field via the renderer-key model: {def.NAME} renders a
value from RenderContext::defines (the --define map, last-wins), empty
when undefined or no map is present. DefField looks up  in the map;
ResolveName routes the def. prefix to it.

Fields-side only; the --define CLI global that populates the map and
wires it onto the render/eval contexts is the follow-up PR (mirrors how
{N} captures were split from their -regex/-exec wiring).
@helly25
helly25 merged commit c781d9e into main Jun 21, 2026
4 checks passed
@helly25
helly25 deleted the feat/field-def-namespace branch June 21, 2026 12:55
helly25 added a commit that referenced this pull request Jun 21, 2026
ResolveDefines collects --define globals into a name->value map (last
wins; VALUE may contain '='). run.cc builds it once and passes it on the
EvalContext and the --template RenderContext; the gated -exec carries it
through too. So {def.NAME} resolves in --format/--template and -exec.

Completes #53 (the fields side landed in PR #58). run_test drives
--define end-to-end through --template, including last-wins.
helly25 added a commit that referenced this pull request Jun 22, 2026
First slice of the config-system (#58) loader, per docs/design-config.md. xff/config/xffrc.{h,cc}: ParseXffrc parses .xffrc text (bazel-rc-style flag bundles) into RcLine{base, config, flags}. The optional leading selector token base:config: gates by style + named config; bare lines are common/any-config; a flag value with a colon (--config=xff:2) is not mistaken for a selector (selectors end in ':').

Parse-only: nothing is interpreted, gated, or executed - the loader (B2/B3) and the policy gate (C) build on this. Tests cover comments/blanks, bare flags, base and base:config selectors, selector-only lines, and the colon-in-value case.
helly25 added a commit that referenced this pull request Jun 23, 2026
…#90)

Second slice of the config loader (#58), per docs/design-config.md. xff/config/ini.{h,cc}: ParseIni parses the system policy file into SystemConfig{defaults, policy}. [defaults] key=value lines render to CLI tokens (--color = auto -> --color=auto); [policy] <layer>.<allow|deny> = <comma-list> lines become PolicyRules carrying flag names and/or @Class tokens (@sensitive). Comments (#/;), blanks, lines outside the known sections, and malformed [policy] lines are skipped.

Parse-only: no registry validation or enforcement - the policy gate (phase C) consumes this. Tests cover [defaults] rendering, allow/deny + @Class tokens, comments/blanks, malformed-line tolerance, and out-of-section ignoring.
helly25 added a commit that referenced this pull request Jun 23, 2026
)

Third slice of the config loader (#58), per docs/design-config.md. xff/config/config.{h,cc}: ResolveConfig combines the parsed layers (system [defaults] + user/project .xffrc) into a precedence-ordered, provenance-tagged flag list (Source: unset/system/user/project/cli). An .xffrc line applies when its base selector is empty/common or names an active --config, and its config selector is empty or names one. --no-config yields empty (pure CLI + built-ins); the system policy is never dropped here.

No discovery (file IO) or capability gating yet - those are the next slice and phase C. Tests cover no-config, system-defaults precedence, common/bare lines, base + base:config gating by active --config, and the system<user<project layer order.
helly25 added a commit that referenced this pull request Jun 23, 2026
)

Fourth slice of the config loader (#58), per docs/design-config.md. xff/config/loader.{h,cc}: Discover(opts, read) locates and parses the layers into ConfigInputs:
- system /etc/xff.ini (always read; its [policy] is never skipped),
- user config via UserConfigPath ($XFF_CONFIG, else $XDG_CONFIG_HOME/xff/config, else $HOME/.config/xff/config),
- explicit --xffrc=FILE armed into the user layer, in order.

The FileReader is injected (run.cc will supply a std::ifstream-backed reader), so discovery is unit-tested without touching /etc or the home dir. --no-config skips the user layer and explicit files (ResolveConfig also drops the system [defaults]) but the system file is still read so its policy is available to the phase-C gate. Project cascade is phase E; no run.cc wiring yet (next slice).
helly25 added a commit that referenced this pull request Jun 23, 2026
…a) (#96)

Fifth slice of the config loader (#58), per docs/design-config.md, phase B5a (load + --explain; the prepend that applies config to the run is B5b).

- config.{h,cc}: SourceName(Source) and ExplainConfig(resolved, cli_globals) render the effective configuration - each resolved flag tagged with its provenance (system/user/project), then the CLI globals (cli), in application order.
- loader.{h,cc}: SelectorsFromGlobals(globals) extracts the --no-config / --config=NAME / --xffrc=FILE selectors from the parsed globals.
- cli/main.cc: after Parse, builds DiscoveryOptions from the selectors + the XFF_CONFIG/XDG_CONFIG_HOME/HOME env, runs Discover + ResolveConfig, and on --explain writes the effective config to stdout and exits 0. Find behaviour is UNCHANGED (no prepend yet) - safe.

--explain makes the loader testable at the binary level (a follow-up adds an sh_test harness + a binary --explain test). Unit tests cover SourceName, ExplainConfig, SelectorsFromGlobals. Verified manually: 'xff --config=xff --explain' with an XFF_CONFIG of common:/xff:/find: lines shows the common + xff: flags as user provenance and omits the inert find: line. Green on default + clang-asan.
helly25 added a commit that referenced this pull request Jun 23, 2026
Final slice of config phase B (#58): config now affects the run. cli/main.cc prepends the resolved config flags to command.globals before RunFind, so config takes effect; the CLI globals (already present, kept last) win on conflict via the last-wins resolvers. The command is moved out of the StatusOr to a mutable local for the prepend.

The binary test (explain_test, helly25/bashtest) gains config_applies_to_the_run: a config of --format=jsonl makes the default print emit a JSONL object while no config keeps it plain - verifying config reaches the run, not just --explain.

Config phase B is now complete end to end: .xffrc + ini parsers (B1/B2), ResolveConfig (B3), Discover (B4), --explain (B5a), apply (B5b). Green on default + clang-asan. Next: phase C, the safe-by-default policy gate.
helly25 added a commit that referenced this pull request Jun 23, 2026
…1) (#100)

* feat(registry): classify exec-family + -delete safety (config phase C1)

First slice of the config policy gate (#58, phase C), per docs/design-config.md. Tags the security-relevant registry primaries via the existing Descriptor.safety field (all previously defaulted to kNone):
- exec family (-exec, -execdir, -ok, -okdir, -capture, -capturedir) -> Safety::kSecurity (sensitive: runs arbitrary commands).
- -delete -> Safety::kSafety (destructive: data loss).
- everything else stays kNone (safe).

registry_test verifies the classification. Nothing reads safety behaviourally yet; C2 builds the per-layer policy table + gate logic keyed off this, C3 wires drop-and-warn into the config resolve/apply path. Green on default + clang-asan.

* style(registry): comment above each exec-family descriptor (keep one-line)

Adding the .safety tag pushed the exec-family descriptors plus their trailing comments past the column limit, so clang-format exploded each into 4 lines. Moving each comment to its own line above the element keeps the descriptor on a single line - no comment at the end of a long line.
helly25 added a commit that referenced this pull request Jun 23, 2026
Second slice of the config policy gate (#58, phase C), per docs/design-config.md. xff/config/policy.{h,cc}:
- LineSafety(line): the most restrictive registry safety class among a line's flags (kNone < kSafety < kSecurity); an attached binding like -capture=tag is classified by its base name.
- LinePermitted(line, layer, policy): the safe-by-default decision. The built-in table denies the project layer any kSecurity (exec family) or kSafety (-delete) line; user and system may do anything. The system [policy] rules then override per layer, deny beats allow, addressing flags by name or by @safe/@sensitive/@destructive class token.

Pure decision logic, no wiring yet; C3 gates the resolved lines in the config path with drop-and-warn + --explain drops + a hostile-.xffrc binary test. Unit tests cover worst-flag classification, built-in project deny, allow-loosen (by name + class), deny-tighten, deny-beats-allow, and per-layer scoping. Green on default + clang-asan.
helly25 added a commit that referenced this pull request Jun 23, 2026
…a) (#102)

Third slice of the config policy gate (#58, phase C). xff/config/policy.{h,cc}: GateConfig(inputs, drops) filters the user and project .xffrc lines through LinePermitted (with inputs.system as the policy), returning a copy with denied lines removed and recording each removed line in drops (line + layer + safety class) for the stderr warning and --explain. The system [defaults] are root-authored and not gated; CLI flags are not config.

Pure filter, no wiring yet; C3b calls it in cli/main.cc after Discover, warns each drop, resolves only the survivors, lists drops in --explain, and adds the hostile-.xffrc binary test. Unit tests: a project -exec is dropped + recorded (project, kSecurity) while the safe project line and a user -exec survive; a null drops sink is tolerated. Green on default + clang-asan.
helly25 added a commit that referenced this pull request Jun 23, 2026
…ig phase C3b) (#103)

Third and final slice of the config policy gate (#58, phase C); the safe-by-default gate is now live end to end.
- loader: Discover now reads ./.xffrc (the cwd) as the untrusted project layer (the full cascade up the tree is phase E; the policy gate is what makes loading this layer safe).
- policy: DropMessage(drop) renders a one-line description for the warning and --explain; ClassName maps the safety class.
- cli/main.cc: GateConfig filters the discovered user+project lines through the policy; denied lines are dropped (never fatal) and warned on stderr; --explain lists them; only the survivors are resolved and prepended.

The canonical threat now holds: cd into an untrusted repo, run xff, and a .xffrc carrying -exec is dropped+warned while a safe --color line still applies. Tests: loader project discovery; policy DropMessage; the hostile-.xffrc binary test (bashtest) asserting via --explain that the project -exec is dropped and the safe line survives.

Also: trailing comma on the Drop{} aggregate (clang-format one-per-line). Green on default + clang-asan. Config phase C complete.
helly25 added a commit that referenced this pull request Jun 24, 2026
… phase E2a) (#112)

Generalize the project layer from the single cwd .xffrc to the gitignore-style
ancestor cascade the design specifies: for each search root, every .xffrc from the
filesystem root down to the root's directory contributes, shallowest first (deeper
overrides), deduped across roots. The whole layer stays untrusted and policy-gated.

- DiscoveryOptions gains `roots` (absolute search-root dirs); main resolves
  command.roots to absolute dirs (a file -> its parent; cwd when none) via
  std::filesystem before discovery.
- Discover walks each root's ancestor chain (AncestorDirs), reading <dir>/.xffrc,
  appending to the project layer and recording each in the --explain source trace.

Per-entry subtree scoping for .xffrc files BELOW a root (config varying mid-walk,
an architectural change on the traversal hot path) is deferred - recorded in
TODO.md. The ancestor cascade covers the common "repo + parents" case.

Tests: loader_test (root-dir .xffrc; ancestors shallowest-first with deeper
override; shared ancestors read once; the source trace records the chain),
explain_test bashtest (a parent-dir .xffrc applies to a subdir search root with
project provenance). Green in both configs; clang-format + em-dash clean.

Completes the config system (#58): phases B-E all shipped.
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