Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6a977d2
feat(hosting): add the hosting provider seam and its cloudflare adapter
REPPL Sep 25, 2026
38301e7
feat(credential): read an external credential by name from a machine-…
REPPL Sep 25, 2026
81e7eb5
feat(site): the closed page set with per-page switches in the composi…
REPPL Sep 25, 2026
593b598
refactor(scaffold): share the all-or-nothing writer as WriteFiles
REPPL Sep 25, 2026
982fc80
feat(site): `site setup` takes a managed repository's site to a live …
REPPL Sep 25, 2026
07440f6
fix(site): admit only this repository's own successful release run to…
REPPL Sep 25, 2026
39cebde
feat(site): wire `site setup` to the CLI and the plugin page
REPPL Sep 25, 2026
ae2f855
chore: capture iss-2609252311550527 — the brief names a timeline page…
REPPL Sep 25, 2026
501e089
docs(brief): the genealogy is folded into the dashboard, not a page o…
REPPL Sep 25, 2026
65d5a87
chore: resolve iss-2609252311550527 — the brief's timeline page claim
REPPL Sep 25, 2026
d35b757
chore: capture the site setup review's four findings
REPPL Sep 26, 2026
ab227d6
fix(site): never rewrite an existing deployment environment
REPPL Sep 26, 2026
484c59d
chore: resolve iss-2609260120383554 — existing environments are never…
REPPL Sep 26, 2026
7247b6c
fix(site): name the environment step when the forge stage is refused
REPPL Sep 26, 2026
a66e413
chore: resolve iss-2609260120380835 — a refused forge stage names the…
REPPL Sep 26, 2026
3e23f26
fix(site): fire the site workflow on every release path
REPPL Sep 26, 2026
a183ea4
chore: resolve iss-2609260120384106 — the site workflow fires on ever…
REPPL Sep 26, 2026
f8320cc
docs(intent): name the site setup's supply-chain posture and exit-1 o…
REPPL Sep 26, 2026
68ed11a
docs(site): say what a no-release completion redeploys after a rollback
REPPL Sep 26, 2026
51c5bbc
fix(site): align the setup text's status column to its longest status
REPPL Sep 26, 2026
c995ff7
chore: capture iss-2609260856423957 — site setup passes an environmen…
REPPL Sep 26, 2026
c13c3e9
fix(site): report an environment a broad custom rule admits as unrest…
REPPL Sep 26, 2026
aadebca
chore: resolve iss-2609260856423957 — site setup names a broad custom…
REPPL Sep 26, 2026
3dc97e2
chore: close spc-2609212141407459 and ship itd-2609061543533170
REPPL Sep 26, 2026
d4acbea
chore: capture review3-sitesetup notes (e) and (f)
REPPL Sep 26, 2026
c2c451e
chore: capture the site header's /docs/ link on a repo with no docs b…
REPPL Sep 26, 2026
8a50e70
Merge branch 'main' into feat/site-setup
REPPL Sep 26, 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
4 changes: 4 additions & 0 deletions .abcd/development/brief/04-surfaces/01-ahoy.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ 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), mode 0600, read-only; abcd never
creates it. The interim source the credential store
replaces (itd-2609221017023290)
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
65 changes: 64 additions & 1 deletion .abcd/development/brief/04-surfaces/22-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,58 @@ production renders from the tag, with the released bytes.
| Verb | Bucket | Status |
|---|---|---|
| `build` | — | shipped |
| `setup` | — | shipped |

## Setting up a managed repository's site

Setting up takes a repository abcd manages from the checkout to a live address
(itd-2609061543533170). It refuses a folder abcd does not manage, and works in
three stages, each reported whether or not the next one runs.

**The repository.** It writes the composition declaration, derived from the
recorded identity block and the documentation's front page (it refuses, naming
the step, where either is missing); the site's static inputs under `site-src/`,
copied from abcd's own; a workflow that renders the site from each published
release with abcd's checksum- and attestation-verified binary and deploys the
rendered archive from a second job; and the provider's host configuration. The
composition and the static inputs are the repository's own once they exist, so
a later run keeps them as they are. The workflow and the host configuration are
abcd's: a copy that differs refuses the whole run, with nothing written and
nothing remote attempted, unless the run is told to replace it.

