Skip to content

fix: CommonMark rendering, home-path redaction, and the command guard cluster - #736

Merged
REPPL merged 87 commits into
mainfrom
integ/land-11
Sep 28, 2026
Merged

REPPL merged 87 commits into
mainfrom
integ/land-11

Conversation

@REPPL

@REPPL REPPL commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Integration branch 11 lands two reviewed chains of lanes as one change: the site, paths, file-system and code-span chain (drainSite, drainPaths, drainFS, drainSpan) and the guard cluster (drainG, drainG2, fix3-drainG), with one window recalibration at the merged tip. For a user: the website renders Markdown the way CommonMark says it should, no report prints your home directory, every place abcd writes refuses a symlink that would carry the write out of the checkout, and the shell guard catches more ways of killing a process found by a name search. No intent ships.

drainSite (the website). A three-backtick fence indented one to three spaces renders as a command block. The site verbs and the lifeboat reports name directories without the home path. The health page carries the share of authored commits that disclose AI assistance. The contribution guide and the security policy live in .github/, where the forge reads them, and the site footer and contributors page follow them there. Both files now fall under the /.github/ code-owner entry, so a change to either needs code-owner review; whether to narrow that entry is a ruling for the technical facilitator.

drainPaths (paths in reports). launch (payload, archive and bundle), the bare board and memory lint report their paths relative to the repository or with the home shown as ~, in their text and their JSON alike. The code that acts on a path still reads the real one.

drainFS (writes through links). The memory lint run log, the drift receipt, the reading run directory, reading assemble --out, history reconstruct --out and every lifeboat operand are proved at each level below the checkout they sit in, so a symlinked ancestor inside a checkout is refused before anything is written. The lifeboat and ideate Markdown renders escape the markers that would turn a field into a heading, list, quote, fence or link reference, and the memory board shows its staleness warning in the text render.

drainSpan (code spans). One CommonMark code-span pairer is shared by the site renderer, the prose cleaner, the record reader, lint and the lab, so a span closes only on a run of backticks of the same length. At the same tree, 117 of the site's 2,271 pages render differently from the previous binary: 115 only because a line ending inside a code span is now a space, as CommonMark reads it, and itd-185 also loses the padding of its padded single-backtick spans. This is expected.

drainG, drainG2, fix3-drainG (the shell guard). A kill fed by a process search is blocked however the search reaches it: through a substitution, a pipe into xargs, a pipe into a brace or paren group, a shell string, a here-string or a process substitution. pkill and killall by user or terminal are blocked, pkill's first signal word is read as its signal in any case, and the BSD xargs value flags are stepped over. The recorded over-blocks are in DECISIONS (2026-09-27). A guard refusal of the hook's own malformed flags fails open at exit 1, as the hook plane's other faults do. The spellings that wait on ruling AS stay open and deferred on their record.

Integration notes: the lane chain carries drainM1's commits (and the four records they resolve), which integration branch 7 landed first, so their four records are already resolved on main and this change does not resolve them again. The release-gate example receipt is re-pinned to the merged manifest's hash, and the docs name gate reads the contribution guide at its new place in .github/, where main's configuration still named the root file. iss-2609261232464351 was fixed on drainFS, whose base predated the record, and is resolved here.

