Skip to content

feat: keep each credential in the home you choose: keychain, an external pointer, or abcd's own file - #746

Merged
REPPL merged 29 commits into
mainfrom
integ/land-14
Sep 29, 2026
Merged

REPPL merged 29 commits into
mainfrom
integ/land-14

Conversation

@REPPL

@REPPL REPPL commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

A person can now keep each credential abcd uses in the home they choose: the platform keychain (the one abcd recommends), an external place abcd only points at (an environment variable, or a field in a JSON file under the home directory), or abcd's own owner-only file. abcd ahoy credential <name> explains what the credential unlocks and what each home means before asking for anything. It then checks the value with the provider's own call, and it stores the value only when that call succeeds. Every reader asks the one store, and an unset credential is refused with a pointer to that walkthrough, never a silent failure. What is now refused: a ~/.abcd that is a symlink (into a dotfiles repository, say), for every reader and writer abcd trusts there, the credential store included and in every home. The refusal holds against a swap made after the check, because every file under ~/.abcd is reached through the directory that was judged. Also refused: an external pointer whose directory is a link, a value written into a git working tree, and a stored secret replaced without being asked. The surface pages and references now say what the code does.

Surface pages (drainDocs). The plugin pages and references for reading, capture, decide, disembark, intent and the CLI reference match the code. reading assemble says whether a run it wrote can be ingested. The decide page tells a person on an older binary how to take the update, and which binaries answer the update check's next step.

~/.abcd behind a link (drainH, drainHome, drainOpenat). drainH and drainHome reached main with integ12 and merge here as already merged: the install repairs an owned dangling path entry and refuses the plugin-root link on a cold cache, and the hook shims refuse a path-entry behind the link before reading it. This branch adds drainOpenat: every file abcd trusts under ~/.abcd, the history registry included, is reached through the descriptor of the directory that was judged, so a link swapped in after the check is refused rather than followed.

Credential store (cred, itd-2609221017023290). There is one store with three homes, one reader, and the ahoy credential walkthrough. ahoy connect and the site setup use it, and the run receipt and the host outcome name the credential, never its value. At integration, the store's own index, its lock and its external pointers are reached through the same descriptor walk as every other ~/.abcd file.

Reviews: drainDocs SHIP (its two wording findings are fixed here). drainH FIX FIRST, then SHIP after its fix round. drainHome FIX FIRST, and its fix round was judged sound at integ12. drainOpenat SHIP, and its fix round verified SHIP. cred SHIP, conditional on the integration routing, which this branch carries with its tests.