**The forge.** Two deployment environments, one for the render and one for the
deploy, each admitting only the default branch and release tags, created
through the forge's API as the person running the verb. An environment that
already exists is never rewritten, because the forge's environment write
replaces its whole protection set, required reviewers included: one on named
rules and no rule beyond those two gains the rules it lacks, and one that
admits more, through its protection mode or a rule of its own such as branch
`*`, is reported `unrestricted`, with restricting it named as a step ahead of
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.

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
writes nothing and says so. One provider ships, behind an adapter seam
([`05-internals/02-adapters.md`](../05-internals/02-adapters.md#hosting-providers)).

## The page set

Every repository gets the same pages: the landing page, the record explorer,
one page per record, the relationship graph, the timeline, the glossary and the
status page. The composition declaration's `pages` block switches pages off and
cannot add one. A page switched off takes its navigation entry and every link to
it with it, and switching the explorer off takes every explorer page. The landing
page and the record pages carry the site, so a declaration switching either off
beneath the explorer is refused.

## The single-source rule

Expand Down Expand Up @@ -197,7 +249,7 @@ _Generated from the command tree; a drift test fails `go test` when this appendi

### `abcd site`

Sub-verbs: `abcd site build`.
Sub-verbs: `abcd site build`, `abcd site setup`.

| Flag | Type |
|---|---|
Expand All @@ -215,4 +267,15 @@ Sub-verbs: none.
| `--preview` | bool |
| `--version` | string |

### `abcd site setup`

Sub-verbs: none.

| Flag | Type |
|---|---|
| `--confirm` | bool |
| `--domain` | string |
| `--name` | string |
| `--yes` | bool |

<!-- surface-appendix:end -->
19 changes: 19 additions & 0 deletions .abcd/development/brief/05-internals/02-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ workspace's root-path field, and matches against `git rev-parse --show-toplevel`
`~/`-relative path normalisation. Source layout:
`~/Library/Application Support/RepoPrompt/Workspaces/<id>/workspace.json`.

## Hosting providers

`abcd site setup` routes a rendered site through a provider seam,
`internal/adapter/hosting`, which is not one of the five capability seams: no
dropped dependency stands behind it, and there is no native default, because a
site is served by somebody. An adapter has two halves. The repository half is
data — the deploy step, the secret names that step reads, and the host
configuration file it deploys from — rendered into files the verb writes. The
host half connects with the person's credential and creates the host, routes a
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.

## Lifeboat source readers

Disembark reads a repo's **own settled artefacts** into the lifeboat through a set
Expand Down
3 changes: 3 additions & 0 deletions .abcd/development/brief/05-internals/03-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ staged worktree store, the run state an autonomous run's sessions share
in the binary resolves the repo-scope `.abcd/config.json`, and no home-scope one
is read at all; the one machine setting read today is `load-limits`, the load
check's two limits, read-only and never created, itd-2609231434459890), the
external credentials adapters resolve by name in `credentials.json` (read-only,
never created, refused unless it is a regular file this uid owns at mode 0600 —
the interim source the credential store, itd-2609221017023290, replaces), 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 Down
15 changes: 13 additions & 2 deletions .abcd/development/brief/05-internals/10-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ intents:
the body verbatim, typed links phrased from that record's own side, and the
forge links to the file and to its commit history), `/record/graph/` (the
chart's stage and its list twin, driven by `site-src/record.js`, reading
`?focus=<id>`), `/record/timeline/` (the five-lane genealogy as one static SVG
emitted in Go), `/record/foundations/` (principles and disciplines as cards
`?focus=<id>`), the genealogy (one static SVG emitted in Go, folded shut into
the `/record/` dashboard rather than served as a page of its own),
`/record/foundations/` (principles and disciplines as cards
that list and link), `/contributors/` and `/references/`. The bibliography is
rendered by a stdlib CSL-JSON formatter and numbered identically to
`ACKNOWLEDGEMENTS.md`, with a build check that the two agree entry for entry.
Expand All @@ -79,6 +80,16 @@ intents:
audit is CI's optional, non-gating job; static and rendered gates are
complementary, and the audit's first run caught an overflow the static
gate cannot see.
- **`abcd site setup`** lays the site into a repository abcd manages: the
composition derived from the identity block and the documentation, the
static inputs seeded from abcd's own `site-src/` (byte copies, held equal by
a test), a render-on-release workflow whose action pins follow abcd's own
site workflow by test, and the provider's host configuration — written
through the launch scaffold's shared writer. The forge environments go
through `gh` as the invoking person, and the host through the hosting
adapter seam (`internal/adapter/hosting`, one provider). The manifest's
`pages` block is the closed page set's switches, resolved once and consulted
by the explorer wherever it adds a page, a tab or a link.
- **The generic/specific boundary** of the verb family is governed by the
itd-140 discipline: repo-agnostic input contract, genericity demonstrated
on a sparse second instance before it is claimed, working-tier ledger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ Ruled by the product thinker on 2026-09-21, in the interview that filed and plan
2. The provider is an adapter behind a seam, one shipped.
3. The same pages for every repository, opt-out per page.