Re-merge: this branch brings main in after integration branches 7 to 10 and the 45-minute macOS leg (#731 to #735). The contribution guide keeps integration branch 10's local-gates wording (preflight runs the format gate on the toolchain go.mod declares) in its new place in .github/, and no root copy of the guide or the security policy remains. The docs link check no longer names the root guide and policy as extra roots, because its .github root already walks both; abcd lint docs reports 0 blockers. No other configuration, workflow, script, Makefile target, hook, command page or site setting that main gained names a root copy. The release-gate example receipt already matches the merged manifest's hash. The branch adds no write under the intent record, so the intent mint lock that integration branch 10 introduced needs no new caller. No intent ships here, so no audit marker is added. The command reference regenerates with no drift, and the two pre-commit hook copies are main's, unchanged. The reading windows were re-measured at the merged tip and none moves: widening 1,325,292 tokens against 1,340,000, entailment 383,234 against 390,000, detection 1,334,328 against 1,350,000.

Reviews: drainSite SHIP. drainPaths SHIP. drainFS FIX FIRST, fixed in fix2 (verified FIX FIRST on one regression), fixed in fix3, READY. drainSpan SHIP. drainG SHIP. drainG2 SHIP. fix3-drainG READY (its report reconstructed; its first test was watched fail at its parent at the integration).

Resolves: iss-2608231008315498
Resolves: iss-2608270540523859
Resolves: iss-2608291957114882
Resolves: iss-2609091647582259
Resolves: iss-2609251355497247
Resolves: iss-2609251600023777
Resolves: iss-2609251640452031
Resolves: iss-2609251755278758
Resolves: iss-2609260948440803
Resolves: iss-2609261232464351
Resolves: iss-2609261848326365
Resolves: iss-2609261848338673
Resolves: iss-2609261950061900
Resolves: iss-2609261950066257
Resolves: iss-2609261950077063
Resolves: iss-2609261954288630
Resolves: iss-2609262148072415
Resolves: iss-2609262156124513
Resolves: iss-2609262231500173
Resolves: iss-2609262235543552
Resolves: iss-2609262237352137
Resolves: iss-2609262237415400
Resolves: iss-2609262241109876
Resolves: iss-2609262309556167
Resolves: iss-2609262322244502
Resolves: iss-2609262350446885
Resolves: iss-2609270028388291
Resolves: iss-2609270028432249
Resolves: iss-2609270036259517
Refs: iss-2609262259360005
Refs: iss-2609270036253187
Refs: iss-2609251824244354
Refs: iss-2609261536147903
Refs: iss-2609020539188868
Refs: iss-269
Refs: iss-81
Refs: iss-2609251455354719

Assisted-by: Claude:claude-opus-5-5

A three-backtick opener indented one to three spaces is a fence by
CommonMark and by mdrecord's walk, but the renderer rendered one only at
the left margin or inside a list item it dedents. Anywhere else the block
became a paragraph holding inline code, with no error. The shape is live
in the record: the fence of a loose list item arrives as its own block,
cut from the item by the blank line above it (itd-5, itd-1, a closed
spec, research notes).

The block now renders as a fence, each line losing up to the opener's
indent, and any lines after an indented closer render as the block they
are. The tilde and four-backtick twins stay refused, and an indented
opener under a line of prose is refused as a fence without a blank line
before it, as the margin one is.

Refs: iss-2609251600023777

Assisted-by: Claude:claude-opus-5-5
…e renders

Resolves: iss-2609251600023777
Assisted-by: Claude:claude-opus-5-5
Status.OutDir, Result.OutDir and CheckResult.OutDir carried the output
directory into `abcd site --json`, `site build --json` and
`lint site --json` as an absolute path: always for the build and the
check, which report the resolved directory, and for the board whenever
--out was absolute. Machine output never carries an absolute
developer-identity path (iss-81).

All three now go through displayOutDir: a directory inside the
repository is named relative to it (fsutil.RepoRel), one outside it has
the home directory redacted to "~" (fsutil.RedactHome), and a relative
--out is reported as given. The text board reads the same fields, so the
two renderings agree.

Refs: iss-2608291957114882, iss-81

Assisted-by: Claude:claude-opus-5-5
…ories

Two siblings of the site verbs' OutDir leak, found in the sweep: the
lifeboat verbs' reports name the lifeboat, target and destination
directories absolutely, and launch ship's payload.dest does the same.

Refs: iss-2609261848326365, iss-2609261848338673

Assisted-by: Claude:claude-opus-5-5
The lifeboat verbs named their directories absolutely in the --json and
text reports: PackResult.Dest, EmbarkPlan and EmbarkResult LifeboatDir
and TargetDir, LessonsResult.LifeboatDir, and the principles,
press-release and review results' LifeboatDir. A lifeboat or target
under the home named the developer, against the iss-81 rule.

Each field now goes through fsutil.RedactHome where the result is built,
so both renderings read the same value. The fields are display-only: no
code reads them back to reach the directory.

Refs: iss-2609261848326365, iss-81

Assisted-by: Claude:claude-opus-5-5
…nd lifeboat report paths

Resolves: iss-2608291957114882
Resolves: iss-2609261848326365
Assisted-by: Claude:claude-opus-5-5
The contributors rethink kept the page to its two folded panels, the
authors of record and the Assisted-by trailers, and dropped the three
stat tiles above them, with the None declaration counted beside the
chart rather than inside it. The share of authored commits that
disclose AI assistance was to move to the health page, and the
contributors page's own comment says it lives there, but no page
rendered it: its interface string sat unread in ui.json.

The rate is now the last tile in the health page's row of counts: the
same figure the contributors page carried, assisted commits over
authored commits with the merges set aside stated beside it.

Refs: iss-2608231008315498

Assisted-by: Claude:claude-opus-5-5
…plete

Resolves: iss-2608231008315498
Assisted-by: Claude:claude-opus-5-5
…e does

The forge reads a repository's contribution guide and security policy
from .github/, the root or docs/. The site and the lifeboat probe read
the root alone, so a repository that keeps its root clear lost its
footer security link silently and could not name its contribution
guide as the contributors page's policy source.

- The footer links the security policy at the first of .github/, the
  root and docs/ that carries it, by its file name; the provenance gate
  reads that name through the same resolution (footerLinks).
- record_pages.contributors.policy.file admits a markdown file directly
  in .github/, and nothing else there: the forge configuration beside it
  stays refused.
- The lifeboat's conventions tier counts .github/CONTRIBUTING.md.

Refs: iss-2608270540523859

Assisted-by: Claude:claude-opus-5-5
The repository root carries the files a reader or a tool looks for
there; the contribution guide and the security policy are community-
health files, which the forge reads from .github/ as readily, so they
move there with every reader in the same change:

- .abcd/site.json names .github/CONTRIBUTING.md as the contributors
  page's policy source; the footer finds .github/SECURITY.md itself.
- .abcd/docs-lint.json drops CONTRIBUTING and SECURITY from the
  stray_root_docs allowlist, so neither can drift back to the root.
- README and ACKNOWLEDGEMENTS link the new paths; the guide's own
  relative links step up a directory.
- CI's inert-path classifier names the two files at their new home.
- The preflight-gates and format-gate tests read the guide there.
- AGENTS.md, the PR template, the attribution workflow and gate, the
  site command page, the site and launch brief chapters and one
  principle name the new path.

The two files now sit under the /.github/ CODEOWNERS entry, so a change
to either takes a code-owner review.

Refs: iss-2608270540523859

Assisted-by: Claude:claude-opus-5-5
… in .github/

Resolves: iss-2608270540523859
Assisted-by: Claude:claude-opus-5-5
…lute paths

Three more --json fields carry an absolute developer-identity path, against
the iss-81 rule: the bare board's dir, memory lint's report_dir and
store_path, and launch archive's archive.path. The first two were named in
the drainSite review; the third was found in the sweep of every path-bearing
--json field.

Refs: iss-2609261950066257, iss-2609261950061900, iss-2609261950077063, iss-81

Assisted-by: Claude:claude-opus-5-5
Found in the sweep by running the read-only --json verbs from a checkout
under the home: every bundle file's resolved_path in launch --dry-run --json
and in launch ship's payload.bundle is the file's absolute on-disk path.

Refs: iss-2609261954288630, iss-81

Assisted-by: Claude:claude-opus-5-5
The site verbs' displayOutDir is the rule every report that names a path
needs: inside the repository, relative to it; outside it, the home redacted
to "~"; a relative path, as given. It moves to fsutil as DisplayPath so the
launch, memory and board fixes that follow share one primitive, and the site
verbs call it directly.

It also judges "inside" over the real locations when the lexical test says
outside, so a path the kernel resolved (/private/var for /var) still reads as
inside a repository root spelled the other way.

Refs: iss-81

Assisted-by: Claude:claude-opus-5-5
… home

Three launch reports named absolute paths in --json, against the iss-81 rule:
launch ship's payload.dest (the resolved staging directory), launch archive's
archive.path (the --out directory made absolute, joined with the archive
name), and every bundle file's resolved_path in launch --dry-run and in a
ship's payload.bundle.

Each field was also the working value something reads back: the archive step
packs from the render's Dest, the archive verb removes a refused archive
through its Path, and the render, the gates, the scan and the parity diff open
every payload file through ResolvedPath. So the working value is not redacted
in place. Each struct keeps it under its Go name, now tagged json:"-", and
gains a display-only field that carries the unchanged JSON key, set once
through fsutil.DisplayPath where the value is made: payload.dest with the home
as "~" (a destination is always outside the repository), archive.path relative
to the repository for the release workflow's --out bin and with the home as
"~" otherwise, and resolved_path relative to the repository. The text reports
print the display fields, so the two renderings agree.

Refs: iss-2609261848338673, iss-2609261950077063, iss-2609261954288630, iss-81

Assisted-by: Claude:claude-opus-5-5
The bare board's dir was filepath.Abs of the working directory, so
`abcd --json` named the developer's home whenever the checkout sat under it,
against the iss-81 rule. core.Status now reports the directory through
fsutil.RedactHome. The board has no repository root to be relative to — the
directory is the thing it reports — so the home-redacted form is the display.
Nothing reads Dir back: the inspection works on the absolute path, and the
text board prints the same field.

Refs: iss-2609261950066257, iss-81

Assisted-by: Claude:claude-opus-5-5
memory lint --json named its run-log directory (report_dir), the store it
read (store_path), the coverage index (coverage_index.path) and every
finding's file absolutely, so a checkout under the home named the developer
in machine output and in the run log's report.json, against the iss-81 rule.

Each is now named through fsutil.DisplayPath relative to the repository, at
the one point Lint assembles its result, after the last finding is made. The
absolute values stay the working ones: the run log is written to the absolute
directory and the pages are read through the store handle. The tests that
read the run log back join report_dir onto the repository, and the ones that
locate a finding compare its repository-relative file.

Refs: iss-2609261950061900, iss-81

Assisted-by: Claude:claude-opus-5-5
…ory lint

The launch render's payload.dest, the archive's archive.path and the
bundle's resolved_path report display paths beside their working values;
the board names its directory with the home as "~"; memory lint names its
paths relative to the repository. All go through the one primitive,
fsutil.DisplayPath (or RedactHome where there is no repository root).

Resolves: iss-2609261848338673
Resolves: iss-2609261950077063
Resolves: iss-2609261954288630
Resolves: iss-2609261950066257
Resolves: iss-2609261950061900
Assisted-by: Claude:claude-opus-5-5
The bare memory board set a drift list that --json emitted and the human
render never printed, so the one line asking the reader to act reached a
parser and not a person. The text board now prints each drift line in the
words the JSON carries, and the lines say, in the present tense, which
file is stale and that `abcd memory ingest` rebuilds it. The brief
chapter and the command page say the board carries drift.

Refs: iss-2609091647582259
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609091647582259
Assisted-by: Claude:claude-opus-5-5
memory lint joined .abcd/.work.local/logs/memory/lint-<ts> onto the
checkout root, MkdirAll-ed it and wrote both reports by path, so a
checkout carrying the local tier (or any level below it) as a committed
symlink had the directory chain and both reports created at the link's
target, outside the checkout.

fsutil.CreateRunDir is the local tier's run-log create path: it proves
every level from the checkout root down with EnsureRealDirAll, then
creates the run directory exclusively (name, name-001, ...), so two runs
in one instant keep two logs. fsutil.OpenRealDir opens the proved
directory as an os.Root only when it is still a real directory, and lint
writes both reports through that handle with WriteFileAtomicInRoot, so a
level swapped for a link after the proof cannot carry the writes away.
The tests plant the link at the tier, at logs/ and at logs/memory/, and
hold that lint refuses with ErrNotRealDir and writes nothing at the
target; the control writes under a real, partly present tier.

Refs: iss-2609260948440803
Assisted-by: Claude:claude-opus-5-5
…nd run directory

The two sibling writers of the memory lint run log had the same shape:
the issue-drift receipt (.abcd/.work.local/logs/audit/issue-drift-<ts>)
and the reading assembler's run directory (by default under
.abcd/.work.local/scratch/reading-runs/) were created with a by-path
MkdirAll, so a committed symlink at any level of the local tier carried
the receipt or the assembled input and its manifest out of the checkout.

