Skip to content

fix: the lint cluster, the cut's captures and the API adapter - #726

Merged
REPPL merged 153 commits into
mainfrom
integ/land-2
Sep 26, 2026
Merged

REPPL merged 153 commits into
mainfrom
integ/land-2

Conversation

@REPPL

@REPPL REPPL commented Sep 26, 2026

Copy link
Copy Markdown
Collaborator

Integration branch 2: four reviewed lanes (lintA, lintB, cutfix, apiadapter) landed as one change, with one window recalibration and one preflight.

lintA (lint cluster A). Every read the lint makes of repository content goes through containment and a guarded read, so a committed symlink or FIFO is refused rather than followed; the release-gate receipts and manifest are read inside the repository root. record_schema refuses exactly the issue records the ledger reader refuses, names an issue body the site renderer cannot render, and names a link filed in a record store's root or buckets. New rules: intent_sota warns on a planned intent with no SOTA declaration, and changelog_unreleased_empty refuses a hand-written Unreleased entry. The issue-resolution gate gains RS006, which refuses a resolution naming a test that does not exist. links_resolve walks the working tier through extra_roots, and a guard refuses curly quotes gofmt writes into Go comments.

lintB (lint cluster B). The docs and record lint prune what git ignores under each root and name what they pruned (pruned in abcd lint docs --json). A names/ ban also reads the configuration's name_roots, every text file there, and reads inside code fences by default. A new link_anchors rule checks a link's #fragment against the target's headings, at warning. The one duplicate-key primitive, jsonstrict, refuses a repeated key and a case twin in the release-gate receipts, the rules overlay, the layered configuration and the reading presets. A bare --- under a paragraph in a record body is named as a setext underline.

cutfix (cut-blocking captures). The shell guard reads an unknown dash-word after an operand-taking wrapper (timeout, chrt, taskset, flock, chroot) as a possible operand. An inbox path that is not a real directory is refused at exit 2 by every inbox verb, naming the level refused, and a refused inbox is named at session start and on the board. abcd report names its kept editor draft on a failure. ahoy install proves the local tier from the resolved checkout root, so a checkout reached through a symlinked path gets its local tier rather than a false refusal. Five command and brief pages say what the code does.

apiadapter (OpenAI-compatible provider adapter). abcd ahoy --providers explains the optional provider adapter and writes nothing; abcd ahoy connect verifies a provider with one call and only then writes the key into the owner-only ~/.abcd/credentials.json and the provider block into ~/.abcd/config.json, under locks, with the key read from stdin alone. A bundled vendor denylist keeps frontier models on the host. No delegating verb dispatches to a provider yet (spc-2609251028149555), so the spec stays open.

Integration. The machine credential store refuses a file naming one credential twice, and so does ahoy connect's rewrite of config.json. The branch carries main after #725 (integration branch 1); the release-gate receipt example carries the manifest hash lab's manifest change implies, and the cold-reading windows are recalibrated at the merged tip (widening 1130000, entailment 370000, detection 1140000).

Reviews:

  • lintA: review-lintA FIX FIRST (fixed in fix2-lintA), review2-lintA SHIP; the fix3 and fix4 rounds carry their own RED-to-GREEN tests.
  • lintB: review-lintB FIX FIRST (fixed in fix2-lintB), review2-lintB SHIP.
  • cutfix: review-cutfix SHIP, review3-cutfix FIX FIRST (a record and a page, fixed in fix3-cutfix).
  • apiadapter: review-apiadapter SHIP, review2-apiadapter SHIP, review3-apiadapter SHIP.

Trailers per lane, deduplicated (each Refs line lists the ids its lane cites beyond those it resolves):

lintA
Resolves: iss-243, iss-256, iss-2608211914592726, iss-2608221457227162, iss-2608230752354927, iss-2608281948289198, iss-2608300205044566, iss-2608300234598982, iss-2608300244483405, iss-2608300929274006, iss-2608301203521317, iss-2608301350287219, iss-2608301519255156, iss-2608301649337920, iss-2608301755006875, iss-2608301844363341, iss-2609012037127981, iss-2609020716579024, iss-2609250937494009, iss-2609261016494611, iss-2609261019593167, iss-2609261133371466, iss-2609261152282753
Refs: iss-2609261034583909, iss-2609261208193041