Taken in the implementing lane (autonomous run A, 2026-09-26), within the rulings above:

4. **The credential's interim source (2026-09-26).** The credential store this intent resolves through is itd-2609221017023290, planned and not built. Until it lands, the hosting credential is read by name through one narrow interface (`internal/core/credential`, `Resolve(name)`) from one machine-scoped file, `~/.abcd/credentials.json`, refused unless it is a regular file this uid owns at mode 0600 or tighter. itd-2609221017023290 is the successor: it replaces the source behind the interface, and no reader changes.
5. **Secrets are the person's step (2026-09-26).** The forge encrypts an environment secret before it accepts it, and doing that here would add a dependency and pass the value through abcd. The verb reads which secret names the deploy environment holds and prints the exact `gh secret set` command for each missing one.
6. **Render on release, whoever made it (2026-09-26).** The workflow runs on `release: published`, on the `release` workflow completing on the default branch (a release created with the workflow's own token fires no release event), and on dispatch. It renders with abcd's latest release, checksum- and attestation-verified, so the file does not change when abcd does.

*Addendum (2026-09-26, the lane's security review, iss-2609260120384106).* As first written the workflow never fired by itself on a repository `launch scaffold` laid out: `auto-release` runs `release` by `workflow_call`, inside its own run, so no run named `release` completes; a hand-pushed tag's `release` run has the tag as its head branch, which the trigger's branch filter dropped; and the token-made release fires no `release: published`. The `workflow_run` entry therefore names both `release` and `auto-release` and filters no branch, and the render job's gate admits a head branch that is the default branch or starts with `v`, beside the fork and pull-request conditions it already held. `auto-release` completes on every push to the default branch, so a push that released nothing redeploys the latest published release: the bytes the site already serves, unless a dispatch rolled it back to an older tag, which that push replaces with the latest release again.
7. **The page set's edges (2026-09-26).** The status page is the record health page; the timeline is the genealogy the dashboard carries; the landing page and the record pages cannot be switched off beneath the explorer. The composition setup derives quotes the recorded identity block and composes the landing page from `docs/README.md`, and the static inputs are seeded as byte copies of abcd's own.
8. **The account is the token's (2026-09-26).** The provider account is the one the credential reaches; a token reaching none or several is refused rather than guessed at, so no account identifier is configured anywhere.
9. **The supply-chain posture and the exit-1 output, named (2026-09-26, the lane's security review).** Accepted as they stand, and named here so no reader has to infer them. The render job takes abcd's latest release, bound to `intentdriven/abcd`'s release workflow by an attestation check; the checksums ship in the same release, so they prove transfer integrity only. A compromised abcd release therefore runs in a job holding a read-only token and can deface the site, and cannot reach the deploy secrets, because the deploy job runs the provider's tool and not abcd. That tool, wrangler, is installed from npm at a named version with no integrity pin, as in abcd's own site workflow. The environments admit tags `v*` and setup checks no tag ruleset, so the right to push a tag is enough to run modified workflow content in `site`. A `--json` run that exits 1 prints two JSON documents, the result and then the CLI's error envelope; that is the repository-wide convention, and a strict consumer of this verb's `--json` reads both.

## Open Questions

_None open._
Expand All @@ -73,7 +84,8 @@ _None open._

## Audit Notes

_Empty. Populated by intent-auditor when intent moves to shipped/._
<!-- abcd-review: OWED receipt=rcp-070279698e5f -->
Fidelity review OWED (receipt rcp-070279698e5f).

## Grounds

Expand Down
35 changes: 35 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,41 @@
"moved_to": "abcd lint site",
"flags": []
},
{
"path": "abcd site setup",
"hidden": false,
"sentence": "Take the website from this checkout to a live address: Writes its files, and the forge and host changes once confirmed; refuses a folder abcd does not manage.",
"flags": [
{
"name": "confirm",
"shorthand": "",
"type": "bool",
"required": false,
"hidden": false
},
{
"name": "domain",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "name",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "yes",
"shorthand": "",
"type": "bool",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd spec",
"hidden": false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
schema_version: 1
id: "iss-2609260120380520"
slug: "the-machine-credential-file-internal-core-credential-abcd"
severity: "minor"
category: "security"
source: "impl-review"
found_during: "autonomous run A resumed 2026-09-25 (fix round, review of lane sitesetup)"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/credential/credential.go"
deferred_after: "v0.10.0"
deferral_reason: "deferred to the integration step (run A, 2026-09-26): the strict duplicate-key decoder jsonstrict lives on the unmerged lintB lane and copying it here would fork it; once lintB lands, credential.go reroutes its decode through jsonstrict and this record is resolved there"
---

The machine credential file (internal/core/credential, ~/.abcd/credentials.json) is decoded with plain json.Unmarshal, so a duplicate key silently takes the last value instead of being refused; the strict duplicate-key decoder (jsonstrict) lives on an unmerged lane, not on this base.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
schema_version: 1
id: "iss-2609260927214634"
slug: "site-setup-default-branch-not-from-forge"
severity: "minor"
category: "drift"
source: "review-followup"
found_during: "autonomous run A resumed 2026-09-25: review3-sitesetup note (f)"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/launch/scaffold/scaffold.go"
---

site setup compares the environment's deployment branch rules against a default branch derived by scaffold.deriveBranch (origin/HEAD, else the checked-out HEAD, else main), not the forge's default branch, so on a checkout with no origin/HEAD run from a feature branch a correctly restricted environment is told to remove its rule for main. The advice is wrong but nothing is written; the spec's 'default-branch name read from the forge' does not describe the code.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
schema_version: 1
id: "iss-2609260927217168"
slug: "site-setup-policy-read-unpaginated"
severity: "minor"
category: "bug"
source: "review-followup"
found_during: "autonomous run A resumed 2026-09-25: review3-sitesetup note (e)"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/site/forge.go"
---

site setup reads an environment's deployment branch policies with per_page=100 and no pagination (forge.go:112), so a broad branch rule past the 100th policy is invisible to the restriction check and the environment can read as restricted when it is not. Reaching it needs an admin who already put over 100 rules on one environment; the environments and secrets reads at :84 and :145 share the shape.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
schema_version: 1
id: "iss-2609260928152365"
slug: "site-header-links-docs-without-a-docs-build"
severity: "minor"
category: "bug"
source: "review-followup"
found_during: "autonomous run A resumed 2026-09-25: sitesetup lane report risk note"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/site/compose.go"
---

The site header always links /docs/ (compose.go:334), so a managed repository set up with site setup and no docs build serves a navigation link that 404s on every page. The renderer's page shapes were out of the sitesetup lane's scope; the link should follow whether a docs tree is rendered.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
schema_version: 1
id: "iss-2609252311550527"
slug: "the-site-internals-chapter-brief-05-internals-10-site-md"
severity: "minor"
category: "drift"
source: "user-observation"
found_during: "autonomous run A resumed 2026-09-25"
origin: researcher-authored
production_mode: hand-written
resolution: "The internals chapter now places the genealogy in the dashboard, and the dead timeline page code is removed."
impact: internal
resolved_by:
commit: "501e0896b04eb1233fe2e3eafbeb803b6f1e9fc8"
---

The site internals chapter (brief 05-internals/10-site.md) lists /record/timeline/ as a page the explorer serves, but the build never emits it: the genealogy renders folded into the /record/ dashboard, and timelinePage in internal/core/site/timeline.go is unreachable code.

## Grounds

- pursued: the chapter names only pages the build writes; shown wrong if a build emits a page the chapter omits or the chapter names one no build emits
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
schema_version: 1
id: "iss-2609260120380835"
slug: "when-abcd-site-setup-s-forge-stage-is-refused-gh-absent-or"
severity: "minor"
category: "security"
source: "impl-review"
found_during: "autonomous run A resumed 2026-09-25 (fix round, review of lane sitesetup)"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/site/setup.go"
resolution: "A refused forge stage prints the create-the-environments step ahead of the secret steps, as the unreachable path does. Impact internal: the verb is unreleased and ships with itd-2609061543533170."
impact: internal
resolved_by:
spec: "spc-2609212141407459"
commit: "7247b6cc"
---

When abcd site setup's forge stage is refused (gh absent or unauthenticated, or a forge call failing) it prints the gh secret set steps for the site environment but no step to create the environments, so a person following the steps sets the token and the first workflow run auto-creates site with no deployment policy, deployable from any branch.

## Grounds

- pursued: a person following the remaining steps of any refused or unreachable run protects the environment before setting a secret; a remaining list with a secret step and no environment step ahead of it would show it wrong
Loading
Loading