The drift receipt goes through fsutil.CreateRunDir and is written through
fsutil.OpenRealDir. The reading assembler proves every level of a run
directory named inside the repository with EnsureRealDirAll and creates
the leaf with EnsureRealDir; a directory named outside the repository
(absolute, or climbing out of it) is the operator's own and is taken as
given, as before. Each has a test planting the link at every level of
its chain, and the drift control holds that two runs in one instant get
two receipts.

Refs: iss-2609260948440803
Assisted-by: Claude:claude-opus-5-5
…ved before they are written

Resolves: iss-2609260948440803
Assisted-by: Claude:claude-opus-5-5
…boat writes

The memory renderers were fixed for iss-2609020539188868; the lifeboat
half was not. The review rendered a finding id through Sanitize alone and
wrapped a severity in its own brackets, the press-release subhead was
wrapped in the render's own emphasis, a principle, body or quote was
written as a bare paragraph with no leading-marker escape, and the packed
brief section docs listed source paths (hostile filenames included)
through Sanitize alone.

mdrender.go holds the one discipline every lifeboat markdown renderer now
goes through: mdInline cleans an untrusted field with termsafe.CleanProse
(a no-op on a field its ingest already cleaned); mdCode sets a value off
with termsafe.CodeSpan where a delimiter is wanted (severity, finding id,
evidence refs, source paths); mdBlock escapes the leading marker of a
value that begins a block. No renderer wraps a cleaned value in a
delimiter of its own: the severity is a code span, the subhead is its own
paragraph, and the mode line is a plain paragraph. The tests drive each
renderer directly with comment openers, script tags, link syntax,
bracket-closing severities and every block-marker lead.

