Skip to content

Latest commit

 

History

History
297 lines (214 loc) · 64.5 KB

File metadata and controls

297 lines (214 loc) · 64.5 KB

Per-Utility POSIX Conformance Audit Playbook

A template + collected wisdom for auditing any single posixutils-rs utility against POSIX.1-2024 (IEEE Std 1003.1-2024). Distilled from the first full audit (more, see display/audit.md).

Goal of an audit: produce <crate>/audit.md — a checkbox-driven punch list a maintainer can work through PR by PR.


0. Inputs you always need

  • Sliced spec: ~/tmp/posix.2024/sliced/ — see its top-level README.md, INDEX.md, ALIASES.md. Use the slice, never the 4107-page PDF.
  • Primary spec file: ~/tmp/posix.2024/sliced/xcu-shell-and-utilities/3-utilities/<util>.md. If the utility name is weird (shell builtin ./:, posixutils-rs pccc17, etc.), consult ALIASES.md first.
  • Implementation: typically <category>/<util>.rs (e.g. display/more.rs, file/cat.rs). Multi-file utilities live in their own crate (sh/, awk/, pax/, cc/, make/, m4/, editors/).
  • Tests: <category>/tests/<util>/mod.rs. Skim for coverage signal, not bug-hunting.
  • Cross-referenced spec sections to consult when the main spec links them:
    • xbd-base-definitions/12-utility-conventions/ — argv parsing rules (POSIX getopt semantics, --, - operand, + prefix exceptions)
    • xbd-base-definitions/9-regular-expressions/9.3-Basic-Regular-Expressions.md and 9.4-Extended-Regular-Expressions.md — every search-capable utility
    • xbd-base-definitions/8-environment-variables/LANG/LC_* precedence, COLUMNS/LINES semantics
    • xrat-rationale/ — the matching appendix when "why" matters (e.g., Austin Group Defects mentioned in CHANGE HISTORY)

1. Workflow

  1. Locate spec + implementation + tests (paths above). Verify alignment via ALIASES.md if name is non-obvious.
  2. Read the full per-utility spec.md. Do not skim. The spec sections set the audit outline.
  3. Read the implementation in full. Files >2000 lines: chunk-read, or delegate to a feature-dev:code-explorer subagent with an explicit template (see §5).
  4. Skim tests for what's covered. Track gaps as "write a test" items.
  5. Verify Critical/Major claims by reading cited line ranges directly, and by grepping for "absent" things (signals, file descriptors, env vars). Do this BEFORE writing the audit doc — never publish unverified claims.
  6. Write <crate>/audit.md using the template in §6. Every actionable finding is a checkbox.
  7. Propose PR groupings at the bottom — 3 to 6 small, themed PRs are easier to land than one mega-PR.

2. Categories every audit must cover

Walk the spec section by section in order. For each section, decide: CONFORMS / PARTIAL / MISSING / DIVERGES / N/A.

Spec section What to verify Common pitfalls
SYNOPSIS Option letters match; option grouping (-abc) works; -- end-of-options handled; + prefix if spec mentions XBD 12.2 exception Clap quirks around +, position-of-- operand, unknown-option behavior
OPTIONS Each declared option is present AND its full semantics match Flag parsed but never consulted in the render/exec path; only first sentence of the spec implemented
OPERANDS - operand routes to stdin; missing-operand → stdin; multiple operands handled in order - ambiguity when mixed with file paths; single-line stdin reads
STDIN Only consumed when spec says so Spurious early stdin slurp; truncation to first line
INPUT FILES File types accepted; for interactive utilities, where commands are read from Reading commands from stdin instead of stderr / /dev/tty
ENVIRONMENT VARIABLES Every variable listed in the spec is read; precedence rules honored Reading once at init and never re-checking on resize/refresh; missing LC_* chain
ASYNCHRONOUS EVENTS Every signal mentioned has a handler; default-others rule respected Missing SIGCONT/SIGWINCH; signal handling via polling instead of signal-hook
STDOUT / STDERR Content target, diagnostic target, prompt target Prompt written to stdout instead of stderr; missing required prompt fields (filename, EOF, next-file)
OUTPUT FILES Usually "None" — quick check
EXTENDED DESCRIPTION Utility-specific rendering / behavior rules Multi-column character handling, control-char display, fold/wrap rules
Interactive commands One row per command; count-prefix; semantics ''-style "last position" trackers not updated; commands that move >1 screenful not flagged as "large movement"
EXIT STATUS 0 success / >0 error mapping Always exits 0; doesn't propagate per-file errors
CONSEQUENCES OF ERRORS Per-command error policy (continue vs abort) :n/:p-class errors abort instead of advancing
Cross-cutting i18n (gettext), regex flavor (BRE vs ERE), locale-driven behavior, security-sensitive syscalls (setuid/setgid) Hardcoded English diagnostics; ERE leaks where BRE required; ignored setgid() return values

3. Severity & status conventions

Per-finding status (use exactly these labels):

  • CONFORMS — matches spec; pre-check the box - [x].
  • PARTIAL — present but incomplete; semantics not fully implemented. Unchecked.
  • MISSING — not implemented at all. Unchecked.
  • DIVERGES — implemented but contradicts the spec. Unchecked.
  • N/A — optional/XSI/CT feature legitimately not provided; track but unchecked.

Per-finding priority (assign to every unchecked item):

  • Critical — user-visible data loss, hang, crash, or "doesn't do what man says" on the golden path (util | more, util file). Block on these for any release pitch.
  • Major — spec-mandated behavior absent on a common path; or wrong exit code; or wrong I/O channel; or missing signal handling for interactive utilities.
  • Minor — strict-conformance gaps that don't bite real users today (string-vs-path comparison, locale env var coverage, undocumented flags, hardcoded English).

When in doubt, prefer the higher severity — easier to demote than to discover the bug in the field later.


4. Collected wisdom — bug patterns to grep for

These are recurring antipatterns surfaced by the more audit. Worth a 30-second grep on every utility.

I/O channels

  • grep -n 'stdin\(\)' <util>.rs — interactive utilities should NOT read commands from stdin. Spec says stderr (with /dev/tty fallback).
  • grep -n 'self.tty\|stdout()' <util>.rs near prompt-writing code — prompts go to stderr per spec.
  • Look for .lines().next() or read_line() where the code expects to ingest a whole file/stream — truncation bug.

Signals

  • grep -nE 'SIGCONT|SIGWINCH|signal_hook|libc::signal' <util>.rs — interactive utilities (more, vi, ed, ex, mailx) MUST handle SIGCONT and SIGWINCH per spec. Zero matches = MISSING.
  • Window-size changes detected via polling terminal_size() in an input loop are DIVERGES from "asynchronous event" semantics.

Environment variables

  • grep -nE 'env::var\("(LANG|LC_ALL|LC_CTYPE|LC_COLLATE|LC_MESSAGES|NLSPATH|COLUMNS|LINES)"' <util>.rs — verify every var the spec lists is read.
  • After a resize or refresh, the env vars COLUMNS/LINES should be re-honored. Common bug: read once at init, then ignored.
  • MORE/LESS/EDITOR-style pre-option env vars: must be processed BEFORE the command line, with CLI args overriding.

Argv / clap

  • Spec says "may treat + as an option delimiter": clap doesn't do this natively — track as MISSING unless explicitly wired.
  • Hidden test flags (--test, --debug) in the public clap surface: should be #[arg(hide = true)] or behind a feature gate.
  • - operand: confirm it routes to stdin AT THAT POSITION in the file list, not just "stdin if - present".
  • Bundled short options (-ceisu vs -c -e -i -s -u): clap handles by default; double-check for utilities written without clap.

