Say which forge is the worn path, and which is not #192

Merged
branderson merged 2 commits from forge-maturity-doc into development 2026-08-23 00:51:25 -07:00
Collaborator

Summary generated by LLM agent

Fixes #188. Stacked on #191 and #190, whose findings it describes; the change itself is one section of prose.

docs/providers.md listed gitea and github in one table with no caveat, which reads as two forges that got the same consideration. They did not. The doc already makes exactly this point for the harnesses, one section further down, and the forges need it more: the less-exercised one is the public forge an operator is more likely to already be on.

The new section says which forge this project is developed against, what has actually been run on the other, and what is known to differ rather than what might. Its evidence is the audit that produced #184 through #189: forge = github could not start a container at all under the only workflow that opens PRs, ten tests exercised the forge without reaching the entrypoint, and fixing that immediately turned up a merge block a single flag walked past. It names the two differences that remain open - #186's thinner review-feedback loop, and #187's --repo reaching past the sandboxed repository - and says what makes the second one moot, which is a per-repository fine-grained PAT.

It closes the way the harness section does: a recommendation for someone with no other reason to prefer one, and an honest statement that choosing the other means being the person who finds the next crack. Choosing github because that is where your repositories are is a good reason, and the section says so.

Testing

test/run-confined.sh green: audit 158, config 141, conversation 12, project 137, providers 79, proxy 32, remote 108, static 342, zero failures. test/providers.bats is what holds this file to documenting every manifest key and every shipped provider, so a doc change that contradicted the manifests would fail there.

Prose, so the real check is reading it. Nothing here asserts a capability that does not exist: every claim about GitHub's state is either from the issues filed today or from running the code.

**Summary generated by LLM agent** Fixes #188. Stacked on #191 and #190, whose findings it describes; the change itself is one section of prose. `docs/providers.md` listed `gitea` and `github` in one table with no caveat, which reads as two forges that got the same consideration. They did not. The doc already makes exactly this point for the harnesses, one section further down, and the forges need it more: the less-exercised one is the public forge an operator is more likely to already be on. The new section says which forge this project is developed against, what has actually been run on the other, and what is known to differ rather than what might. Its evidence is the audit that produced #184 through #189: `forge = github` could not start a container at all under the only workflow that opens PRs, ten tests exercised the forge without reaching the entrypoint, and fixing that immediately turned up a merge block a single flag walked past. It names the two differences that remain open - #186's thinner review-feedback loop, and #187's `--repo` reaching past the sandboxed repository - and says what makes the second one moot, which is a per-repository fine-grained PAT. It closes the way the harness section does: a recommendation for someone with no other reason to prefer one, and an honest statement that choosing the other means being the person who finds the next crack. Choosing `github` because that is where your repositories are is a good reason, and the section says so. ### Testing `test/run-confined.sh` green: audit 158, config 141, conversation 12, project 137, providers 79, proxy 32, remote 108, static 342, zero failures. `test/providers.bats` is what holds this file to documenting every manifest key and every shipped provider, so a doc change that contradicted the manifests would fail there. Prose, so the real check is reading it. Nothing here asserts a capability that does not exist: every claim about GitHub's state is either from the issues filed today or from running the code.
branderson changed target branch from main to development 2026-08-22 23:54:13 -07:00
branderson added 9 commits 2026-08-22 23:54:13 -07:00
A forge CLI has to sit behind the shadow whether or not this repo wrote
it: that is the fixed path forge-guard.sh execs the real command
through, and the entrypoint refuses to start when the command a workflow
blocks is not there. A forge whose CLI comes from a package had no way
to say so.

Declared in the manifest rather than special-cased in the image build,
so it stays true that reading a forge's manifest tells you everything
that forge puts on PATH.
forge = github with workflow = worktree-pr never got past the
entrypoint: worktree-pr is the only workflow that blocks a merge, the
block names gh, and the entrypoint refuses to start when the command it
is meant to shadow is not behind the shadow. That refusal is right - a
merge block nobody can enforce must not look enforced - but gh had no
way to get there, so github failed in the one mode its capabilities
advertise and worked only in the modes where it never opens a PR.
The invariant #184 broke: a forge naming a command nothing installs is
not a weaker guard, it is a container that does not start. Checked
against install-forge-bins.sh's real output rather than against the
manifest, because what matters is where a command ends up.

