Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .redocly.lint-ignore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ api/openapi.bundled.yaml:
- '#/paths/~1identity~1ready/get'
- '#/paths/~1integrator~1health/get'
- '#/paths/~1integrator~1api~1v1~1templates~1{sector}/get'
- '#/paths/~1integrator~1api~1v1~1schemas/get'
- '#/paths/~1integrator~1api~1v1~1schemas~1{sector}/get'
- '#/paths/~1integrator~1api~1v1~1schemas~1{sector}~1{version}/get'
- '#/paths/~1dpp~1{dppId}/get'
- '#/paths/~1dpp~1{dppId}~1qr/get'
- '#/paths/~101~1{gtin}/get'
Expand Down
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ under the pre-1.0 conventions in [VERSIONING.md](docs/governance/VERSIONING.md):

## [Unreleased]

## [0.12.0] - 2026-08-23

### Breaking

- **`publishValid` is now `sectorDataValid`.** *(Breaking: a response field is
Expand Down Expand Up @@ -180,6 +182,47 @@ under the pre-1.0 conventions in [VERSIONING.md](docs/governance/VERSIONING.md):

### Added

- **Sector JSON Schemas are fetchable.** `GET /integrator/api/v1/schemas` lists
every sector with a schema, the version a new passport is validated against,
and every version a stored passport may legitimately record;
`/schemas/{sector}` serves the current one and `/schemas/{sector}/{version}`
a pinned one (a leading `v` is accepted). Unauthenticated.

An SDK or dashboard previously had no way to see the contract before building
a body — the only feedback was a rejection from the create route, or the CSV
import template, which is an import artefact rather than a schema. These
resolve through the same registry the publish gate validates against, never a
copy: a second copy would drift, and the direction it drifts is the one where
a body passes here and fails at publish.

**Every `description` is omitted from the served document, deliberately and
temporarily.** Those fields make regulatory assertions — act numbers,
adoption dates, effective dates, product-class scope, annex references — that
have not been verified against primary text; two electronics descriptions once
asserted an adoption date, an effective date, three named priority product
classes and a phase-two date for an act that does not exist. Inside a library
those are developer-facing comments; on a public endpoint they become a
product surface a consumer reads, caches and relies on. Everything that
decides accept or reject — types, `enum`, `required`, `pattern`, bounds,
`additionalProperties` — is served in full, so a client can pre-validate a
body and get the verdict the create route would give. The prose is restored
once the audit has verified it; the stripping is one function and one call
site, marked as such.

`title` is kept: they are short labels, not assertions.

- **A node says which catalogued sectors have no plugin loaded.** One line at
boot naming them, at `warn`.

Passthrough is a legitimate configuration, so this does not refuse to boot.
But a sector with no plugin and a sector whose plugin found nothing wrong
produce the same thing — a determination with no findings — so from the
outside they were indistinguishable, and "no violations" read as "checked and
clean" when it could mean "never checked". `warn` rather than `info` because a
production node loads a full signed set from the release pipeline, so a gap
there is a misconfiguration, and `info` is where it would be missed.


- **The CLI has an automated test tier.** `cli/tests/` runs the `odal` binary as
a child process and asserts on exit codes, output, and what lands in
`config.toml`. Nothing in the suite previously reached the CLI's behaviour —
Expand Down Expand Up @@ -502,6 +545,33 @@ under the pre-1.0 conventions in [VERSIONING.md](docs/governance/VERSIONING.md):

### Changed

- **Shared test scaffolding has one home, and a gate that keeps it that way.**
The throwaway-Postgres harness and the in-memory `PassportRepository` double
now live behind `dpp-dal`'s dev-only `test-harness` feature, as
`dpp_dal::test_harness` and `dpp_dal::in_memory_repo`.

Rust cannot share `#[cfg(test)]` code across crate boundaries, so copying is
the path of least resistance and nothing signalled when it happened. `start_pg`
had reached **eight copies that had drifted into six distinct
implementations**, each carrying its own hardcoded readiness sleep; the
repository double had reached three, with the `impl` blocks byte-identical and
the structs already diverging. `just harness-check` now fails the build when
either is defined outside its home, and was verified to fail before being
wired in. No new crate: every consumer already depends on `dpp-dal`, which is
`publish = false`, so none of this ships.

Checked and deliberately left alone: the wire-shaped `serde_json::Value`
passport builders (a different thing from the typed ones) and the per-suite
auth doubles (different implementations, small, purpose-built). Merging those
would couple unrelated tests to one double's behaviour.

- **Test keystores use `tempfile::tempdir()`.** Nine sites opened an Ed25519
keystore at a hand-built path under `std::env::temp_dir()`, which gives no
restrictive permissions and no cleanup — every run left the file behind.
Severity was low and stays stated plainly: the passphrases are literals, the
keys are throwaway, and nothing production-adjacent reads those paths.


- **The API description is authored multi-file and shipped as one file.**
`api/openapi.yaml` is now a thin root — `info`, `servers`, security schemes,
tags and a `$ref` per path — over `api/paths/` and `api/components/`.
Expand Down
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,10 @@ Test tiers:
Two things that bite:

- **A feature-gated suite that stops compiling fails only in CI.** `just test` skips them entirely. Run `just check` before pushing, not `just test`.
- **Adding a `#[cfg(test)]` helper does not make it reachable from another crate.** Rust cannot share test code across crate boundaries, which is why the Postgres harness is duplicated per suite. Follow the local copy rather than inventing a new one.
- **Adding a `#[cfg(test)]` helper does not make it reachable from another crate.** Rust cannot share test code across crate boundaries, so the reflex is to copy — and the Postgres harness reached eight copies that had drifted into six different implementations before anyone noticed.
- **Shared test scaffolding has one home, behind `dpp-dal`'s `test-harness` feature**, enabled from `[dev-dependencies]`. Two things live there today: `test_harness` (`start_pg`, `start_pg_raw`, `start_pg_before`) and `in_memory_repo` (`InMemoryPassportRepo`). **Do not write another one** — if the shared version cannot do what a suite needs, extend it there rather than forking a copy. `just harness-check` fails the build if you do.
- **Not everything that shares a name is duplication.** Checked and deliberately left alone: the three `serde_json::Value` passport builders (wire-shaped, for HTTP tests — a different thing from the typed `Passport` builders), and the `TestAuthProvider` / `AlwaysFail` doubles (different implementations per suite, small, purpose-built). Merging those would couple unrelated tests to one double's behaviour. The three *typed* `Passport` builders are a real candidate and are deferred, not rejected — extract them when a fourth appears, or when two of the three need the same new field.
- **A test keystore uses `tempfile::tempdir()`, never `std::env::temp_dir()`.** These files hold Ed25519 private keys; `tempfile` creates the directory with restrictive permissions and removes it on drop, and the hand-rolled path did neither. Return the `TempDir` alongside the store so the directory outlives it.

## Standing Conventions

Expand Down
29 changes: 16 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ members = [
]

[workspace.package]
version = "0.11.0"
version = "0.12.0"
edition = "2024"
authors = ["Odal Node <dev@odal-node.io>"]
license = "BSL-1.1"
Expand Down
34 changes: 34 additions & 0 deletions api/components/schemas/Problem.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
type: object
description: |
RFC 7807 / RFC 9457 problem details. The shape
`dpp-common::http_problem::Problem` produces, served as
`application/problem+json`.

`type` is derived from `title`, so each distinct `title` used across the
codebase is a stable catalogue key that clients may depend on.
required:
- type
- title
- status
properties:
type:
type: string
format: uri
description: Absolute URI identifying the problem type.
example: https://problems.odal-node.io/not-found
title:
type: string
description: Short human-readable summary of the problem type.
example: Not Found
status:
type: integer
description: The HTTP status code, mirroring the status line.
example: 404
detail:
type: string
description: Human-readable explanation for this specific occurrence.
example: "No schema for sector 'nosuchsector'. Known sectors: aluminium, battery."
instance:
type: string
format: uri-reference
description: URI reference identifying this specific occurrence.
Loading
Loading