Path / environment-variable string comparisons

  • EDITOR == "vi" / EDITOR == "ex" antipattern — won't match /usr/bin/vi, vim, view. Use Path::new(&editor).file_name().
  • Same pattern for SHELL, PAGER, TERM-prefix checks.

Editor / external-command invocation

  • For vi/ex invocations, POSIX mandates -c linenumber. Many implementations historically use the older +N form — DIVERGES from spec even though most editors accept both.
  • setuid(getuid()) / setgid(getgid()) return values being ignored (let _ = ...) is a security smell worth flagging.

Regex

  • Search/match utilities default flavor: grep -nE 'RegexFlags::|Regex::new' <util>.rs. POSIX grep (no -E), sed (without -E), more/ed/ex/vi/expr use BRE. awk and grep -E / egrep use ERE. Easy to wire the wrong flavor.
  • Tag-style lookups: if the spec says "tag" (literal name), don't use grep (regex). Use literal match or grep -F.

i18n

  • grep -n 'gettext\|gettextrs' <util>.rs — clap help strings often are gettext'd, but runtime diagnostic strings (errors, prompts) often aren't. POSIX requires LC_MESSAGES to affect diagnostic text.
  • setlocale(LC_ALL, "") should be present near main(). Missing → LC_* env vars do nothing.

Output / rendering

  • Lines longer than the terminal: fold vs truncate vs wrap — check spec wording.
  • Multi-column (wide) Unicode characters at the wrap boundary: POSIX leaves this unspecified, but truncating mid-codepoint is a crash risk.
  • Backspace/underscore/overstrike → underline/bold translation: check the FULL POSIX sequence (char + n*BS + n*'_'), not just the n=1 case.
  • \r at end of line: should be silently discarded before \n, not displayed as a control character.
  • Non-printable characters: spec usually says "use the same format as ex print" — ^X for control, \NNN octal for high bytes.

Exit status

  • exit(0) everywhere → never propagates errors. Should set a had_error flag and exit non-zero at the end.
  • Per-command error policy (CONSEQUENCES OF ERRORS) is often missed: :n/:p-style errors should advance/retreat AND set non-zero exit, not abort.

5. Delegating to a subagent

For utilities >1500 lines, delegate to feature-dev:code-explorer. It's read-only and good at cross-referencing two large files.

Prompt template (paste, fill in <util>, <path>):

Perform a systematic POSIX.1-2024 conformance audit of <util>.

Spec (read in full): /home/jgarzik/tmp/posix.2024/sliced/xcu-shell-and-utilities/3-utilities/<util>.md Implementation (read in full): /home/jgarzik/repo/posixutils-rs/<path>/<util>.rs Tests (skim for coverage signal): /home/jgarzik/repo/posixutils-rs/<path>/tests/<util>/mod.rs

Supporting spec sections to consult as referenced:

  • xbd-base-definitions/12-utility-conventions/ — argv rules
  • xbd-base-definitions/9-regular-expressions/ — BRE/ERE if applicable
  • xbd-base-definitions/8-environment-variables/ — LC_*/COLUMNS/LINES

Output sections (in order): SYNOPSIS, OPTIONS (table), OPERANDS, STDIN, INPUT FILES, ENVIRONMENT VARIABLES (table), ASYNCHRONOUS EVENTS, STDOUT/STDERR, OUTPUT FILES, EXTENDED DESCRIPTION, Interactive commands (table — only if utility has them), EXIT STATUS, CONSEQUENCES OF ERRORS, Cross-cutting (i18n, regex flavor, signal handling).

Per finding: cite implementation line numbers; mark CONFORMS / PARTIAL / MISSING / DIVERGES / N/A; do NOT flag things the spec leaves "implementation-defined" or "unspecified" unless they cause a crash/hang.

End with: Summary table — top 5–10 issues ranked Critical / Major / Minor with one-line fix sketches.

~2000–3000 words. Tables liberally. No code blocks >5 lines. Read-only — do NOT modify files.

After it returns: always spot-check at least the Critical findings. Read the cited lines. Grep for "absent" claims. Agent summaries describe what the agent intended to find, not necessarily what's true. (The more audit had two such checks that both confirmed real bugs — but treat verification as mandatory, not optional.)


6. Output template — <crate>/audit.md

# POSIX.1-2024 Conformance Audit — `<util>`

**Implementation:** `<path>/<util>.rs` (N lines) + `<path>/tests/<util>/mod.rs` (N lines)
**Spec:** POSIX.1-2024 (IEEE Std 1003.1-2024), Vol. 3 §3, pp. X–Y
**Reference slice:** `~/tmp/posix.2024/sliced/xcu-shell-and-utilities/3-utilities/<util>.md`
**Date:** YYYY-MM-DD

