Skip to content

Releases: DeDuva/adp

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 16:03
d982600

The first five minutes, walked from a clean clone. A first-run evaluation on 2026-09-01 ran
every command the README, the published site and the tooling tell a new user to run, in that order,
against a fresh git clone. The product held up — the whole suite, 772 tests with zero skipped plus
conformance and acceptance, went green in 2m39s on the first attempt. The path to it did not.

make demo failed eight seconds into a fresh clone. sh: 1: tsx: not found, reported as
demo failed: migrations failed — naming neither the cause nor the remedy, on the one command the
README, the landing page and make help all point a visitor at. It never installed the server's
dependencies, and the CI job that exists to keep this path working ran npm ci --prefix server
immediately before it: the only caller of make demo that never walked a visitor's path was its
own gate. The install moved into the demo, that step came out of CI, and the failure this
repository already knew as sh: 1: vitest: not found is now closed at the front door too.

The published site advertised contract 0.5.0 while the server served 0.6.0 — three mastheads
and one paragraph, deployed straight from the tree on every push to main. That was one defect
wearing three hats: recorder was stamped 0.5.0 in the release that is about the recorder, and
deploy/docker-compose.yml deployed v0.3.0 under a comment promising the released image.
check-release.sh reported "consistent" through all three, because it watched four surfaces where
there were seven. It watches the site, the compose tag and the recorder now, and each new check was
tested by breaking the thing it guards.

