Skip to content

feat: site setup takes a managed repository's site to a live address - #722

Merged
REPPL merged 27 commits into
mainfrom
feat/site-setup
Sep 26, 2026
Merged

REPPL merged 27 commits into
mainfrom
feat/site-setup

Conversation

@REPPL

@REPPL REPPL commented Sep 26, 2026

Copy link
Copy Markdown
Collaborator

With this change, a managed repository can take its site to a live address with one verb: abcd site setup checks the forge, the hosting account and the deploy environment, writes a site workflow that renders with abcd's latest verified release, and names every step it cannot take itself.

  • site setup writes the site workflow and its page switches all-or-nothing, and never rewrites an existing deployment environment. An environment that a broad custom branch rule admits is reported unrestricted, gets no forge write, and is told to restrict before any secret step.
  • The workflow fires on every release path (a published release, the default branch's release run, and a dispatch), and renders only this repository's own successful release run.
  • Deploy secrets are never set by abcd: setup reads the secret names and prints the gh secret set … --env site line to run.
  • A hosting provider seam with a Cloudflare adapter, and an external credential read by name from a machine-scoped store (an interim file source, 0600, owned, never through a symlink).
  • The closed page set carries per-page switches in the composition; the brief no longer names a timeline page the build never emits.

Nothing here has run against the real hosting service or a live Actions run: every provider and forge call is exercised against in-process fakes.

Review: Fable 5.1, three rounds including a security review (criterion 6). Round 1 FIX FIRST (two mediums: an environment write that dropped required reviewers, and a workflow that never fired on a scaffolded repo), fixed in round 2; round 2 SHIP with four notes, fixed in round 3; round 3 grew new trust-path behaviour (the broad-rule check), so it was verified: SHIP. Three follow-ups are captured open: an unpaginated policy read, a default branch derived locally rather than read from the forge, and a header link to /docs/ on a repo with no docs build.

Delivers: itd-2609061543533170
Resolves: iss-2609252311550527
Resolves: iss-2609260120383554
Resolves: iss-2609260120380835
Resolves: iss-2609260120384106
Resolves: iss-2609260856423957
Refs: iss-2609260120380520, iss-2609260927217168, iss-2609260927214634, iss-2609260928152365
Assisted-by: Claude:claude-opus-5-5

The seam `abcd site setup` routes a rendered site through
(itd-2609061543533170, spc-2609212141407459 scope 2): an Adapter carries the
repository half as data (the deploy secrets, the deploy step, the host
configuration file) and Connect returns the host half (inspect, create,
route, address).

One provider ships: an assets-only Cloudflare Worker, the host abcd's own
site uses. Its calls are written against the published v4 API reference
and exercised only against cloudflaretest, an in-process fake that can fail
every route it answers. The token lives inside the connected client, is
scrubbed from every host message before it can reach an error, and
redirects are refused rather than followed.

Assisted-by: Claude:claude-opus-5-5
…scoped store

The one reader adapters resolve a credential through. The interim source
is ~/.abcd/credentials.json, a JSON object of names to values, refused
loudly unless it is a regular file owned by the caller at mode 0600 or
tighter. The credential store proper (itd-2609221017023290) replaces the
source behind the same interface; no reader changes.

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

The manifest gains a `pages` block (itd-2609061543533170 criterion 4): the
landing page, the explorer, the record pages, the graph, the timeline, the
glossary and the status page render for every repository, and a switch can
only take one away. Switching a page off removes the page, its navigation
entry and every link the renderer drew to it; the explorer's switch takes
every explorer page with it. The landing page and the record pages carry
the site, so switching either off beneath the explorer is refused, and a
key outside the set is refused as any unknown manifest key is.

The manifest also gains the `hosting` block `abcd site setup` reads
(provider, host name, optional domain), validated against the shipped
provider list; the build never reads it.

Assisted-by: Claude:claude-opus-5-5
`abcd site setup` lays machinery into a managed repository the way the
release scaffold does (spc-2609212141407459: the verb reuses the launch
scaffold's workflow writer), so the two-pass classify-then-write logic moves
into WriteFiles, which Scaffold now calls unchanged. It gains one
disposition: a seed file the repository owns once it exists is kept,
never refused and never overwritten.

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

The verb's core (itd-2609061543533170, spc-2609212141407459 scope 1),
in three stages reported one by one:

- the repository: the composition derived from the identity block and the
  documentation, the site's static inputs seeded from abcd's own, the
  render-on-release-then-deploy workflow, and the provider's host
  configuration, written through the scaffold's writer so a second run
  writes nothing and drifted machinery refuses the whole run;
- the forge: the site-render and site environments, restricted to the
  default branch and release tags, created through the GitHub API as the
  person who invoked the verb, only after a confirmation naming the changes
  (adr-44);
- the host: with a hosting credential on this machine, the adapter creates
  and routes the host and reports the address, again only once confirmed;
  without one the stage stops and the exact remaining step is printed.

No secret value passes through abcd: the deploy environment's secrets are
checked by name and their `gh secret set` commands printed. The forge and
the provider are exercised against in-process fakes that fail every call.

Assisted-by: Claude:claude-opus-5-5
… the render

A workflow_run fires for every run of the release workflow, and its branch
filter matches a head branch a fork can name. The render job now requires a
successful run that is not a pull request's and whose head repository is this
one. Nothing from the triggering run was used, so the gate removes noise
redeploys rather than an injection path.

Assisted-by: Claude:claude-opus-5-5
`abcd site setup` is reachable from the CLI (flags --name, --domain,
--confirm, --yes) and documented on commands/site.md, with its sentence in
the surface manifest. An unanswered run declines both remote stages and
exits 1, as the remote apply does. The brief's site surface chapter states
the verb and the page set, the internals chapters the hosting seam and the
interim credential file, and the intent's Decisions record what the lane
settled within the rulings, the credential's interim source among them with
itd-2609221017023290 named as its successor. ACKNOWLEDGEMENTS.md credits
the provider the adapter targets.

Assisted-by: Claude:claude-opus-5-5
… the build never emits

Refs: iss-2609252311550527
Assisted-by: Claude:claude-opus-5-5
…f its own

The site internals chapter listed /record/timeline/ among the explorer's
pages; the build never emitted it. The chapter now says where the genealogy
renders, and the unreachable timelinePage and its route constant are gone.

Refs: iss-2609252311550527
Assisted-by: Claude:claude-opus-5-5
Resolves: iss-2609252311550527
Assisted-by: Claude:claude-opus-5-5
The lane review of `abcd site setup` (criterion 6 of spc-2609212141407459)
named two medium and two low findings. Each is captured before it is
fixed. The duplicate-key finding in the credential file is deferred to
the integration step: the strict decoder it needs lives on an unmerged
lane, and copying it here would fork it.

Refs: iss-2609260120383554
Refs: iss-2609260120384106
Refs: iss-2609260120380835
Refs: iss-2609260120380520
Assisted-by: Claude:claude-opus-5-5
The forge's environment write (PUT /repos/{r}/environments/{env})
replaces the environment's whole protection set, and setup sent only a
deployment policy through it. A `site` environment a person had put
required reviewers or a wait timer on lost them after a confirmation
that named only a branch restriction.

Setup now writes that endpoint only for an ABSENT environment. An
existing one already on custom policies gains the policies it lacks
through their own endpoint, which touches nothing else. An existing one
that admits more refs is left as it is, reported `unrestricted`, and
restricting it is a remaining step printed ahead of any secret step.
Resending the listed protection rules was not taken: the listing and
the write do not carry the same fields, so it cannot be shown lossless
offline.

A run that leaves an environment unrestricted and writes nothing else
still reports no_change: the overall status says what was written, and
the step says what is left.

The forge fake models the replacement (a put clears the environment's
protection rules), and TestAnExistingEnvironmentIsNeverRewritten was
watched fail against the old plan (a `put site`, the reviewers lost, no
step) before the change.

Refs: iss-2609260120383554
Assisted-by: Claude:claude-opus-5-5
… rewritten

Resolves: iss-2609260120383554
Assisted-by: Claude:claude-opus-5-5
When the forge stage was refused (gh absent or unauthenticated, or a
forge call failing) setup printed the `gh secret set … --env site`
steps and no step to create the environments. A person following the
steps set the token, and the committed workflow's first run then
auto-created `site` with no deployment policy, deployable by any
write-access dispatch from any branch.

The refused path now prints the same "create the forge environments"
step the unreachable path does, ahead of the secret steps, and the step
says to do it before setting any deploy secret.
TestEveryForgeFailureIsReportedAndStopsTheRemoteWrites was extended and
watched fail for all four forge calls before the change.

Refs: iss-2609260120380835
Assisted-by: Claude:claude-opus-5-5
… environment step

Resolves: iss-2609260120380835
Assisted-by: Claude:claude-opus-5-5
On a repository `launch scaffold` laid out, the workflow `abcd site
setup` writes never fired by itself. auto-release runs release.yml by
workflow_call, inside its own run, so no run named `release` completes
for workflow_run; a hand-pushed tag's `release` run has the tag as its
head branch, which `branches: [<default>]` dropped; and a release made
with the workflow's GITHUB_TOKEN fires no release:published. Only a
hand dispatch deployed, while the verb said the site deploys on the
next published release.

The workflow_run entry now 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 (TestAWorkflowRunFromAFork-
NeverRenders still covers those). auto-release completes on every push
to the default branch, so a push that released nothing redeploys the
latest release over itself; the header says so. The intent's Decision 6
carries a dated addendum, and the verb's closing note names both
publishers.

TestTheWorkflowFiresOnEveryReleasePath renders the workflow for a
non-default branch name, reads the trigger and the gate, and holds the
listened-for names to the scaffolded workflows' own; it was watched
fail on the old gate before the change.

Refs: iss-2609260120384106
Assisted-by: Claude:claude-opus-5-5
…y release path

Resolves: iss-2609260120384106
Assisted-by: Claude:claude-opus-5-5
…utput

The lane's security review (item 5, informational) asked that the
posture it accepted be named in the intent rather than left to be
inferred: the render takes abcd's latest release, attestation-bound; a
compromised release can deface the site but not reach the deploy
secrets; wrangler has a named version and no integrity pin; `v*` tags
are admitted with no tag ruleset checked; and a `--json` exit-1 run
prints the result and then the error envelope.

Assisted-by: Claude:claude-opus-5-5
The generated site workflow's header said a release-workflow completion
that released nothing "redeploys the latest release over itself, which
changes nothing". After a deliberate rollback dispatch (`tag: vOLD`) the
next such completion puts the latest release back, so the claim was
untrue in that case. The header and Decision 6's addendum, which carried
the same sentence, now say so. The workflow itself is unchanged.

Assisted-by: Claude:claude-opus-5-5
`site setup`'s text render padded every file and environment status to
eight characters, so `unrestricted` (twelve), `unreachable` and
`not_reached` (eleven) pushed their environment's name out of the column
and left its change lines under the status instead. The column is now as
wide as the longest status in the result, never narrower than eight, and
an environment's change lines are indented to sit under its name.

Assisted-by: Claude:claude-opus-5-5
…t a broad custom rule admits

Review 2 of the site setup lane noted that an existing environment on
custom deployment policies is judged only by the rules it lacks, never
by the rules it holds, so one that also admits `*` is reported current.

Refs: iss-2609260856423957
Assisted-by: Claude:claude-opus-5-5
…ricted

An existing environment on custom deployment policies was judged only by
the two rules setup needs, never by the rules it already held, so one
that also admitted branch `*` was reported current (or gained the tag
rule and was reported written) with no step, while it admitted every
branch to the deploy environment's secrets. A rule beyond those two now
makes it `unrestricted`, with a step that names the rules to remove;
setup writes nothing to it, since removing a rule a person put there is
theirs to do. Any extra rule counts, including one narrower than `v*`:
the step is conservative rather than a glob comparison.

The command page and the brief's surface chapter say so.

Refs: iss-2609260856423957
Assisted-by: Claude:claude-opus-5-5
… rule

Resolves: iss-2609260856423957
Assisted-by: Claude:claude-opus-5-5
`abcd site setup` lays out a managed repository's release-rendered site:
the repository half, the forge's two deployment environments and the
host, with every step it did not take named. The intent declares its
impact (additive), so the close stamps none.

Delivers: itd-2609061543533170
Assisted-by: Claude:claude-opus-5-5
The deployment-policy read is unpaginated past 100 rules, and the
default branch the check compares against is derived locally rather
than read from the forge.

Refs: iss-2609260927217168, iss-2609260927214634
Assisted-by: Claude:claude-opus-5-5
…uild

Refs: iss-2609260928152365

Assisted-by: Claude:claude-opus-5-5
scaffold.go: kept this branch's shared WriteFiles and carried main's
CIChecks report field and its fourth scaffolded file (the reviews-charter
check) into the planned list.

Assisted-by: Claude:claude-opus-5-5
@REPPL
REPPL enabled auto-merge September 26, 2026 09:46
@REPPL
REPPL added this pull request to the merge queue Sep 26, 2026
Merged via the queue into main with commit a4d0980 Sep 26, 2026
13 checks passed
@REPPL
REPPL deleted the feat/site-setup branch September 26, 2026 10:33
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