Component Licence File Contract v1
Contract: stella.ops/componentLicence@v1 (CONTRACT-COMPONENT-LICENCE-FILE-V1-075). Schema: component-licence-file.v1.schema.json (JSON Schema 2020-12, the payload). DSSE payload type: application/vnd.stellaops.component-licence.v1+json. Signature algorithm: ES256 (ECDSA P-256 with SHA-256; the signature is the 64-byte r || s concatenation). Signing root: a root of purpose vendor-entitlement in the vendor trust-root set. Examples: valid payload, invalid payload. Implementation: src/__Libraries/StellaOps.Components.Licensing/ (ComponentLicenceVerifier, ComponentLicenceFileParser, ComponentLicenceTrustRoots, ComponentLicenceLoader, ComponentLicenceEvaluator, ComponentLicenceNotice, ComponentLicenceNoticeFormatter); tests in src/__Libraries/__Tests/StellaOps.Components.Licensing.Tests/, which also hold the examples and schema to this contract. Issuer: src/Tools/StellaOps.EntitlementIssuer (issue-component), see the issuer runbook.
Purpose and boundary
A component licence record records, for one Installation, the production-permission lines of the customer’s order: one line per component (the Router, the Workflow engine, or both), each with the licensee display name agreed for it and its entitlement entries. Stella Ops signs it; the customer supplies it to the components as a file or as a key (the same signed record as one base64 text value). Each component reads it offline, reads only its own line, reports a status and shows a licence notice. In this contract “licence file” means the record in either form.
The record evidences the order:
- It grants no rights by itself, and its absence, an invalid record, a status or a notice withdraws no right. The order and the applicable software licences prevail over the record and over any status or notice a component shows. The record is never the sole test of entitlement.
- Neither component refuses, restricts, slows or stops registration, import, execution, routing or any other operation because of a count, a notice level, the record or its status. The status is a log line and an informational health detail only. Neither component fails readiness or liveness or restarts because of the record or its status.
- Reading the record needs no network. A component never contacts Stella Ops and sends no telemetry about the record, its status or its counts.
The record is limited to closed licence administration between Stella Ops, the customer of the order, each end customer named for an embedded line of the identified Installation and bound by the applicable licence-administration terms, and the persons who administer that Installation for them:
- It is not a credential for reliance by anyone else. It does not certify actual usage, compliance, customer evidence keys or the authenticity of customer outputs.
- The Installation identifier is an opaque random UUID, not linked to any customer evidence-signing key.
- Each line names its licensee by the display name agreed for that line in the order, so that the component can show whom the line records. The display name identifies the operating entity of the line; it is not an identity-verification field. The record holds no other data about the licensee or any person: no address, registration number, personal identification number, contact details or e-mail address. Where the licensee of a line is a natural person, for example a sole trader, the display name is that person’s name. The serial and the order references tie the record to the order; any purchaser-contact mapping is kept by Stella Ops in its protected licensing records, under its privacy notice and retention policy, outside the record. The record does not name the licensor.
- Lines are combined in one record only where every recipient may receive the administration information of every included line. Combining lines never combines their capacities and never turns an embedded line into a permission outside its named product.
- The signing function accepts only this payload. It never signs customer documents, customer evidence or statements about customer operations.
What a loaded record shows
A record whose signature matches a pinned vendor-entitlement root shows only:
- that the vendor key
issuerKeyIdsigned this payload; - that revision
revisionof the recordseriallists, for the InstallationinstallationId, the lines inlines; - for each line, that the order names the licensee
licenseeDisplayNamefor the componentcomponentunder the permissionpermission, and that each entry’s orderorderReferencerecords its capacity and version scope.
It does not show that any count is correct, that the Installation is used as the order describes, that a use is within an embedded line’s named product, or that an Installation without a record, or with an older record, lacks a permission: the free limits and every other permission of the component licence apply independently.
Envelope
The file is a DSSE envelope (JSON), exchanged as a .dsse.json file:
{
"payloadType": "application/vnd.stellaops.component-licence.v1+json",
"payload": "<base64 of the canonical payload bytes>",
"signatures": [
{ "keyid": "<issuerKeyId>", "sig": "<base64 of the ES256 signature>" }
]
}
payloadis the standard base64 encoding of the payload’s canonical JSON bytes in Stella Ops canonical JSON version 1 (stella:canon:v1, the form ofStellaOps.Canonical.Json; see the CanonJson API reference): object keys normalized to Unicode NFC and sorted by ordinal (UTF-16 code unit) comparison, string values normalized to NFC, numbers kept as written (negative zero as0), strings escaped asSystem.Text.Jsonwrites them with the relaxed encoder, no whitespace, UTF-8. The form is not RFC 8785; an RFC 8785 serializer can give other bytes for the same payload. A payload that is not byte-identical to its own canonical form is refused.- Each signature covers the DSSE pre-authentication encoding
PAE(payloadType, payload bytes) = "DSSEv1" SP len(type) SP type SP len(payload) SP payload. The signature covers every line, so a change to any line refuses the whole record for both components. - The envelope holds exactly
payloadType,payloadandsignatures; a file is at most 64 KiB. - The record is authenticated when a signature whose
keyidequals the payload’sissuerKeyIdmatches under the pinned root with that key identifier.
File or key
The same signed record is accepted in two forms with the same role:
| Setting | Form |
|---|---|
StellaOps:ComponentLicence:Path | A licence file, or a directory whose *.dsse.json files are read (at most 64, in ordinal name order). Local path only. |
StellaOps:ComponentLicence:Key | The licence key: the standard base64 encoding (RFC 4648, with padding) of the licence file, as one line. White space around it is ignored; any other character refuses it. |
Base64 is an encoding, not a protection: the key is the same public, signed record as the file, and it is verified exactly as the file. When both settings are set, the component reads neither and reports one record not loaded with the refusal conflicting_sources, so the ambiguity shows in the licence details instead of one setting silently shadowing the other. With neither, no record is read.
Payload fields
All fields are required, and no other field is allowed, at any level.
| Field | Type | Meaning |
|---|---|---|
schemaVersion | string | stella.ops/componentLicence@v1. |
purpose | string | licence-administration: the only purpose of the record. |
serial | string | The opaque record identifier: the record of one Installation for its whole life, ^[A-Z0-9][A-Z0-9-]{2,63}$. |
revision | integer | Revision of the serial, from 1. |
issuerKeyId | string | Key identifier of the vendor-entitlement root that signs this revision: sha256: and the lowercase hexadecimal SHA-256 of the key’s SubjectPublicKeyInfo. |
installationId | string | The opaque Installation identifier the components are configured with, a random lowercase UUID. Every line is for this Installation. |
issuedAt | string | Time the issuer signed this revision, UTC, YYYY-MM-DDTHH:MM:SSZ. Informational. |
lines | array | One or two lines, at most one per component. |
Each line has exactly these fields:
| Field | Type | Meaning |
|---|---|---|
component | string | router or workflow. |
permission | string | standard-capacity or embedded (below). |
licenseeDisplayName | string | The organisation name, trading name or neutral display label agreed for this line in the order, shown in the licence notice. 1 to 128 Unicode characters: letters, combining marks, decimal digits, the punctuation ! " & ' ( ) , . / : + - and the quotation marks «, », ’, “, ” and „, with single spaces between words and at least one letter or digit. No control, format or markup character, and no leading or trailing space. |
entries | array | The line’s entitlement entries, in the form of its permission. |
A standard-capacity line has 1 to 64 entries, in strictly ascending order of coversVersionsPublishedBefore, each with exactly these fields:
| Field | Type | Meaning |
|---|---|---|
capacity | integer | The recorded capacity as a total, 1 to 1,000,000: Endpoints for the Router, Designated Workflows for the Workflow engine. The order buys capacity in whole blocks above the free limit; the issuer computes the total from the blocks the order records, and the record carries the total. |
coversVersionsPublishedBefore | string | The entry’s release-date scope: it covers the versions first published before this date. A UTC calendar date YYYY-MM-DD, exclusive. |
orderReference | string | Reference of the order or transaction that records the entry: ^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$. |
There is one entry per capacity level and release-date scope. The capacity of an entry is a total; it is never added to another entry’s capacity.
An embedded line records the permission of an accepted order for the customer’s named product on the named end-customer Installation. It has exactly one entry, with exactly these fields:
| Field | Type | Meaning |
|---|---|---|
capacity | string | unlimited, for the component within the named product on that Installation. |
versionScope | string | delivered-without-cutoff: any version of the component that the customer delivers to the named end customer as part of the named product, including versions first published later. The entry has no release-date cutoff, and no date stands in for one. |
effectiveDate | string | The UTC calendar date YYYY-MM-DD on which the order granting the line was accepted. |
orderReference | string | Reference of the order that records the line. |
The order references of a line are the orderReference values of its entries. The record carries no named-product or end-customer field: the order identifies them, and the display name of an embedded line is the label agreed for the end customer.
Verification rules
The component verifies each record on its own and loads it only when every check passes. A record that is not loaded counts for nothing, and the reason is shown as a detail. The refusal codes are:
| Code | Cause |
|---|---|
unreadable | The file could not be read, the path does not exist, or a directory holds more than 64 licence files. |
malformed_envelope | The file is empty, larger than 64 KiB, or not a DSSE envelope with exactly the three fields and base64 values; or the key is not one line of standard base64 or longer than a file can be. |
unsupported_payload_type | The payload type is not the one above. |
missing_signature | The envelope has no signature. |
non_canonical_payload | The payload is not in its canonical form (Stella Ops canonical JSON version 1, see above). |
schema_violation | The payload does not match the schema or the rules above (field sets at every level, patterns, licensee display names, one line per component, the entry form of each permission, ascending dates). A field the contract does not define, such as a contact or e-mail field, is refused at every level. |
no_trust_roots | No vendor-entitlement root is usable (see Trust roots). |
unknown_key | issuerKeyId names no active root, or a superseded one. |
issuer_key_mismatch | No signature carries the payload’s issuerKeyId. |
signature_invalid | No signature with that key identifier matches the payload under the root. |
wrong_installation | installationId is not the identifier the component is configured with, or none is configured. |
wrong_component | The record carries no line for the component that reads it. |
conflicting_sources | Both StellaOps:ComponentLicence:Path and StellaOps:ComponentLicence:Key are set; neither is read. |
Trust roots
- The component library embeds the published
vendor-entitlementroot (the same public key ashttps://stella-ops.org/keys/and the release bundle’sconfig/vendor-roots/). - When
STELLAOPS_VENDOR_TRUST_ROOTSnames a directory, the vendor trust-root set in that directory replaces the embedded root; the two are never merged. The set is read in thevendor-trust-root-set-v1layout for purposevendor-entitlement: anchors, then root updates applied in sequence. Root updates must be ES256. - Any issue in the set leaves no root trusted, with the set’s issue codes as the reason: every record is then not loaded, and the component keeps running.
Supersede and continuity rules
- One serial is the record of one Installation. Each later file for it is the next revision of that serial.
- Within a serial, the highest loaded revision replaces every lower revision completely. Two different files with the same highest revision are both ignored, with a detail.
- A later revision preserves every continuing entry of each lower revision, on its line, including an earlier entry with a higher capacity and an embedded line’s entry. Its entries are the entries of the order after the transaction: a renewal adds an entry with its total capacity and a later date; a capacity increase raises the capacity of the entry whose scope ends at the current coverage end date; an earlier entry stays.
- An inaccurate entry may be corrected in a later revision to match the order. A correction changes the record, never the rights the order grants.
- Separate serials for the same Installation are read together; their entries are never added together either.
Evaluation
The component evaluates its status from three inputs: its local count, its own lines of the loaded records, and the first-publication date of the running version.
- Local count. The Router supplies its local Endpoint count; the Workflow engine supplies the count of recorded workflow names on its technical counter, or reports that the count is unavailable. The local count is the component’s own and is not conclusive of the contractual count.
- Levels. Each component has a notice level and a free limit (
ComponentFreeLimitsin the implementation). The notice level is informational: a count above it is not an excess. The free limit is the component licence’s numerical free-production limit. - First-publication date. The release pack stamps each component assembly with the assembly metadata
StellaOps.Component.PublishedAt, the UTC calendar dateYYYY-MM-DDof the release’s first publication; the release record is the authority for that date. A build or adaptation of a released version keeps the stamp of its source release; its own build date is never used. A build without the stamp is a development build. - Coverage. A standard-capacity entry covers the running version when the version’s first-publication date is earlier than the entry’s
coversVersionsPublishedBefore. A version first published on that date or later is not covered by that entry. An embedded entry has no cutoff and covers every version, a development build included; the component cannot verify the named product, the end-customer Installation or the delivery, so it describes the recorded permission and certifies none of them. The current date never changes the result: a covered version stays covered after a date has passed. - Capacity. The capacity that applies is the highest capacity among the entries that cover the running version; unlimited is the highest. Entries and lines are never added together, and a recorded capacity below the free limit never lowers the free limit.
The first status that holds is reported:
| Status | When | Message |
|---|---|---|
count-unavailable | The Workflow engine’s local workflow-name count is unavailable. | The local workflow-name count is unavailable. |
within-free-limit | The count is at or below the notice level. No record is needed. | Router: Local Endpoint count: [count]. Workflow: Recorded workflow-name count: [count]. |
embedded-scope-not-established | The count is above the notice level, and an embedded entry decides. | The local record does not establish that this use is within its named product and end-customer Installation scope. Check the applicable Order and any other permission. |
record-matches | The count is above the notice level, a standard-capacity entry covers the running version, and the count is within its capacity or the free limit. | This record covers versions first published before [date]. |
count-exceeds-recorded-capacity | The count is above the free limit and above the capacity of the standard-capacity entry that decides. | The local assessment could not confirm recorded capacity for this version and count. Check the applicable LICENSE, Order and any other permission. |
above-notice-level | The count is above the notice level and within the free limit, and no entry covers the running version. | Router: Local Endpoint count: [count]. This is above the [notice level]-Endpoint notice level. Check permitted use under the applicable LICENSE and any Order. Workflow: Recorded workflow-name count: [count]. This is above the [notice level]-name notice level. This technical count is not the contractual Designated Workflow count. |
development-build | The count is above the free limit, no entry covers the running version, and the running build has no first-publication date. The details add: This build carries no first-publication date, so the licence records are not assessed. | The local assessment could not confirm recorded capacity for this version and count. Check the applicable LICENSE, Order and any other permission. |
no-matching-record | The count is above the free limit, and no loaded entry covers the running version. The details show the date of every loaded entry in the record-matches message form. | The local assessment could not confirm recorded capacity for this version and count. Check the applicable LICENSE, Order and any other permission. |
Messages suggesting missing capacity are suppressed when an applicable standard-capacity entry covers the running version and the count. within-free-limit and record-matches are written to the log on a change; every other status is a warning in the log at startup and hourly (ComponentLicenceAssessment.Warns). Nothing is refused, and readiness and liveness never change. The licence-status explanation accompanies the messages:
The notice levels are informational and are below the standard free-production limits of [free limit] Endpoints for Router and [free limit] Designated Workflows for Workflow. Crossing a notice level does not itself establish that additional production permission is required and does not create a fee. The applicable LICENSE, any Order and other independently applicable permissions determine permitted use.
No status or message says that an Installation is unlicensed, non-compliant or expired, and none states free use. The status describes the records available to the local reader; it is not a determination of the customer’s legal entitlement or compliance, and the absence of a warning is not a compliance attestation.
Licence notice
The component also shows a licence notice: the Workflow engine in its SVG renders, its startup log and an hourly log line, and its health and licence API output; the Router in its logs and its health output. The notice is built from the evaluation (ComponentLicenceNotice), so its licensee comes only from a record that is authenticated and carries a line for the component and the Installation. The first case that holds applies:
| Case | Notice |
|---|---|
| An entry of the component’s line covers the running version. With several loaded records, the line of the highest applicable entry decides. | Licensed to [licenseeDisplayName] |
| No licence record is read. | No local licence record is loaded. See the applicable LICENSE and Order for permitted use. |
| Licence records are read, and none is in force (each is not loaded, carries no line for the component, or conflicts with another file of the same revision). | The local licence record could not be authenticated or matched to this Installation. |
| Standard-capacity records are in force, and the running build carries no first-publication date. | This build carries no first-publication date, so the licence records are not assessed. |
| Standard-capacity records are in force, and no standard-capacity entry covers the running version ([date] is the latest cutoff). | This record covers versions first published before [date]. It does not record paid coverage for this version. |
“Licensed to [licenseeDisplayName]” identifies the local licence record. It does not certify the author, content, authenticity or compliance of a workflow, diagram or other customer output. A diagram carrying the label may be shared; it is not a vendor-signed assertion about customer output.
The one-line form for logs and health output (ComponentLicenceNoticeFormatter.Format) follows the notice with its fields, separated by |:
Licensed to Northwind Traders Ltd. | component: router | licensed capacity: 1000 | covers versions first published before: 2027-11-03 | status: record-matches | licence records in force: 2, the highest applicable entry applies
Licensed to Fabrikam Clinics | component: workflow | permission: embedded | licensed capacity: unlimited | version scope: delivered-without-cutoff | effective date: 2026-12-01 | status: embedded-scope-not-established
The licensed capacity and the date appear only with a covering entry; an embedded line shows its permission, unlimited capacity, version scope and effective date instead of a cutoff. licence records in force appears when several records are in force, and licence files not loaded: [n] when a record that was read is not loaded. The licensee display name holds no control or markup character, so a log line takes it unchanged; an SVG or HTML renderer escapes &, ' and " as its format requires.
Issuance
The vendor issues records offline with the entitlement issuer’s issue-component command, from an order record stella.ops/componentLicenceOrder@v1 that lists the Installation’s lines: for each, its component, permission, licensee display name and entries (a standard-capacity entry with its blocks, cutoff and order reference; an embedded entry with its effective date and order reference). The issuer signs only with an active vendor-entitlement root, computes each standard-capacity entry’s total from its blocks, checks the signed record with the verifier above for every line, as a file and as a key, before releasing it, refuses a revision that drops or lowers an entry of the previous revision unless the order record names it as a correction, and records each issuance, with every line’s component, permission, licensee display name and order references, in its append-only ledger. It writes the .dsse.json file and prints the licence key. The customer sends its Installation identifier and the display name agreed for each line; there is no activation server. A missing, inaccurate or mismatched record is delivered or corrected on request, for standard-capacity and embedded lines alike, at the request of the customer or of an end customer named for an embedded line. Procedure: issuer runbook and fulfilment runbook.
Examples
examples/component-licence-file.v1.valid.jsonis a record for one Installation that matches the schema: a Router standard-capacity line for the licensee Northwind Traders Ltd. with two entries, and a Workflow embedded line.examples/component-licence-file.v1.invalid.jsonis refused: it carries a purchaser e-mail field the contract does not define, its Workflow standard-capacity entries recordunlimitedand are not in ascending order, and its Router embedded entry uses a distant cutoff date instead of the version scope.
The examples are payloads only. A real record is signed by the vendor entitlement key, which is never present in the product or in this repository; tests sign with ephemeral keys they generate.
Change log
| Date | Change |
|---|---|
| 2026-11-03 | v1 published. |
