fix: the scanner, rules loader and reading fixes, and the name guard reads escaped spellings - #733
Merged
Merged
Conversation
…home paths The transcript store scans raw JSONL, and a token or home path written beside a JSON string escape is invisible to the raw-line pass. Captured before the fix, per the capture-first rule. Refs: iss-2609261647358395 Assisted-by: Claude:claude-opus-5-5
The transcript store scans raw JSONL, and a JSON string escape changed what the detectors saw without changing what the text says. A token written after a \n or \t escape had the escape letter as a word byte before it, so no leading-\b token pattern matched; a home path beside a \n, \t or \" escape failed the home anchor's boundaries; a home written with the solidus escape (\/ or /) had no '/' on the line; and a non-ASCII name written as é matched no configured name. Each line carrying an escape is now decoded one JSON layer at a time (at most three, a layer that decodes nothing ends the walk) and every layer is scanned as a view of the line, the way the percent-decode pre-pass scans its decoded copy: each hit is mapped back to the raw bytes it came from, so Redact masks the live spelling on disk. Every layer is scanned, not only the last, because a literal backslash an earlier layer uncovers is read by the next as an escape. The view scan is shared with the percent pre-pass (viewFindings), and the new stage charges the scan meter, with four linearity fixtures. This changes the scan engine, not the canonical pattern set, so it reaches every ScanText and ScanBundle consumer: the history transcript store, the capture and memory redactors, scanner.CheckOutbound behind `abcd lint outbound`, and the launch bundler and lifeboat packer. The repolint privacy rule and the harness_leak lint rule run the patterns' regexps directly and are not reached. Refs: iss-2609261647358395, iss-2609251639263391 Assisted-by: Claude:claude-opus-5-5
home_path_other was POSIX-only, so a third party's home written as <drive>:\Users\<name> (a WSL session, a pasted PowerShell transcript, a Windows CI log) raised nothing, as typed or JSON-escaped. genericHomeRe gains the Windows alternative, each separator a run of backslashes so one JSON layer (doubled) or two (quadrupled) read the same as the typed spelling. Its friends follow it: the trailing boundary takes the backslash; the system-directory allowlist recognises the Windows Users root and gains Default and the All Users junction beside Public; the traversal walk reads a backslash run as ONE separator, so an escaped separator inside a system root is not taken for an empty traversal segment; and the home_path_other skip compares the caller's home against the match with its separator runs collapsed, so the caller's own escaped home is never reported as a third party's. The caller's own home is home_path_self at any depth through the JSON-escape views of the previous commit; abcd builds for darwin and linux only, so the caller's home is never itself a Windows path. The allowlist is shared with the repolint privacy rule, which applies it to its own C:\Users branch; that rule's twin regexp is otherwise unchanged. Refs: iss-2609251639261103 Assisted-by: Claude:claude-opus-5-5
…ytes The byte scan dropped every short single-token real_name as chance noise, so a name the text scan hard-fails on shipped unreported in a PDF /Author entry or any other author stamp. The existing shape test pinned the defect itself, expecting /Author (Zedqx) to raise nothing. Decision (recorded here, not in the record): the byte scan keeps a short single token where a metadata key that names a person ends within 96 bytes before it, and drops it everywhere else. The keys are author, artist, creator and lastModifiedBy, which cover a PDF Info /Author, XMP dc:creator, pdf:Author and tiff:Artist (pretty-printed across lines, as XMP writers lay it out), PNG text Author and Artist, and an OOXML or ODF dc:creator or cp:lastModifiedBy inside a decoded zip. That is the anchored context the record names as closing it, and it neither lowers the threshold nor accepts a false-positive rate: the same token incidental to binary content, a hundred times over or past the key's reach, still raises nothing, so the report is not flooded. The threshold keeps counting bytes, now with the reason stated: a chance collision needs that many specific bytes in a row. A key held in binary structure (EXIF's Artist tag, a UTF-16 PDF string) is not text in the bytes and stays out of reach. Refs: iss-2609090934372160 Assisted-by: Claude:claude-opus-5-5
The residue iss-96 names (a bare value with no key name, a labelled value under the entropy floor) is reached only by an always-on entropy or charset detector, which the 2026-08-28 ruling rejected for its redaction false-positive cost on transcript prose when it shipped the opt-in external-scanner adapter. Building one here would re-open that ruling, so nothing is built: the deferral is carried past v0.11.0 with the tradeoff named, and a dated re-check records that both pins still hold after this lane's JSON-escape views. Refs: iss-96 Assisted-by: Claude:claude-opus-5-5
Found by the sibling sweep and left for their own lanes: the repolint privacy rule and the harness_leak rule read committed lines raw, so the escaped spellings the scanner's views now read pass them; the literal caller-home backstop reads no escaped spelling of the home; and a short name in a binary EXIF Artist tag has no metadata key text beside it. Refs: iss-2609261658553101, iss-2609261659041553, iss-2609261659051539 Assisted-by: Claude:claude-opus-5-5
The fix is c55ae5e: the scanner reads each line's JSON-escape layers as decoded views mapped back to raw spans. Resolves: iss-2609261647358395 Assisted-by: Claude:claude-opus-5-5
…eparator The fix is c55ae5e: the JSON-escape views decode \/ and /, and the resolution note records the decision on each other escape spelling the scanner's input can carry. Resolves: iss-2609251639263391 Assisted-by: Claude:claude-opus-5-5
…home path The fix is 4013610: the one home matcher reads <drive>:\Users\<name> at any escaping depth, with its boundary, allowlist and traversal walk. Refs: iss-2609261658553101 Resolves: iss-2609251639261103 Assisted-by: Claude:claude-opus-5-5
… is kept on bytes The fix is 7babc8d: a short single-token real name standing in a person metadata field is kept by the byte scan; the binary-structured residue (EXIF Artist, a UTF-16 PDF string) is captured on its own. Refs: iss-2609261659051539 Resolves: iss-2609090934372160 Assisted-by: Claude:claude-opus-5-5
A home that is itself a git working tree (dotfiles in the home) made the home the git toplevel for every non-repo directory beneath it, so the rules root walk stopped at ~/.abcd and read it a second time as the REPO layer: the home's guard.json, which has no user layer at all, and its config.json over itself as the machine layer. Resolve now passes over the home in the walk, and a toplevel that IS the home takes the non-repo route (cwd, no walk) when nothing below it carries a .abcd. The user layer still reads ~/.abcd/rules.json, once, as the user layer. Decision taken in the lane (the run A orchestrator's brief rules the exclusion; the record's owed question was whether a home toplevel is a legitimate repo-scope root): only the home itself is excluded, not every ancestor of it. A toplevel that CONTAINS the home, the shape of a hermetic harness that points HOME inside its checkout, stays the root, because it is a repository git vouched for and its own .abcd is its own; the walk still skips the home on the way up to it. A session whose working directory IS the home keeps reading a .abcd there as cwd's, the working-directory read that stays a posture question (DECISIONS.md, 2026-09-25). Tests watched fail first: TestResolveRootNeverAdoptsTheHomeDirectory, TestResolveRootNeverAdoptsTheHomeThroughTheMarker and TestResolveRootNeverAdoptsTheHomeBeneathAnotherToplevel each resolved the temp HOME as the root before the change. Refs: iss-2609020219198779 Assisted-by: Claude:claude-opus-5-5
…epo root Resolves: iss-2609020219198779 Assisted-by: Claude:claude-opus-5-5
The ownership refusal's note told the session that the refused root's rules.json and guard.json were NOT read and that the bundled defaults stood in. Resolve returns the working directory as the root on that refusal, so a .abcd there is read: a session started at the refused root reads that root's configuration while being told it did not, and so does one beneath it that carries its own .abcd. 0434d47 corrected AGENTS.md and the marker block ahoy writes; the note itself, the configuration chapter and the install how-to still made the same claim. The note now names which of the two happened: from a directory with no .abcd the old wording stands, and where the working directory carries one it says the refusal bounds the walk, not the working directory, and that the .abcd there IS read. The posture change that would make that read refuse too stays deferred as recorded (DECISIONS.md, 2026-09-25). Test watched fail first: TestResolveRootRefusalSaysWhatItStillReads, both subtests (the note said "NOT read" and "fall back to the bundled defaults" while Load read the working directory's kill switch). Refs: iss-2609251522588539 Assisted-by: Claude:claude-opus-5-5
…reads Resolves: iss-2609251522588539 Assisted-by: Claude:claude-opus-5-5
A rules.json list replaces the bundled list wholesale, so a repo that pinned PII's rules before iss-156 added the network-identifier rule kept the old set with no notice: a stale guardrail set that looks current. The merge stays per field (the documented behaviour a repo relies on to say a rule in its own words); the loss is no longer silent. For the guardrail domains, COMMITTING, LOAD and PII, Load compares every recall, alias and rule list an override set against the list this binary bundles, and adds a note naming each bundled entry left out and the file whose list is in force. The hook and `abcd rules` print it on stderr, never in the injected context or the --json document. Decisions taken in the lane: the check covers the three guardrail domains and not every bundled domain, because the others are conventions a repository restates in its own words (this repository's own INTENTS and ROADMAP overrides do exactly that), and a note on each deliberate restatement would teach the reader to skip the one that matters. The comparison is against the bundled list as shipped, not a hash recorded at override time, so it needs no new schema field. Whether security-bearing lists should union instead of replace, or take a replace-versus-extend marker, stays the product thinker's question, recorded with itd-117's finer-grained-merging follow-up. Two front-door tests read `rules --json` through the combined output, and the new stderr note broke their parse; they read stdout alone now, which is what the JSON contract is. Tests watched fail first: TestLoadNamesTheBundledSecurityRulesAnOverrideWithholds, TestLoadNamesTheBundledRecallAnOverrideWithholds and TestLoadNamesTheUserLayerThatWithholds (against a stub list, before noteWithheld existed); TestRulesNamesAWithheldGuardrailOnStderr and TestHookPromptRouterNamesAWithheldGuardrail on a scratch copy with the note switched off. Refs: iss-174, iss-156 Assisted-by: Claude:claude-opus-5-5
…ry load Resolves: iss-174 Assisted-by: Claude:claude-opus-5-5
No mechanical close leaves rules reading as they do. Every CommonMark construct that makes a rule body a heading has to be closed: ATX on a continuation line and on the first line (`- # x` is a list item holding a heading, which the record did not name), a setext underline, and an HTML h1-h6 block. That takes either a code-safe rendering (a relative indent of four or more, or a fence), which flattens every legitimately structured multi-line rule wherever the block is rendered, or a fence-aware escaper complete only by enumeration, which writes escapes into the raw text the model reads. Both change how rules read, so the choice is the product thinker's. The v0.10.0 grant lapsed at the v0.11.0 anchor; the renewal quotes the question and the record carries the first-line finding. Refs: iss-2609020219265817 Assisted-by: Claude:claude-opus-5-5
…l owed Refs: iss-2609020219198779, iss-174, iss-2609251522588539, iss-2609020219265817 Assisted-by: Claude:claude-opus-5-5
…ings The security review of the rules-loader lane found the home exclusion comparing path strings, so a case-variant HOME is adopted as the repo root, and the refusal note deciding "IS read" through a stat that follows a symlinked .abcd the loader then refuses. Refs: iss-2609261753285273, iss-2609261753290536 Assisted-by: Claude:claude-opus-5-5
The rules root resolver passes over the home in its walk and sends a toplevel that is the home down the non-repo route, but it asked both questions by comparing path strings. HOME is the caller's string and the walk climbs the physical path git reports, and filepath.EvalSymlinks keeps the caller's case, so a HOME spelled as a case variant of the on-disk path on a case-insensitive volume was not recognised: the version-controlled home became the repo root and its .abcd was read a second time as the repo layer. Both sites now ask one helper, homeMatcher, which stats the home once and compares each candidate with os.SameFile. The home is still read through userHomeDir, so the user layer and the declined directory stay the same one; a home that is absent or cannot be stat'd matches nothing, as an unset one did. TestResolveRootNeverAdoptsTheHomeAtAnySpelling pins the four spellings the review named (trailing slash, symlinked HOME, cwd through a symlinked HOME, case variant) at both sites, with and without a ~/.abcd. Watched fail first on a scratch copy of the unfixed tree: both case-variant subtests failed (the home adopted as the root), the other six passed as the review's probes had. The case-variant subtests skip, and say so, where the test filesystem is case-sensitive. Refs: iss-2609261753285273 Assisted-by: Claude:claude-opus-5-5
The foreign-owner refusal note says the working directory's .abcd "IS read" when one is there, and decided that with os.Stat, which follows a symlinked .abcd. readRepoLayer Lstat-refuses that shape, so in that edge the note told the user a layer governs the session that the loader then refuses. The check is Lstat plus IsDir, so the note and the loader give the same answer. TestResolveRootRefusalNeverSaysASymlinkedAbcdIsRead stages a refused root with a symlinked .abcd at the working directory, proves the loader refuses it, and requires the note to say NOT read. Watched fail first on a scratch copy of the unfixed tree (the note said IS read). Refs: iss-2609261753290536 Assisted-by: Claude:claude-opus-5-5
…identity Resolves: iss-2609261753285273 Assisted-by: Claude:claude-opus-5-5
…ike the loader Resolves: iss-2609261753290536 Assisted-by: Claude:claude-opus-5-5
SweepCallerHome and the user-segment rewrite behind SurvivingCallerHome matched the home only as written, so the solidus escape, its / form in either case, a second JSON layer and the percent-encoded separator all passed the one stage the store-before-commit redactors keep independent of the detector. Both now collect their spans through backstopSpans: the text as written plus the decoded views of every line carrying a backslash or a '%' (percentDecodeBounded and jsonEscapeLayers, the scanner's one definition), with each view hit mapped back through its position map so the rewrite covers whole escape units. An occurrence straight after an odd backslash run is the tail of an escape, so the raw reading leaves it to the view: judging it as written read the escape's backslash as a boundary, swept "x\/root" under HOME=/root, and rewriting from the '/' left a dangling "\~" that no JSON reader accepts. The anchors run on the text the occurrence was found in, so a control escape before a single-segment home is judged by its decoded newline. Cost stays linear: TestSweepCallerHomeWorkIsLinear holds five dense shapes to linearCostBar. Refs: iss-2609261659041553 Assisted-by: Claude:claude-opus-5-5
… spellings Resolves: iss-2609261659041553 Assisted-by: Claude:claude-opus-5-5
…e escapes Refs: iss-2609261811321435 Assisted-by: Claude:claude-opus-5-5
The json_unicode_escaped_own_homes linearity fixture carried the literal home path: its six-byte escapes had been folded back into slashes when it was written, so TestScanLineWorkIsLinear held a plain path to the bar and never the unicode-escape decode the fixture names. The line is now assembled from escapeSeparators and uSolidus, which no tool can fold, and TestMeterFixturesCarryTheSpellingTheyName pins both escaped fixtures to the bytes their names promise. Refs: iss-2609261811321435 Assisted-by: Claude:claude-opus-5-5
…capes Resolves: iss-2609261811321435 Assisted-by: Claude:claude-opus-5-5
The redactor splits on "\n", so each line's carriage return is the first half of the CRLF pair that ends it. When an excluded section is the last one, the drop takes the newline after the last kept line and the join left that line's carriage return behind alone. The verifier then refused the document for a lone CR the source does not carry, so a CRLF record whose excluded section came last could never be assembled. The carriage return now goes with its newline, which is what an LF document already loses at the same place: a CRLF document redacts to its LF twin's text, line endings aside, wherever the section sits. A CR ending the source's own last line had no newline to lose and is still refused. Output bytes change only for documents that were refused before, so the assembler version does not move. Refs: iss-2609251600019863 Assisted-by: Claude:claude-opus-5-5
… the tail Resolves: iss-2609251600019863 Assisted-by: Claude:claude-opus-5-5
The status render read a stage's commit marker through an unbounded Lstat and a parked run's outcome through an os.Root, so the two disagreed on a symlink: with the readings directory symlinked out of the checkout, a parked run refused the render while a stage alone was classified by a marker read outside the repository. Describe now opens one root when anything is parked or staged and both probes read through it. Refs: iss-2609261905354450 Assisted-by: Claude:claude-opus-5-5
…efused Resolves: iss-2609261900095459 Assisted-by: Claude:claude-opus-5-5
…run through one root Resolves: iss-2609261905354450 Assisted-by: Claude:claude-opus-5-5
…uns, PDF hex Three findings of the review of lane drainS3, captured before their fixes: the private-banlist guard reads staged blobs only as written, so an escaped spelling of a private name passes it; the UTF-16 byte view ends a run at a surrogate pair, so a name after an emoji is missed; and a PDF hex string, the hex half split from iss-2609261831352258, is never decoded. Refs: iss-2609261909106167 Refs: iss-2609261909101409 Refs: iss-2609261909108726 Refs: iss-2609261831352258 Assisted-by: Claude:claude-opus-5-5
utf16TextRune refused every surrogate, so a character outside the Basic Multilingual Plane, an emoji above all, ended a byte-order-marked run: a name after an emoji in one string was cut off, and read not at all when fewer than two units stood before the pair. utf16RuneAt decodes a valid high-low pair as one character and lets the run continue; a lone surrogate, a pair spelling a noncharacter and every unit the text rule refuses still end it. TestUTF16RunContinuesPastAnAstralCharacter reads an emoji, a space and a fake name behind either byte-order mark. Refs: iss-2609261909101409 Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609261909101409 Assisted-by: Claude:claude-opus-5-5
Past maxEscapeRunWalk the raw reading of the home backstop judges an occurrence as written and spans the home alone; the escape unit is still masked whole only because the JSON view of the same line spans it and disjointSpans unions the two readings. The comment said the text as written decides, which undersold where the correctness comes from. Assisted-by: Claude:claude-opus-5-5
A PDF writer may spell a UTF-16 text string in hex, <FEFF005A...>, which never puts the UTF-16 bytes in the file, so the byte scan's UTF-16 view had nothing to read and the caller's name in such an /Author raised nothing at any length. pdfHexView decodes the hex pairs of every string between '<' and '>' (white space skipped, an odd last digit read as its byte's high nibble, a '<<' dictionary opener passed over), maps each byte to the raw offset of its first digit, and hands a byte-order-marked result to utf16View; the findings are re-homed through both maps, so the person-key rule judges a short name by the raw bytes before its first digit. No escape grammar is involved: the octal-literal spelling stays deferred on iss-2609261831352258. The walk reads each raw byte at most twice; TestUTF16ViewWorkIsLinear gains the hex and astral shapes. launch --dry-run on this tree is unchanged: 126 files, 1 finding, 0 hard fails. Refs: iss-2609261909108726 Refs: iss-2609261831352258 Assisted-by: Claude:claude-opus-5-5
…ings The hex half of iss-2609261831352258 is resolved; the record keeps the octal-literal half, re-deferred past v0.11.0 with its reason narrowed to the literal-string syntax that half needs. Refs: iss-2609261831352258 Resolves: iss-2609261909108726 Assisted-by: Claude:claude-opus-5-5
The private-banlist guard matched every pattern against the staged bytes as written, so a name spelled with JSON string escapes (a \u escape of any letter, plain ASCII included, or an escaped solidus) or with percent-encoding passed it, and a JSON transcript, export or fixture is where such spellings live. Every staged line that holds such an escape is now also decoded, into the two views the scanner reads beside the text as written (the JSON escape layers, surrogate pairs joined, and the percent view), and each pattern is matched against the decoded copy as well as the text as written. The decode stays in the hook, in awk and the shell's printf %b, rather than in the abcd binary: no binary front door checks staged content through the scanner's views, and the guard must hold before abcd is built and in every clone the dispatcher runs it in, the scaffolded copy included. Its reading is a deliberate superset (any backslash run before an escape decodes as one escape, a chain of %25 layers decodes to the byte it names), which can only refuse more. Every step fails closed and names itself, and nothing decoded is printed. Applied to this repository's hook and to the scaffolded template alike; the scaffold cites no record id. Measured on this machine (macOS awk): 30 MB of plain staged text costs about as before (0.9s to 1.3s); 20 MB of JSON with escapes on every line goes from 0.8s to about 5.5s, the awk decode being the cost. Refs: iss-2609261909106167 Assisted-by: Claude:claude-opus-5-5
…ellings The private-banlist guard and its scaffolded copy decode the JSON escape layers and the percent view of every staged line that holds one, and match each pattern against the decoded copy beside the text as written. Resolves: iss-2609261909106167 Assisted-by: Claude:claude-opus-5-5
The scanner cluster: fix/drain-scanner-siblings contains fix/drain-scanner-identity (drainS1, aaf4dd3), so its tip lands both. Conflict: .githooks/pre-commit, the environment pin's `unset -f` list. main added `go cd pwd trap` for the sources refresh; the lane added `awk` for the name guard's decoder. Combined: both sets are pinned. The scaffold copy (internal/core/ahoy/defaults/pre-commit) merged clean; the two copies differ only where they differed at the base, plus comment wording. Assisted-by: Claude:claude-opus-5-5
…61103 review-drainS1 (MINOR) found that the non-user home allowlist entries Default and All, added for the Windows Users root, are shared with the POSIX /Users root and the repolint privacy rule, so a third-party macOS account literally named Default or all raises no home_path_other (the WARN-level kind). The resolution text now says so. Checked against the code: nonUserHomeSegments in scanner/network.go is one case-folded map, isNonUserHomeMatch applies it under /Users/ and <drive>:\Users\, and repolint's isUsersRoot does the same; neither word is a generic account name, so the caller's own login under either is still local_username (hard_fail) and the caller's home still home_path_self. Amended rather than captured: the limit is a stated consequence of the fix the record already describes, not a separate defect. Refs: iss-2609251639261103 Assisted-by: Claude:claude-opus-5-5
The rules loader's security records: the home directory is never a session's repo root, the foreign-uid refusal says what it still reads, and a bundled guardrail an override withholds is named on every load. iss-2609020219265817 stays deferred past v0.11.0 (ruling AN). Clean merge: DECISIONS.md takes the lane's line as the last append; commands.md auto-merged and `go generate ./internal/surface/cli` regenerates it and surface.json with no drift. Refs: iss-2609020219265817 Assisted-by: Claude:claude-opus-5-5
The reading bugs: a CRLF pair is dropped whole at the tail, a nested mapping is refused behind every block indicator, the escaped-key refusal states only what it knows, and the bare render lists only the parked runs awaiting an outcome. The lane is based on drainM1 (a1c5dfc), so drainM1's commits come with it; integ7 lands them first. No textual conflicts; `go generate ./internal/surface/cli` regenerates commands.md and surface.json with no drift. Semantic conflict, fixed here: drainM1's b2fc7f3 rewrites the release-gate manifest's _comment, which changes the manifest's sha256, while main's fc345f9 pins receipt.example.json's manifestHash to the committed manifest (TestReceiptExampleManifestHashIsTheCommittedManifests). The example now carries the merged manifest's hash, sha256:0262c01b...; the manifest itself is drainM1's text unchanged. Assisted-by: Claude:claude-opus-5-5
…line decode cost Two findings of the verify of fix3-drainS3: a backslash the guard decodes from an escape is never read again as an escape, so a name one layer behind it commits while the scanner reads it; and the awk decode is superlinear in line length on macOS awk. Refs: iss-2609280944560197, iss-2609280945018822 Assisted-by: Claude:claude-opus-5-5
Measured on a clean `git clone --no-local` of ecbaf85 with `reading assemble --position P --target HEAD --dry-run --json`; each window is ceil(tokens * 1.01 / 10000) * 10000. - widening: 1,263,373 tokens (4,863,987 bytes); window stays 1,280,000. - entailment: 376,646 tokens (1,450,090 bytes); window 380,000 -> 390,000, since 380,000 left under one per cent of headroom. - detection: 1,272,409 tokens (4,898,775 bytes); window stays 1,290,000. Refs: iss-2609251455354719 Assisted-by: Claude:claude-opus-5-5
…r time A backslash the guard decoded from an escape (the unicode escape of U+005C, or %5C) was never read again as an escape, so a banned name one layer behind it committed while scanner.DecodedViews reads it on its second layer. Each decoded layer is now read again for the escapes it still holds, up to decode_layers, which the hook declares once and a scanner test holds equal to maxJSONDecodeLayers (3). A %5C before a JSON escape, which the scanner does not decode, is read here too. The awk decode split every line on a regular expression, which makes the split of the one true awk (macOS) quadratic in line length: one 19 MB line took 156 s to decode and 172 s to commit. The splits take one-character strings, which POSIX, gawk and mawk read as the character itself; the decoded output is byte-identical on a 4,000-line corpus of escape edge cases and on the 19 MB fixtures, and the same line now decodes in 2.5 s (commit 28 s at load 20). Both hook copies carry the same decode block, and a test holds them to the same bytes and every split to a one-character separator. The hook comments and the banlist brief chapter say what the guard reads now. Refs: iss-2609280944560197, iss-2609280945018822 Assisted-by: Claude:claude-opus-5-5
…line decode cost Both findings of the verify of fix3-drainS3 are fixed in 5c5a859: the guard reads each decoded layer again, as many layers as the scanner, and its awk decode is linear in line length. Resolves: iss-2609280944560197, iss-2609280945018822 Assisted-by: Claude:claude-opus-5-5
integ6, integ7 and integ8 landed on main after this branch's base. One file conflicted: .abcd/config/reading-presets.json, whose three window figures both sides re-measured; main's figures are taken by hunk and the windows are re-measured at this tip in a following commit. The pre-commit hook merged clean: main's itd-131 identity gate (author and committer resolved in git's own order) and this branch's layered name-guard decoder sit in different hunks of .githooks/pre-commit. main did not touch the scaffold copy, so the two copies diverge exactly as they did on each side. The release-gate manifest is unchanged by this merge and receipt.example.json's manifestHash already equals its sha256. commands.md and surface.json were regenerated with no drift. Assisted-by: Claude:claude-opus-5-5
The name guard's decoder reads up to three escape layers (decode_layers=3, pinned to the scanner's maxJSONDecodeLayers by a scanner test) and splits each line on a one-character string, which is linear in the line length. It merged clean: the decode block is byte-identical in both hook copies, and main's identity gate in .githooks/pre-commit is untouched by it. Refs: iss-2609280944560197, iss-2609280945018822 Assisted-by: Claude:claude-opus-5-5
Measured on a clean clone of 35483ff by dry-run assemble. Every window keeps at least 1% headroom, so none moves: widening 1,280,867 tokens (4,931,339 bytes) under 1,300,000; entailment 382,072 (1,470,980) under 390,000; detection 1,289,903 (4,966,127) under 1,310,000. The comparative position is not measured this way, as its preset comment states. Refs: iss-2609251455354719 Assisted-by: Claude:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch 9 lands four reviewed lanes as one change: the scanner cluster (drainS1 and drainS3), the rules loader (drainS2) and the reading bugs (drainRd), with one window recalibration at the merged tip.
drainS1 (scanner identity). The redactors read the JSON escape layers of a transcript line as decoded views, so a secret, a home path or a real name written straight after a
\n,\tor\"escape, or spelled with the\/solidus escape, is a finding and is redacted before it is stored. A Windows home (C:\Users\<name>, typed or JSON-escaped) is a third-party home path, and a short name in author metadata is kept on bytes. The Windows allowlist entries Default and All also cover the POSIX/Usersroot, so a third-party macOS account literally named Default or all raises nohome_path_other; the resolution of the Windows-home record says so.drainS3 (scanner siblings). The caller's own home is caught in its escaped spellings by the backstop sweep, the privacy-hygiene rule and the harness-leak lint read the same decoded spellings, a UTF-16 byte view catches a value written in UTF-16, and the private name guard in the pre-commit hook reads the decoded spellings of staged text. The EXIF and PDF-string residues are deferred on their records.
drainS2 (rules loader). The home directory is never a session's repo root, the foreign-uid refusal says what it still reads at the working directory, and a bundled guardrail entry that an override withholds is named on stderr on every load.
drainRd (reading bugs). A CRLF document redacts like its LF twin wherever the excluded section sits, a nested mapping is refused behind every block indicator, the escaped-key refusal states only what it knows, and the bare
readingrender lists only the parked runs still awaiting an outcome. The lane carries drainM1's commits, which integration branch 7 lands first.drainS3 fix round (name guard decoding). The pre-commit name guard reads a name hidden behind escape-spelled backslashes (
\u005c,%5C) up to three layers deep, the same depth the scanner decodes, and a test holds the two bounds equal. It decodes a very long staged line in linear time, so a commit carrying one no longer stalls for minutes. The decode block is byte-identical in the repository hook and the hookabcd ahoyscaffolds.Re-merge. Main moved after this branch was cut: integration branches 6, 7 and 8 landed (#730, #731, #732). The re-merge conflicted only on the reading window figures. Integration branch 8's git identity gate and this branch's name-guard decoder sit side by side in the repository hook, and both hold under their tests. Four records that drainM1 resolved landed with integration branch 7, so they are no longer listed here. Recalibrated at the re-merged tip, every window keeps at least 1% headroom and none moves: widening 1,280,867 tokens under 1,300,000, entailment 382,072 under 390,000, detection 1,289,903 under 1,310,000.
Reviews: drainS1 SHIP. drainS3 SHIP; its third fix round SHIP on verification, and its fourth fix round SHIP on verification. drainS2 SHIP; its fix round done. drainRd SHIP; its fix round done. Integration branch 9 SHIP.
Resolves: iss-174
Resolves: iss-2608301237450573
Resolves: iss-2608301421381157
Resolves: iss-2608311621412224
Resolves: iss-2609020219198779
Resolves: iss-2609090934372160
Resolves: iss-2609251522588539
Resolves: iss-2609251600019863
Resolves: iss-2609251639261103
Resolves: iss-2609251639263391
Resolves: iss-2609261647358395
Resolves: iss-2609261658553101
Resolves: iss-2609261659041553
Resolves: iss-2609261753285273
Resolves: iss-2609261753290536
Resolves: iss-2609261811321435
Resolves: iss-2609261827066511
Resolves: iss-2609261900095459
Resolves: iss-2609261905354450
Resolves: iss-2609261909101409
Resolves: iss-2609261909106167
Resolves: iss-2609261909108726
Resolves: iss-2609280944560197
Resolves: iss-2609280945018822
Refs: iss-2609020219265817
Refs: iss-2609261659051539
Refs: iss-2609261831352258
Refs: iss-96
Refs: iss-2609261536147903
Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5