lintB
Resolves: iss-131, iss-279, iss-303, iss-336, iss-2608221342508878, iss-2608261533419897, iss-2608300848049813, iss-2608301221402131, iss-2608301634520703, iss-2608301634527391, iss-2608301808197261, iss-2608301813259334, iss-2608301901260461, iss-2609151150180583, iss-2609151952353626, iss-2609181121522692, iss-2609210748122003, iss-2609231715081185, iss-2609252251311346, iss-2609252251317108, iss-2609252251320133, iss-2609252251320497
Refs: iss-28, iss-30, iss-34, iss-35, iss-122, iss-2608301744300631

cutfix
Resolves: iss-2609260543090196, iss-2609260552246303, iss-2609260552247252, iss-2609260552247395, iss-2609260552249742, iss-2609260552250826, iss-2609260552256401, iss-2609260552256523, iss-2609261106286306, iss-2609261106287078, iss-2609261106287627, iss-2609261108448674
Refs: iss-2609261232464351, iss-2609261232477655

apiadapter
Resolves: iss-2609260904161412, iss-2609260904163830, iss-2609260904167076, iss-2609260958580553, iss-2609260958588580
Refs: iss-2609260958587561

integration
Resolves: iss-2609252145018018, iss-2609260120380520, iss-2609261312108500
Refs: iss-2609012037127981, iss-2609251455354719, iss-2609260120380520

No lane carries a Delivers: trailer; spc-2609221011153746 stays open.

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

The Phase 0 harness-interface note was demoted to a dated research note and
its two dangling references repaired by f1644d4 (first carried in v0.6.2),
so the record closes as already fixed and stays out of the current cut.

Resolves: iss-2608221457227162
Assisted-by: Claude:claude-opus-5-5
…ation

sota-per-intent's promotion path named an intent_sota record-lint gate and a
template slot, and neither existed: three of forty-eight planned intents carry
a `## SOTA` section. The rule reads the shared intent-tree scan and flags a
planned/ intent with no `## SOTA` heading outside a fence, or a heading with
nothing under it. It is armed at warn, the warn-first rung of the ratchet, so
the forty-five planned intents that predate it are named on every run without
blocking one; promoting it to blocker is a later act once they are back-filled.

Scope decisions: planned/ only, because the principle binds a promoted intent
and a draft has not been shaped nor a shipped record able to act on it; and
presence plus non-emptiness only, because the corpus names its path in several
honest spellings and a spelling check would judge wording, not the answer.

The intent template in intents/README.md gains the `## SOTA` slot, and the
brief's intent chapter lists the rule among the armed intent-tree rules.
`intent create` still seeds no SOTA section: the declaration is written at
planning, which is where the rule reads it.

Refs: iss-243
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-243
Assisted-by: Claude:claude-opus-5-5
The "formatter hook" that turned straight quotes typographic is gofmt itself:
go/doc/comment rewrites a doubled backtick in doc-comment prose to U+201C and a
doubled apostrophe to U+201D. So the substitution is deterministic, runs in
every editor and hook that formats, and `make fmt-check` then demands the
rewritten form. Seven comments on main had already been rewritten this way,
among them the two in the guard package that spell git's '\'' escape and the
capture remedy's shell-quoting note, each now saying something other than what
its author wrote.

TestNoCurlyQuotesInGoSource walks internal/, cmd/ and evals/ (tests included)
and refuses any of the four glyphs outside an allowlist of the three files that
use them deliberately, each with its exact count and a reason, so a rewrite in
an allowlisted file is caught too; a stale entry is refused as well.
TestCurlyQuoteGuardRefusesAPlantedGlyph plants one in a scratch tree and sees
the refusal. The seven comments move the spelling into an indented code block,
which gofmt keeps verbatim, or reword it, and the tree passes the pinned-
toolchain fmt-check.

