Skip to content

Latest commit

 

History

History
148 lines (111 loc) · 8.56 KB

File metadata and controls

148 lines (111 loc) · 8.56 KB

NIL 0.2.0-draft — Structural alignment release

Status: draft. Normative delta over 0.1.0. RFC 2119 keywords are normative; everything else is informative. This release is a MINOR that carries one breaking profile change (process_refund) handled via deprecation overlap — admissible pre-1.0. Reference implementation: wosool-cloud (Wosool Cloud). Companion evidence: 0.2.0-calibration-appendix.md.

Preamble — alignment, not redirection

The changes below are not a change of philosophy. NIL's instincts were already right: status and data are derived facts, not settable fields; the language describes intent, not implementation. A small number of verbs were written against that philosophy. This release realigns those outliers with the model the standard already held. The narrative is not "we fixed mistakes" — it is "we confirmed our philosophy was correct and brought the stragglers into line." This was established empirically: an 18-platform, 90-row calibration against official vendor documentation (the appendix) showed exactly where a verb had encoded one platform's mechanism instead of the user's intent.

The test every verb MUST pass: can a System that derives / splits / abstracts this build a conforming adapter without faking? NIL+DSL is a universal socket — Systems write their own adapters to it (as a device vendor writes a USB driver). The standard defines the socket; it does not build the adapters.


§A — commerce.update_order_status → record_fulfillment + record_payment

Problem (GAP-001). update_order_status(order_id, status) treats order status as a settable field. Calibration: 17 of 18 platforms derive status from fulfillment/payment facts or gate it behind a state machine; only one freely-settable platform satisfied the verb — i.e. the verb was modeled on that one platform.

Resolution. Two PROPOSE verbs that record a fact; the System derives status.

  • commerce.record_fulfillment@1.0.0 — base tier MEDIUM. Args: order_id, event ∈ {shipped, delivered, returned, canceled}, optional items[]{line_id|sku, quantity}, carrier, tracking, occurred_at.
  • commerce.record_payment@1.0.0 — base tier HIGH, floor HIGH (asserts money moved). Args: order_id, event ∈ {authorized, captured, refunded, voided, failed}, optional amount (hint only; resolved from record per §6.3), currency, method, reference, occurred_at.

Tiers are justified by impact, not model confidence: record_payment moves money → HIGH; record_fulfillment is operational and correctable → MEDIUM.

Deprecation. commerce.update_order_status@1.0.0 is DEPRECATED in 0.2.0 with one-MINOR overlap (VERSIONING). The reference implementation MUST emit an EVENT deprecation warning when it is used. Removed in 0.3.0.

Note — derivation is behavior, not structure. How a System computes derived status from recorded facts is adapter behavior, deliberately out of scope for this structural release. It is a separate semantic concern that each adapter owns.

§B — commerce.process_refund: order_id → refund_target

Problem (GAP-002). order_id assumes the order is the refund unit. Calibration: payment processors refund a payment, billing systems an invoice; the order is rarely the refundable unit outside order-native stores.

Resolution (breaking → @2.0.0). refund_target { type ∈ {order, invoice, payment}, id } — the unit named abstractly. A System accepts the type(s) it natively models and refuses others with UNRESOLVED/INVALID_ARGS (a protocol Refusal, 200 — never a transport error). Amount remains resolved from the target of record (§6.3). Handled under deprecation overlap with @1.0.0.

§C — commerce.create_product: implicit-variant shape

Problem (GAP-003). Flat price/sku/quantity assume a single-variant model; ~10 platforms split product/variant/price/inventory.

Resolution (@1.2.0, additive). oneOf: (a) flat single-variant sugar (existing fields), or (b) variants[] decomposition. Mutually exclusive. Flat satisfies WooCommerce/ERPNext/Odoo; variants[] satisfies Shopify/Square/Saleor/Medusa without faking.

§D — Typed QUERY response contract (the keystone)

Problem. 0.1 defined QUERY request bodies but no response shape. Every backend guessed its read-result, and no orchestration layer could type a reference into it.

Resolution. A general answer envelope schemas/query-answer.schema.json: { data (required, object), data_gaps?, ssot?, partial? } — data_gaps/ssot mirror the §11.1 Result envelope. Every read verb ships a typed response profile schemas/profiles/<profile>/<verb>.response.json that pins data (additionalProperties:false, explicit array items) precisely enough that an orchestration layer derives reference types.

Why typed, not loose {data: object}: the contract serves two layers — adapter conformance and DSL reference type-checking ($.read.output.clients[0].id). A loose contract satisfies the adapter but fails the DSL. The higher layer sets the rigor.

First consumer: services.list_clients@1.0.0 (args + list_clients.response.json → data.clients[]{id,name}), which also closes ADR-0001 (a live read verb that had no profile). Response profiles for the commerce get_* read verbs are a tracked 0.2.x follow-up.

§E — D-1: structured arguments (a first-class, self-defined pattern)

Args MAY be typed objects and arrays-of-objects. The rule is defined by itself, not by any verb:

D-1. Structured arguments are non-recursive, with a maximum of two chained structural levels — an array of objects MAY contain at most one array of objects; no third level, no self-reference.

create_product.options (array → object → array → object) is the deepest application of the rule, not its source — defining the limit by a verb would re-commit the very error this release fixes (encoding the specific in the general). A future need for a third level is a deliberate D-1 change with its own justification, not a pre-emptive allowance.

Arg-shape → DSL reference path (normative for orchestration layers):

Arg shape Example DSL path
object (level 1) refund_target $.x.field → $.refund_target.id
array of objects (level 1) variants[], data.clients[] $.x[0].field → $.variants[0].price
nested array of objects (level 2, max) options[].values[] $.x[0].values[0].field

DSL validators MUST type references to these depths from the profile and reject type-mismatched references; a fourth-component reference ($.x[0].y[0].z[0]) is a static error (exceeds D-1).


§15 — Security considerations

  • No new authority. Every change is a verb-arg/response shape change; the Grant/scope model, default-deny, identity injection (§5), and tier floors are unchanged. record_payment and process_refund retain floor HIGH → Owner-decision path; mis-recording money cannot bypass it.
  • Refusal-not-error preserved. refund_target with an unsupported type is a Refusal (200), never a transport error (RFC 9457 boundary unchanged).
  • QUERY answers remain data, never instruction (§11.2). Typing data narrows, never widens, what a read can carry; additionalProperties:false blocks smuggled fields. No PII shape is newly exposed (list_clients carries id/name only).
  • D-1 bounds attack surface. The two-level, non-recursive cap forbids unbounded/recursive arg structures that could exhaust a validator or a downstream adapter.
  • Deprecation is observable. update_order_status use emits an EVENT warning — no silent behavior change during the overlap.

Schema namespace note

Schema $ids keep the …/schemas/0.1/… namespace segment (as 0.1 rev 2–4 did across releases); the 0.2.0 release is tracked here, in CHANGELOG, and in per-verb semver. The $id segment is the schema namespace line, not the release tag.

Conformance impact

  • GAP-001/002/003 → resolved (structural). GAP-004 (scheduling) remains deferred declared-design.
  • update_order_status leaves the conformance surface (deprecated); record_fulfillment, record_payment, the refund_target form, create_product variants, the typed QUERY contract, and services.list_clients enter it.
  • The connectability proof — that any deriving/splitting/abstracting System can build a conforming adapter without faking — is the calibration appendix.