Skip to content

GOBL Net: port CLI + ops from invopop/gobl@net - #2

Open
samlown wants to merge 24 commits into
cli-trialfrom
net
Open

GOBL Net: port CLI + ops from invopop/gobl@net#2
samlown wants to merge 24 commits into
cli-trialfrom
net

Conversation

@samlown

@samlown samlown commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the CLI half of the GOBL Net effort across from
invopop/gobl@net (PR
#785, where the library half lives) into this repo, now that
cmd/gobl/, internal/ops/, internal/mcp/, pkg/api/, and
wasm/ live here.

The gobl module is pinned to ff04d20e7388 — the post-merge tip of
the net branch in gobl after cli-to-gobldev was merged in.

New commands

  • gobl init <domain> — scaffold ~/.config/gobl/<domain>/ (ES256
    keypair + party.json template with a pre-filled gobl:<domain>
    endpoint).
  • gobl net who <address> --from <domain> — authenticated mutual
    party exchange; returns the target's verified org.Party
    envelope.
  • gobl net send <envelope> --to <domain> --from <domain> — deliver
    a signed envelope to a remote /inbox.
  • gobl net serve — HTTPS server with
    /.well-known/gobl/keys/<kid> (per-key lookup),
    /.well-known/jwks.json (bulk JWK Set sorted by valid_from
    desc), /who, and /inbox. Multi-tenant. ACME. Open CORS for
    browser-based JOSE tooling. Optional allow.json gate.

Extended commands

  • gobl sign --domain X [--to Y] stamps
    iss=gobl:X / aud=gobl:Y into the signed payload.
  • gobl verify gains --address / --remote for remote key
    discovery.
  • gobl keygen is deprecated in favour of gobl init.

Operator output

  • --json flag toggles all operator-facing log output between
    text (default) and JSON via log/slog. Logs go to stderr; result
    output (signed envelopes, /who party JSON, version JSON) stays on
    stdout. slog is set up in PersistentPreRunE on the root
    command (not cobra.OnInitialize, which would race in parallel
    tests).
  • Structured HTTP access logs (http_request, keys.lookup,
    jwks.served, who.exchange / who.rejected,
    inbox.accepted / inbox.rejected, inbox.write_failed) plus
    structured startup messages.

On-disk layout

<config>/<domain>/{private.jwk, keys/<kid>.json, party.json, allow.json, inbox/} — one file per kid (filename equals kid,
validated at startup). Maps 1-to-1 to a future row-per-kid
database.

Security

/inbox re-parses the document UUID with uuid.Parse before
writing to disk, as a defence-in-depth check against path
traversal (alongside the existing strict 36-char format check from
google/uuid).

Test plan

  • go build ./... clean.
  • go vet ./... clean.
  • go test -race ./... green across api, cmd/gobl,
    internal/mcp, internal/ops, wasm.
  • golangci-lint run ./... clean.
  • End-to-end smoke: gobl init smoke.example (UUIDv7 kid),
    followed by gobl sign --domain alice.example --to bob.example
    → signed payload {uuid, dig, iss=gobl:alice.example, aud=gobl:bob.example, iat=<unix>}.

🤖 Generated with Claude Code

Brings the CLI half of the GOBL Net effort across from
`invopop/gobl@net` (PR #785, where the library half lives), now that
the CLI / ops / MCP / api / wasm directories have moved to this repo.

New commands:

- `gobl init <domain>` — scaffolds `~/.config/gobl/<domain>/` (an
  auto-generated ES256 keypair + a `party.json` template pre-filled
  with a `gobl:<domain>` endpoint).
- `gobl net who <address> --from <domain>` — authenticated mutual
  party exchange; POSTs a signed request and returns the target's
  verified `org.Party` envelope (any authority countersignatures
  carried along).
- `gobl net send <envelope> --to <domain> --from <domain>` —
  delivers a signed envelope to a remote `/inbox`.
- `gobl net serve` — HTTPS server with `/.well-known/gobl/keys/<kid>`
  (per-key lookup), `/.well-known/jwks.json` (bulk JWK Set, sorted
  by `valid_from` descending), `/who`, and `/inbox`. Multi-tenant:
  auto-discovers every `<domain>/` directory under the config dir and
  routes by `Host`. Open CORS (`*` + OPTIONS preflight → 204) so
  browser-based JOSE tooling can fetch the JWKS. Optional per-domain
  `allow.json` gates `/who` and `/inbox` by signer address. ACME
  issues for all discovered domains.

Extended commands:

- `gobl sign --domain X [--to Y]` stamps `iss=gobl:X` / `aud=gobl:Y`
  into the signed payload.
- `gobl verify` gains `--address` / `--remote` for remote key
  discovery via the new per-key endpoint.
- `gobl keygen` is deprecated in favour of `gobl init`.

Operator output:

- Top-level `--json` flag flips all operator-facing log output between
  human-readable text (default) and structured JSON (one entry per
  line). Logs go to stderr; result output (signed envelopes, /who
  party JSON, version JSON) stays on stdout. `slog` is set up on the
  root command's `PersistentPreRunE` (not `cobra.OnInitialize`, which
  would race in parallel tests). CLI errors emit a single
  `command failed` entry with `key` / `message` / `faults`.
- `gobl net serve` emits structured access logs:
  `http_request` per request plus handler-specific
  `keys.lookup`, `jwks.served`, `who.exchange` / `who.rejected`,
  `inbox.accepted` / `inbox.rejected`, `inbox.write_failed`. Startup
  messages (`generated keypair`, `initialised domain`,
  `GOBL Net listening`, `ACME enabled`, `Shutting down`) are
  structured too.

On-disk layout (operator-visible):

- `<config>/<domain>/{private.jwk, keys/<kid>.json, party.json,
  allow.json, inbox/}`. One file per `kid` (filename equals `kid`,
  validated at startup) — the model maps 1-to-1 to a future
  row-per-kid database. Rotation is filesystem ops.
  `gobl net serve --keys` → `--keys-dir`.

Security:

- `/inbox` re-parses the envelope's UUID with `uuid.Parse` before
  writing to disk as a defence-in-depth check against path
  traversal. UUIDs already pass `env.Validate()` +
  `uuid.HasTimestamp` + the strict 36-char `[0-9a-f-]` format check
  from `google/uuid`; the re-parse keeps the filesystem write site
  self-contained.

The `gobl.dev/cmd/gobl` and `gobl.dev/internal/ops` trees match
`gobl@main`'s pre-extraction layout, so the bulk of the port applies
verbatim — `cmd/gobl/main.go` and `internal/ops/bulk_test.go` (which
gobl.dev had already edited) merged by hand. The `gobl` module is
pinned to the post-PR-785-merge `ff04d20e` commit, which itself
merges `cli-to-gobldev` into `net`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.35394% with 128 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.95%. Comparing base (777724c) to head (812fc38).
⚠️ Report is 5 commits behind head on cli-trial.

Files with missing lines Patch % Lines
internal/ops/net_serve.go 86.04% 46 Missing and 26 partials ⚠️
internal/ops/net_who.go 80.82% 7 Missing and 7 partials ⚠️
cmd/gobl/net_who.go 70.45% 13 Missing ⚠️
cmd/gobl/main.go 41.17% 9 Missing and 1 partial ⚠️
cmd/gobl/net_serve.go 90.14% 4 Missing and 3 partials ⚠️
cmd/gobl/root.go 42.85% 3 Missing and 1 partial ⚠️
internal/ops/net_init.go 84.61% 2 Missing and 2 partials ⚠️
internal/ops/net_send.go 88.57% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           cli-trial       #2      +/-   ##
=============================================
+ Coverage      72.71%   77.95%   +5.23%     
=============================================
  Files             47       57      +10     
  Lines           1983     2908     +925     
=============================================
+ Hits            1442     2267     +825     
- Misses           463      527      +64     
- Partials          78      114      +36     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

samlown and others added 11 commits June 4, 2026 22:30
Adds operator-facing documentation for the new GOBL Net commands
ported onto this repo in the previous commit: `gobl init`,
`gobl net who`, `gobl net send`, and `gobl net serve` (the
reference server). Also extends `gobl sign` and `gobl verify` rows
in the command table with the new `--domain` / `--to` /
`--address` / `--remote` flags.

The new section covers what an operator needs to deploy a node:
on-disk identity layout, startup checks, TLS sources
(`--acme-live`/`--acme-test`/`--tls-cert`), party-derived ACME
defaults, the three operational stances, Docker recipes (incl.
unprivileged), `--json` logging, the structured-access-log
catalog, and CLI error reporting.

Points readers at `gobl/net/README.md` for the wire-protocol
spec (addresses, key format and validity windows, signed
payload, per-kid + JWKS + /who + /inbox endpoints, errors,
security model) — that file is now scoped to the spec alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add TestApprovedAddonsAreBundled: every key in tax.ApprovedAddons must
resolve once the bundle is loaded, catching a missing blank import (or a
major-version registry split) in CI.

The test surfaced that the bundle no longer imported github.com/invopop/
gobl/addons, so neither the in-core addons nor the approved-addon
registry (addons/external.go) were being registered — restore that
import alongside the external gobl.fr.ctc/addon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`POST /.well-known/gobl/inbox` previously enforced `aud == self`
only when the signature carried an aud at all; a signature without
an aud was let through. That meant the same valid envelope could
be POSTed to any number of inboxes with no replay protection from
the signature alone.

Tighten the handler to require `aud != ""` *and* `aud == self`.
Envelopes without an audience get a new `aud_missing` rejection
reason in the access log; existing `aud_mismatch` semantics are
unchanged. The status code stays `401 Unauthorized` for both.

The library-level `net.Client.VerifyEnvelope` keeps `expectedAud`
optional — it serves non-inbox callers too (e.g. archival
verifiers that just want issuer attestation). The strict
"must be bound to me" rule lives in the inbox handler, where the
receiver always knows its own address.

Tests:

- TestNetServeInboxAudMismatch: comment refreshed; behaviour
  unchanged (aud != self → 401).
- TestNetServeInboxAudMissing: new — envelope signed with
  empty aud is rejected with 401.

Also bumps the `github.com/invopop/gobl` pin to pull in the
matching spec/IDN/x5c changes on the gobl `net` branch (commit
0dd345f0), and refreshes the README + CHANGELOG to flag the
inbox aud requirement and the new `aud_missing` access-log
reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Become the base of operations: gobl CLI + gobl.dev web/API + wasm
Resolves Dependabot CRITICAL/HIGH advisories flagged in Vanta.
Bumped: axios -> 1.18.0, tmp -> 0.2.7, esbuild -> 0.28.1, fast-uri -> 4.0.0
chore(deps): bump vulnerable dependencies (CRITICAL/HIGH)
Fixes the Fly deploy build: the Dockerfile pinned golang:1.24-alpine while
go.mod requires go 1.25.0, so `flyctl deploy` could not build the module.
Bumped the builder image to golang:1.25-alpine.

Updates the wasm dev/test dependencies to clear open Dependabot alerts
(supersedes #6-#12):
- wasm/worker: vite 6 -> 8, postcss -> 8.5.15 (0 vulns)
- wasm: cypress 14 -> 15 (drops vulnerable uuid), and patches the
  transitive form-data 4.0.6, joi 17.13.4, qs 6.15.2, ip-address 10.x,
  socks 2.8.9; bumped the ws override to ^8.21.0.

Remaining: 3 moderate dev-only advisories in the markdownlint-cli chain
(markdown-it, js-yaml) have no forward fix published — the only "fix" is a
downgrade to markdownlint-cli 0.43.0. These are DoS-on-malicious-input
issues in a linter that only runs against our own files in dev/CI, so the
downgrade is not worth the regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore: fix deploy Go version and update vulnerable npm deps
v0.0.4 is the first tag containing the Flow 6 state-handling work
(the previous v0.0.3 predated it due to a merge-order issue, now
fixed by gobl.fr.ctc#4). The bundle picks up the new flow6 behavior
for the playground / CLI / MCP with no code changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
migueltorresvalls and others added 12 commits June 17, 2026 17:02
Bump gobl.fr.ctc to v0.0.4 (Flow 6 state handling)
Add bundle/bundle_test.go which fails if any addon on GOBL's approved
external list (tax.ApprovedAddons) is not registered through the bundle,
guarding against the bundle drifting out of sync with the addons GOBL
endorses.

To make the bundle complete, import the remaining approved addon modules
(br.nfe, br.nfse, mx.cfdi, pt.saft) alongside the existing fr.ctc and
sa.zatca imports.

Bump gobl v0.501.0 -> v0.502.1. In v0.502.x several addons (mx, br, pt)
moved out of core into standalone modules, so internal/ops tests now
blank-import the bundle to register the same addon set the production
binaries use (restores the mx food-vouchers / fuel-account-balance
schemas in the schema list).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add bundle addon-coverage test; bump gobl to v0.502.2
Add tzdata to container
# Conflicts:
#	bundle/bundle.go
#	bundle/bundle_test.go
#	go.mod
#	go.sum
#	internal/ops/sign.go
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.

5 participants