Refs: iss-2608301844363341
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608301844363341
Assisted-by: Claude:claude-opus-5-5
The lint reads files whose paths and contents a cloned repository controls:
the paths come out of the committed lint config, and the files sit in the
committed tree that CI's record-lint walks. The roots, citation, cross-store
and changelog reads were contained and guarded; seventeen others were a bare
os.ReadFile, so a symlink carried the read out of the checkout, a FIFO hung
the gate, and a link to /dev/zero was read unbounded.

Two helpers now carry the stack the roots walk uses: readRepoFile for a
configured repo-relative path (refused if absolute or climbing out, resolved
leaf held inside the repository, then fsutil.ReadGuarded) and readRepoAbs for
a leaf a walk found. The persona roster, the surface registry, the context
targets of both context rules, the index_drift document, the gate_lockstep
runbook and workflow, the sub-verb snapshot and surface files, the spec-link
index, the intent tree, the issue ledger and the forbidden-synonyms walk read
through them. A missing file keeps its IsNotExist error, so absence is still a
state where a rule treats it as one.

record_schema's store walk reads each record with fsutil.ReadGuarded on the
unresolved leaf, as the reading walk does over the same trees, and a record
it cannot read safely is a finding on that file rather than a followed link
or an aborted crawl; the configured store paths are contained too. The
receipt gate reads each receipt and the release-gate manifest the same way,
refusing a symlinked leaf outright, and reports an unreadable receipt as a
fail-closed finding: a FIFO receipt hung the gate, and a link to an
out-of-tree forged PROMOTE satisfied it. Its configured receipts_dir is
contained.

TestLintReadsNothingUnguarded refuses a raw os.ReadFile, os.Open or
os.OpenFile in any production file of the package, so the next rule cannot
reopen the class.

Refs: iss-2608211914592726, iss-2608301203521317, iss-2609012037127981
Assisted-by: Claude:claude-opus-5-5
…guarded

Resolves: iss-2608211914592726, iss-2608301203521317, iss-2609012037127981
Assisted-by: Claude:claude-opus-5-5
parseSubVerbTable reported the table found as soon as it found the heading, so
a surface file carrying `## Sub-verbs` followed by prose passed with no
finding, while the brief's surfaces README promises that a file without the
table is a finding whatever its verb registers. The table is now found by its
`| Verb | Bucket | Status |` header row, and a heading without one draws the
same missing-table finding as a file without the heading.

Refs: iss-2609250937494009
Assisted-by: Claude:claude-opus-5-5
…ader

Resolves: iss-2609250937494009
Assisted-by: Claude:claude-opus-5-5
The padding and bucket legs of record_schema ended on "goes on being reported
as unanswered", which the walk never established: a widening item carrying a
declined or held disposition is answered, and neither leg reads a
disposition. Both now say what the position leg already says, that the record
counts for nothing and no line reports an answer was written for the item it
names. The bucket leg still appends its tail only where the family's reader
opens the target file.

Refs: iss-2608301755006875
Assisted-by: Claude:claude-opus-5-5
Every Unsafe finding in the reading report ended by saying `abcd capture`
refuses the same paths. That is true of the readings and dispositions trees
capture reads before it writes, and false of admissions and surprises, which
core/capture never reads. The clause is now appended only off those two
families.