## TL;DR
[2-4 sentences: what works, what doesn't, headline risks.]

## Priority issues

### Critical
- [ ] **#1 — <one-line summary>.** `<file>:<line>`. Fix: <one-line sketch>.

### Major
- [ ] **#N — ...**

### Minor
- [ ] **#N — ...**

## Detailed conformance matrix

### Options
- [x] `-a` CONFORMS — ...
- [ ] **`-b` PARTIAL** — ...

### Operands / STDIN
- [x] ...
- [ ] ...

### Environment variables
- [x] `VAR` CONFORMS — ...
- [ ] **`VAR` MISSING** — ...

### Asynchronous events
- [ ] **SIGFOO MISSING** — ...

### STDOUT / STDERR
- [ ] **...**

### Interactive commands (if applicable)
- [ ] **`cmd` PARTIAL** — ...

### Extended description / rendering
- [ ] **...**

### Exit status / consequences of errors
- [x] ...
- [ ] **...**

## Test coverage signal

Not covered:
- [ ] <behavior>
- [ ] <behavior>

## Suggested PR groupings

- **PR A — "<theme>"**: #X, #Y, #Z
- **PR B — "<theme>"**: ...

Rules for the doc:

  1. Every actionable item is - [ ] (unchecked). Future sessions will tick boxes as PRs land.
  2. Things already CONFORMS are - [x] (pre-checked) so the doc shows what's done vs outstanding at a glance.
  3. Every Critical / Major / Minor priority item gets a number (#1, #2, …) that the PR groupings can reference.
  4. Every finding cites a <file>:<line> range. "CONFORMS" without a line is useless.
  5. The doc is the contract. Don't rewrite it during fix PRs — only tick boxes and append "✓ fixed in PR #NNN" inline.

7. Anti-patterns to avoid in the audit itself

  • Don't paraphrase the spec. Cite the section name and quote the operative shall verbatim when something is on the line. (The slice gives you line numbers and section headers for free.)
  • Don't audit by running a subagent on the spec alone. It hallucinates code structure. Always pair spec + implementation.
  • Don't trust "the agent said the file doesn't contain X." Verify with grep.
  • Don't propose fixes for things the spec calls "unspecified" or "implementation-defined" — unless the current behavior crashes, hangs, or is actively wrong (negative count, division-by-zero on empty file, etc.).
  • Don't audit tests. Tests are signal, not source-of-truth. The implementation is what conforms (or doesn't).
  • Don't bundle the fixes into the audit. Audit = punch list. Fixes = separate, focused PRs. The whole point of the checkbox format is to defer execution.

8. After the audit

  • Commit <crate>/audit.md (alongside source). Reviewers can compare against the spec at any time.
  • When picking up the next session: read the file, choose 1–3 unchecked items that share a theme, write the fix + a test that proves the box now ticks, and tick the box in the same PR.
  • If a finding turns out to be wrong on closer inspection: do NOT silently delete the box. Tick it with a one-line note (- [x] ~~#7~~ — re-examined; actually conforms because of <reason>). This preserves history for the next auditor.

9. Reference audits

  • awk/audit.mdawk (full language audit, behaviorally verified; 1 Critical close() crash, 6 Major: -F escapes, byte-vs-char, printf *, -f -, field uninitialized comparison, 1024-field cap).

  • calc/audit.mdexpr, bc (behaviorally verified). expr: 4 Critical (precedence ignored, regex-not-BRE :, div-by-zero panic, exit status). bc: 2 Critical (quit-in-for-in-function panic, diagnostics on stdout), Major (no BC_SCALE_MAX/BC_BASE_MAX/BC_DIM_MAX, no 70-col wrap). Note: behavioral verification refuted several agent-proposed findings (three Critical "division/sqrt rounds" claims and the -1^2 precedence Major) — bc truncates correctly and matches GNU on -1^2.

  • cron/audit.mdcrontab, at, batch, crond (the cron family; crond audited vs. Vixie cron + implicit crontab/at/batch requirements + secure-daemon practice). Headline risks: crond executes spool files with no ownership/permission/symlink checks; crontab -e truncates the existing entry; at/batch jobs are filed but never run; multi-operand at timespecs and the crontab stdin form are unsupported.

  • datetime/audit.mdcal, date, sleep, time (the date/time crate; four single-file utilities, static spec-vs-code against the sliced POSIX.1-2024 tree, every "absent"/"wrong-value" claim grep-verified). cal is the cleanest (Julian→Gregorian Sep-1752 gap, operand forms, ranges, and TZ-driven current month all conform; only LC_TIME month/weekday names are hardcoded English). time carries both Criticals: the end-of-run tms_end is mem::zeroed() and never refilled by a second libc::times() call, and the arithmetic reads the parent's own tms_utime/tms_stime subtracted start−end instead of the child's tms_cutime/tms_cstime end−start — so the reported User/System CPU time is a near-zero constant unrelated to the invoked utility (the utility's whole purpose). time also discards the child's exit status (let _ = child.wait()) and always exits 0, violating "exit status of time shall be the exit status of utility". Major elsewhere: sleep 0 is rejected (range(1..)) though POSIX's time operand is a non-negative integer; and date's 2-digit-year century inference is off by one (yy=69 → 2069, but POSIX mandates 69–99 → 1969–1999). Minors: date's empty-strftime-is-an-error and from_utf8_lossy mangling, and hardcoded-English diagnostics in date/time (LC_MESSAGES inert). Totals: 2 Critical, 3 Major, 6 Minor. All actionable findings have since been remediated on the datetime-audit branch across five themed, independently-committed phases (each with regression tests; build / clippy --all-targets / fmt / datetime tests clean): Phase 1 time CPU accounting + exit-status propagation (#T1/#T2/#T3/#T5); Phase 2 sleep 0 + the crate's first sleep test module (#S1); Phase 3 date century off-by-one (infer_century + unit test), grow-until-fits strftime, raw-byte output (#D1/#D2/#D4); Phase 4 crate-wide plib::diag::init_locale + gettext diagnostics for date/time (#T4/#D3/#T6, with #S2 dispositioned a deferred clap/tree-wide concern); Phase 5 cal LC_TIME-aware month/weekday names via plib::locale::strftime (#C1). All four utilities promoted to README Stage 6 — Audited. Deferred: .mo catalog shipping (tree-wide, so LC_MESSAGES stays inert) and two minor test-coverage niceties.

  • dev/audit.mdyacc, lex, ar, nm, strings, strip (development utilities; shared plib::diag/io/archive/locale infrastructure).

  • display/audit.mdmore (the first full pass; covers 15 priority items, 28 interactive commands, signal handling, i18n).

  • editors/audit.mded, ex, vi (the editor family; Critical/Major findings behaviorally verified). Four cross-cutting themes: the regex crate is ERE where the spec requires BRE (ed does no translation at all; ex address searches bypass the converter); vi/ex install zero signal handlers (no SIGWINCH/SIGCONT/SIGHUP — resize corrupts the screen, hangup loses the buffer); ed never sets a non-zero exit status on command errors; and vi/ex never call setlocale. Other headline risks: ed reports an identical-result substitution as "no match", vi -r/-t hard-error and exit, and ex -s still sources EXINIT/.exrc.

  • fs/audit.mddf only (the fs/ crate now ships a single POSIX utility plus the Linux mntent.rs mount-table helper; cp/mv/rm moved to tree/, basename/dirname/pathchk/realpath to pathnames/). Static spec-vs-code audit against the sliced POSIX.1-2024 tree, grep-verified for every "absent" claim. The block-figure path (-k, -P, default 512-byte units, the -P data-line format, size/used/avail values, POSIX-locale header strings) is largely correct, but df is materially incomplete: -t is unimplemented (XSI, in the SYNOPSIS); the default and -t output never report free inodes / "file slots" despite a DESCRIPTION shall; and the Capacity percentage uses the wrong denominator (f_bfree instead of f_bavail), so it disagrees with df -P/coreutils on any reserved-block filesystem and can never exceed 100% as the spec note requires. Two robustness defects compound this: a non-UTF-8 mount path panics the golden no-operand path (to_str().unwrap() on kernel-supplied names), and a failed or unmatched file operand silently falls back to printing every filesystem (ensure_masked masks all when nothing matched). Locale init (setlocale/textdomain) is wired but runtime diagnostics are hardcoded English. 1 Critical, 4 Major, 7 Minor. All findings have since been remediated (5 phases on the fs-audit branch): byte-faithful OsString/PathBuf names + no all-filesystems fallback + newline guard (#1/#5/#8/#9); correct f_bavail capacity % + u128-safe scaling + zero-denominator guard (#4/#10/#11); free-inode columns + -t/-P|-t exclusion (#2/#3/#7); gettext diagnostics + coreutils-aligned non-fatal enumeration errors (#6/#12) — each with regression tests (24 fs tests) and behaviorally cross-checked against GNU coreutils df. df promoted to README Stage 6 — Audited.

  • file/audit.mdcat, cmp, dd, file, find, od, split, tee (the file/ crate; eight utilities, behaviorally verified against the mega-PDF, no sliced tree). Headline risks: tee does not copy stdin→stdout and rejects file operands; find -name is regex not fnmatch (bracket ranges like [a-z] match nothing) and -iname/-mount are missing; file magic </> comparisons are inverted and symlinks are followed by default; od -c emits named chars instead of C escapes; split - operand is not stdin and the {NAME_MAX} check is absent; cmp -s leaks an EOF diagnostic; dd conv ordering / SIGINT re-raise. Cross-cutting: hardcoded-English diagnostics.

  • i18n/audit.mdgencat, gettext, ngettext, iconv, locale, localedef, msgfmt, xgettext (the i18n/ crate; eight utilities, all behaviorally verified against the mega-PDF, no sliced tree). Real in-tree implementations (not host delegations), but every utility has gaps and several have Critical defects. Headline risks: gencat panics (exit 101) when merging into a pre-existing catalog and processes no escape sequences/line-continuation (catalogs miscompiled); iconv prints a conversion/codeset error yet exits 0 (and derives its default codeset from a naive LANG split that hard-fails under LANG=C); locale returns hardcoded d_fmt/t_fmt/yesexpr/noexpr regardless of locale and is missing ~30 LC_MONETARY/LC_TIME keywords; localedef writes only a one-line LC_IDENTIFICATION marker and never parses -f charmap/LC_CTYPE/LC_COLLATE (no usable locale); msgfmt ignores domain directives so a multi-domain .po collapses into one file named after the input; gettext/ngettext append a spurious trailing <newline> outside -s, lack -E, and ngettext can't take the optional [textdomain] operand; xgettext writes messages.pot (spec: messages.po), registers -X for the spec's -x, and sorts output alphabetically instead of in extraction order. Cross-cutting: error paths exiting 0, hardcoded-English diagnostics (LC_MESSAGES inert), and unimplemented NLSPATH/LANGUAGE. All priority findings have since been remediated on the i18n-audit branch (13 phases): seven utilities — gencat, gettext, iconv, locale, msgfmt, ngettext, xgettext — are now promoted to README "Stage 6 — Audited"; localedef received bounded parser/exit-code/directive fixes but stays at Stage 3 because emitting a real libc-consumable compiled locale (LD-1) and LC_CTYPE-driven byte interpretation (cross-cutting theme 4) are explicitly deferred.

  • m4/audit.mdm4 (macro processor; behaviorally verified against the mega-PDF spec, no sliced tree available). 2 Critical panics (index empty-needle windows(0); eval /0,%0), 8 Major (eval ignores radix/min-digit args + no octal/hex + shift-vs-relational precedence; $#==0 for macro(); m4wrap not rescanned; defn can't reproduce built-ins; recoverable errors abort the run; byte-not-character semantics). Core path (spec EXAMPLES, $@/$*/shift, divert/undivert, pushdef/popdef, m4exit-after-error) conforms.

  • mailx/audit.mdmailx (the message send/receive utility; ~4.4 kloc across nine modules, 141 tests; static spec-vs-code audit with grep verification of every Critical/Major "absent" claim). Headline risks: the -E option — one of only two required on all systems — is unimplemented and errors out; the entire ASYNCHRONOUS EVENTS section is absent (no SIGINT handler anywhere, so SIGINT kills mailx and -i/ignore is dead code keyed off an ErrorKind::Interrupted that never fires); and header-summary display panics on multibyte text (byte-offset &s[..n] slicing in message.rs/mailbox.rs). Major band: every sh -c invocation omits the -- argument mandated by Austin Group Defect 1528 (pipe/!/~!/~|/~r !); the -f file operand is mis-parsed into Send Mode unless glued to -f (breaking the RATIONALE's required mailx -fin mymail.box); -n wrongly skips the user MAILRC (spec attaches "unless −n" only to the system start-up file); no setlocale/gettext and hardcoded-English diagnostics; loaded messages are never given the new state (always unread, so N shows as U and :n matches nothing); the ~: command-level escape is a stub whose set only echoes; ~w truncates instead of appending; and reply-all ignores Reply-To. The command/escape/msglist surface is otherwise broad and mostly conforming. No fixes applied — audit only.

  • make/audit.mdmake (behaviorally verified against the mega-PDF spec, no sliced tree). 5 Critical (recipe lines containing = abort the parse with EmptyIdent; .POSIX rejected as unsupported; missing include panics; make -k reports failure+exit 2 on success; command-line macro=value operands unsupported), plus Major gaps: no $(VAR:a=b) substitution, no backslash-newline continuation, single-suffix inference unused, no -j/token-pool/.WAIT/.NOTPARALLEL, no multiple -f, no shell -e, SHELL env var misused, .SUFFIXES BTreeSet loses order, MAKEFLAGS ignored. Behavioral verification refuted four agent-proposed findings ($(VAR)-verbatim-to-shell, $$ passthrough, inverted ?=, -include panic).

  • man/audit.mdman (the man crate plus the mdoc/roff engine the maintainer built implicitly to satisfy POSIX man; behaviorally verified against the mega-PDF, no sliced tree). Every priority finding has since been remediated on the man-audit branch (Phases 1–5 plus a follow-on engine rewrite: a single terminal backend, a full roff front-end, pest replaced by a total hand-written recursive-descent parser, and tbl/eqn preprocessors). Original headline risks: a .Xr name page panicked the process and deeply nested macros overflowed the stack; legacy man(7)/roff pages rendered as an empty page yet exited 0; no bold/italic/underline was emitted and \fB…\fR font escapes leaked as literal text; PAGER was spawned even when stdout is not a terminal; width was capped at 78; and -k did literal-substring matching rather than the spec’s grep -Ei (ERE).

  • misc/audit.mdtrue, false, test/[ (the misc crate; behaviorally verified against the mega-PDF, no sliced tree). The cleanest crate audited so far: no Critical or Major defects. true/false are textbook-conforming; test implements the full primary set, the 0/1/2/3/4/>4 argument-count precedence algorithm, the [ … ] form, and correct 0/1/>1 exit codes. Only real divergence: </> compare by byte/scalar order instead of LC_COLLATE collation. Informational: the POSIX-2024-removed -a/-o/(/) operators survive as a >4-arg extension (legal — that path is "unspecified") but the 3-arg -a/-o form errors; integer operands are i64-bounded and reject leading blanks.

  • pathnames/audit.mdbasename, dirname, pathchk, realpath (the pathnames/ crate; all findings behaviorally verified against the release binaries). dirname is the cleanest (lexical PathBuf::pop() conforms). The other three are broken on common paths: basename panics (exit 101) on /, .., and any …/.. operand and strips the suffix from the whole pathname before extracting the component (missing the step-6 "suffix identical to result" guard → basename /usr/bin/env env prints bin); pathchk's default filesystem mode errors pathconf error for every existing file and every creatable relative name (find_fshandle returns ""), wires -p/-P as mutually exclusive (spec mandates using them together), and uses is_ascii() instead of the portable-filename character set for -p; realpath does not resolve symbolic links in default or -E mode (purely lexical normalize()) — only -e (via fs::canonicalize) is correct, and the existing tests codify the divergent lexical behavior. Cross-cutting: hardcoded-English diagnostics (LC_MESSAGES inert) and to_string_lossy/String non-UTF-8 mangling. All priority findings have since been remediated (Phases 1–5): basename rewritten to the POSIX 6-step byte algorithm; pathchk's find_fshandle repaired and -p/-P made combinable; realpath now resolves symlinks in default & -E modes; all four adopt plib::diag + byte-faithful I/O. All four utilities promoted to README "Stage 6 — Audited".

  • pax/audit.mdpax (the archiver; ~10.3k lines across three on-the-wire formats — ustar, cpio ODC, pax interchange — five modes, multivolume, and -s substitution; comprehensive all-surface audit, behaviorally cross-checked in both directions against GNU tar 1.35 and GNU cpio 2.15, since no reference pax is installed). Verdict: the hard parts are right and the easy parts are wrong. All three formats interoperate bidirectionally with tar/cpio on the everyday path (regular/dir/symlink/hardlink/fifo, ustar prefix/name long-name split ≤255, special-char/Unicode names, EOF markers, input auto-detection); the pax self-counting extended-header length is correct (incl. the digit-carry boundary) and the -s BRE substitution engine (&, \1\9, g, p, in-order/first-success, empty→skip) and fnmatch patterns (correctly do not cross /) conform. Headline defects are process semantics, not wire formats: exit status is never set non-zero for unmatched patterns / missing operands / per-file failures (Critical), and a per-file error aborts the run so extraction stops mid-archive and later members are silently lost (Critical, data loss). Major band: ustar/cpio numeric fields corrupt members ≥8 GB (dropped terminator / silent high-digit truncation); pax drops sub-second mtime (20 mtime=… vs GNU 30 mtime=….123456789); default extraction ignores umask (mode 0777 vs tar's 0755); default blocksize is 10240 for every format (spec: 5120 for cpio/pax) and -b misreads 1–511 as a GNU blocking factor (-b 20→10240 bytes); -s rejects the s/S flags; append silently coerces a mismatched -x format; a directory pattern doesn't select its subtree (-d inert in read/list); and several -o keyword behaviors (:= standard-keyword overrides, delete/override/invalid in read/list, the POSIX listopt=%(keyword)s form) are unimplemented. Non-POSIX extensions (kept, documented): -z gzip and -M GNU-style multivolume. No crashes/hangs. All findings have since been remediated on the pax-audit branch (8 phases): both Critical findings (exit status; per-file abort/data-loss) and every Major/Minor item are fixed and covered by per-phase regression tests (120 integration tests), each behaviorally re-verified against GNU tar/cpio; only #26 (non-UTF-8 path bytes; cpio TRAILER c_mode) is dispositioned WON'T-FIX. pax is promoted to README Stage 6 — Audited.

  • print/audit.mdlp only (the print/ crate ships a single POSIX utility; pr/printf live in text/). Static spec-vs-code audit against the sliced POSIX.1-2024 tree, every "absent" claim grep-verified. This lp is a thin IPP (Internet Printing Protocol) client — destination is an ipp:// URI, data uploaded synchronously via the ipp crate, no CUPS/spool//dev/lp integration and no system-default printer — a deliberate minimal-deps architecture divergence (audited the way uucp's SSH transport was: spec-intent gaps recorded Major, not Critical). No Critical defects. The POSIX surface is complete and the mechanics conform: -/no-operand→stdin, multiple files printed in order as integral wholes, -d>LPDEST>PRINTER precedence, request-ID-to-stdout gated by -s, diagnostics-to-stderr, and — the cleanest i18n wiring audited so far — setlocale+textdomain+gettext on every diagnostic. Four Major findings: -m (mail) and -w (terminal write) are parsed-but-ignored no-ops (-c is also ignored but is a conforming no-op — IPP always slurps + uploads the whole file before exit, so the -c guarantee always holds); no system-default destination so bare lp/lp file always exits 1 (permitted via "no device available→non-zero" but violates the RATIONALE's "should always execute with no options or operands" intent); and the destination must be an ipp:// URI, so historical LPDEST=name/PRINTER=name values are rejected. Minors: first-error aborts remaining operands (permitted — CONSEQUENCES=Default), copies u32→i32 overflow, no ipps://, silent malformed--o, <dest>-0 request-ID fallback, no NLSPATH. LC_TIME/TZ are N/A (banner is the IPP server's job). All actionable findings have since been remediated (5 phases on the print-audit branch): resolve_uri accepts bare printer names → ipp://localhost/printers/<name> (#4); -n bounded to i32::MAX, malformed -o warns, missing job-id is an error (#6/#8/#9); per-file errors continue instead of aborting (#5); and -m/-w now poll IPP job-state to completion (120 s bound) then mail via local sendmail / write /dev/tty (#1/#2) — each phase committed with regression tests (22 integration + 5 unit). Dispositioned WON'T-FIX: #3 (no system default — conforming per 103059–61), #7 (ipps:///TLS — minimal-deps), #10 (NLSPATH — tree-wide). lp promoted to README Stage 6 — Audited.

  • process/audit.mdenv, fuser, kill, nice, nohup, renice, timeout, xargs (the process/ crate; eight utilities plus the shared signal.rs table; static spec-vs-code audit against the sliced POSIX.1-2024 tree, with every Critical/Major claim confirmed by reading the cited code + spec lines, and several agent-proposed findings refuted and recorded inline). Headline risks cluster in two families. The exec-launcher exit-status contract is split: nohup/timeout/xargs map 126/127 correctly, but env and nice propagate the exec error through main's ? and always exit 1 — and nice additionally aborts instead of honoring the spec's central "a failed nice() shall not prevent invoking the utility" guarantee (plus a wrong res < 0 errno check and a clap range that rejects -n 40). renice accepts only a single ID where POSIX mandates ID..., so per-ID error continuation is structurally impossible, and it wrongly rejects numeric ID 0 and excludes +20. nohup never sets the mandated 0600 mode on nohup.out, reads dirs::home_dir() instead of $HOME, misses the "redirect stderr to the same open file description as stdout" case, and panic!s (exit 101) on a file-open failure. timeout truncates sub-second/fractional durations via alarm(2) (so timeout 0.5s never fires), unconditionally resets the child's SIGTTIN/SIGTTOU to SIG_DFL instead of inheriting timeout's own disposition, and forwards only 5 signals rather than the full terminate-default set. xargs lacks the POSIX.1-2024 -r option and runs the utility zero times on empty input where the spec requires exactly once, plus a c8 as char cast that corrupts multibyte UTF-8 arguments. fuser's stdout PID format uses two spaces for ≤4-digit PIDs vs the spec's " %1d", and its /proc/maps device decode uses obsolete 8-bit minor encoding. kill is the cleanest — no Critical/Major; the agent's "kill -l 6 prints IOT not ABRT" headline was refuted (ABRT is present and ordered first). Cross-cutting: locale is initialized everywhere but runtime diagnostics are hardcoded English (LC_MESSAGES inert), and env/nice/nohup/renice have zero tests. Totals: 10 Critical, 16 Major. Refuted findings (recorded, not actionable): env -u (not in POSIX.1-2024), fuser exit>0-on-no-match (GNU-only), xargs signal→125 / 255-diagnostic (folds to conformant exit 1), kill ABRT/IOT + 128+N boundary + negative-pid, renice obsolescent positional form. All priority findings have since been remediated on the process-audit branch across 8 themed phases (one per utility), each independently committed with regression tests and behaviorally re-verified: a shared posixutils_process::exec::exec_error_exit 126/127 mapper (env/nice); nice invoke-anyway + errno check + full range; renice ID... + per-ID continuation + ID 0 + -20..=20 (and plib::priority no longer prints its own diagnostics); nohup 0600 + $HOME + spec stderr-fd routing + no-panic; timeout setitimer sub-second + SIGTTIN/SIGTTOU inheritance + full forwarding set + WCOREDUMP re-raise; xargs -r/run-once-on-empty + UTF-8 word-split + quoted-newline error + -E in insert mode; fuser " %1d" format + makedev + error propagation; kill per-signal -l + -l-option edge; and a crate-wide plib::diag i18n migration (#C1). Validation: full workspace build + clippy --all-targets + fmt --check clean; 132 process integration tests + 71 plib tests green. All eight utilities promoted to README Stage 6 — Audited.

  • sys/audit.mdgetconf, ipcrm, ipcs, ps, uname, who (the system-information crate; six utilities plus the pslinux.rs/psmacos.rs back-ends; static spec-vs-code against the sliced POSIX.1-2024 tree, every Critical/Major claim confirmed by reading the cited code + spec lines, and the libc-0.2.180 crate source for portability claims). uname is the cleanest (no Critical/Major) and ipcrm nearly so (only argv-order + decimal-key Minors; macOS msg-queue omission is a documented platform limitation). The headline defects are correctness bugs on the golden path: ipcs parses the /proc/sysvipc/* mode field as decimal when the kernel writes it in octal (%4o), so the MODE/permission column is wrong on every Linux run (Critical) — compounded by QBYTES always -, the S/R/C MODE status flags hard-coded -, and /proc read errors silently swallowed (exit 0 on failure); and ps etime mixes time bases — it subtracts the process start_time (clock-ticks-since-boot on Linux) from the current Unix epoch, so elapsed time is meaningless (Critical, the code admits it's "simplified"), while CPU time hardcodes 100 ticks/s instead of sysconf(_SC_CLK_TCK), the macOS PID enumeration uses a fixed 1024-entry buffer that silently truncates the process list, and SYNOPSIS options -w/-n + the COLUMNS env var are absent. getconf is current-spec-stale: its -v gate accepts only obsolescent POSIX_V6_*/POSIX_V7_* and rejects the POSIX.1-2024 POSIX_V8_* names (Issue 8 / Defect 1330), and the <limits.h> Maximum/Minimum constants aren't accepted as operands. who omits the -d <exit> field and the -l literal LOGIN name. Cross-cutting: locale init is wired in all six (setlocale+textdomain+gettext) but no .mo catalogs ship, so LC_MESSAGES is inert; ipcs/who honor TZ incidentally via localtime_r, ps ignores TZ/LC_TIME entirely. Refuted (recorded, not actionable): getconf's _SC_PASS_MAX/_PC_FILESIZEBITS "macOS build break" — both are defined for Apple in libc — ps's -o all-null-header "inverted logic" and -f CMD header (#P12, correct per spec 112543), and who's extra-operand handling (#W3, clap already exits 2). Totals: 2 Critical, ~12 Major. All findings have since been remediated on the sys-audit branch across 11 themed phases (one or two utilities each, independently committed with regression tests; build/clippy --all-targets/fmt clean, 151 sys + 71 plib tests green): ipcs MODE octal-parse + 11-char POSIX MODE + QBYTES + facility-not-in-system + error propagation; ps elapsed/CPU time-base normalization (epoch seconds + _SC_CLK_TCK + /proc/stat btime), stime/TZ, -w/COLUMNS width, -n, <defunct>, controlling-tty, and the macOS PID-buffer/argv//dev fixes; who -d <exit> + -l LOGIN + LC_TIME + idle clamp; getconf POSIX_V8_* + <limits.h> LONG_BIT/WORD_BIT + NPROCESSORS/option sysconf + POSIX2_SYMLINKS + verbatim confstr; ipcrm argv-order + full key range; and uname's direct libc::uname() (dropping the unmaintained crate). macOS-only changes are cargo check/clippy --target x86_64-apple-darwin clean (runtime macOS verification pending CI). Dispositioned WON'T-FIX/deferred: _CS_POSIX_V8_* confstr + pure-header limit macros (not in libc), macOS SysV message queues (#IR3), and crate-wide .mo catalogs (tree-wide i18n, as in dev/). All six utilities promoted to README Stage 6 — Audited.

  • screen/audit.mdstty, tabs, tput (the terminal-screen crate; three utilities plus stty-private osdata.rs termios tables; static spec-vs-code against the sliced POSIX.1-2024 tree, with every Critical/Major claim behaviorally confirmed by running the release binaries — a pty.fork() harness exercised the TTY-only stty set-mode paths). tput is the cleanest (exact clear/init/reset operand set, full 0/2/3/4/>4 exit ladder, continue-past-unavailable) and tabs is solid (full XSI preset set, -0..-9, n[[sep[+]n]...] operand with +N increments + strict-ascending validation, gettext diagnostics). But stty's set path — its primary job — is severely broken: stty <single mode operand> (stty sane/stty raw/stty cs8) panics (assert!(args.operands.len() > 1) at stty.rs:642, exit 101, PTY-confirmed), and every negation operand (-echo, -icanon, …) is rejected by clap as an unknown option because the positional lacks allow_hyphen_values — so mode-setting only works with 2+ positive operands. Major stty gaps: single-character control-char assignment (stty erase x) is unsupported (only ^c/^-/undef); the Issue-8 rows/cols/size window-size operands are entirely absent; and the speed table mis-keys B50 as "54" (so stty 50 fails, stty 54 mis-programs). stty correctly uses standard input for get/set (the security RATIONALE) and its -a/-g/short displays conform; diagnostics are hardcoded English (vs gettext in tabs/tput). stty ships zero executable tests (TTY excuse). Headline counts: 2 Critical + 4 Major (stty), 1 Major (tabs), 0 Critical/Major (tput). All findings have since been remediated on the screen-audit branch across 5 themed phases: stty's set path (single-operand panic + allow_hyphen_values negation), single-char control-char assignment, rows/cols/size window-size operands, the "50" speed-key fix + cfg-gated higher bauds, locale-faithful control-char rendering + gettext/stty:-prefixed diagnostics, and the -a/-g+operands guard; tabs' default-terminal fallback, terminal-width-derived cap + beyond-width rejection, leading-+ rejection, and non-terminal-stdout guard; and tput's if/rf file-contents emission + iprog execution, reset→init fallback, and valid-before-invalid operand ordering. A new portable-pty-backed integration harness brings stty from zero tests to a full suite (8 unit + PTY regressions); full workspace build/clippy --all-targets/fmt clean, 46 screen tests green. All three utilities promoted to README Stage 6 — Audited.

  • sccs/audit.mdadmin, delta, get, prs, rmdel, sact, sccs (front-end), unget, val, what (the full POSIX SCCS family; ten utilities plus the shared plib::sccsfile file-format core, all behaviorally verified against GNU CSSC 1.4.1 in both interop directions). The shared core (checksum, SID, delta-table, body weave, p-file) was already byte-interoperable with CSSC; defects were mostly CLI-layer. Original headline risks: get emitted CSSC encoded/binary (e-flag) bodies un-decoded (uuencoded garbage) and what aborted on the first non-UTF-8 file (every binary — its core use case); rmdel never reweaved the body (orphan ^AI/^AE block left behind) and gated removal on a spoofable $LOGNAME compare; the sccs front-end's -r (run-as-real-user) was a parsed-then-ignored no-op and it spawned siblings by bare name; prs emitted no trailing newline per delta (default output corrupted) and dumped Rust Debug for :FL:; admin -i/-t/-y ate the following operand; delta was missing -m/-g/the stdin-comment path and the v-flag MR check; val could not produce its 0x80/0x40 exit bits. Cross-cutting: -/directory operands missing in get/delta/admin, $LOGNAME instead of getpwuid, TZ-blind timestamps, the No id keywords warning absent, no z-file locking, hardcoded-English diagnostics. All findings have since been remediated (14 phases): a shared-core foundation (real_login_name/TZ-aware now(), remove_delta reweave, uudecode_sccs/uuencode_sccs, prs_fl_line, expand_operands, a ZLock RAII lock + SIGINT cleanup) plus per-utility fixes, each behaviorally re-verified byte-for-byte against CSSC. All ten utilities promoted to README "Stage 6 — Audited"; the handful of remaining Minor items are documented WON'T-FIX/N-A (BSD sccs -p, permissive delta -r, :KV: extension, p-file 0644, etc.).

  • sh/audit.mdsh (the POSIX shell command language in full: §2 Shell Command Language incl. §2.15 special built-ins, the sh utility page, and all 16 regular built-ins; the most exhaustive audit to date, behaviorally verified against dash and bash --posix). The shell is broad and feature-complete, but behavioral testing found 7 Critical defects — five process-aborting panics and two silent-wrong-result bugs on common paths: case pattern matching is unanchored (case ab in a) matches → wrong branch); read -r x y panics; $((1/0))/$((5%0)) panic; a missing script-file operand panics instead of exiting 127; a []] bracket pattern panics; and set -u is inert (unset vars expand to empty, status 0). ~30 Major gaps cluster in unimplemented POSIX.1-2024 additions ($'…', ;&, set -o pipefail, {varname}<), arithmetic edge cases (no unary chaining/comma op; div-by-zero panics), error/exit semantics (return ignores $?, command returns 1 not 127, signal status off-by-one, special-builtin expansion errors don't abort), break/continue escaping function boundaries, symbolic umask, glob skipping symlinks, non-re-inputtable -p/list output quoting, and the ENV/MAIL* startup machinery. i18n is initialized but diagnostics are hardcoded English. Behavioral verification refuted several agent-proposed findings (hash-store, kill -l SIGKILL, exec N>file persistence, read x y without options, literal-. glob, release-build arithmetic overflow/shift/0x panics). All findings have since been remediated across 11 themed phases: 54 of 59 numbered items fixed (every Critical and every Major), each behaviorally re-verified vs dash/bash --posix and covered by a tests::audit_regressions module — the panics, case anchoring, set -u, the POSIX.1-2024 additions ($'…'/;&/set -o pipefail/{varname}< partly), arithmetic edge cases, exit/error semantics, break/continue scoping, symbolic umask, glob symlinks, re-inputtable -p output, ENV/MAIL* startup, the vi-mode internals, and a gettext pass over the diagnostic surface. The 5 remaining items (#33 async signal/stdin, #48 bg/fg strictness, #51 read PS2, #54 dot-readable message, #57 IO_LOCATION) are documented WON'T-FIX/deferred with zero-or-minimal runtime impact; #56's interpolated-message gettext wrapping is a documented zero-effect residual. sh is promoted to README Stage 6 — Audited (253 unit + 178 integration tests green, clippy clean, fmt clean).

  • tree/audit.mdcp, mv, rm only (a partial tree/ audit), plus the ftw/ race-free traversal dependency — these data-destroying utilities are the canonical TOCTOU/symlink-swap targets, so the audit adds an explicit filesystem-race / security-hardening lens on top of POSIX conformance. Static spec-vs-code + grep proofs + behavioral spot-checks vs GNU coreutils (release binaries; baseline tree 143 passed / 9 root-ignored, ftw 8 passed). The recursive interior of all three is correctly built on ftw's dir-fd model (unlinkat/openat/mkdirat/symlinkat relative to a pinned parent fd; ancestor path-swaps provably defeated), but three Critical issues cut across the family: (#F1) ftw's directory-descent openat uses bare O_RDONLY with no O_NOFOLLOW/O_DIRECTORY and no post-open dev/ino re-verification, leaving the historically-exploited leaf symlink-swap window — under an adversarial rename it turns rm -r into an arbitrary-directory-deletion primitive (coreutils fts guards exactly this); and (#C1/#M1) the shared copy_characteristics re-applies the source's S_ISUID/S_ISGID via fchmodat even when the chown to duplicate ownership failed — the privilege leak POSIX 90720-90721 / 108104-108105 forbids (behaviorally: cp -p /usr/bin/passwd → mode 4755 owned by the caller; GNU clears it). Major band: cp a b c / mv a b c with a non-directory final operand silently process only the first source (mv removes it) and exit 0 instead of erroring (90605-90606); cp aborts the whole hierarchy on the first per-file error instead of continuing with same-level siblings (90829-90832); and rm is missing two POSIX.1-2024-mandated options — -d (remove empty dir, Austin Group Defect 802) and -v (verbose, Defects 1154/1365/1487), both rejected as unknown arguments. Minors: several panic!/unreachable!()/.unwrap() abort sites in ftw (getrlimit, DeferredDir reopen, unknown S_IFMT) and rm; symlink-cycle detection only on ftw's deferred slow path; the top-level single-file rm path and mv/cp target probes are path-based (not dir-fd-relative); hardcoded y affirmative (no LC_MESSAGES yesexpr); prompt_user read_line().unwrap(). CONFORMS: prompts→stderr, default symlink semantics, mv same-fs atomic rename + CONSEQUENCES-OF-ERRORS guarantee, mv same-file/hard-link/dir-mismatch steps, rm dot/dotdot// guards + no-symlink-follow + arbitrary-depth/fd-budget. All findings have since been remediated (8 phases on the audit branch): #F1 (ftw descent now O_NOFOLLOW/O_DIRECTORY + (dev,ino) re-verification, with a deterministic ftw/tests/race.rs regression), #C1/#M1 (shared copy_characteristics masks setuid/setgid when chown fails), #C2/#M2 (multi-operand non-dir target errors), #C3 (cp continue-on-error + mv characteristics exit-status), #R1/#R2 (rm -d/-v added), and the panic/yesexpr/errno/symlink-cleanup minors (new plib::locale::is_affirmative); #C4/#C5/#C6 are documented WON'T-FIX. Each phase is independently committed with regression tests; clippy/fmt clean. cp, mv, rm promoted to README Stage 6 — Audited. Residuals documented: the rare fd-conserving ftw DeferredDir path lacks a (dev,ino) baseline (fails closed via O_NOFOLLOW) and #F3's deep-tree reopen still panics on a mid-walk race.

  • users/audit.mdid, logname, logger, mesg, newgrp, pwd, tty, write, talk, plus the talkd daemon (the user/terminal crate; nine POSIX utilities + one implicitly-required daemon — talkd audited under the project's crond policy: not POSIX-specified, but required by POSIX talk, so judged against talk's implicit requirements + the BSD ntalk protocol + secure-daemon practice). Static spec-vs-code against the sliced POSIX.1-2024 tree, every Critical/Major claim confirmed by reading the cited code and spec lines (and plib/src/curuser.rs for the shared helpers). Totals: 15 Critical, 26 Major. Four cross-cutting themes: exit-status ladders are ignored (mesg always exits 0 vs the spec's 0/1/>1 messaging-state ladder; talk exits 130 on SIGINT where the spec mandates 0; logname never fails even with no login name); the shared plib::curuser helpers diverge (login_name() falls back to $USER then "unknown" and never fails — logname inherits this; tty() searches stdin→stdout→stderr where tty must report stdin only); setlocale is wired everywhere but runtime diagnostics are hardcoded English (LC_MESSAGES inert); and the security/correctness-critical utilities (newgrp, mesg, pwd, logname, talkd's wire path) have zero tests. Headline defects: newgrp is fundamentally broken — in -l mode it execs the login shell with the old GID before any group change (blocking status() at set_login_environment), in normal mode it never creates a shell at all, the mandatory-but-should-be-optional group operand makes "no operand = restore login groups" unreachable, and group-password verification is gshadow/Linux-only with a weak empty-string compare; write routes the typed alert and erase/kill output to the sender's own stdout instead of the recipient's terminal and panics (.expect) on a closed recipient tty; talk writes its UI/echo to stderr, sets raw mode in a thread-local scope that isn't restored on most exit paths, ignores Answer::PermissionDenied/NotHere, and never checks stdout-is-a-terminal; talkd's ANNOUNCE is a self-admitted stub that never writes to the recipient tty (the daemon's entire purpose), validates no callee/mesg/privilege, binds a Unix socket not UDP port 518 (no interop), and keeps an unbounded invitation table (DoS); logger implements none of the -i/-f/-p/-t options or stdin reading and defaults to user.err not user.notice; id -G can omit a distinct effective GID; pwd -P is a dead flag (no last-one-wins). All findings have since been remediated on the users-audit branch across 12 themed, independently-committed phases (build + clippy --all-targets + fmt clean, tests green per phase). A shared foundation landed first — plib::curuser::{login_name_strict, ttyname_of}, a new plib::exec::exec_error_exit (POSIX 126/127), a ported portable-pty integration harness (users/tests/common), and a crate-wide migration to plib::diag + gettext. Highlights: newgrp was rewritten to exec a new shell in both modes with the invoke-anyway guarantee, no-operand login-group restore, and fail-closed gshadow+group-DB password verification with a kept privilege-drop (the audit's "remove setuid" note was refuted as a security regression); write was rebuilt around the spec's canonical-input model (per-character rendering, alert→recipient, superuser override, no panic paths, SIGHUP/SIGPIPE, /dev char-device validation); mesg now honors the 0/1/>1 exit ladder (the audit's #MG3 "before the command" framing was refuted by behavioral check against the system mesg); logger gained -i/-f/-p/-t + stdin bodies + the user.notice default; id -G emits the full {gid,egid}∪getgroups() union; pwd implements -L/-P last-wins; tty reports stdin's terminal only; logname enforces the strict getlogin() contract; and talkd became a functional hardened local daemon (announce-to-tty with passwd/utmp/mesg validation, bounded invitation table, 0600 symlink-safe socket, daemonization, OnceLock signal path, syslog) with the no-UDP-518 transport documented WON'T-FIX. Eight utilities — id, logname, logger, mesg, newgrp, pwd, tty, write — are promoted to README Stage 6 — Audited. talk is deliberately NOT promoted (stays Stage 3 with its bundled talkd): its Criticals (#TK1 SIGINT→0, #TK2 stderr-UI, #TK3 terminal-restore) and Majors (#TK5 answer handling, #TK6 stdout check, #TK8 termination) are fixed, but #TK7 (full POSIX character-processing) and Minors #TK9/#TK10/#TK14 are deferred — the thread-heavy full-screen curses/input engine cannot be behaviorally verified in CI, so it was not rewritten blind. Two audit findings were refuted during remediation and recorded inline: mesg #MG3 (exit reflects the resulting state, not the pre-command state) and newgrp #NG6 (the setuid(getuid()) is the privilege drop, not dead code).

  • uucp/audit.mduucp, uux, uustat (the uucp/ crate, audited under the project's "UUCP is dead — functional but absolutely minimal" stance; static spec-vs-code with light behavioral confirmation, no reference UUCP installed). This implementation uses SSH as the transport rather than the legacy g/f/t protocols + uucico daemon, so findings are partitioned into POSIX-mandated surface gaps vs accepted architecture divergences. The CLI is already option-minimal (zero non-POSIX flags). No Critical defects. One Major: uux cross-system output-file routing is an unimplemented TODO, and a null-system (!) output redirect is routed to the execution host instead of local, so POSIX uux EXAMPLE 3 does not fully work. Minors: hardcoded-English diagnostics (LC_MESSAGES inert), .unwrap() on locale setup, throwaway -j job IDs (no persistent job for immediate transfers; uux never persists one), and $USER-based identity/ownership in uustat -k/-r. Accepted by design (DIVERGES, no action): uucp -r queues a J.* record but no daemon ever drains it (so the file never transfers); -c/-C are inert without spool staging; multi-hop routes (a!b!path) are rejected; remote wildcard expansion is unsupported; uux discards un-redirected command stdout. Only non-POSIX extension is the UUCP_SPOOL env var (kept for tests). shell_escape is sound and consistently applied to SSH command construction. All actionable findings have since been remediated (5 phases): #UX1 fixed minimal-correct (null-system⇒local + ssh_fetch_file/ssh_send_file delivery, third remote system a hard error); #G1 diagnostics routed through gettext(); #G2 locale-setup .unwrap().ok(); #G3 real-login identity (getpwuid(getuid())) + getuid()==0 root check for uustat ownership; #UC1 unified -j job ID (the -r -j ID is the one uustat lists; immediate-transfer IDs documented informational); #G4 newline-in-destination rejected; #UC2 documented WON'T-FIX (single-quoted remote paths). All three utilities promoted to README "Stage 6 — Audited".

  • text/audit.md — the full text/ crate: 22 utilities (asa, comm, csplit, cut, diff, expand, fold, grep, head, join, nl, paste, patch, pr, sed, sort, tail, tr, tsort, unexpand, uniq, wc), static spec-vs-code against the sliced POSIX.1-2024 tree with every claim grep-verified, one subagent per utility. ~25 Critical, ~60 Major, ~75 Minor across the crate. Headline defects: sort used Rust byte order instead of strcoll, concatenated rather than merged under -m, broke -u for 0/3+ keys, used f64 for -n, and processed only two -k keys; join was an O(N×M) stub with the wrong default reconstruction, single-value -a/-v, panicking -o 0, and byte-equal keys; csplit dropped each split file's trailing newline (corrupting output and the byte count), reset its line counter (breaking absolute line numbers), and never consumed a bare line-number operand; sed never implemented the POSIX l command, set the -E flag after compiling patterns, appended a/r text into the pattern space, cleared the hold space on D, and lacked s///i; patch silently appended a newline to no-newline-at-EOF files and never honored /dev/null deletions; diff had wrong context single-line ranges and a missing unified <frac>/<zone>; the column tools (expand/fold/unexpand) and wc -m/-w/cut -c/tr classes were ASCII-only rather than LC_CTYPE-aware; and - was honored only as a sole operand across many utilities. All actionable findings have since been remediated on the text-audit branch across 18 themed, independently-committed phases (each with regression tests; per-phase build / clippy --all-targets / fmt / posixutils-text suite clean, and every data-affecting change cross-checked byte-for-byte against GNU coreutils 9.4 in C and UTF-8 locales). Foundation: plib::locale gained wcwidth_char + libc-backed ctype classifiers + an MbDecoder, and a -→stdin sweep routed dashed operands through plib::io::input_stream_dashed. 20 of 22 utilities are promoted to README "Stage 6 — Audited": asa, comm, cut, diff, expand, fold, grep, head, join, nl, paste, patch, pr, sed, sort, tail, tsort, unexpand, uniq, wc. csplit and tr deliberately stay at Stage 3: csplit has two open items (#4 a SIGINT created-file-cleanup handler, #8 erroring when an operand references a line past EOF — its file output is already byte-for-byte GNU-correct, only the exit status differs); tr is fully POSIX-conformant in the C/POSIX locale but its multibyte items (-c vs -C, LC_CTYPE character classes, [=equiv=] expansion, LC_COLLATE range order) are documented POSIX-locale limitations requiring a predicate-based class-matching rewrite (the [:upper:][:lower:] case pairing can't be made locale-aware without it). Deferred tree-wide: shipping .mo catalogs, so LC_MESSAGES text stays English even where diagnostics now route through plib::diag.

  • xform/audit.mdcksum, compress (+ uncompress/zcat via argv[0]), uuencode, uudecode (the data-transformation crate; static spec-vs-code against the sliced POSIX.1-2024 tree, every "absent" claim grep-verified — isatty/is_terminal, chown, newline-in-pathname checks all confirmed absent). cksum is the cleanest (no Critical/Major — its CRC core is a line-for-line transcription of the POSIX model program). The other three each have real defects. Headline risks: uudecode panics (exit 101) on malformed/binary input (String::from_utf8().unwrap() plus a cluster of expect/panic!/index sites), never honors the - magic cookie (Austin Group Defect 1544 — only /dev/stdout is special-cased), does not "scan…searching for" the begin line (header must be input line 1, so mail-wrapped data aborts), uses a strict Base64 decoder that rejects CRLF/whitespace transport artifacts, and skips the existing-file write-permission check while treating a failed set_permissions as fatal; uuencode mis-parses the single-operand form (… | uuencode decode_pathname) because the optional file operand is positionally first, so clap binds the lone operand to file and defaults the decode pathname to /dev/stdout (and the required decode_pathname is unenforced); compress never checks whether stdin is a terminal before prompting for overwrite (spec: non-terminal + no -f → diagnostic + exit >0, no prompt), runs its hard-link guard even under -c (which removes nothing), has a suffix-less-file decompress path that can delete the decompressed output (data loss), and its -b LZW validation range (9–14) contradicts the actual default (16). Cross-cutting: locale is initialized everywhere but runtime diagnostics are hardcoded English (LC_MESSAGES inert), and the Austin-Group-251 newline-in-pathname FUTURE DIRECTION is unimplemented. Totals: 1 Critical, 9 Major, 14 Minor. All findings have since been remediated across themed, independently-committed phases (build + clippy --all-targets + fmt clean, tests green); all six utilities promoted to README "Stage 6 — Audited". See xform/audit.md.

When you finish a new audit, add the link here.