Refs: iss-2609251355497247, iss-2609020539188868
Assisted-by: Claude:claude-opus-5-5
…e markdown discipline

Resolves: iss-2609251355497247
Assisted-by: Claude:claude-opus-5-5
…with Sanitize alone

Refs: iss-2609262148072415
Assisted-by: Claude:claude-opus-5-5
…a terminal

renderLintReportMD rendered the store path and each finding's file,
message and suggestion through termsafe.Sanitize alone, which defangs a
terminal and leaves an HTML comment opener or link syntax live in the
markdown report. Each field now goes through termsafe.CleanProseLine and
the store path and file are set off with termsafe.CodeSpan, the
discipline the memory and lifeboat renderers already follow.

Refs: iss-2609262148072415
Assisted-by: Claude:claude-opus-5-5
…its fields as markdown

Resolves: iss-2609262148072415
Assisted-by: Claude:claude-opus-5-5
The operand walk this branch needs reads a chain without creating it,
which is fsutil.ProbeRealDirAll, landed on main by 7170858 after this
branch's base was cut. The two hunks (the function and its test) are
taken from main unchanged, so the integration merge sees the same
insertion on both sides and no second walker exists.

Refs: iss-2609261232464351
Assisted-by: Claude:claude-opus-5-5
…e a checkout