make local ended by telling you to run a command this project documents as unsupported.
gh repo create resolves the owner through GET /api/v3/users/{owner} before it creates anything,
that route is not served, and the README's compatibility table has said so all along — so the last
line the script printed was a guaranteed 404, as the first thing anyone did with a new instance. It
prints gh api -X POST /repos/{org} -f name=widget now. It also advertised a supervision UI that
returned 404: main.ts decides whether to serve /ui/* once, at boot, from whether
server/web/dist exists, and make deps never built it. make local builds the UI when it is
missing, and says so plainly when it is not there rather than printing a dead link.

adp init left a repository in a state adp connect refused. #153 created the repo on the
server and added no git remote; #154 then declined with "no git remote points at ". The two
commands this release is about are meant to run back to back, and on the native path the second
could not follow the first — while adp watch, which init recommended next, truthfully answered
"no open pull request yet" about a repository nothing had ever been pushed to. init adds the
remote (adp, never over an existing name — repointing someone's origin is how an upstream gets
lost), names the push in its Next block, and says how git authenticates here, because
"Username for https://…" is not a question whose answer is obvious.

Its failure mode is answerable now too. With no --repo, init infers the owner from the parent
directory name
, which is a reasonable default and was a terrible thing to fail on silently: it
reported Not a member of this organization (HTTP 403) and stopped, naming no organization, no
source for the name, and not the flag that overrides it. The house standard was already set twice —
adp connect's refusal names both remedies, and the land-policy 422 names the command that
satisfies each unmet requirement.

The README's promise about 404s held for one of the eleven families it lists. "Unimplemented
REST endpoints return 404 with a body naming the ADP equivalent. A broken call that explains itself
costs an agent one turn" — kept by the Actions passthrough alone, while search, releases, users,
branch protection and the rest returned Fastify's stock body, which names nothing. The worst case
was /users/{owner}: the route whose absence breaks gh repo create, whose replacement the README
knows and whose 404 did not say it.

It is a not-found handler and not a set of routes, which is the only shape that could work here:
spec-coverage.test.ts fails when the server serves a route spec/openapi.yaml does not describe,
and it is right to — the spec is a published contract and a downstream consumer generates its client
from it. Eleven families of stubs would have meant eleven families of spec entries for endpoints
that do nothing, or a hole in the guard. A not-found handler serves no route, appears in no route
table, and changes no generated client. The rule for adding an entry is in the file: name the ADP
capability that replaces it, or do not add one.

adp-recorder declared a bin that could not execute. dist/main.js had no shebang, so the
symlink npm link and npm i -g create ran JavaScript as sh and hung with no output. Nothing had
caught it because nothing documented installing it — the README showed node dist/index.js once and
then wrote adp … and adp-recorder … about twenty times, with no bridge between the two. Both are
npm link now, said where the build is, and cli/, runner/ and recorder/ have README files
that point at the reference rather than copying it. The runner names how it is actually
started — adp runner up --here — because it declares no bin at all.

Two blemishes at the demo's own ending. The evidence bundle rendered gates[0], and the bundle
is sorted newest-first with sbom generated at merge — so the closing artifact deterministically
showed the one gate the visitor had nothing to do with, and hid the test result whose absence
caused the 422 three steps earlier. It prints every gate now. And the handoff section printed
$ adp-recorder wrap … -- claude --output-format stream-json for a stream that is replayed from a
fixture; the recorder, the readers, the session lifecycle and the chain are all real, and the
comments in demo.sh were scrupulous about which is which, but comments are not what a visitor
reads and a $ prompt is a claim. Both lines are labelled.

Smaller, and each a published claim that was not true: make doctor failed on a fresh clone over a
missing dependency tree rather than over either prerequisite it is advertised as checking (a warning
now — it is the one command that is only ever asked a question); the demo announced "a five-minute
test drive" for a run that takes well under one; make local-status rendered "not running" as a
bare make: *** Error 1; and adp connect ended by asking for a .claude/settings.json edit whose
shape it never showed.

Also cleared: fast-uri (high) and hono (moderate, via the MCP SDK) in the server's production
dependencies, both by a semver-compatible npm audit fix. The critical that npm ci prints in
every tree is vitest/vite and is dev-only. One production advisory is deliberately left:
drizzle-orm <0.45.2, GHSA-gpj5-g38j-94v9, whose fix is a breaking upgrade across nine minors of
the data layer. The exploit path is an identifier built from untrusted input, and this codebase
builds none — all seven sql.raw call sites take a compile-time literal, and every user-supplied
value on those paths is bound as a parameter. It is tracked in PLAN.md as 0-19 with that analysis,
so it is picked up on the merits rather than waved through on an advisory ID inside a release branch.

make demo ends on the handoff: one task, two harnesses, one continuous signed
history (#160).
D2 is the capability that most distinguishes ADP from a forge with
signed commits, and the argument for it was entirely structural — the endpoints existed,
resumed_from_session_id was self-referencing so a chain across three harnesses was
walkable without a join table, and none of it had been shown working.

Nothing calls checkpoint or resume. Two streams go through adp-recorder wrap,
one shaped like Claude Code's --output-format stream-json and one like Codex's
exec --json, and --continue is the only instruction — it exists because no stream can
signal a handoff the other harness has never heard of. That is the ordering note #160
made and the reason it waited for #151: a demo driven by a script calling the API on the
harnesses' behalf is evidence that a script can call two endpoints, not evidence of
portability.

The first harness exits non-zero mid-task, which is what a handoff looks like from
outside, and #151 turns that into a suspended session with a checkpoint to resume
from rather than one that looks finished. The op log reads
session.start → session.checkpoint → session.suspend → workspace.create → session.resume → session.checkpoint → session.close.

Both halves of "one continuous signed history" are checked, not claimed. The lineage
is walked back from the session that finished the work, and every session in the chain is
verified per session — recomputed from the stored rows by that request — using the
session-scoped endpoint #152 added. These sessions belong to no run: a developer handing
work between their own harnesses is not an orchestrated fan-out, and until that endpoint
existed there was no way to verify them at all.

It lives in make demo rather than a script of its own, because that script's port
picking, process-group kill and log-line readiness checks each exist because something
failed in a way that cost a debugging session — and a third copy of them would reacquire
the same bugs.

adp init — one command, against a repository that already exists (#153). The
strongest brake on adoption was never scepticism about signed evidence; it is that moving
repositories is unthinkable. ADP already had the answer — mirror mode makes it additive to
a repo that stays on GitHub — and offered it as an operator feature reached through
`adp repo...

Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 23:09
6373423

Two contract moves reach a tag here. 0.4.0 was bumped in-tree when M4-5
landed and never tagged, released, or published as an image, so no consumer
could pin it; it ships inside this release rather than being retro-tagged, and
its entry is kept below verbatim. Both moves are additive — a client generated
against 0.3.0 is unaffected by either.

0.5.0 — the org storage quota

M4-3: the per-org storage quota. orgs.max_storage_bytes — the last of
the four org quotas, and the one the milestone never built. It was deferred to
M4-8 ("it needs the object store to meter against") while M4-8's own sizing was
deferred to it ("depends on M4-3's storage-quota shape existing to bound it"),
a deadlock that held for the whole milestone with nothing bounding how much one
org could write. It is broken from this side: the meter counts the bytes that
exist today — an org's rows in Postgres plus its git repos on disk — and gains
the object store as a third term the day there is one, without the ceiling's
shape changing.

Additive: max_storage_bytes joins the PATCH /api/adp/orgs/{orgId} body and
the quotas object on org detail. The latter is a wider shape than the three
counting quotas (OrgStorageQuota), because a measured used can be null and
carries the measured_at that says how stale it is.

Three behaviors worth knowing before setting a ceiling. The meter is
sampled, not synchronous
— a ten-minute tick
(STORAGE_METER_INTERVAL_MS), because measuring is a full scan of the org's
rows in ten tables and cannot live on the trajectory hot path; that interval is
exactly the overshoot an org can achieve past its ceiling. An org that has
never been metered is under quota, not over
— storage_bytes_used is null
until the first tick, and failing closed on null would refuse every write on
every instance for one interval after every restart. And gate-job completion
is never refused
: the gate has already run, so refusing would wedge the job
until the reaper and leave its signed evidence unwritten, blocking any commit
under a gates_green policy — a storage quota turning into a land outage.
Instead the completion lands and its logs are dropped, with the drop recorded
on the operation so the empty logs reads as a decision rather than data loss.

Reads are never refused. An org at its ceiling can still clone what it already
has, because a quota that takes the data hostage is a lockout, not a ceiling.

New gauge: adp_storage_bytes{org}, the first storage metric this server has
had. docs/observability.md §5 amends its own "no per-org labels" position to
say why this one is the exception.

0.4.0 — OIDC login (shipped here, never separately released)

M4-5: OIDC login (#103). The standard authorization-code flow with PKCE,
mapped onto identities through a new external_identities table keyed on
(issuer, subject) — not on email, because Google's sub is the stable
identifier and an address that gets reassigned must not hand over an account.

Additive: two new operations, GET /auth/oidc/start and
GET /auth/oidc/callback. No existing operation changes shape, and both
routes are absent entirely on an instance with no IdP configured, so a client
generated against 0.3.0 keeps working untouched.

Two things worth knowing before enabling it. Auto-provisioning is off by
default
— OIDC_ALLOWED_DOMAINS is empty, and empty means a verified
account with no existing link is refused rather than welcomed. And a login
mints repo:read + repo:write only; admin is not reachable from the
login route by any input
, the same bound POST /api/adp/tokens carries.

ID tokens are verified against Node's own crypto rather than a JWT
dependency, with an allowlist of exactly one algorithm. The negative cases —
algorithm confusion, alg: none, unknown kid, tampered payload, wrong
issuer, wrong audience, expired, replayed nonce — are each a test, because
that is the only thing that makes the trade sound.

Image: ghcr.io/deduva/adp:v0.5.0
Digest: sha256:e6a9191df51d52f2dbfd3d291575ea7ffd4de9f6c31ae64f371c659a34a0ba2c — pin with ADP_IMAGE=ghcr.io/deduva/adp@sha256:e6a9191df51d52f2dbfd3d291575ea7ffd4de9f6c31ae64f371c659a34a0ba2c

Runner image: ghcr.io/deduva/adp-runner:v0.5.0
Runner digest: sha256:4155c5cf82e54487a8b9d0f3d1e74f15261a6b4cad92074bbd9201dff30dfce7

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 14 Aug 04:00
86bb2e5

The coordinated breaking contract release (#97), plus the full M4
post-landing-audit remediation (docs/m4-postmortem-audit.md,
tracked in #87) — every fix its own PR carrying the audit's named
negative-case test.

Security (P0).

  • Gate-job checkout/complete bind to the identity that claimed the job
    (#88) — previously any runner token could tarball any org's source and
    forge signed gate evidence.
  • repos(owner,name) unique; owner validated against path traversal;
    repos.org_id NOT NULL and indexed (#89).
  • POST /api/adp/tokens mints bounded tokens; admin no longer satisfies
    runner (#90).
  • Org isolation enforced on every plane — REST, git http, GraphQL,
    /api/adp — with explicit, audited org provisioning and the
    org-isolation matrix test, M4 exit criterion #1 (#91).

Queue reliability (P1a).

  • Claims are leases; a reaper requeues what a dead runner held, up to a
    retry cap, with operations recorded at every lifecycle transition, an
    oldest_running_age gauge, and an alert on the reaper itself (#92).
  • FOR UPDATE OF gate_jobs — issue/PR numbering no longer starves the
    claim path (#93).
  • All three org quotas enforce inside the transactions that consume them
    (#94). Completion and its signed evidence are one transaction (#95).
  • Background writers (sweeper, reaper) take advisory locks — a second API
    replica is safe (#96).

Contract (breaking).

  • ADP-API-Version 0.2.0 → 0.3.0, with an operation-set snapshot guard so
    the version can never silently not-move again.
  • Every 4xx carries a shared Error schema; validation errors are a stable
    {path, message, code} projection, never raw validator internals.
  • Auth and per-operation scopes declared in the spec (x-required-scope)
    and asserted against the code in both directions.
  • Every list endpoint is bounded: per_page/page on the compat plane,
    limit + ADP-Next-Cursor keyset cursor on the native plane. The
    gate-jobs listing no longer inlines logs.
  • Org administration (quota ceilings, policy repo, kill switch) has
    audited REST write paths, and org-level operations appear in the org
    audit-log export.
  • The run/compare/verify/eval responses squad-lab consumes are typed.
  • URLs keep {owner}/{repo}: the owner string is the org's immutable URL
    slug; org rename is unsupported pre-1.0 by design.

Hardening & hygiene (P2).

  • Gate containers: --cap-drop=ALL, --security-opt no-new-privileges,
    --pids-limit, and a host-operator image allowlist
    (RUNNER_IMAGE_ALLOWLIST) (#100).
  • The acceptance walkthrough runs in CI; make runner requires a real
    docker daemon; every self-skipping suite routes through the shared
    skip-into-failure guards (#99).
  • web/ runs tests in CI, with its hand-copied server enums bound to
    their sources (#98).
  • This release identity itself: tag, GitHub release, published image with
    digest, image: in the compose file (#101).

Image: ghcr.io/deduva/adp:v0.3.0
Digest: sha256:dcec032d7b48b895a888167a682f7e183570d8b0b47830aa1094a1ce44d1b377 — pin with ADP_IMAGE=ghcr.io/deduva/adp@sha256:dcec032d7b48b895a888167a682f7e183570d8b0b47830aa1094a1ce44d1b377