The two tests that changed had encoded the old fact - that github ships
nothing - and are updated to the new one rather than relaxed. gh is
stubbed in both, so neither says anything about the machine it runs on.
Stop a flag before the subcommand walking past the merge block
tests / confined (audit) (pull_request_target) Successful in 8m58s
tests / confined (config) (pull_request_target) Successful in 7m0s
tests / confined (conversation) (pull_request_target) Successful in 1m25s
tests / confined (project) (pull_request_target) Successful in 5m15s
tests / confined (providers) (pull_request_target) Successful in 2m19s
tests / confined (proxy) (pull_request_target) Successful in 1m14s
tests / confined (remote) (pull_request_target) Successful in 3m48s
tests / confined (shellcheck) (pull_request_target) Successful in 52s
tests / privileged (pull_request_target) Successful in 3m56s
tests / runner-env (confined) (pull_request_target) Successful in 36s
tests / runner-env (privileged) (pull_request_target) Successful in 38s
tests / confined (static) (pull_request_target) Failing after 4m18s
4b73b370a8
gh --repo owner/name pr merge N reached the real gh. The match was
anchored at the first non-flag argument, which for that command is
owner/name, so the guard gave up there and handed the merge over. That
is the ordinary way to name a PR from outside its checkout, and it
mattered the moment gh got behind the shadow at all.

The sequence is now looked for as a run of consecutive non-flag
arguments, anywhere in them. Knowing which flags take a separate value
would mean knowing each CLI, which is the coupling this guard exists to
avoid. The cost is that a command passing the blocked words as
consecutive values is refused; that is loud, and the other way round is
silent.

hooks/merge-guard.sh has the same hole and is not fixed here: it is
handed a command string rather than argv. It is also redundant now -
the shadow is linked whenever anything is blocked, so every session has
it, and only a harness with hooks has both. See #189.
The marker itself stays core's: a forge says which commands carry a body
a person did not write, and what kind of artifact each one is. What
dejima then says about agent authorship is one wording in one place,
rather than one per forge that happens to have a CLI of its own.
dejima wrote two of the three forge CLIs, so a disclosure that lives in
the tools is one that depends on which forge an operator picked. The
shadow is the one place they all pass through, and it already compares
argv, which is what finding a body argument needs.

Handles the four spellings both CLIs accept, including --body-file -,
where reading stdin here is the only chance to prepend to it at all.
Rewritten into a temp file rather than turned into --body, which would
put a whole body in argv against a 128KB per-argument limit. A command
with no body argument is passed through: there is nothing to mark.
Not conditional on the workflow, unlike the merge block: a block is a
policy about what a workflow permits, this is a statement about who
wrote the text, which holds for every session whatever it may do.

The entrypoint now shadows every CLI named rather than the single one
whose merge is blocked - a forge can have a CLI per artifact, and one
with nothing blocked can still have something to disclose.
Move the authorship marker out of the CLIs dejima happens to have written
tests / confined (proxy) (pull_request_target) Successful in 1m9s
tests / confined (conversation) (pull_request_target) Successful in 1m20s
tests / runner-env (confined) (pull_request_target) Successful in 32s
tests / confined (shellcheck) (pull_request_target) Successful in 46s
tests / confined (providers) (pull_request_target) Successful in 2m1s
tests / confined (remote) (pull_request_target) Successful in 3m24s
tests / privileged (pull_request_target) Successful in 3m45s
tests / confined (static) (pull_request_target) Failing after 4m5s
tests / runner-env (privileged) (pull_request_target) Successful in 38s
tests / confined (project) (pull_request_target) Successful in 4m55s
tests / confined (config) (pull_request_target) Successful in 6m40s
tests / confined (audit) (pull_request_target) Successful in 8m40s
d9a99a0285
The disclosure existed because this repo wrote two of the three forge
CLIs, not because anything had decided which artifacts get marked. So a
session under one forge opened PRs, filed issues and left comments that
said an agent wrote them, and under another it opened, filed and left
exactly the same things saying nothing - with the public forge being the
one that said nothing.

The wording is unchanged, and so is what gets marked. This only changes
where it happens, which is now somewhere both forges pass through.