The lifeboat operand of embark, the graveyard lessons ingest, the
synthesis verbs and manifest verification, the embark target and the
pack destination were proved with a leaf-only IsRealDir (or Lstat), so a
committed symlink above the leaf was followed while the leaf check
passed.

proveOperand proves each of them with fsutil.ProbeRealDirAll from the
checkout the operand sits in (the .git marker walk) down to the operand,
then moves out to any checkout enclosing that root and proves the chain
down to it too, so a committed link pointing into another checkout is
refused rather than taken as the base. Outside every checkout the path
is the operator's own and is taken as given (macOS's /var and /tmp links
stay usable), and the pack's resolved-path overlap and .git checks still
apply there. Embark proves both operands before verifying the manifest.
The review's source operand is not proved: nothing under it is read or
written and only its base name reaches the audit. The site output
directory is out of this change: resolveOutDir already walks every
component and refuses a link inside the checkout.

The tests plant the link inside a checkout, and into a second checkout,
for every lifeboat gate, the embark target and the pack destination, and
hold that a plain nested path and a link outside every checkout pass.
The disembark command page and the disembark and embark brief chapters
state the rule.

Refs: iss-2609261232464351
Assisted-by: Claude:claude-opus-5-5
Every ordinary spelling the capture named is read by the guard fix
before this commit. What remains are the three the lane may not decide:
a pid list across lines through a file, a ps | grep | awk source, and a
kill by the holder of a port or file (lsof -t, fuser -k). Each waits on
ruling AS, so the record stays open and is deferred past v0.11.0 with
that reason.

