Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
400625a
fix(reading): assemble says when its run can never be ingested
REPPL Sep 28, 2026
07af2eb
docs: surface pages and references say what the code does
REPPL Sep 28, 2026
25af5c4
chore: defer iss-2609231016278107 on the theme D ruling it needs
REPPL Sep 28, 2026
3e10357
chore: resolve the surface-page and doc drift cluster
REPPL Sep 28, 2026
a07ad67
fix(fsutil): hold the symlinked ~/.abcd rule through descriptors
REPPL Sep 28, 2026
3688bbe
fix: reach every ~/.abcd file through the descriptor that was judged
REPPL Sep 28, 2026
9f907de
chore: resolve iss-2609281310017733 — the ~/.abcd rule holds through …
REPPL Sep 28, 2026
c00dd4d
feat(credential): one store, three homes, one reader, and the walkthr…
REPPL Sep 28, 2026
984371f
chore(spec): close spc-2609221017544877 and ship itd-2609221017023290
REPPL Sep 28, 2026
3eceedf
docs(brief): list ahoy credential in the ahoy chapter's sub-verbs table
REPPL Sep 28, 2026
0c598e4
fix(credential): judge the store's mode on the file that is read
REPPL Sep 28, 2026
6ae0a58
fix(ahoy): remove path-entry through the ~/.abcd that was judged
REPPL Sep 28, 2026
278e266
fix(credential): refuse only the abcd home's write inside a working tree
REPPL Sep 28, 2026
f16941a
fix(credential): hold one lock across the whole of Set
REPPL Sep 28, 2026
89e77b7
test(credential): say the reader grep catches drift, not evasion
REPPL Sep 28, 2026
03374c1
chore(capture): the keychain home's real round trip, deferred to the …
REPPL Sep 28, 2026
3cee198
Merge docs/drain-surface-pages (lane drainDocs) into integ/land-14
REPPL Sep 28, 2026
4c59c30
Merge fix/drain-home-openat (lanes drainH, drainHome, drainOpenat) in…
REPPL Sep 28, 2026
5923c49
Merge feat/credential-store (lane cred, itd-2609221017023290) into in…
REPPL Sep 28, 2026
02b6ed5
fix(credential): route the store's ~/.abcd reads and writes through t…
REPPL Sep 28, 2026
3d21d68
docs: say which binaries answer check.next_step, and what iss-2609061…
REPPL Sep 28, 2026
02c7513
merge: bring main (#741) into the integration branch
REPPL Sep 29, 2026
16b7e58
fix(credential): judge the working tree where a linked home leads
REPPL Sep 29, 2026
5010088
chore: resolve iss-2609290259108077 — a linked home is judged where i…
REPPL Sep 29, 2026
8fe7e62
chore: capture and defer the credential index's split under a same-ui…
REPPL Sep 29, 2026
05dfaeb
fix(credential): word a pointer's link refusal for the tool's file
REPPL Sep 29, 2026
a887092
docs(ahoy): the credential home question names the technical facilitator
REPPL Sep 29, 2026
1e0e6f5
chore: recalibrate the reading windows at the integration tip
REPPL Sep 29, 2026
daaa25a
Merge branch 'main' into integ/land-14
REPPL Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .abcd/config/reading-presets.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"test"
],
"window": {
"tokens_est": 1360000,
"measured_tokens_est": 1344909,
"measured_bytes": 5177902,
"measured_at": "db30f1a10992df69d1253260a88065884a21937d"
"tokens_est": 1370000,
"measured_tokens_est": 1346832,
"measured_bytes": 5185304,
"measured_at": "a887092f79d847680e09a6f0a7a447bfb6f01749"
}
},
"entailment": {
Expand Down
93 changes: 77 additions & 16 deletions .abcd/development/brief/04-surfaces/01-ahoy.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ repo whose stamp says it is current.
| Verb | Bucket | Status |
|---|---|---|
| `connect` | — | shipped |
| `credential` | — | shipped |
| `doctor` | — | shipped |
| `install` | — | shipped |
| `remote apply` | gate | shipped |
Expand Down Expand Up @@ -122,10 +123,10 @@ authenticated identity: abcd never holds a token.

The setup takes the provider's name, its base URL, its first allowlist (every
model it may serve) and where its key lives. It verifies the provider with one
call to the first model listed and, only when that call succeeds, writes the key
and then the provider block, both under `~/.abcd/`: the key into the owner-only
`credentials.json`, the block (base URL, the key's name, the models) into
`config.json`. A failed verification writes nothing. Nothing reaches the
call to the first model listed and, only when that call succeeds, keeps the key
in the home chosen through the credential store's walkthrough and then writes
the provider block (base URL, the key's name, the models) into
`~/.abcd/config.json`. A failed verification writes nothing. Nothing reaches the
repository or the harness's settings. Every fault the configuration read would
refuse (a denylisted or malformed model, a base URL that is plain HTTP to
another machine, a provider already configured, a key name already holding a
Expand All @@ -140,13 +141,55 @@ the same reason the walkthrough is this sub-verb, which the person runs with the
key piped in, rather than a question the install pass asks: declining is not
running it, and changes nothing.

Of the three homes a key may live in, the setup builds the abcd-only one. The
environment-variable-or-external-tool home and the platform keychain arrive with
the credential store (itd-2609221017023290); asked for either, the setup refuses
naming it. A fourth answer, no key, sets up a local server that takes none.
The key lives in one of the credential store's three homes (below), and a
fourth answer, no key, sets up a local server that takes none.
No delegating verb sends a step to a configured provider until provider dispatch
lands (spc-2609251028149555), and both the board and the setup say so.

### The credential store and its walkthrough

Every external credential abcd holds goes through one store
(`internal/core/credential`, adr-2609221017021499): configuration names a
credential, and the value lives in the home the person chose for it, once, in
the credential walkthrough at `ahoy`. Without a name, the walkthrough lists
every credential an adapter reads (the site setup's hosting token, each
configured provider's key) with whether it is set and in which home, never the
value; with a name and no home, it explains what the credential unlocks and
what works without it, then the three homes, the keychain recommended in the
prose above them and never marked as an option. Given a home, it runs: the
reading adapter's own verification call (the provider's one short exchange,
the hosting provider's account read) with the value, and only when that
succeeds, the write. The provider setup runs the same walkthrough for a new
provider's key.

The three homes:

- `external` — a setup outside abcd: an environment variable, or a dotted
field of a tool's JSON configuration file under the home directory. The
store keeps only the pointer, in
`~/.abcd/credential-homes.json`, and follows it on every read. A file
pointer is refused, naming the link, when any directory between the home and
the tool's file is a symlink, wherever the link leads; the
environment-variable pointer stays open.
- `abcd` — the owner-only `~/.abcd/credentials.json`, which holds the value.
- `keychain` — the platform keychain under the service name `abcd` (the
Keychain through `/usr/bin/security` on macOS, the secret service through
`/usr/bin/secret-tool` on Linux), the value handed over on stdin, never in an
argument; `credential-homes.json` records only that the name lives there. A
platform with neither tool refuses this home and names the other two.

One reader, `credential.Store(home).Resolve(name)`, serves every adapter; a
name no home holds is a refusal naming the walkthrough, and the caller makes no
call. A test walks the production tree for any other read (a store file named,
a keychain command run, a secret-shaped environment variable read). One write,
`credential.Set`, is reached only through the walkthrough: it refuses the abcd
home when `~/.abcd` lies inside a git working tree, since that home alone keeps
a value there, a name another home already holds, and a different
value for a name already kept, and the secret scanner reads the index's bytes
before they are written, refusing any finding. A value is read from stdin only,
and never printed, logged or written to a record; a call's record names the
credential it used.

## What abcd manages — repos and `~/.abcd/`

abcd manages exactly one kind of folder, a **repository**, and keeps one
Expand Down Expand Up @@ -186,13 +229,17 @@ user-scope directory for machine-local state.
load-limits the load check's per-machine limits (stray-minutes,
extreme-load), read-only; abcd never creates it
(itd-2609231434459890)
credentials.json external credentials by name (a hosting token for
setting up a site, a provider's key), mode 0600;
only the provider setup writes it, one new name at
a time, never replacing a stored value, holding
.credentials.json.lock beside it across the read
and the write. The interim source the credential
store replaces (itd-2609221017023290)
credentials.json the credential store's abcd home: external
credentials by name (a hosting token, a provider's
key), mode 0600; only the walkthrough writes it,
one new name at a time, never replacing a stored
value, holding .credentials.json.lock beside it
across the read and the write (itd-2609221017023290)
credential-homes.json the credential store's index: which names live in
the keychain, and the pointer for each in the
external home; never a value, scanned before it is
written, mode 0600, .credential-homes.json.lock
beside it
rules.json the machine's rule conventions, the user layer
between the bundled domains and each repo's
.abcd/rules.json, read-only; abcd never creates it
Expand Down Expand Up @@ -722,7 +769,7 @@ _Generated from the command tree; a drift test fails `go test` when this appendi

### `abcd ahoy`

Sub-verbs: `abcd ahoy connect`, `abcd ahoy doctor`, `abcd ahoy install`, `abcd ahoy remote`, `abcd ahoy uninstall`.
Sub-verbs: `abcd ahoy connect`, `abcd ahoy credential`, `abcd ahoy doctor`, `abcd ahoy install`, `abcd ahoy remote`, `abcd ahoy uninstall`.

| Flag | Type |
|---|---|
Expand All @@ -738,10 +785,24 @@ Sub-verbs: none.
| Flag | Type |
|---|---|
| `--base-url` | string |
| `--env` | string |
| `--field` | string |
| `--file` | string |
| `--home` | string |
| `--key` | string |
| `--model` | stringArray |

### `abcd ahoy credential`

Sub-verbs: none.

| Flag | Type |
|---|---|
| `--env` | string |
| `--field` | string |
| `--file` | string |
| `--home` | string |

### `abcd ahoy doctor`

Sub-verbs: none.
Expand Down
10 changes: 6 additions & 4 deletions .abcd/development/brief/04-surfaces/22-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,17 @@ any secret step.

**The host.** With a hosting credential on this machine, the provider adapter
creates the host, routes the custom domain to it and reports the live address.
Without one, the stage stops and says what remains: store the credential and
re-run, or create the host in the provider's console.
Without one, the stage stops and says what remains: store the credential through the credential
walkthrough at `ahoy`, which verifies it with the provider's own account read
before it keeps it, and re-run, or create the host in the
provider's console.

Both remote stages write only after a confirmation that names each change, and
an unanswered run declines them. The deploy environment's secrets are never
set by abcd, because the value would pass through it: the verb reads which
secret names are present and prints the exact command for each one that is not.
The credential is read by name from the machine and never written into the
repository or the report. A second run over an unchanged repository and host
The credential is read by name through the credential store and never written
into the repository or the report, which names only the credential's name. A second run over an unchanged repository and host
writes nothing and says so. One provider ships, behind an adapter seam
([`05-internals/02-adapters.md`](../05-internals/02-adapters.md#hosting-providers)).

Expand Down
6 changes: 5 additions & 1 deletion .abcd/development/brief/04-surfaces/23-reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ copy there when the pair was sent elsewhere. A run assembled to any other direct
ingest, and bare `abcd reading` does not list it among the staged runs either,
because that listing reads the same one directory. A named directory is for a run whose
artefacts are being inspected or archived; a run meant to come back through
ingest lets the default run directory name itself.
ingest lets the default run directory name itself. The assembly says which it
made before the reading is commissioned: The result carries `ingestable`,
false for a run written to a named directory and for a dry run, and a run
written to a named directory renders an `ingest:` line naming the run directory
the ingest reads.

An output directory the include table can reach is refused when it is named,
because writing a run where the table reaches it commits the next run's
Expand Down
24 changes: 18 additions & 6 deletions .abcd/development/brief/05-internals/02-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ judged by the caller's output contract, the one the host sub-agent's payload is
judged by. The request is the host's brief in the protocol's two roles: the
agent's prompt as the system message, the verb's request as the user message.

The key is resolved by name through `internal/core/credential`, the one reader;
the adapter reads no file and no store of its own. The one environment it
The key is resolved by name through the credential store
(`internal/core/credential`, `Store(home).Resolve`), the one reader, from
whichever of its three homes the person chose; the adapter reads no file and no
store of its own, and a key that resolves to nothing refuses before any call,
naming `abcd ahoy credential <name>`. A call's record names the credential it
used, never the key. The one environment it
honours is the HTTP stack's: the standard proxy variables (`HTTPS_PROXY`,
`NO_PROXY`) and the platform's trust roots. An https call through a proxy is a
tunnel, so the key and the brief stay inside TLS, and a call to this machine is
Expand Down Expand Up @@ -125,10 +129,18 @@ domain to it and reports the address, after a read that writes nothing.
One provider ships: an assets-only Cloudflare Worker, the host abcd's own site
uses. A second is one implementation of the interface and one entry in the
site package's provider list; the verb does not change. The credential is
resolved by name through `internal/core/credential`, whose interim source is
`~/.abcd/credentials.json` until the credential store (itd-2609221017023290)
replaces it; the connected adapter holds it, and it is scrubbed from every host
message before one can reach an error.
resolved by name through the credential store (`internal/core/credential`),
the one reader; the connected adapter holds it, and it is scrubbed from every
host message before one can reach an error. The provider's `Verify`, the same
account read the host stage begins with, is the verification call the
credential walkthrough makes before it stores a token.

**Every external credential goes through one store** (adr-2609221017021499):
configuration names a credential, and its value lives in the home the person
chose once at `abcd ahoy credential`, the external setup, the abcd-only file or
the platform keychain ([`04-surfaces/01-ahoy.md`](../04-surfaces/01-ahoy.md)).
An adapter that reads a secret any other way is a defect, and a test walks the
production tree for one.

## Lifeboat source readers

Expand Down
47 changes: 34 additions & 13 deletions .abcd/development/brief/05-internals/03-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ The transcript corpus is a **sibling** user-scope store rather than a sub-tree o
the registry, at `~/.abcd/transcripts/<root-sha>/`, holding redacted records and a
staging area for raw transcripts awaiting redaction
([adr-2609091248201071](../../decisions/adrs/2609091248201071-the-transcript-corpus-is-a-sibling-store-that-creates-itself.md)).
Every machine-scoped store keyed on the root commit takes the full object name
as its `<root-sha>`: Forty hex digits under SHA-1, sixty-four under SHA-256, the
form `gitutil.RootCommit` returns and `gitutil.IsFullSHA` admits as a path
segment. An abbreviated key is a different directory, and no verb reads it.
One package owns its layout: `internal/core/history` declares both the user-scope
default and the opt-in per-repo location, and every resolver goes through it. The
rule is a convention with nothing behind it, and it already has one exception —
Expand Down Expand Up @@ -352,13 +356,13 @@ keys, each read beneath the repo-scope `.abcd/config.json`, and its one write
the provider block `ahoy connect` adds; every other config read resolves the
repo-scope `.abcd/config.json` alone), the load check's two limits in
`load-limits` (read-only and never created, itd-2609231434459890), the external
credentials adapters resolve by name in `credentials.json` (refused unless it is
a regular file this uid owns at mode 0600 that names each credential once, a
repeated key or a case twin included; `ahoy connect` adds one name at a time and
never replaces a stored value, holding the file's lock across the read and the
write as the provider block's write holds `config.json`'s, so concurrent setups
lose nothing — the interim source the credential store, itd-2609221017023290,
replaces), the
credentials adapters resolve by name through the credential store, whose abcd
home is `credentials.json` and whose index is `credential-homes.json` (each
refused unless it is a regular file this uid owns at mode 0600 that names each
credential once, a repeated key or a case twin included; the walkthrough adds
one name at a time and never replaces a stored value, holding the file's lock
across the read and the write as the provider block's write holds
`config.json`'s, so concurrent setups lose nothing), the
machine's rule conventions in `rules.json` (the user layer of the rules loader,
read-only and never created, itd-117 — see
[the rules layers](#the-rules-layers--bundled-user-repo) below), user-scope memory for personal cross-project knowledge (a later
Expand All @@ -373,11 +377,20 @@ the two are one list and must agree.
**A symlinked `~/.abcd` hosts nothing abcd trusts.** Every file in the user
scope whose contents abcd acts on — `rules.json`, `trusted-roots`,
`local-transcript-roots`, `path-entry`, `cache-attestation`, `config.json`,
`oracle-routing.json`, `statusline.json`, `load-limits` and `credentials.json` —
is refused when `~/.abcd`, or a directory below it on the way to the file, is a
`oracle-routing.json`, `statusline.json`, `load-limits`, `credentials.json` and
`credential-homes.json` — is refused when `~/.abcd`, or a directory below it on the way to the file, is a
symlink: the rule the rules loader states for `rules.json`, applied by one check
(`fsutil.HomeScopeLink`, read through `fsutil.ReadHomeDeclaration`) so it cannot
drift per file. A symlinked `~/.abcd` holding no such file reads as absent and
drift per file. The rule holds against a race as well as a layout: a reader or
writer opens `~/.abcd` and each level below it relative to the descriptor of the
level above (`fsutil.OpenHomeScope`, or `fsutil.EnsureHomeScope` to create the
missing levels), confirms each descriptor is the real directory its judgement
saw, and reaches the file only through that descriptor, so a process swapping
`~/.abcd` for a link between the check and the use is refused rather than
followed (iss-2609281310017733). The file's own guards are judged on that
descriptor too: the credential store's mode 0600 is judged on the fstat of the
file that is opened (`fsutil.ReadHomeDeclarationDenying`), never on its path,
so a store swapped for a group-readable file after any check is refused. A symlinked `~/.abcd` holding no such file reads as absent and
costs nothing. A file that is there behind the link is refused the way its reader
refuses any declaration that is not the caller's word: the rules load fails, a
declaration is ignored with a note, the path entry and the cache attestation
Expand All @@ -386,15 +399,23 @@ into those files — the credential and the provider block `ahoy connect` adds,
path entry, the routing table and the status-line setting `ahoy install` writes,
and the path entry and cache attestation `hooks/bootstrap.sh` writes — refuses
the link rather than writing through it, naming it and the repair: replace the
link with a real directory. The hook shims refuse a `path-entry` behind the link
link with a real directory. The path entry's removal on uninstall goes through
the same descriptor, so it removes nothing behind the link. A credential
setup refuses a symlinked `~/.abcd` in every home, the keychain and external
homes included, before it creates anything: the index, the value and both
locks are reached through the one walk that created and judged `~/.abcd`,
with the index's lock taken there and the abcd home's lock nested inside it.
An external home's pointer at a file under a symlinked directory (a
`~/.config` linked into a dotfiles repository) is refused the same way,
naming the link, and the file is read through the descriptor walk. The hook shims refuse a `path-entry` behind the link
too, before they read it. The home directory itself may be a link; only
`~/.abcd` and what lies under it are judged. The stores are not declarations:
`transcripts/`, `voyage/`, `lab/`, `inbox/` and `runs/` refuse a symlinked
level through their own create-then-prove seam (`fsutil.EnsureRealDir`), and the
`sources/` corpus is the caller's to place. The `history/` registry applies
both: it is neither read nor written behind a symlinked `~/.abcd` or
`~/.abcd/history`, and it is created through the same create-then-prove seam
(iss-2609281129171021). `ahoy install` skips the registration with a note naming
`~/.abcd/history`, and it is created, locked, read and written through
`fsutil.EnsureHomeScope`'s descriptor (iss-2609281129171021). `ahoy install` skips the registration with a note naming
the link and the repair, and the detector reports it as a diagnostic rather
than a gap install would try and fail to close.

Expand Down
Loading
Loading