Two behaviours follow from that and are intended. A command carrying no
body has none invented for it. And running a forge CLI on the host marks
nothing, because there the person running it wrote the body.
Say which forge is the worn path, and which is not
tests / confined (audit) (pull_request_target) Successful in 9m7s
tests / confined (config) (pull_request_target) Successful in 7m5s
tests / confined (conversation) (pull_request_target) Successful in 1m25s
tests / confined (project) (pull_request_target) Successful in 5m21s
tests / confined (providers) (pull_request_target) Successful in 2m20s
tests / confined (proxy) (pull_request_target) Successful in 1m14s
tests / confined (remote) (pull_request_target) Successful in 3m48s
tests / confined (shellcheck) (pull_request_target) Successful in 51s
tests / privileged (pull_request_target) Successful in 3m43s
tests / runner-env (confined) (pull_request_target) Successful in 37s
tests / runner-env (privileged) (pull_request_target) Successful in 37s
tests / confined (static) (pull_request_target) Failing after 4m26s
c941abb1a4
Two forges in one table reads as two forges that got the same
consideration. The harness section already makes this point for the
harnesses; the forges need it more, because the untested one is the
public one an operator is more likely to already be on.
dejima-agent added 3 commits 2026-08-23 00:41:40 -07:00
Stub a forge's system commands in the tests, so CI is not the machine
tests / confined (proxy) (pull_request_target) Successful in 1m15s
tests / confined (conversation) (pull_request_target) Successful in 1m22s
tests / runner-env (confined) (pull_request_target) Successful in 35s
tests / confined (shellcheck) (pull_request_target) Successful in 50s
tests / confined (providers) (pull_request_target) Successful in 2m19s
tests / confined (remote) (pull_request_target) Successful in 3m44s
tests / privileged (pull_request_target) Successful in 3m44s
tests / confined (static) (pull_request_target) Successful in 4m18s
tests / runner-env (privileged) (pull_request_target) Successful in 35s
tests / confined (project) (pull_request_target) Successful in 5m11s
tests / confined (config) (pull_request_target) Successful in 6m49s
tests / confined (audit) (pull_request_target) Successful in 8m45s
b31c6aa540
The tests that run install-forge-bins.sh took the machine's word for
whether a declared system: command exists. Inside a dejima container gh
does, on the CI runner it does not, so every one of these passed here
and failed there.

Two of the five expected a nonzero exit, which is worse than the one
that failed: they would have passed on the runner for entirely the
wrong reason. Both now check what the failure actually says.

The stub names come from the manifests rather than a list here, so a
forge declaring another one later needs no change to this.
Merge branch 'forge-github-shadow' into forge-disclosure
tests / confined (proxy) (pull_request_target) Successful in 1m12s
tests / confined (conversation) (pull_request_target) Successful in 1m24s
tests / runner-env (confined) (pull_request_target) Successful in 36s
tests / confined (shellcheck) (pull_request_target) Successful in 55s
tests / confined (providers) (pull_request_target) Successful in 2m17s
tests / privileged (pull_request_target) Successful in 3m43s
tests / confined (remote) (pull_request_target) Successful in 3m44s
tests / runner-env (privileged) (pull_request_target) Successful in 39s
tests / confined (project) (pull_request_target) Successful in 5m18s
tests / confined (static) (pull_request_target) Successful in 4m35s
tests / confined (config) (pull_request_target) Successful in 7m2s
tests / confined (audit) (pull_request_target) Successful in 8m52s
8eda94f997
Merge branch 'forge-disclosure' into forge-maturity-doc
tests / confined (proxy) (pull_request_target) Successful in 1m13s
tests / confined (conversation) (pull_request_target) Successful in 1m25s
tests / privileged (pull_request_target) Successful in 3m36s
tests / confined (remote) (pull_request_target) Successful in 3m33s
tests / confined (shellcheck) (pull_request_target) Successful in 50s
tests / confined (providers) (pull_request_target) Successful in 2m14s
tests / runner-env (confined) (pull_request_target) Successful in 35s
tests / runner-env (privileged) (pull_request_target) Successful in 36s
tests / confined (project) (pull_request_target) Successful in 5m2s
tests / confined (static) (pull_request_target) Successful in 4m20s
tests / confined (config) (pull_request_target) Successful in 6m48s
tests / confined (audit) (pull_request_target) Successful in 8m57s
6f9485733a
branderson merged commit 3e5fc982bb into development 2026-08-23 00:51:25 -07:00
Sign in to join this conversation.