Refs: iss-2609262259360005, iss-2609251824244354
Assisted-by: Claude:claude-opus-5-5
…ne at a time

lint's stripInlineCode and lab's parseCorrections pair single backticks
inside longer runs, so a double-backtick span reads as two empty spans
with live prose between them. Found by the one-pairer guard while fixing
iss-2609262322244502.

Refs: iss-2609262350446885, iss-2609262322244502
Assisted-by: Claude:claude-opus-5-5
The site renderer closed an inline code span where the opening run's text
first recurred, not on a run of exactly the opening length, and trimmed
every space from a multi-backtick span. A span opened by two backticks
around a run of three was refused as unclosed and failed the whole page,
a span of a lone space rendered empty, and a value the block escapers had
judged balanced (termsafe.OpensBalancedCodeSpan) could still be refused.

termsafe.PairCodeSpan is now the tree's one pairer: the closer is the
first later run of exactly the opening length, runs of other lengths are
content skipped whole. termsafe.CodeSpanText applies CommonMark's content
rules: a line ending is a space, and one space comes off each side only
when both are there and the content is not all spaces. The renderer, the
prose cleaner, OpensBalancedCodeSpan, mdrecord's OpensComment and
CodeSpanRanges, and the surface appendix's codeRegions all pair through
it; the three private walks are deleted. termsafe hosts it because it is
the lowest leaf: it imports only the standard library, core packages
already import it, and no non-core internal package imports core.

One table (termsafe/testdata/codespan_agreement.json) is read by the
renderer's test, lifeboat's escapeLeadingMarker test and ideate's
blockText test, so an escaper and the renderer that disagree about a
leading span fail together.

