Trust map and bounded reach #

Only .nomarmy/trust-map.yml in the operator checkout is authoritative. The executor snapshots it before worker execution. Editing that file in a worker worktree is itself a human-level change, including deletion or replacement. The same gate protects .nomarmy/trust-map.proposed.yml and its provenance receipt, .nomarmy/trust-map.provenance.json.

Mapping and ratification #

The CLI does not have a job-dispatch client. nomarmy trust map writes .nomarmy/trust-map.scout.md, a read-only scout brief for the General to dispatch through local_worker_start. It selects the repository's security-analyst review role, or the first configured review-phase role. The General should use a vendor independent of the builders. The brief lists eligible files, excluding usual generated/vendor directories and files marked linguist-generated or linguist-vendored in .gitattributes.

The scout returns a YAML array:

- symbol: _enforce_scope
  file: lambda/rls/common.py
  category: tenant
  reason: Enforces the caller's tenant scope before accessing records
  line: 12

Save that response, then run nomarmy trust map --from scout.yml --scout-job <scout-job-id>. This validates the schema and definition citations before writing .nomarmy/trust-map.proposed.yml. Proposed entries are not active boundaries, but any worker edit to the proposal file is human-gated. The command records its writer name, scout job id and a digest of the exact proposal bytes in the operator-side receipt. The job id is supplied by the General, not inferred from scout text; it is attribution, not proof of the scout's identity.

nomarmy trust review presents each proposal and its scout job origin for accept, drop, or edit. JSON listings include a parallel origins array. --accept-all requires an unchanged proposal with a receipt written by nomarmy trust map and a recorded scout job id. Missing or invalid receipts, missing job ids, manual proposals, and edited proposal bytes require explicit per-entry decisions. Unknown origins are displayed as unknown-or-edited. Accepted entries merge into the existing map, without the proposal-only line field. Dropping a proposal does not delete an already accepted map entry. Categories are access, tenant, data, money, delete, deploy, and secrets.

For scripting:

nomarmy trust review --json
nomarmy trust review --accept-all --json
nomarmy trust review --decisions '[{"action":"accept"},{"action":"drop"}]' --json

--json alone only lists pending entries. Decisions must cover every entry in order. An edit is {"action":"edit","entry":{...}}, where entry has exactly symbol, file, category, and reason. The whole batch is validated before writing. Review clears the proposal once accepted entries have been saved.

Reach #

Before worker edits or continuation changes are applied, the executor uses the untouched base worktree to compute downward symbol dependencies with lib/repo-query.mjs outlines and references. This is heuristic symbol-name resolution, not a compiler call graph: ambiguous definitions can overapproximate reach. ES named aliases and namespace members, CommonJS destructuring aliases, and Python from-import aliases and module aliases resolve to repository-local definitions. Unresolved used import bindings record a cap rather than implying that their targets are safe. Both K&R and Allman function bodies are included. Depth defaults to 3 and fan-out to 25. The computation API accepts smaller or larger explicit bounds (depth 0 to 10, fan-out 1 to 100).

The job state directory contains a trust-reach cache keyed by base commit, accepted map, algorithm version, and bounds. Records state the base commit, cache key, bounds, heuristic nature, and any caps. Unresolved mapped symbols and capped scans conservatively require review for a nonempty diff.

Changing a mapped symbol or a reachable helper requires review. Reasons identify the changed symbol, file and line, boundary category, and dependency path. A removed-check finding within that reach raises the level to human. Other trust floors and previous job escalations cannot be lowered by this analysis.

Limits #

Reach is static and heuristic. Dynamic dispatch such as user.handler(user) cannot be resolved statically and is left to the diff judgment, not certified by an empty reach result. Runtime module loading, re-exports, computed property names, binding shadowing and language-specific module resolution can also need judgment. Import-looking comments and strings do not create import bindings. Prompt, policy or query files (.md, .txt and similar) count as production only when production code names them in a string literal. A file the code loads by a computed path ("sys" + ".md", a template, a directory read) stays outside the judgment and the detector, so list such files under trust.sensitive.paths in .nomarmy.yml: the floor gates any change to them. Files a test runner loads by convention (conftest.py, a configured setup file) are treated as tests unless production code imports them.

Test-named files outside test directories are production when their language has no import extraction. Go's _test.go files are the exception because the Go toolchain never links them into production builds. Test directories retain their existing rule (tests/, test/, __tests__/, and spec/ with test evidence). The JS/TS import scan follows only relative ./ and ../ specifiers. A production file loading a test-named file through a path alias (@/x, tsconfig paths, or package.json "imports"), a bare specifier, or a computed import is not seen by this scan. List such files in trust.sensitive.paths in .nomarmy.yml so changes remain human-gated even when the scan excludes them from judgment.

The proposal receipt detects edits; it is not a cryptographic attestation of a scout job. Its authority depends on the operator checkout and the human gates on both the proposal and receipt, just as the active map does.

Human sign-off and learning #

Outside or inside /feature, the operator records a human gate decision with:

nomarmy trust ack <job-id> --accept --reason "Reviewed the tenant boundary"
nomarmy trust ack <job-id> --reject --reason "Missing tenant filter"

Only a job from this repository with trust.level: human can be acknowledged. trust.ack is an append-only array of decisions with decision, who, when and reason. Identity is the repository's Git user.email, falling back to the OS user. The last decision is current; earlier decisions remain in the record. Sign-off never changes the level, outcome, tests or review requirements. A reject is an acknowledged refusal, not permission to integrate. The status report and jobs listing carry the history; run_finish labels gates pending or acknowledged and includes the latest decision. Only pending gates count in the PR block's "Needs human review" row.

Finished independent review scouts with reviews: <job-id> contribute supported findings with explicit defect language and citations to that job's changed files. Failed, same-provider, weak and unsupported findings do not contribute. Defect classification and symbol extraction from cited excerpts are heuristic. Every human rejection contributes its changed files and the recorded floor/reach reasons. The executor records review evidence immediately; trust review also collects persisted review records idempotently, including older reviews.

Evidence is append-only, repository-scoped by canonical checkout path, in $NOMARMY_AGENT_STATE/trust-learning/<repo-hash>/evidence.jsonl (the normal nomArmy state directory if unset), never in the repository. Two or more distinct evidence records touching a symbol or file produce an inert suggestion. Each suggestion shows the event count, total recorded defects/rejections, and job ids. A review contributes once even if several findings cite the same symbol. The initial category is access as a review placeholder; edit it to the appropriate category. If no symbol has enough evidence, a file-level suggestion uses symbol: "*". An accepted whole-file entry gates changes anywhere in that file, including code without a recognized definition; removed checks raise it to human.

Learning suggestions appear after scout proposals in trust review, with the same accept/drop/edit decisions. --accept-all cannot approve learning suggestions. Nothing becomes active until explicitly accepted or edited. Dropping a suggestion persists the evidence fingerprint outside the repository, so rereading or collecting the same evidence cannot propose it again. New evidence for that target makes it eligible again. Already mapped targets are not suggested again.