Re-merge after integ13 (#741). Main (integ11, the v0.11.1 release, integ12, the owed-intents audit and integ13) is merged in. The one conflict, the root command's long help, keeps this branch's wording, which names the same seven record families as main's; the generated references were regenerated; the decision log is main's, which already carries every entry of this branch's. Release files are main's and the changelog's Unreleased section is empty. Then, from the security review of this branch: (a) a home directory that is itself a symlink into a git checkout is now judged where it leads, so the abcd home is refused there with nothing written, and a pointer under that home is not read; the home is never refused for being a link (iss-2609290259108077, resolved). (b) The index can split between two ~/.abcd directories under a same-uid swap of one real directory for another between two walks; it carries names, homes and pointers only, never a value, and is captured and deferred past v0.11.1 with its fix named (iss-2609290300313698). (c) The refusal of a pointer through a link now speaks of the tool's file, not abcd's. (d) The credential intent stays in shipped/ with its spec closed; its fidelity request was re-emitted with main's intent audit verb and reads as already owed. (e) No new verb or flag; the generated files are current and every path-entry shim keeps the ~/.abcd link check. One semantic conflict: main's roles rule wants every question a plugin page puts to a human to name its addressee, so the credential store's two home questions in the ahoy page now ask the technical facilitator. The widening reading window rose from 1,360,000 to 1,370,000 tokens, because its headroom fell under 1% at this tip; entailment and detection keep theirs.

A pointer at a tool's file is refused when any directory between the home and that file is a symlink, wherever the link leads; the environment-variable pointer stays open.

Operational: the fidelity audit for itd-2609221017023290 (receipt rcp-ebf7d171b544) is owed after merge. The macOS keychain home's real round trip is deferred to the technical facilitator (iss-2609281654467661).

Trailers, per lane (first-parent, deduplicated):

  • drainDocs: Resolves: iss-2608221254560250, iss-2608301244458074, iss-2609061503374089, iss-2609091648476051, iss-2609120452369809, iss-2609151150180399, iss-2609190338070038, iss-2609240519413467, iss-2609240519418856, iss-2609240519427388, iss-2609240646458365, iss-2609251645376219; Refs: iss-2609231016278107 (deferred)
  • drainH/drainHome/drainOpenat: Resolves: iss-2609281310017733 (the group's five other resolved records reached main with integ12)
  • cred: Delivers: itd-2609221017023290; Refs: iss-2609281654467661 (deferred)
  • integration: Refs: iss-2609281310017733, iss-2609061503374089
  • re-merge: Resolves: iss-2609290259108077; Refs: iss-2609290300313698 (deferred)

Delivers: itd-2609221017023290
Resolves: iss-2608221254560250
Resolves: iss-2608301244458074
Resolves: iss-2609061503374089
Resolves: iss-2609091648476051
Resolves: iss-2609120452369809
Resolves: iss-2609151150180399
Resolves: iss-2609190338070038
Resolves: iss-2609240519413467
Resolves: iss-2609240519418856
Resolves: iss-2609240519427388
Resolves: iss-2609240646458365
Resolves: iss-2609251645376219
Resolves: iss-2609281310017733
Resolves: iss-2609290259108077
Refs: iss-2609281654467661
Refs: iss-2609231016278107
Refs: iss-2609290300313698
Assisted-by: Claude:claude-opus-5-5

`reading ingest` resolves a run's manifest only under the local-tier run
directory by its run id, so an assembly written with --out is an inspection
copy no ingest can prove. The terminal help and the plugin page both taught
exactly that invocation as the worked example, and the failure surfaced only
at ingest, after the reading had been commissioned and returned.

The result now carries `ingestable` (false for a run written to a named
directory and for a dry run), the text render adds an `ingest:` line for a
run written elsewhere, the example assembles into the default run directory,
and the --out help and the page state that a named directory is for
inspection. The reading surface chapter moves with the surface.

Tests watched RED on a scratch copy before the change:
TestAssembleSaysWhetherTheRunCanBeIngested and
TestAssembleRenderSaysANamedRunCannotBeIngested.

Refs: iss-2609091648476051
Assisted-by: Claude:claude-opus-5-5
Each change reads the code first and makes the text match it.

- commands/abcd.md and the root help: an id's digits are a short ordinal
  or the sixteen-digit minted stamp, both resolve, and nothing renumbers;
  the help also names the adm, srp and rfm families record.IDRe admits.
- commands/capture.md: the convention that keeps an id out of two status
  folders (resolve a record on the branch that carries it) is stated for
  managed repositories, not only in this repository's AGENTS.md.
- commands/decide.md: the verb needs abcd 0.8.0 or later, and an older
  binary's refusal is met with the release check, spelled as that binary
  spells it. commands/intent.md: the decomposition note's research/notes/
  folder is one no install scaffolds.
- commands/disembark.md: the argument hint lists all eight sub-verbs with
  the operands the CLI takes.
- commands/intent.md: grounds.redacted is omitted when zero, as every
  write verb's redacted count is (omitempty throughout).
- docs/reference/terminology.md: memory retrieval is `abcd memory ask`'s
  word-overlap ranking over classes, domain and summary; the recall field
  is never consumed and no budget brackets exist.
- internal/README.md: core/frontmatter gets its package-map entry.
- AGENTS.md, the configuration chapter and the worktree-store draft: a
  store's <root-sha> is the full object name; the draft gains an open
  question on listing a hand-laid short-key lane.
- docs cite confirm --receipt: the help describes the receipt schema
  instead of a checklist page that does not exist.
- docs/reference/cli/README.md: `abcd --help` lists the verbs a person
  types, and `abcd --help --agent` adds the ones agents and hosts call.

Refs: iss-2609120452369809, iss-2609190338070038, iss-2609061503374089
Refs: iss-2609240519413467, iss-2609151150180399, iss-2608221254560250
Refs: iss-2608301244458074, iss-2609240646458365, iss-2609240519418856
Refs: iss-2609251645376219
Assisted-by: Claude:claude-opus-5-5
itd-162 ac-2 promised the adopt phase's pre-commit asset (a secrets and
absolute-path gate) would resolve from the record or the binary; the
delivery scaffolds the private name guard instead. Restoring the gate or
amending ac-2 is narrowing a shipped promise, which only the product
thinker rules on (run A's rulings-owed list, theme D), so the record is
deferred out loud to the v0.11.0 anchor with the question verbatim rather
than narrowed here.

Refs: iss-2609231016278107
Assisted-by: Claude:claude-opus-5-5
Twelve records move to resolved/, each naming the commit that fixed it:
the reading assemble fix (400625a), the documentation sweep (07af2eb),
and, for the prepare-this-repo docs-lint.json promise, d0c1899, which
corrected the page before this lane and left the record open.

Resolves: iss-2609091648476051
Resolves: iss-2609120452369809
Resolves: iss-2609190338070038
Resolves: iss-2609061503374089
Resolves: iss-2609240519413467
Resolves: iss-2609151150180399
Resolves: iss-2608221254560250
Resolves: iss-2608301244458074
Resolves: iss-2609240646458365
Resolves: iss-2609240519418856
Resolves: iss-2609251645376219
Resolves: iss-2609240519427388
Assisted-by: Claude:claude-opus-5-5
HomeScopeLink judges ~/.abcd by path and every reader and writer then
reached the file by path again, so a process running as the same uid that
swapped ~/.abcd for a symlink between the two read or wrote through the
link. OpenHomeScope and EnsureHomeScope walk the directories below home
one level at a time relative to the level above: Lstat (never following),
refuse a symlink by name, open the level through an *os.Root, and confirm
with os.SameFile that the descriptor is the directory that was judged. The
caller then reaches the file only through the returned root.
ReadHomeDeclaration reads through it, applying ReadDeclaration's guards to
the descriptor and confirming the owner on the opened file.

The standard library does not export Openat on darwin (syscall.Openat is
linux-only) and golang.org/x/sys is only an indirect dependency, so the
walk uses os.Root (openat underneath) plus the identity check. That gives
the same guarantee as openat with O_NOFOLLOW: the directory held is the one
that stood at the name, a real directory, when it was vetted. os.Root by
itself would not be enough, because it follows a symlink that stays inside
the root.

Refusals and messages stay the same. A level swapped for a link reads as a
*HomeScopeLinkError naming it, and any other replacement is
ErrHomeScopeSwapped. home itself is still opened by path and never judged
(decision 2).

Refs: iss-2609281310017733
Assisted-by: Claude:claude-opus-5-5
Every writer that creates a file in ~/.abcd used to check by path, run
MkdirAll by path and write by path. Each now creates, judges and opens
the directory with fsutil.EnsureHomeScope and writes through the returned
root. That covers credential.SetMachine (lock and store), oracle
writeProviderBlock (lock and config.json), ahoy writePathEntry,
writeMachineRouting (whose appeared-while-open check now runs inside the
root), the status-line setting (its rollback remove included) and the
history registry. historyDir replaces ensureHistoryRoot, so the history
registry's lock, bootstrap temp file and link, index read and index write
all go through the same root. It also drops the EvalSymlinks base,
because the walk opens home by path and judges only the levels below it.
statusline.ReadSettingsFile reads through OpenHomeScope.

The early by-path HomeScopeLink checks stay where they were, so each
caller refuses in the same words before doing other work. The locks move
from WithFileLock to WithFileLockIn. That drops the re-check that the
path still names the locked inode, which only matters when a holder
unlinks the lock, and none of these callers does.

The brief's configuration chapter states the rule at this strength.

Refs: iss-2609281310017733
Assisted-by: Claude:claude-opus-5-5
…descriptors

Resolves: iss-2609281310017733
Assisted-by: Claude:claude-opus-5-5
…ough

The credential store proper (itd-2609221017023290, adr-2609221017021499).
internal/core/credential gains Store(home).Resolve, the one reader, and Set,
the one write, over three homes: external (a pointer at an environment
variable or a dotted field of a tool's JSON file under ~, kept in the new
index ~/.abcd/credential-homes.json), abcd (the existing owner-only
credentials.json) and keychain (/usr/bin/security on macOS, /usr/bin/secret-tool
on Linux, run by absolute path, the value on stdin and never in an argv). Walk
is the walkthrough: explain, then the adapter's own verification call, then Set.

- An unset name is ErrNotSet naming `abcd ahoy credential <name>`; the
  oracle refuses before any call, the site setup contacts nothing and its
  remaining step names the walkthrough.
- Set refuses a home inside a git working tree, a name another home holds, a
  different value or pointer for a kept name, and runs the secret scanner over
  the index's bytes before writing them.
- The oracle's Connect and the site setup resolve through Store; a test walks
  cmd/ and internal/ for any other read (Machine/SetMachine calls, store file
  names, keychain commands, secret-shaped env reads).
- CallRecord and HostOutcome name the credential used, never the value.
- `abcd ahoy credential [<name>] [--home ...]` lists, explains and runs the
  walkthrough; `ahoy connect` gains the external and keychain homes.
- hosting.Provider gains Verify (cloudflare: the account read).

Decisions taken here, not in the record:
- The scanner runs over the index, the one file the store writes that must
  never hold a value; credentials.json holds values by design and is guarded
  by location (owner-only, outside any working tree) instead.
- "A tracked path" is read conservatively as any path inside a git working
  tree (a .git entry at or above ~/.abcd), checked without running git.
- The CLI takes the home by --home after the bare explanation rather than an
  interactive prompt, because stdin carries the value; the plugin page asks
  the home through the host's question tool.
- CallRecord's comment said it never names a key's name; criterion 5 requires
  the record to name it, so it now does (the value still never).
- Machine and SetMachine stay exported as the abcd home's own seam for test
  fixtures; the reader grep refuses them outside the package. UserMachine is
  removed.

Assisted-by: Claude:claude-opus-5-5
The credential store, its three homes, the walkthrough, the one reader and
the record of credential names are delivered in the previous commit; every
acceptance criterion is met, so the spec closes whole and the intent ships
with impact additive.

Delivers: itd-2609221017023290
Assisted-by: Claude:claude-opus-5-5
The surface_coverage gate reads a row per registered sub-command; the
walkthrough verb lacked one.

Assisted-by: Claude:claude-opus-5-5
readStore refused a group- or other-accessible credential store on a
by-path Lstat, while the bytes came through ReadHomeDeclaration, whose
fstat checked only the owner and the file's type. A same-uid swap of the
store for a 0644 file between the two was read once.

fsutil gains ReadHomeDeclarationDenying: ReadHomeDeclaration with a
permission mask judged on the fstat of the opened file, refused as
DeclarationExposed with a *DeclarationModeError naming the mode.
ReadHomeDeclaration is the mask-free form of the same primitive, so its
eleven readers are unchanged. readStore drops its by-path Lstat, link,
type and mode checks and maps every refusal from the one read, which
decides absence on its own Lstat.

Refs: iss-2609281310017733

Assisted-by: Claude:claude-opus-5-5
removePathEntry judged ~/.abcd by path (homeScope) and then removed
~/.abcd/path-entry by path, so a ~/.abcd swapped for a symlink into a
dotfiles checkout after the check unlinked the checkout's copy. The
removal now opens ~/.abcd through fsutil.OpenHomeScope and removes the
record through that root, as the status-line rollback does; a symlinked
or absent ~/.abcd leaves nothing to remove.

Sweep of internal/ for os.Remove, os.RemoveAll, os.Rename and os.Chmod
on a path built from ~/.abcd: path-entry was the one trust file. The
rest are stores (transcripts staging and records, the history meta
registration, sources, lab) or lie outside ~/.abcd (PATH bin targets,
the rules state in the user cache dir).

Refs: iss-2609281310017733

Assisted-by: Claude:claude-opus-5-5
Set refused every home when ~/.abcd lay inside a git working tree, though
only the abcd home writes a value there. With the home directory itself a
dotfiles repository, the keychain (the recommended home) and the external
home were refused too. The check now gates the abcd home alone; the index
beside it holds names, homes and pointers only and is scanned before every
write, so it stays writable. An external file pointer inside a working tree
is still refused by the pointer's own check.

Review finding 2 of review-cred. The plugin page and the ahoy brief chapter
say the same.

Refs: itd-2609221017023290
Assisted-by: Claude:claude-opus-5-5
Set read where a name was held outside any lock, and the abcd home and the
index each took their own lock, so two Sets of one name to two homes could
both land; Resolve then refused the name loudly as held in two homes. Set now
holds the index's lock from that read to its last write. The abcd home's own
lock is taken inside it (SetMachine, unchanged), always in that order, and no
writer takes the two the other way round. setIndex no longer locks or creates
~/.abcd itself; Set does both before it.

Review finding 4 of review-cred. At integration, the index lock's routing
through an OpenHomeScope root applies to Set, where the lock now lives,
rather than to setIndex.

Refs: itd-2609221017023290
Assisted-by: Claude:claude-opus-5-5
The grep over cmd/ and internal/ catches a new reader written the obvious
way. An aliased or dot import, an environment variable named through a
variable, os.Environ, or an argv built by concatenation passes it. Its
comment claimed more than that; it now says what the grep is for.

Review finding 8 of review-cred.

Refs: itd-2609221017023290
Assisted-by: Claude:claude-opus-5-5
…facilitator

Review finding 5 of review-cred: the macOS keychain write (security -i, the
value as -X hex on one line) and its read (-w) have only run against the test
binary's fake. The record is captured minor and deferred out loud past
v0.11.0: the check is a real-machine round trip the run must not make, owed
to the technical facilitator (rulings-owed section K). It is not resolved.

Refs: iss-2609281654467661
Refs: itd-2609221017023290
Assisted-by: Claude:claude-opus-5-5
Surface pages and references say what the code does; reading assemble
reports whether its run can be ingested. Merged cleanly; the generated
CLI reference was regenerated and matched.

Assisted-by: Claude:claude-opus-5-5
…to integ/land-14

Every ~/.abcd file is reached through the descriptor of the directory
that was judged, and a symlinked ~/.abcd is refused in every reader and
writer abcd trusts there.

Conflict: hooks/hooks.json, the PreToolUse guard shim. Main (#733)
added the AskUserQuestion routing (i=$(cat), $k in both messages, the
input piped to guard hook); the lane added the `-L ~/.abcd` branch
(w=2 and its message). Combined by hunk: main's line with the lane's
two edits applied, so all five path-entry shims carry the -L branch.

Assisted-by: Claude:claude-opus-5-5
…teg/land-14

One credential store with three homes (abcd, keychain, external), one
reader, and the ahoy credential walkthrough.

Conflict: commands/ahoy.md, the connect setup paragraph. Combined by
hunk: the lane's three-home text; main-side's sentence that the
external and keychain homes "arrive with the credential store" is
dropped (this lane delivers them); drainHome's refusal of a symlinked
~/.abcd is kept, now stated for every home (the routing that follows
this merge makes it hold for the index too).

Semantic: drainH seeds ~/.abcd/cache-attestation in the CLI tests'
hermeticEnv, so TestAhoyCredentialExplainsAndWritesNothing's "~/.abcd
does not exist" check failed on a directory the fixture made. It now
compares the ~/.abcd tree before and after the explanation.

Left red at this merge, closed by the next commit (the owed MUST
routing of the credential store through drainOpenat's primitives):
fsutil TestHomeDeclarationsReadThroughReadHomeDeclaration (store.go and
external.go read by bare ReadDeclaration) and oracle
TestConnectRefusesASymlinkedAbcdHome/abcd (the index lock is created
behind a symlinked ~/.abcd).

Assisted-by: Claude:claude-opus-5-5
…he descriptor walk

The credential store judged ~/.abcd on its lexical path: a ~/.abcd
symlinked into a dotfiles repository passed the working-tree check, and
Set wrote the index (keychain and external homes) or left the index
lock (abcd home) inside the repository. An external pointer through a
symlinked directory (~/.config linked into a repository) was followed
and read.

- Set runs fsutil.HomeScopeLink on the index's path before the
  working-tree check, so a symlinked ~/.abcd is refused first, in every
  home, with nothing created.
- Set creates and opens ~/.abcd with fsutil.EnsureHomeScope and takes
  the index lock with WithFileLockIn over that root; setIndex writes
  with WriteFileAtomicInRoot through it.
- readIndex reads through fsutil.ReadHomeDeclarationDenying with deny
  0o077 (the index's owner-only rule), judged on the opened file.
- resolvePointer runs HomeScopeLink on the pointer's path, then the
  working-tree check, then reads through ReadHomeDeclaration.

Lock order is unchanged: the index lock is taken only in Set, the store
lock only in SetMachine, and SetMachine's one production caller runs
under the index lock.

This closes review-cred findings 1 and 3 (the integration routing,
item 7). It also clears two failures the merge left: the fsutil detector
that refuses a bare ReadDeclaration of a home-scoped file, and oracle's
TestConnectRefusesASymlinkedAbcdHome/abcd.

Refs: iss-2609281310017733
Assisted-by: Claude:claude-opus-5-5
…503374089 dropped

review-drainDocs' two wording findings. commands/decide.md routes an
older binary's refusal to `version --check --json`, whose
check.next_step exists from 0.7.1 (CHANGELOG 0.7.1); a 0.7.0 or older
binary answers without it, so the page says so and names the fallback.
The resolved record's resolution states that the "ahoy status reports
the gap" remedy was dropped, and why: the payload lag is gone at 0.11.0.
The record stays in resolved/.

Refs: iss-2609061503374089
Assisted-by: Claude:claude-opus-5-5
Brings integ11 (#736), the v0.11.1 release (#737, #738), integ12 (#739),
the owed-intents audit (#740) and integ13 (#741) into integ/land-14.

Conflicts, resolved by hunk:
- internal/surface/cli/cli.go, the root command's long help: both sides
  rewrote the record-id sentence. This branch's wording (drainDocs) names
  the same seven families main's does and adds the ordinal-or-stamp
  sentence, so it is kept; TestRootSentenceNamesEveryDispatchedFamily
  still holds.
- docs/reference/cli/commands.md and surface.json: regenerated with
  go generate ./internal/surface/cli.
- .abcd/work/DECISIONS.md auto-merged, but the union repeated five entries
  main already carries (this branch's two tail entries arrived on main
  through integ12, and the three base-tail entries sit elsewhere there).
  Main's ledger removes no line of this branch's, so the merge takes it:
  0 lines removed against either parent.

Release files (CHANGELOG.md, RELEASE.md, marketplace.json, releases/)
are main's, byte for byte; ## [Unreleased] is empty.

Assisted-by: Claude:claude-opus-5-5
workingTreeAbove climbed only the lexical path, so a home directory that
is itself a symlink into a git checkout (~ -> <repo>/home) was never seen
as lying inside it, and Set with the abcd home wrote credentials.json, a
value, inside the checkout (review-integ14 LOW (a)).

The check now judges the place twice: by its lexical path, and with the
home replaced by where it resolves (filepath.EvalSymlinks). The home is
never refused for being a link (review-integ14 decision 2 stands); only
the working tree it leads into is judged. Both callers take it: Set's
abcd-home refusal and the external pointer's read.

TestAHomeThatIsItselfALinkIntoACheckoutIsJudgedWhereItLeads was watched
RED on the unfixed code (Set changed=true, err nil) and is GREEN here:
the abcd home is refused naming the working tree, credentials.json is
absent inside the checkout, the fake keychain holds 0 items, and a
pointer at a file under that home is not read.

Captures the record in this commit; it is resolved in the next.

Refs: iss-2609290259108077
Assisted-by: Claude:claude-opus-5-5
…t leads

Resolves: iss-2609290259108077
Assisted-by: Claude:claude-opus-5-5
…d swap

review-integ14 LOW (b), item 4: readIndex re-walks ~/.abcd under the
index lock while setIndex writes through Set's held root, so a same-uid
swap of one real directory for another between the two walks splits the
index (names, homes and pointers only, never a value). Captured minor,
security, and deferred out loud past v0.11.1 with the fix named: an
exported in-root declaration read over the held root, used by readIndex
under the lock, and SetMachine taking Set's root.

capture defer refuses a minor record ("only a major or critical record
blocks a cut"), so deferred_after and deferral_reason were set by hand,
as the iss-2609281654467661 precedent did.

Refs: iss-2609290300313698
Refs: iss-2609281654467661
Assisted-by: Claude:claude-opus-5-5
A pointer through a symlinked directory was refused with
fsutil.HomeScopeLinkError's sentence, "replace the link with a real
directory to keep abcd's files there", which misdescribes a pointer: the
file is the tool's, not abcd's (review-integ14 LOW (c)). Both of
resolvePointer's link refusals (the early HomeScopeLink and the
descriptor walk's BehindSymlink) now go through pointerLinkRefusal, which
names the link and says what a pointer needs: a pointer is refused when
any directory between the home and the tool's file is a symlink, wherever
it leads; name the file through real directories under the home, or use
an environment-variable pointer. Any other error keeps its own words, and
the primitive's text for abcd's own files is unchanged.

TestAPointerThroughALinkIsRefusedInAPointersWords (a ~/.config linked
outside any repository, through resolvePointer and through Set) was
watched RED on the old sentence and is GREEN here.

commands/ahoy.md (connect paragraph) and the ahoy brief's external home
state the rule in plain words; --env stays open. No new ruling is
claimed: the breadth question stays with rulings-owed AU.

Assisted-by: Claude:claude-opus-5-5
A semantic conflict of the merge of main (#741): integ12's roles lane
(on main) requires every question a plugin page puts to a human to name
which role it asks and set the mode first
(TestPluginQuestionBlocksNameTheAddressee), and the credential store's
two home questions in commands/ahoy.md (connect, and the credential
walkthrough) asked "the person". Both now set `abcd mode facilitator`
and ask the technical facilitator, as the page's other setup questions
do. The value is still never asked for; the person runs the command in
their own shell.

The test failed at the merged tip under both toolchains and passes here.

Assisted-by: Claude:claude-opus-5-5
Measured on a clean clone of a887092 (reading assemble --dry-run):
widening 1,346,832 tokens / 5,185,304 bytes against 1,360,000 left 0.98%
headroom, so its window rises to ceil(tokens*1.01/10000)*10000 =
1,370,000. Entailment 389,508 (400,000, 2.69%) and detection 1,355,868
(1,370,000, 1.04%) keep their windows and their recorded measurements.

Refs: iss-2609251455354719
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-opus-5-5
@REPPL
REPPL enabled auto-merge September 29, 2026 05:03
@REPPL
REPPL added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit d06610b Sep 29, 2026
13 checks passed
@REPPL
REPPL deleted the integ/land-14 branch September 29, 2026 05:59
REPPL added a commit that referenced this pull request Sep 29, 2026
Main carries #743 (gateFix), #742 (drainAhoy) and #746 (integ14).
One conflict, .abcd/config/reading-presets.json (widening's measured
figures): main's figures taken on the conflict hunk only; the windows
are re-measured at the integration tip afterwards. DECISIONS.md and
CHANGELOG.md are main's byte for byte; [Unreleased] is empty.

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