Sweep: fence() judged a block's last line a closer by a prefix test, so
the last line of an unclosed fence opening with three backticks and an
info string was dropped. Whether it closes is now mdrecord's reading of
the block (ListNested, the walk's rule); indentedFence hands down its own
reading's Closed. The block boundaries are unchanged.

A site build of the same tree before and after differs in 117 of 2271
files, each for a CommonMark content rule: a line ending inside a code
span is a space (115 files), a padded single-backtick span loses its
padding (itd-185), and a span of a lone space keeps it
(iss-2609262309556167). Every committed record still renders.

Refs: iss-2609262322244502, iss-2609262309556167
Assisted-by: Claude:claude-opus-5-5
lint's stripInlineCode paired single backticks one at a time, so a span
opened by two backticks read as two empty spans with live prose between
them: a glossary synonym quoted in one was flagged by GL002 and a link
quoted in one was checked, while a word between a single backtick and a
double run, which no span holds, was blanked. It now pairs through
termsafe.PairCodeSpan, keeps the escaped-opener rule, and blanks rune for
rune so columns are unchanged.

lab's parseCorrections closed a quoted retract literal on the next single
backtick, so a literal quoted in a double-backtick span because it holds a
backtick read as empty and was refused as noise. It reads the literal as
the code span it opens, by CommonMark's content rules.

Refs: iss-2609262350446885
Assisted-by: Claude:claude-opus-5-5
TestNoSecondCodeSpanPairer is the one-canonical-primitive detector for
the code-span rule, the counterpart of mdrecord's TestNoSecondFenceRule:
every non-test Go file outside termsafe that seeks a backtick (the rune
literal, or a backtick string handed to a strings or bytes search) is
named with a pinned count and the reason it is not a pairer. Its first
sweep found the two walks fixed under iss-2609262350446885.

Refs: iss-2609262322244502, iss-2609262350446885
Assisted-by: Claude:claude-opus-5-5
…reader

Resolves: iss-2609262322244502
Assisted-by: Claude:claude-opus-5-5
…termsafe

Resolves: iss-2609262350446885
Assisted-by: Claude:claude-opus-5-5
review-drainG2 found two missed kill-by-search readings. A pipe into a
group or a redirect into a command string reaches every command there,
but the tokenizer and payloadInput hand it on only in part. And the
xargs grammar lacks BSD's -J, -R and -S value flags, so those spellings
warn instead of blocking.

Refs: iss-2609270028388291, iss-2609270028432249
Assisted-by: Claude:claude-opus-5-5
A stream piped into a group reaches a shell after a separator inside it
unseen by interpreter-reads-stream, from the same root as the kill
reading. And a here-document body's substitution, a substitution that
inherits its command's pipe, and a shell string's positional parameter
each carry a search to a kill the guard does not read.

Refs: iss-2609270036259517, iss-2609270036253187, iss-2609270028388291, iss-2609251824244354
Assisted-by: Claude:claude-opus-5-5
A pipe into a brace or paren group is the standard input of every
command in it, but a separator inside the group started a pipeline of
its own, so only the commands before it read the pipe. A search piped
into a group whose kill came after a sleep, a read or an and-list
allowed, and so did a stream piped into a group whose shell came after a
separator. A group's opening word now records what is piped into it,
and every command emitted inside reads it (stdinIn, and stdinStream for
the interpreter reading), nested groups and substitutions included. The
open groups' runs are held as one covering run, so each command reads
one feed however deep the groups nest.

A shell passes its standard input to the commands of its string, and a
here-string or a process substitution redirected into it is that input,
but payloadInput handed on only the pipe. It now adds the running
command's here-string and process-substitution feeds, held as one run.
A command string's own group input is kept when its runner's input is
added to it.

The two readings keep over-blocks recorded in DECISIONS 2026-09-27.
Every NO-LEAK probe of both reviews of the kill reading is in one table,
and every block shape they probed in another.

Refs: iss-2609270028388291, iss-2609270036259517
Assisted-by: Claude:claude-opus-5-5
…and redirect input

Both are fixed by the group-input and redirect reading on this branch.

Resolves: iss-2609270028388291, iss-2609270036259517
Assisted-by: Claude:claude-opus-5-5
The xargs of macOS and the BSDs takes a value after -J (the replacement
string), -R (the most replacements) and -S (the replacement size). The
wrapper walk did not know them, so it read the value as the command
xargs runs, and a kill behind one warned as an unrecognised launcher
instead of blocking as kill-by-search.

Refs: iss-2609270028432249
Assisted-by: Claude:claude-opus-5-5
Fixed by the wrapper-grammar change on this branch.

Resolves: iss-2609270028432249
Assisted-by: Claude:claude-opus-5-5
Two hygiene items from review-drainG2, neither a captured defect.

The signal table held exit and null, which no pkill accepts, and
unused, which BSD pkill reads as -u nused. Read as signals they hid a
by-owner selector in their letters; their letters are options now, as
pkill reads them (TestSignalTableHoldsOnlySignals).

TestArgsReaderReadsEachWordOnce pins the argsReader's read-once
contract directly: asking every place of a 602-word xargs segment counts
at most two units per word. The growth shapes miss a reader that
re-reads its words per place, because the speculation caps make that a
constant factor; on a scratch copy with before() resetting its cursor
per call this test counted 180,904 units and failed, while the growth
shapes passed.

Assisted-by: Claude:claude-opus-5-5
…ce596)

Lanes drainSite 085ad84, drainPaths 91b4cea, drainFS 4e93aab (with
fix2 and fix3) and drainSpan e4ce596, merged as one tip.

Conflicts, resolved by hunk:
- internal/core/lifeboat/synthesis_principles.go: main's four claim keys
  (Claim type, Reference, Comparison) kept, rendered through the lane's
  markdown discipline (mdInline/mdBlock/mdCodeList); claimOrNone now cleans
  with mdInline in place of sanitize.
- internal/core/lint/lint.go: both imports kept (gitutil from main,
  termsafe from the lane).
- internal/core/site/compose.go: the lane's footerLinks resolution kept,
  each link built through main's forgeBlob (blob/HEAD, no branch name).

Semantic conflicts, fixed here:
- synthesis_principles_keys_test.go expects the evidence as a code span
  (`adr-24`), the lane's mdCodeList rendering.
