Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
![Hullwork — from production errors to reviewable draft pull requests. Self-hosted, with your forge,
your error tracker, your model endpoint and a human gate on every merge.](images/banner.svg)
![Hullwork — verifies which of your tools' claims are actually true, before a person is asked.
Self-hosted, with your forge, your error tracker, your model endpoint and a human gate on every
merge.](images/banner.svg)

[![CI](https://github.com/easybytehub/hullwork/actions/workflows/ci.yml/badge.svg)](https://github.com/easybytehub/hullwork/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/easybytehub/hullwork?include_prereleases&label=release&color=blue)](https://github.com/easybytehub/hullwork/releases)
[![Image](https://img.shields.io/badge/ghcr.io-hullwork-blue?logo=docker&logoColor=white)](https://github.com/easybytehub/hullwork/pkgs/container/hullwork)
[![Python](https://img.shields.io/badge/python-3.12-blue?logo=python&logoColor=white)](pyproject.toml)
[![Licence: FSL-1.1-ALv2](https://img.shields.io/badge/licence-FSL--1.1--ALv2-blue)](LICENSE.md)

**Pre-alpha.** Both halves run end to end, five attempts have reached a draft pull request, and nobody
outside this project has installed it. What works, what does not, and what nobody has demonstrated are
all in **[docs/status.md](docs/status.md)** — read that before relying on any of this.
**Hullwork verifies which of the things your tools claim are actually true, before a person is
asked** — errors, dependencies, incidents. What that means, and everything that follows from it, is
**[what Hullwork is](docs/what-hullwork-is.md)**.

**Pre-alpha.** The error signal runs end to end, five attempts have reached a draft pull request, and
nobody outside this project has installed it. What works, what does not, and what nobody has
demonstrated are all in **[docs/status.md](docs/status.md)** — read that before relying on any of this.

> **What this repository is.** All of Hullwork, under a source-available licence that becomes
> Apache-2.0 two years after each release: the whole loop, uncapped, for as many projects as you like.
Expand All @@ -35,6 +40,28 @@ all in **[docs/status.md](docs/status.md)** — read that before relying on any

## What it does

Every signal it accepts arrives from a tool that **asserts something and proves nothing**. A tracker
says *something broke*. An advisory says *this version is vulnerable*. A scanner says *this could be
exploited*. None of them ran anything.

So Hullwork takes the claim into a sandbox, submits it to an oracle **it cannot influence**, and
returns a verdict with the run attached. The oracle changes per signal; the machine does not.

| what arrives | what it really says | what settles it here |
|---|---|---|
| a production error | *something broke* | a test that fails before the change and passes after |
| a dependency advisory | *this version is vulnerable* | your own suite, run against the upgrade |
| a static finding | *this could be exploited* | a test naming the hostile input |

**Only the first row is in a release.** The second is built and unreleased — it is in no image you
can pull, and this page will say so until it is. The third does not exist.

**"I could not verify this" is a first-class answer**, and on this repository's own numbers — 160
code scanning alerts, five real — it is the answer about nine times in ten. Delivering it honestly
is worth more than a fix, because nobody else delivers it at all.

### The path the error signal takes

![Two things arrive — a production error by webhook, and a human report through a normaliser. Hullwork
triages, deduplicates and assigns a risk lane. Green items are attempted unattended, amber wait for your
approval, red are never attempted at all; anything matching no lane is red. A green item goes to a coding
Expand Down Expand Up @@ -146,7 +173,7 @@ instance has ever run it, which is not support.
| | | |
|---|---|---|
| **Forges** | ✅ Forgejo · ✅ Gitea · ✅ GitHub | ⚠️ GitLab — the adapter is written, no instance has run it |
| **Error trackers** | ✅ GlitchTip, and anything posting a Sentry-compatible payload | ⚠️ Sentry's signed webhooks — the route is written and switched off, because verifying an HMAC means storing a secret reversibly and that decision has not been made |
| **Error trackers** | ✅ GlitchTip, and anything posting a Sentry-compatible payload | ⚠️ Sentry's own route is built and **not in a release yet**; when it lands it is authenticated by the token in the URL and its signature is not verified — which is what GlitchTip offers at all. [`SECURITY.md`](SECURITY.md) says what that does not cover |
| **Model endpoints** | ✅ anything speaking the Anthropic or OpenAI protocol family — Anthropic and OpenRouter both exercised | Your key, your endpoint. No provider is integrated and none is privileged |
| **Agents** | ✅ `claude-code`, exercised | Any container that takes a worktree and returns changed files qualifies: the agent is a contract, not an integration |
| **Your stack** | ✅ any Linux image with a shell, on this instance's architecture — you name the image your CI already uses | ❌ `distroless` and `scratch`, refused at registration rather than at attempt time |
Expand Down
12 changes: 12 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ Each of these is a property of the code, and the file that implements it explain
- **The two halves hold different credentials** (DR-0009). The half that answers webhooks — the one an
attacker can reach — cannot push, and refuses to start if it finds a credential that can. The half
that can push listens on nothing.
- **A webhook is authenticated by the token in its URL, and by nothing else.** That token is minted
once, shown once, and stored as a one-way hash; a wrong one is refused identically on every
provider's route, so the door cannot be used to confirm which tracker a project uses.

**What that does not cover, stated once for both providers** — Sentry's route is built and not in
a release yet (`docs/status.md`), and this is what it will be authenticated by when it lands.
GlitchTip cannot sign its webhooks —
no header, no secret, no setting — so the URL is the credential. Sentry *can* sign, and Hullwork
does not verify it: doing so requires holding Sentry's client secret in a form it can be read back
from, which is a storage decision this project has not made. So **anyone who obtains the URL can
post to it**, whichever tracker you use. Treat it as a secret: it is in your tracker's
configuration, and `hullwork projects rotate-secret` replaces it.

### What leaves the instance

Expand Down
23 changes: 23 additions & 0 deletions docs/hullwork-yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,29 @@ incomplete. That ordering is what makes this a translation rather than a treadmi
that describe an environment are about three and language-neutral, while ecosystems are about fifty
and keep arriving.

**And they compose, which is easy to miss and worth a paragraph.** *Bring your own image* and
*name an installer* read as a choice, and they are not one. `base` takes any image; `install` takes
**your own command** rather than only a name from the recipe list. So a third shape has always been
legal:

```yaml
runtime:
base: ghcr.io/acme/ci-base:2026.7 # yours, unchanged
install: "pip install -r requirements.txt" # your line, not a recipe of ours
dependencies: [requirements.txt] # the file your versions are pinned in
```

That is **one layer on top of the image you named**, not a rebuild from scratch — and it is what
makes a dependency upgrade measurable: an upgrade can only be checked against a suite that actually
runs it, which means the image has to be refreshed from the file that pins. With `install: none`
your image is used exactly as it comes, which is right for running tests and means a changed pin
changes nothing your suite would see.

Nothing was added to permit this. `install` has accepted an arbitrary command since DR-0007 was
built, and this paragraph exists because nothing said so — the example at the top of this file
shows `install: none`, and a reader with an image of their own could reasonably conclude the field
was not for them.

**The one frontier that will not move**: any Linux image with a shell, on this instance's
architecture. Both are checked when you register, and `distroless`/`scratch` are permanently out.

Expand Down
32 changes: 28 additions & 4 deletions docs/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ Pre-alpha. This page is the honest scope, kept apart from the README so it has r
so nothing in it has to be shortened to keep an introduction readable. It changes weekly; the date on
each claim is part of the claim.

> **What all of this is the state of.** Hullwork verifies which of the things your tools claim are
> actually true, before a person is asked — [what Hullwork is](what-hullwork-is.md). Three signals,
> three oracles, one mechanism. This page was accurate about the halves and silent about what they
> were halves of, so here is the row that was missing (item 181, 2026-08-09):
>
> | signal | oracle | state |
> |---|---|---|
> | a production error | a test that fails first and passes after | **released**, and everything below describes it |
> | a dependency advisory | your own suite, run against the upgrade | **built and unreleased** — in no image you can pull |
> | a static finding | a test naming the hostile input | does not exist |
>
> The second row is work items 172–180. Its command is deliberately **not named here**: the guard on
> this repository's documentation refused the sentence that named it, and it was right to — a command
> a reader cannot run is an invitation to type it and be told it does not exist. What is actionable
> is the state, and the state is *not in an image you can pull*.
>
> This page will keep saying so until a release contains it: documentation describes the released
> artefact, not the working tree (`CONTRIBUTING.md`, and item 165 records the three times that rule
> was learnt the hard way).

## What works today

A production error posted by your error tracker is authenticated, stored, normalised, deduplicated,
Expand Down Expand Up @@ -39,10 +59,14 @@ its suite runs against a blank one per phase. That path has been exercised by **

## What does not exist yet

- **Only the GlitchTip webhook route is enabled.** Sentry signs its webhooks properly and would be
verified by HMAC — that route is written and switched off, because verifying a signature means
storing a client secret in reversible form, which is a different storage decision from the one-way
hash used here and has not been made.
- **Only the GlitchTip webhook route is enabled in a release.** Sentry's is **built and unreleased**
as of 2026-08-09 (item 189): in the working tree it is authenticated by the token in the URL — the
same credential GlitchTip has, checked the same way, because GlitchTip cannot sign at all. Its
signature is deliberately not verified; that would need Sentry's client secret held in reversible
form, which is a storage decision this project has not made.

The image you can pull still answers `501` there, and this page will say so until a release
carries it — the same rule as the dependency signal above, and for the same reason.
- **Of the notification channels, only `none` and `console` deliver.** `telegram` and `email` parse in
the manifest and are refused at delivery, because a transport nobody has exercised is a transport
whose first real run happens in front of a user.
Expand Down
112 changes: 112 additions & 0 deletions docs/what-hullwork-is.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# What Hullwork is

> **The canonical statement of the product, fixed 2026-08-09 by the operator.** Every other document
> describes a part; this one says what the parts are parts of. When a document and this page
> disagree, this page is what was decided and the other one has not caught up yet.
>
> The reasoning behind it is DR-0017. This page is
> the short form, kept separate so it can be read in a minute and quoted without a link.

## In one sentence

**Hullwork takes the work off a developer that nobody wants to do — errors, dependencies,
incidents — by verifying, before a person is asked, which of the things their tools claim are
actually true.**

## Why it is not a list of features

Every signal Hullwork accepts arrives from a tool that **asserts something and proves nothing**:

| what arrives | from | what it really says | verified by |
|---|---|---|---|
| a production error | Sentry, GlitchTip | *something broke* | a test that reproduces it |
| a dependency advisory | Renovate, Dependabot, OSV | *this version is vulnerable* | the project's own suite |
| a static finding | CodeQL, Opengrep | *this could be exploited* | a test naming the hostile input |

Three signals, three oracles, **one mechanism**: take the claim into a sandbox, submit it to an
oracle the agent cannot influence, return a verdict with the run attached.

That is why these are not three features that happen to share a repository. The oracle changes; the
machine does not.

## The three properties everything else follows from

**No oracle is written by the agent to make itself look right.** A reproducing test must fail first
on untouched code; the project's suite belongs to the project; a hostile input has to be nameable.
Remove this and the verdicts are worth nothing.

**"I could not verify this" is a first-class answer.** On this repository's own numbers — 160 code
scanning alerts, five real — that is the answer roughly nine times in ten, and delivering it
honestly is worth more than a fix, because nobody else delivers it at all.

**What is measured is how much left a person's desk with evidence attached.** Not a success rate.
An instance computes its own, on its own code, from its first day.

## What that means against the tools it sits beside

None of them verify anything, and that is the whole position:

- **Renovate and Dependabot** open the pull request and let the reviewer find out. Their own
documented weakness is noise — *"here is every update, you decide"* — and it is structural: they
do not execute, so they cannot rank. Hullwork runs the suite first and hands over the ones that
pass.
- **Sentry Seer and Copilot Autofix** fix from unverified claims, with the same confidence for the
five that are real and the hundred and fifty-five that are not.
- **Reachability vendors** reduce the same noise with static analysis, which is another unverified
claim about an unverified claim. Executing is more expensive and it is not arguable.

## What Hullwork does not do, and will not

Merge by itself (constitution principle 1). Attempt a fix without a reproducing
test (DR-0003). Match a competitor's breadth for its own sake:
depth over coverage, because a verified verdict in five ecosystems is worth more than an unverified
one in ninety.

## Which documents have caught up, and when

Recorded so this page can be checked rather than believed. **Kept as a record rather than deleted**:
a page that could be checked and then cannot is a page that got weaker as it got more accurate.

All five were rewritten on **2026-08-09** (work item 181). None of them had been wrong; all of them
were partial, describing the product by one signal's pipeline where a reader was deciding what it is.

| | what it said before | what it says now |
|---|---|---|
| `pyproject.toml` | one signal's two endpoints, in the line PyPI shows | the one-sentence claim above |
| `README.md` | opened with the pipeline as *what it does* | opens with what is verified; the pipeline is named as the error signal's path |
| the roadmap | a segment and an obstacle order, with no product above them | says what is being roadmapped, and that the sections are not three products in a queue |
| the interface document | readers and their three questions | says what the surface is a surface *of*, and the constraint that follows |
| `docs/status.md` | accurate about the halves | says what they are halves of, per signal, with the state of each |

**The images caught up too**, on the same day and after the sentence above first said they would
not. `images/banner.svg` is the top of the README; `images/the-pipeline.svg` now says it is the path
*a production error* takes rather than what Hullwork does; and `images/social-preview.png` — the
card a link to this repository renders as, anywhere it is pasted — was the last one and the one with
the most reach.

That PNG had **no source in the repository**, which is what made it look unfixable. It has one now:
`images/social-preview.svg`, rasterised by `scripts/render-social-preview.sh`, both committed.

**What is guarded rather than remembered.**
`test_no_published_document_describes_the_product_by_its_plumbing` asserts that the old sentence
appears in no published document, in no packaging metadata, and in **no image source**. It failed
the day it was written, which is what made it a gate rather than a decoration, and it caught three
more instances afterwards — including the first line of the README, which is the banner's alt text
and the first thing a screen reader announces.

## What is released, and what is only built

Stated here because everything above describes a mechanism with three oracles, and a reader who
takes that as an inventory would be misled by this page rather than by the ones it corrects.

| signal | state, 2026-08-09 |
|---|---|
| a production error | **released** |
| a dependency advisory | **built and unreleased** — work items 172–180, absent from `published-surface.json`, which records `0.1.0a7` |
| a static finding | does not exist |

`CONTRIBUTING.md`'s rule is that documentation describes the released artefact rather than the
working tree, so no document may show that command until a release carries it — which is why it is
not named on this page either. The guard refused the first draft of this section for exactly that,
and naming a command a reader cannot run would invite them to type it and be told it does not
exist.
6 changes: 3 additions & 3 deletions hullwork.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Hullwork, watched by Hullwork.
#
# The lanes below are matched against the *error* — its title and culprit — not against the parts
# of the repository a work item would touch. Those are two different questions, and the worker contract
# answers the other one: what an unattended worker may change. This file answers what an incoming
# of the repository a fix would touch. Those are two different questions: the derived path policy
# (`hullwork projects lanes`) answers the second one. This file answers what an incoming
# production fault is allowed to become.
#
# Red wins over amber, amber over green, and anything matching nothing at all is red.
Expand Down Expand Up @@ -36,7 +36,7 @@ autofix:
- indexerror
amber:
# Anything about the shape of the database or what is installed: recoverable, but a fix is a
# migration or a dependency change, which the worker contract already keeps away from a worker.
# migration or a dependency change, which the derived path policy already keeps a human on.
- alembic
- migration
- integrityerror
Expand Down
2 changes: 1 addition & 1 deletion hullwork/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Hullwork — from production errors to reviewable pull requests, on your own infrastructure."""

__version__ = "0.1.0a7"
__version__ = "0.1.0a8"

__all__ = ["__version__"]
Loading
Loading