Skip to content

Releases: lambda-twelve/one-record

1.0.0-beta7

Choose a tag to compare

@hexblot hexblot released this 05 Oct 09:58
Immutable release. Only release title and notes can be modified.
1.0.0-beta7
fc5d397

Added

  • Model\GraphValidator: one validator for a whole logistics-object or
    event graph, root and every embedded node, used by ChangeApplier,
    POST /logistics-objects and the logistics-events endpoints alike, so no
    ingestion path accepts what another refuses (adversarial review 10).

Fixed

  • A change could introduce an embedded node of an unknown class, which then
    escaped validation along with any property attached to it; a node of a
    logistics object class; or a node of the wrong class for the property.
    ChangeApplier refuses the class up front and the validator judges the
    rest (R10-001, R10-002).
  • Comparer normalised only five of the twelve XSD integer types, so a
    delete spelled as xsd:byte did not match a stored xsd:integer and an
    add of the same number was not a duplicate; it now takes the integer
    family from Rdf\Xsd (R10-003).
  • POST /logistics-objects validated the root's properties only; embedded
    nodes, property kinds and ranges are now validated (R10-004). Posted
    events are validated the same way, embedded locations included (D10-001).
    One exception stays deliberate: a nested logistics object in a POST body
    (the spec's own example A2) is still accepted and kept embedded, since
    splitting it into an object of its own is not implemented (spec question
    33); a change or an event may not introduce one.
  • An xsd:date offset is bounded to 14:00 like a dateTime's (D10-002).
  • An embedded node a client identified itself (an https: or urn: id,
    NE:ONE's neone: scheme) escaped validation on creation and on posted
    events, could not be addressed by a change afterwards and was never
    cleansed when unlinked. Every non-root subject of a graph is now an
    embedded node: the validator judges it, withEmbeddedIds() gives it a
    server-minted internal: id when the object is stored, and the change
    applier addresses and cleanses it whatever id it carries (adversarial
    review 11).
  • A typed link, a reference that states the class of what it points to and
    nothing else, escaped validation when its class was unknown to the
    ontology (R12-001), and an unrelated change removed its class as an
    orphan (R12-002). Its class must now exist, as a class of the data model
    or as a code list, and whatever a reachable node links to is kept. A
    type-only node with an id of its own is a reference by design, not an
    embedded node: a change cannot describe it in place, and the guide says
    how to embed instead (R12-003).
  • A code list did not count as a class when a property's range was checked,
    so a unit typed with the wrong list, or a code list where a class was
    expected, passed (R13-001). It counts now, and an untyped member IRI of
    the wrong list is caught by its IRI when the property expects a list.
  • A nested logistics object that creation had accepted (spec question 33)
    made every later change to the object fail, since the change applier
    judged the finished graph without that allowance (R14-001). It is allowed
    there now; a change still cannot introduce one, and the type of any node,
    embedded nodes included, can no longer be changed through a change.
  • A language-tagged literal skipped the datatype range check, so "many"@en
    entered an integer property (R14-002). Tagged text now fits a string range
    and nothing else.
  • The bulk event route dropped a malformed cargo:eventFor value and
    reported no failure (R14-003); it answers 400 like the single-object route.
  • A non-string or empty @id on creation was treated as absent and a URI
    with a space became a 500 (R14-004); every malformed @id is a 400.
  • JwksKeyResolver ignored key_ops; a key published for encryption only
    could verify tokens (D14-001). A present key_ops must include verify,
    and the selection policy is in the guide. A key_ops, use or alg
    member that is present but malformed, null included, now fails closed
    as the guide says, and a skipped key is logged with its reason (R15-001,
    D15-001).

1.0.0-beta6

Choose a tag to compare

@hexblot hexblot released this 04 Oct 20:57
Immutable release. Only release title and notes can be modified.
1.0.0-beta6
14c88fb

Added

  • ServerConfig::problems(array $settings): what is wrong with a set of
    settings, in plain sentences, without constructing anything, for a host's
    status page on an install that is not configured yet. The constructor
    throws the first of them.
  • tools/consumer-smoke.php and a CI job that installs the package into an
    empty project without development dependencies and exercises the runtime.
  • Server\Deprecations logs, at notice level, every deprecated term an
    object carries when it is created or revised, as the compatibility guide
    promised.

Changed

  • ActionRequests::create() queues the Pending notification before
    dispatching ActionRequestCreated, and every decision queues its
    notification before its event, so a listener that decides a request
    synchronously cannot put its decision's notification ahead of the state it
    decided; create() and every decision return the stored request, which a
    listener may have advanced. DataHolder::change() and the holder's HTTP
    change path no longer decide a request a creation listener already decided;
    ChangeFailed is also thrown when such a listener rejected it
    (adversarial review 7, R7-001).

  • An integral xsd:double is written as an explicit value object, since a
    JSON number without a fraction reads back as xsd:integer in any JSON-LD
    processor; the client uses the package encoder (R7-002).

  • The expander applies a term's datatype coercion to native numbers and
    booleans, and an explicit value object without @language is a plain
    string under a default language (R7-003). A term definition never expands
    a document-relative @id (R7-004).

  • JWT time claims are NumericDate with their fraction or an absolute RFC 3339
    instant with a zone; anything else present is refused with
    JwtException::INVALID_CLAIM rather than read as absent (R7-005).

  • A posted logistics event may name only the object it is posted on in
    cargo:eventFor, however many values and in any order (R7-006).

  • Changes validate literals by each datatype's own grammar and against the
    property's range, as the checked builder does (R7-007).

  • DeliveryVerdict rejects a request PSR-18 reports as unusable
    (RequestExceptionInterface) instead of retrying it (R7-008).

  • The token endpoint refuses two client authentication methods in one
    request and repeated form parameters with invalid_request (R7-009).

  • Services validates against the newest configured data model version
    (ServerConfig::validationModel()), as the configuration documented; a
    server configured for 3.2 alone refuses 3.3-only terms (D7-001).

  • Claims::expiresAt(), notBefore() and issuedAt() return ?float
    instead of ?int
    , so a fractional NumericDate keeps its fraction. A
    consumer that needs whole seconds decides its own rounding, for example
    (int) floor($claims->expiresAt()) before DateTimeImmutable::setTimestamp();
    under strict_types passing the float directly is a TypeError.

  • DataHolder::subscribe() honours a creation listener's decision instead of
    accepting a second time (R8-001). Range checks follow XSD derivation
    (Rdf\Xsd): an xsd:int satisfies an xsd:integer range, any integer or
    decimal type satisfies a double, and bounded integer types are checked
    against their bounds; the builder and the change applier share the rules
    (R8-002). A native number or boolean under an @id-coerced term expands to
    the value it is rather than being refused (R8-003). The JWT verifier
    compares claims against the clock with its fraction (R8-004), refuses a
    present null or malformed nbf/iat, and validates the hour, minute,
    second and offset ranges of an RFC 3339 claim before parsing it (R8-005).

  • ActionRequestStore::save() states its envelope: the server saves a
    request once and moves it on with transition(); a later save() under
    the same IRI may replace status, history and errors, while type, payload,
    objects, requester and request time are fixed by the first save. The
    contract no longer asks a store to move a re-saved request to another
    object (beta5's test is withdrawn), since the SDK never does that and two
    hosts' query columns had been caught between the two readings (Laravel
    integration review, round 3).

  • AccessDelegation keeps each permission, delegate and logistics object
    once, in order, so a store projecting one row per (request, object) never
    sees a repeat.

1.0.0-beta5

Choose a tag to compare

@hexblot hexblot released this 04 Oct 19:14
Immutable release. Only release title and notes can be modified.
1.0.0-beta5
52473b5

Added

  • Client\TokenEndpointException (extends ClientException) with the status
    and the OAuth error code, thrown by ClientCredentialsTokenProvider when
    the token endpoint answers without a token.
  • The ActionRequestStore contract checks that replacing a request under the
    same IRI moves it to the object it now concerns, in auditTrail() and
    pendingChanges().

Fixed

  • Client\DeliveryVerdict::of() walks the chain of previous exceptions, so a
    transport failure the SDK client wrapped in a ClientException, or a
    token-endpoint outage, is Retry rather than Reject; refused credentials
    stay final (Drupal integration review, round 3).

1.0.0-beta4

Choose a tag to compare

@hexblot hexblot released this 04 Oct 14:57
Immutable release. Only release title and notes can be modified.
1.0.0-beta4
95bb3e1

Added

  • Spi\Volatile, a marker for stores with nothing to roll back; the
    in-memory stores carry it and the identity-unit-of-work warning is decided
    by it rather than by class name. ServerBuilder::check(Services) returns
    the same findings for a host's status page.
  • Testing\RacingActionRequestStore: stages a decision lost to another
    worker, outright or through a host callback that flips its own row first.
  • Testing\FixedClock::set() for jumping to an absolute instant.
  • Client\DeliveryVerdict: the retry classification every outbox worker
    needs (Retry for transport failures, 5xx, 408 and 429; Reject
    otherwise), and a guide section specifying the outbox row, claim and
    outcome model two hosts converged on.

Changed

  • ActionRequestStatusChanged and the status notification fire after the
    decision's side effects (grants, revision, revocation) are in place, once
    per decision, with the status the request had before it; a change that
    fails to apply reports Failed from Pending even though the store
    recorded Accepted in between (Drupal integration review, round 2).

1.0.0-beta3

Choose a tag to compare

@hexblot hexblot released this 04 Oct 14:14
Immutable release. Only release title and notes can be modified.
1.0.0-beta3
fa29128

Changed

  • Every decision on an action request stores its new status (the
    compare-and-set) before any side effect: grants, the new revision,
    notifications. A decision that loses the race against another worker now
    writes nothing even under a host without a transactional unit of work; it
    used to commit the grants first (Laravel integration review, round 2).
  • Services logs a warning when no UnitOfWork is given and a store is not
    one of the SDK's in-memory ones, since operations are then not atomic.
  • The contract traits' fixture constants are prefixed (CONTRACT_OBJECT,
    CONTRACT_PARTNER, ...) so a host test case with constants of its own can
    use the traits.
  • NotificationOutbox and the SPI guide state when enqueue() runs relative
    to the unit of work, that delivery is at least once, and that PSR-14
    listeners run inside the unit of work before the fan-out.

1.0.0-beta2

Choose a tag to compare

@hexblot hexblot released this 04 Oct 08:51
Immutable release. Only release title and notes can be modified.
1.0.0-beta2
a3547e5

Added

  • Testing\Contract\*ContractTests: every store contract is also a trait,
    for hosts whose test cases must extend a framework base class (Testbench,
    KernelTestBase) and so cannot extend the abstract contract; the abstract
    class is now the trait on a bare TestCase, so the two cannot drift.

Changed

  • LogisticsEventStore states that every event it receives carries
    cargo:eventDate (the server and the checked builder both refuse one
    without), so stores need no semantics for date-less events.

  • A status-change notification for an action request over several logistics
    objects (an access delegation, typically) no longer names an arbitrary first
    object in api:hasLogisticsObject, whose cardinality is at most one; it
    names none and the request in api:isTriggeredBy lists them all (spec
    question 32, IATA-Cargo/ONE-Record#437). Type subscriptions keep matching
    declared @type values only, now recorded as spec question 31
    (IATA-Cargo/ONE-Record#412).

1.0.0-beta1

Choose a tag to compare

@hexblot hexblot released this 03 Oct 22:12
1.0.0-beta1
de3941b

First release: the server and the client for API 2.2.0 and 2.3.0 with data
model 3.2 and 3.3, the compliance collection green for both editions, the
NE:ONE interoperability suite green, and five independent adversarial review
rounds folded in. Public API changes remain possible between betas and are
recorded here.

Added

  • Repository skeleton: Composer package, PHPUnit 11, PHPStan (level max) with an
    SDK-boundary rule, php-cs-fixer (PER-CS 2.0), DDEV configuration, GitHub
    Actions, MkDocs documentation site.
  • Spec\Edition, Spec\ApiVersion, Spec\DataModelVersion and
    Spec\Namespaces: the supported editions (2025-07: API 2.2.0 / data model
    3.2; 2026-07: API 2.3.0 / data model 3.3) and the IRIs ONE Record is built
    from.
  • Rdf\Graph and its terms (Iri, BlankNode, Literal, Triple): the RDF
    layer everything else works on.
  • Generated vocabulary (Vocabulary\Generated): every class, property and
    named individual of the cargo and API ontologies and every code list, merged
    across both editions with per-term since / deprecatedIn / removedIn
    metadata, plus Vocabulary\Vocabulary for runtime questions (accepted
    properties per class, logistics-object classes, most specific type, code-list
    membership) and version-limited views.
  • bin/generate-vocabulary: regenerates the vocabulary from IATA's ontologies
    at pinned commits; CI fails on a diff.
  • JsonLd: the restricted JSON-LD processor ONE Record needs. Expander
    turns a compacted document into triples (inline object contexts with
    prefixes, @vocab, @base, @language and @type-coercing term
    definitions; @id, @type, value objects, arrays, embedded objects and
    references) and rejects everything outside that subset with a message
    naming the construct and its path. Writer compacts a graph back to
    deterministic JSON-LD. Comparer decides graph isomorphism modulo blank-node
    labels, with embedded-object IRIs (internal:, neone:) treated as blank
    nodes and numeric literals normalised; Diff reports the differing triples.
  • Model: LogisticsObject, LocalGraph (objects linked by local key, resolved
    to URIs with an IriMinter; UuidIriMinter mints deterministic UUIDs under a
    base URL), ObjectBuilder/Embedded/Values builders validated against the
    ontology (with an optional data model version ceiling), and
    EmbeddedIdMinter for the spec's stable internal:<uuid5> embedded ids.
  • Change: the api:Change model with JSON-LD read/write, ChangeBuilder
    (diff two versions of an object into ADD/DELETE operations, editing embedded
    objects in place or replacing them via blank nodes) and ChangeApplier
    (apply a change atomically with the spec's rules: revision check, no event
    edits, deletes before adds, ontology validation, orphan cleanup).
  • Api\Error, Api\ErrorDetail, Api\Severity value objects.
  • Auth: Rs256Verifier (RS256 only, keys from a resolver, issuer/expiry/
    not-before/audience checks), Rs256Signer, StaticKeyResolver,
    JwksKeyResolver (PSR-18 + PSR-16, refresh on unknown key id), Jwk (RSA
    JWK to PEM), JwtAuthenticator for the server's Authenticator SPI, and
    TokenEndpoint, a PSR-15 OAuth 2.0 client-credentials endpoint with a
    ClientCredentialsVerifier SPI and an in-memory reference implementation.
  • Server: the PSR-15 ONE Record server. OneRecordServer negotiates the
    API version, routes, authenticates and answers every failure as an
    api:Error; ServerConfig holds base URL, base path, data holder,
    versions, languages and limits. Endpoints for server information, logistics
    objects (read with ?at= and ?embedded=, create, change, verify), audit
    trail, logistics events (single and 2.3 bulk), notifications,
    subscriptions, access delegations and action requests. ActionRequests
    implements the lifecycle (accept applies changes, grants delegations,
    rejects stale pending changes); DataHolder is the host's PHP API
    (create, update, publish, announce, accept/reject/acknowledge/
    revoke, subscribe, forget); Notification\Fanout queues notifications
    in the outbox. PSR-14 events for created/revised objects, received events
    and notifications, and action-request changes.
  • Server\Spi: the interfaces a host implements (LogisticsObjectStore,
    LogisticsEventStore, ActionRequestStore, SubscriptionStore,
    AccessDelegationStore, NotificationOutbox, Authenticator,
    AccessPolicy) with in-memory implementations in Server\InMemory and
    InMemoryServer wiring them all; SystemClock.
  • Api: value objects for every API document (Subscription,
    AccessDelegation, Verification, Notification, ServerInformation,
    ActionRequest, Collection, ErrorDocument) and Spec\ApiFeatures,
    the table of properties gated by API version.
  • Compliance collection (tests/Compliance/): a newman collection generated
    from the specification's examples with assertions from its MUST tables, run
    in CI against bin/serve for API 2.2.0 and 2.3.0 (ddev compliance locally).
  • bin/serve: the in-memory server under PHP's built-in web server with an
    OAuth 2.0 token endpoint, for development and the compliance collection.
  • Review round with the Laravel and Drupal wrapper teams:
    Server\Spi\UnitOfWork (the host's transaction boundary around every
    mutating request and holder operation); Server\Spi\IdGenerator as an
    interface with UuidIdGenerator; an id on OutboundNotification;
    SubscriptionStore::offer()/withdraw(), ActionRequestStore::accepted(),
    LogisticsEventStore::eraseFor(), AccessDelegationStore::eraseFor() and a
    scoped DataHolder::forget(); Server\GrantAccessPolicy (public grants
    stored as grants to EVERYONE; InMemoryAccessPolicy deprecated);
    millisecond SystemClock, ActionRequest::toStorageJsonLd(),
    LogisticsEvent::fromStored() and ObjectBuilder::ofEvent();
    Auth\Jwt\ChainKeyResolver, an optional, fault-tolerant cache on
    JwksKeyResolver, Auth\JwksEndpoint; ServerBuilder::routes(); objects
    created over HTTP fan out to subscribers; and the Testing namespace with
    the doubles and Testing\Contract store contract tests.
  • Adversarial review round (2026-10-03), one regression test per finding:
    client credentials bound to the partner's origin (additionalOrigins for
    multi-host partners); ActionRequestStore::transition() compare-and-set
    and lifecycle decisions made on stored state; shared visited set in graph
    collectors; strict xsd:dateTime parsing (a malformed expiry is a 400, not
    "no expiry"); the client refuses to request an expiring delegation from a
    2.2 partner; fan-out consults the access policy before disclosing a body;
    DataHolder::change()/update()/publish() throw ChangeFailed;
    language-tagged literals refused in changes; exact xsd:decimal
    comparison; sound canonical blank-node labels; order-independent change
    validation; discovery falls back through API versions on 406; publishers
    may query offers for their own objects; bulk events validated like single
    ones; client checks the answer is about the requested object; typed links
    rewritten in historical reads; RFC 6749 form-encoding of Basic
    credentials; q=0 exclusions and configured body versions honoured;
    isTriggeredBy never names an organisation; global event IRI uniqueness;
    malformed ?at= and non-finite numbers answer 400; Idempotency-Key on
    sent and received notifications; JWKS refresh cooldown; only the requestor
    or the policy may revoke; reference stores keep snapshots; position-aware
    JSON-LD compaction, RFC 3986 @base resolution, term-scoped coercion and
    labelled blank roots.
  • Second adversarial round (2026-10-03), again one regression test per
    finding: canonical blank-node labels by exhaustive individualisation search
    (ComparisonBudgetExceeded when a graph is too symmetric, instead of a
    guess); the writer uses a term alias only when its coercion fits the values
    and never writes a node id as a bare prefix; historical reads with
    embedded=true accepted by the client; a subscriber may revoke a
    SubscriptionRequest a third party created (spec question 30); the in-memory
    event store and outbox keep snapshots, with contract tests; changes may not
    write API-namespace properties and final validation tolerates only the two
    revision properties; content negotiation evaluates every served version
    against the most specific matching range; xsd:dateTime ranges checked,
    24:00:00 accepted as the next midnight; dot-segment removal for
    network-path references; the JWKS refresh cooldown kept in the shared
    cache; a shared embedded node introduced once in a change; a store's status
    conflict answers 409.
  • Third adversarial round (2026-10-03): the change builder plans deletions
    over the whole before/after graphs, so a shared embedded node is edited
    once, keeps its triples while any link reaches it, and loses them once when
    none does; a node under several root properties reports every one of them
    as changed; every compact key the writer emits must read back as its IRI,
    so shadowed @vocab names and wrong coercions are skipped instead of
    dropping a predicate or turning a string into an IRI; the client names both
    identities it accepts before a flattened answer's root is chosen; in-memory
    outbox reads hand out copies and the outbox contract states ownership.
  • Fourth adversarial round (2026-10-03): the change builder now builds a
    correspondence between old and new embedded nodes over the whole graphs
    before emitting any operation (unchanged content first in every slot, then
    one-to-one in-place pairs), so an unchanged branch is never edited to serve
    another, shared nodes split and merge exactly as the target does, and
    topology-only changes are changes; term definitions in a written @context
    use prefixes only and the reader resolves definitions...
Read more