Refs: iss-2608301649337920
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608301755006875, iss-2608301649337920
Assisted-by: Claude:claude-opus-5-5
agent_contract enumerated agents/*.md non-recursively, so a prompt filed at
agents/<name>/<name>.md drew no trust-contract finding, no canary demand and
no changelog demand: a silent opt-out of the rule by choosing a directory.
The flat layout is the documented convention, so a markdown file anywhere
below the top level, outside a fixtures/ directory, is now a misfiled-prompt
finding. agents/README.md says so.

Refs: iss-2608281948289198
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2608281948289198
Assisted-by: Claude:claude-opus-5-5
…fuses

The issue-store legs of record_schema re-derived capture's ledger reader one
property at a time, and every shape they did not re-derive was lint-green on
a record the reader refuses and skips, invisible to every capture surface
while it sat in the ledger: a single-quoted severity or lapsed_at (the legs
stripped the quotes the reader keeps), a stray indented line after a valued
or null key, a key whose only continuation is an indented comment, a key led
by a Unicode space, a schema_version other than the integer 1, a list where a
string belongs. The opposite error stood too: a double-quoted value with a
backslash escape the reader decodes was red.

Two changes. The issue store's legs now decode a value as its reader does
(schemaRecord.scalar picks readerScalar for that store, issueScalar for the
reading families, whose readers strip either quote pair). And a backstop leg
asks the reader itself: when no other leg found anything on an issue record,
capture.ReadRefusal (the parse, validateStrict and validateInvariants that
List runs) is called on the file, and its refusal is the finding. The lint
cannot import core/capture, whose tests import the lint, so the reader is
registered through lint.SetIssueReader by the front doors: cmd/record-lint
and the CLI, each with a test that fails without the registration.

TestRecordSchemaAgreesWithTheLedgerReader is the differential: each case's
expectation is asserted against capture.ReadRefusal itself before the gate
is judged, in both directions.

Refs: iss-2608300205044566, iss-2608300234598982, iss-2608300244483405, iss-2608301519255156
Assisted-by: Claude:claude-opus-5-5
…e record

Resolves: iss-2608300205044566, iss-2608300234598982, iss-2608300244483405, iss-2608301519255156
Assisted-by: Claude:claude-opus-5-5
links_resolve is a blocker, but it only walked record-lint's roots
(.abcd/development), so no relative link in .abcd/work was ever resolved:
the issue ledger, DECISIONS.md and CONTEXT.md were never checked. Adding the
tier to roots would arm every content rule there too, so links_resolve gains
its own extra_roots list, walked for links alone, contained and read through
the guarded read like the roots walk, with a missing tree refused as
misconfiguration. The shipped config names .abcd/work and exempts the
append-only reviews charter, whose committed files RD002 forbids editing.

Arming it found eight dead links in resolved ledger records. One was a real
depth error (a principles link one level short from resolved/); the rest
quoted link-shaped text from the defects they record, and now carry it in a
code span so it reads as the quotation it is.

Refs: iss-2608230752354927
Assisted-by: Claude:claude-opus-5-5
…ord-lint

Resolves: iss-2608230752354927
Assisted-by: Claude:claude-opus-5-5
…grammar

record_schema held the reading-item and disposition stores to the resolver's
<prefix>-<N>[-<slug>].md grammar, while every reader of those families (the
outstanding report, the item locator, capture's disposition walk) opens a
record as <handle>.md and nothing else. A hand-written rdi-2-widen-the-frame.md
therefore passed the gate and was read by nothing, and the admission join had
to stand silent on it. recordid.BareFilenameNumRe is now the one grammar for
both families, in the gate and in the report: a slugged file is refused at the
walk, and the join naming it is the not-in-the-corpus finding. The stand-down
the padding, position and bucket legs kept for such a file has no case left,
so it is removed, with the two tests that pinned it rewritten to the new
behaviour and the third deleted.

The resolution note of iss-2608301755006875, written earlier on this branch,
named the deleted test; it no longer does.

Refs: iss-2608300929274006, iss-2608301755006875
Assisted-by: Claude:claude-opus-5-5
…and dispositions

Resolves: iss-2608300929274006
Assisted-by: Claude:claude-opus-5-5
…sed entry

The changelog is derived: launch ship inserts a dated section beneath
`## [Unreleased]`, and the ingest refuses a section that already holds prose.
Nothing stopped the entry landing, so the habit of writing one wedged the
next cut the day after the cutover. record-lint now arms
changelog_unreleased_empty as a blocker: a change that puts anything under
the heading fails its record gate with a message naming the record-first flow
(resolve the issue, or close the spec and ship the intent, in the same
change), and a changelog with no anchor is a finding too, as the ingest
refuses it.

The gate and the ingest read the section through one predicate,
changelog.UnreleasedSection, which replaces the ingest's own loop, so the two
cannot disagree about what counts as an entry. AGENTS.md says the refusal now
happens at the change. Also carries a gofmt alignment fix in
readingoutstanding.go left by the previous commit.

Refs: iss-256
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-256
Assisted-by: Claude:claude-opus-5-5
No gate read an issue record's body as markdown before site-render, at the
far end of preflight: abcd capture accepts any body, so a construct the
renderer refuses (an indented code block held site-render red on a branch for
two commits) was committed and found by whoever next ran the whole gate. The
record gate now asks the renderer itself. site.CheckRecordBody runs the same
sections and block renderer a record page is built with (renderMarkdownBody
and the check share renderBody; only the link rewrite and image assets are
neutral), and record_schema reports its refusal on the issue record that
carries it. core/site imports the lint, so the check is registered through
lint.SetRecordBodyCheck by cmd/record-lint and the CLI, beside the ledger
reader, with a front-door test that fails without it.

Refs: iss-2608301350287219
Assisted-by: Claude:claude-opus-5-5
…e renderer

Resolves: iss-2608301350287219
Assisted-by: Claude:claude-opus-5-5
…xist

A resolution note is what every later reader trusts about how a fix was
proved, and one named three tests for a guard none of them exercised. The
issue-resolution gate now reads the resolution field of every record entering
resolved/ or wontfix/ in the range and refuses a TestX no _test.go file at the
head defines. Records already terminal are not re-read: eight historical notes
name tests renamed or removed since, and a later rename does not make a note
false when it was written. The cases script watches it refuse an invented
test name and pass a real one and a note naming none; against the gate at
e076c8e the refusal case fails.

The mutation half the record describes (a mutation of the named limb should
fail one of the tests) stays a review discipline, as the record says.

Refs: iss-2609020716579024
Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609020716579024
Assisted-by: Claude:claude-opus-5-5
…t exist

Resolves: iss-2609020716579024
Assisted-by: Claude:claude-opus-5-5
A tool-mandated mirror of a root file (a byte-identical copy of AGENTS.md a
tool reads from .github/) carries links that resolve from the root and not
from the mirror's directory, and nothing could excuse it from links_resolve:
exempt_paths covers the content-authoring checks only, and the rule's own
`exempt` key decoded cleanly and was read by nothing. The link check now
skips a file its `exempt` globs name; every other file is still checked.
The docs surface chapter and the plugin page say how.

Refs: iss-2609151150180583
Assisted-by: Claude:claude-opus-5-5
…se mirror files

Resolves: iss-2609151150180583
Assisted-by: Claude:claude-opus-5-5
A gitignored path is not the repository's documentation, yet the docs lint
walked a cached clone a fetch script wrote under a root and reported
thousands of findings from it. The per-file walk and directory_coverage now
ask git once per root (gitutil.IgnoredUnder: ls-files --others --ignored
--directory, isolated, core.excludesFile neutralised, fail-open) and prune
what it lists. A committed file is never pruned, since git ignores no
tracked file. lint.PrunedInRoots names what was pruned: docs lint carries it
as `pruned` in --json and one line in the text render, and record-lint names
it on stderr. DocumentsInRoots counts the pruned walk, so `documents` agrees
with what was read.

Refs: iss-2609151952353626
Assisted-by: Claude:claude-opus-5-5
…links

Resolves: iss-2609261152282753
Assisted-by: Claude:claude-opus-5-5
Refs: iss-2609261208193041
Assisted-by: Claude:claude-opus-5-5
The wontfix reason held that stepLocalTier can only refuse the level its
message names, because install refuses a non-real .abcd up front. Review
disproved it: EnsureRealDirAll also proves its base, and the base is the
shell's logical working directory, so a checkout entered through a
symlinked path has its local tier refused, naming .abcd/.work.local while
the refused level is the checkout path. The record moves back to open/
with its text amended to the reproduced shape; its declined grounds
entry stays, append-only.

Refs: iss-2609261108448674
Assisted-by: Claude:claude-opus-5-5
…ng the refused level

stepLocalTier proved .abcd/.work.local with EnsureRealDirAll from a.cwd,
the shell's logical working directory, and EnsureRealDirAll proves its
base as well as every level below it. A checkout entered through a
symlinked path (cd ~/proj where ~/proj -> ~/src/proj) therefore had its
local tier refused, with a message naming .abcd/.work.local while the
refused level was the checkout path. The step now resolves the root with
fsutil.RealExistingPath, the canonical existing-prefix resolver, so only
a symlink at or below the checkout's .abcd is refused, and a refusal
names the refused level repository-relative.

Sweep of every EnsureRealDirAll / ProbeRealDirAll caller:
- launch WritePreflightReport (launch --dry-run and launch ship hand it
  os.Getwd()): the same class, fixed the same way.
- intent ensureRecordDir: its base is gitutil.CheckoutRoot, git's
  --show-toplevel, a physical path; safe.
- implement Open and the report inbox (EnsureRealDirAll and
  ProbeRealDirAll): the base is the caller's home (os.UserHomeDir), not a
  logical working directory, so outside this class; the inbox refuses a
  symlinked home on purpose, naming ~ (brief 30-inbox).

Refs: iss-2609261108448674
Assisted-by: Claude:claude-opus-5-5
…he resolved checkout root

The fix in 973a73d resolves the checkout root through its symlinks
before proving the local tier, and names a refused level
repository-relative; the sweep fixed the same class in launch's
pre-flight report writer.

Resolves: iss-2609261108448674
Assisted-by: Claude:claude-opus-5-5
…t reads as no inbox

The page said every inbox verb refuses a symlink or a file at any level
of the inbox, naming it. The reading verbs read a symlink or a file at
the home or at ~/.abcd with nothing behind it as no inbox, while filing
a report still refuses; the brief chapter 30-inbox and the reader
already say so, and the page a plugin reader gets now carries the same
clause.

Assisted-by: Claude:claude-opus-5-5
Operand paths proved one level only in lifeboat and the site output
directory, and ahoy install reporting clean beside a refusal note.

Refs: iss-2609261232464351, iss-2609261232477655
Assisted-by: Claude:claude-opus-5-5
Lane lintA: the lint cluster A fixes (guarded reads, record_schema
parity and link legs, intent_sota, changelog_unreleased_empty, RS006,
the curly-quote guard), reviewed SHIP.

Conflicts, resolved by hunk:
- internal/core/lint/subverbs.go loadSnapshotSubVerbs: main's two-map
  signature and moved-spelling doc comment, read through lintA's
  readRepoFile guarded read.
- internal/core/lint/subverbs_test.go: both tests kept (main's
  TestSubVerbMovedSpellingsNeedNoRowAndMayHaveNone and lintA's
  TestSubVerbHeadingWithoutTableFails).

Semantic conflict fixed here: lintA's TestNoCurlyQuotesInGoSource
names two files main gained after lintA's base.
- internal/core/launch/gates.go carries nine deliberate curly quotes
  (the narration gate's tokeniser and clause breaks): allowlisted with
  that count and reason.
- internal/core/guard/heredocpayload_test.go carried a gofmt-corrupted
  doubled backtick in a doc comment: reworded.

Assisted-by: Claude:claude-opus-5-5
Lane lintB: the lint cluster B fixes (gitignore pruning, name_roots,
link_anchors, jsonstrict with case-twin folding, setext detection,
the guarded manifest read), reviewed SHIP.

Conflicts, resolved by hunk per review-lintB item 4 and review2-lintA
item 7:
- lint.go LintAt: both blocks kept, lintA's links_resolve extra-roots
  walk and lintB's name_roots pass.
- lint.go checkReceiptGate: lintA's os.OpenRoot + defer root.Close()
  read shape for the manifest and every receipt, and lintA's
  guardedReason finding; lintB's NoDuplicateKeys block kept.
  lintB's maxReleaseGateJSONBytes and its syscall import dropped.
- config.go: knownRules is the union (ruleLinkAnchors added beside
  lintA's ruleIntentSOTA and ruleChangelogUnreleasedEmpty); the Exempt
  doc comment carries both lanes' readings.
- schema.go tail: both appended blocks kept (lintA's reader-parity,
  seam and body-render legs; lintB's setext detector).
- brief 04-surfaces/05-intent.md: lintB's rule-list line with lintA's
  intent_sota added.
- commands/docs.md: main's consolidated page kept (the docs lint moved
  to `abcd lint docs`); lintB's `pruned` bullet and its links_resolve
  exempt / link_anchors sentences carried to the lint section of
  commands/lint.md, where that lint is documented.
- internal/surface/cli/cli.go: lintB's pruned line kept, spelled with
  main's `abcd lint docs` prefix.

Semantic conflicts fixed here (main's verb consolidation):
- lintB's TestDocsLintNamesTheGitignoredPathsItPruned ran `docs lint`;
  it runs `lint docs`, and lintB's config-error prefix reads
  "lint docs:" like its siblings.
- lintA's issuereader.go comment named `abcd docs lint`.
- lintB's bucketfield_test.go comment carried a gofmt-corrupted
  doubled apostrophe that lintA's curly-quote guard names: reworded.

Generated files regenerated (go generate ./internal/surface/cli): no
change.

Assisted-by: Claude:claude-opus-5-5
lintA's b48fd58 (iss-2609012037127981) rerouted every site this record
names through readRepoFile/readRepoAbs or fsutil.ReadGuarded, and the
go/ast guard TestLintReadsNothingUnguarded holds the package to it, per
review-lintB item 4. Resolved on the integration branch, where both
lanes meet.

Resolves: iss-2609252145018018
Refs: iss-2609012037127981
Assisted-by: Claude:claude-opus-5-5
The machine credential file was decoded with plain json.Unmarshal, so
a repeated key resolved last-wins and a case twin sat beside its
spelling unseen. The read now runs lintB's jsonstrict.NoDuplicateKeys
first, the one duplicate-key primitive, and refuses the store without
echoing either spelling: a key is file content too.
TestAStoreNamingACredentialTwiceIsRefused (exact repeat, escaped
repeat, case twin) was watched RED before the change.

Refs: iss-2609260120380520
Assisted-by: Claude:claude-opus-5-5
…twice-named key

Resolved by ae89fe2. The record's deferral waiver, which pointed at
this integration step, is dropped with it: a resolved record carries no
deferral.

Resolves: iss-2609260120380520
Assisted-by: Claude:claude-opus-5-5
Lane cutfix: the cut-blocking captures (guard dash-word operands, inbox
refusals naming their level on every verb, the kept report draft,
ahoy's local tier proved from the resolved checkout root, and the page
fixes), reviewed SHIP.

No conflicts. Generated files regenerated (go generate
./internal/surface/cli): no change.

Assisted-by: Claude:claude-opus-5-5
Lane apiadapter: the OpenAI-compatible provider adapter (the client,
provider blocks with the vendor denylist and routes, `ahoy --providers`
and `ahoy connect`, the setup's locked writes), reviewed SHIP three
times. Its spec spc-2609221011153746 stays open: provider dispatch from
a delegating verb waits on spc-2609251028149555.

Conflicts, resolved by hunk:
- internal/core/credential/credential.go: apiadapter's readStore
  refactor and SetMachine kept; the jsonstrict duplicate-key refusal
  this branch added to Resolve (iss-2609260120380520) moved into
  readStore, so Resolve and SetMachine both refuse a store naming one
  credential twice. TestSetMachineRefusesWhatResolveRefuses gains that
  case, watched RED on a scratch copy of feat/api-adapter.
- brief 05-internals/03-configuration.md: apiadapter's machine-layer
  sentence, with the names-each-credential-once clause added.

Semantic conflict fixed here: lintA's TestNoCurlyQuotesInGoSource names
a gofmt-corrupted doubled apostrophe in client_test.go's awkwardKey
comment: reworded.

Owed at landing (review3-apiadapter INFO-1): the openaiapi package doc
no longer lists HTTP_PROXY as honoured, since no plain-http call is
ever proxied.

Generated files regenerated (go generate ./internal/surface/cli): no
change beyond the lane's own.

Assisted-by: Claude:claude-opus-5-5
…ast-wins

Found while merging apiadapter onto the credential store's jsonstrict
reroute: the provider-block write re-reads config.json under its lock
with plain json.Unmarshal.

Refs: iss-2609261312108500, iss-2609260120380520
Assisted-by: Claude:claude-opus-5-5
…wice

writeProviderBlockLocked re-reads ~/.abcd/config.json under its lock and
rewrites it from that read, which used plain json.Unmarshal: a key
named twice collapsed last-wins and the rewrite dropped the other
spelling. The re-read now meets jsonstrict.NoDuplicateKeys, the check
LoadAPI already makes through layered, and the file is left as it
stands. TestTheProviderBlockWriteRefusesAConfigNamingAKeyTwice (a top
repeat, a case twin under oracle, a repeat inside a provider) was
watched RED before the change.

Refs: iss-2609261312108500
Assisted-by: Claude:claude-opus-5-5
…s a twice-named key

Resolves: iss-2609261312108500
Assisted-by: Claude:claude-opus-5-5
…n branch

main moved to 811fba1 while the four lanes were integrated. No
conflicts; build, vet and the touched packages' tests pass, and the
generated reference is unchanged by go generate.

Assisted-by: Claude:claude-opus-5-5
Measured by dry-run assembly on a clean clone of d3dc346 (the tip after
lintA, lintB, cutfix, apiadapter and main #724), and each window set to
ceil(tokens * 1.01 / 10000) * 10000:
- widening:   1082705 tokens, 4168417 bytes -> 1100000 (was 1060000)
- entailment:  349697 tokens, 1346335 bytes ->  360000 (was 350000,
  under one per cent headroom)
- detection:  1091741 tokens, 4203205 bytes -> 1110000 (was 1060000)
Comparative is untouched (its window is measured against the fixture
run, not the tree).

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
main moved to fb76742 with integ1 (glossary, lab, itd28, daport).

Conflicts, resolved by hunk:
- internal/core/lint/config.go knownRules: the union, glossary's
  ruleGlossaryFamilyPointer and ruleRecordFamilyKey beside lintA's
  ruleIntentSOTA and ruleChangelogUnreleasedEmpty and lintB's
  ruleLinkAnchors.
- internal/surface/cli/cli.go board constructor: cutfix's
  boardInbox(cmd.ErrOrStderr()) and integ1's Reviews: boardReviews(...)
  both kept.
- .abcd/config/reading-presets.json: this branch's figures kept; the
  windows are re-measured at the merged tip in the next commit.

Semantic conflict fixed here: lintB's
TestReceiptExampleManifestHashIsTheCommittedManifests pins
receipt.example.json's manifestHash to the committed manifest, and lab
changed manifest.json (31-lab.md, checkerCount 41, promptHash) without
the example, which lintB's side of the three-way merge kept at the old
manifest's hash. The example carries the new manifest's sha256.

Generated files regenerated (go generate ./internal/surface/cli): no
change beyond the merge. go test ./... passes.

Assisted-by: Claude:claude-opus-5-5
TestAStoreNamingACredentialTwiceIsRefused's "escaped repeat" input was
byte-identical to "exact repeat": a tool layer had decoded its \u escape
before the file was written (review-integ2, LOW). The twin key is now
spelt with \x5c bytes, so the store the test writes carries a JSON
e escape. A guard in the subtest fails if the escape is missing
from the input. Checked with od -c; the case passes with the escape in
place.

Refs: iss-2609260120380520
Assisted-by: Claude:claude-opus-5-5
Measured by dry-run assembly on a clean clone of 8fd646f (this branch
after main #725 merged in), each window set to
ceil(tokens * 1.01 / 10000) * 10000:
- widening:   1115292 tokens, 4293878 bytes -> 1130000 (was 1100000)
- entailment:  357546 tokens, 1376554 bytes ->  370000 (was 360000,
  under one per cent headroom)
- detection:  1124328 tokens, 4328666 bytes -> 1140000 (was 1110000)
Comparative is untouched.

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
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