- communityhealth_test.go reads the forge link as blob/HEAD, main's
  forgeView rule, not blob/main.
- release-gate receipt.example.json manifestHash re-pinned to the merged
  manifest (the lane edited the manifest comment; main pinned the example
  to the manifest's hash in fc345f9).

Assisted-by: Claude:claude-opus-5-5
…ry level

The fix landed on the drainFS lane (862809b, with the shared proof in
b794114), whose base predated the record, so it resolves here, where the
record and the fix first meet.

Resolves: iss-2609261232464351
Assisted-by: Claude:claude-opus-5-5
Lanes drainG 09ce02b, drainG2 419d7bf and fix3-drainG d833a6f,
merged as one tip. No conflicts; the DECISIONS.md appends (two 2026-09-27
entries) stay the last lines, after main's. commands.md and surface.json
regenerate unchanged. iss-2609262259360005 stays open, deferred on ruling
AS.

Refs: iss-2609262259360005
Assisted-by: Claude:claude-opus-5-5
Measured on a clean clone of efe074c (dry-run assemble, rule (e)):
widening 1,268,327 tokens / 4,883,062 bytes, 1,280,000 -> 1,290,000
(0.92% headroom was below the floor); entailment 377,826 / 1,454,631,
390,000 kept (3.22%); detection 1,277,363 / 4,917,850, 1,290,000 ->
1,300,000 (0.99% was below the floor). Comparative is unchanged.

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
main's name_roots (dd43ce1) name the root CONTRIBUTING.md, and drainSite
moved the file to .github/ (4d4bbfb), so after the integration the
configured root no longer resolved and `lint docs` refused the whole
configuration. The root follows the file; the coverage pin, the harness
gate's fixture and the banlist chapter say the same. A semantic conflict
of the drainSite merge, found by the integration preflight.

Assisted-by: Claude:claude-opus-5-5
The name-root fix edits the banlist chapter, which the reading corpus
carries, so the positions are measured again on a clean clone of
c250bc7 (dry-run assemble, rule (e)): widening 1,268,334 tokens /
4,883,086 bytes, entailment 377,828 / 1,454,639, detection 1,277,369 /
4,917,874. Every window stays (1,290,000, 390,000 and 1,300,000, each
with at least 1% headroom).

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
The two banlist tests that load the real docs-lint configuration build
every name root it names, and the root now names .github/CONTRIBUTING.md
(c250bc7). Missed there, caught by the integration preflight.

Assisted-by: Claude:claude-opus-5-5
Brings integ7 (#731), integ8 (#732), integ9 (#733), cap45 (#734) and
integ10 (#735) into integ/land-11.

Conflicts, resolved by hunk:
- .github/CONTRIBUTING.md: integ10 edited the root guide this branch moves
  into .github/. Its local-gates text is carried into .github/CONTRIBUTING.md
  (preflight runs the format gate on the declared toolchain), with the hooks
  link re-rooted to ../.githooks/. No root CONTRIBUTING.md or SECURITY.md
  remains in the tree.
- .abcd/config/reading-presets.json: main's figures for the conflict hunks;
  the windows are re-measured at the tip in a follow-up commit.

Semantic, fixed here:
- .abcd/docs-lint.json: integ10's links_resolve extra_roots named the root
  CONTRIBUTING.md and SECURITY.md, which no longer exist, and `lint docs`
  refused the config. Both entries are dropped: the ".github" entry already
  walks .github/CONTRIBUTING.md and .github/SECURITY.md.

Assisted-by: Claude:claude-opus-5-5
Measured on a clean clone of 0dd22bd (dry-run assemble). Every window
keeps at least 1% headroom, so none moves:
- widening 1,325,292 tokens / 5,102,376 bytes: 1,340,000 kept (1.11%)
- entailment 383,234 / 1,475,453: 390,000 kept (1.77%)
- detection 1,334,328 / 5,137,164: 1,350,000 kept (1.17%)
comparative is unchanged.

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
@REPPL
REPPL enabled auto-merge September 28, 2026 18:57
@REPPL
REPPL added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit ed46472 Sep 28, 2026
13 checks passed
@REPPL
REPPL deleted the integ/land-11 branch September 28, 2026 19:57
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