Metering and Entitlement Spec
Status: Draft
Implements: ADR-OS-012 — Metering and the Billing Unit; Tenancy Spec, which names the usage ledger a control-plane table and assigns metering and entitlement to this document; Core Principles Principle 11 (Open by default, sovereign by design)
Authority for: the metered unit and its counting rule; which terminal states are billable; the usage ledger (what it records, how it is written, protected and corrected); the metering service’s obligations; usage reporting and the signed export; the distinction between a commercial entitlement and an entitlement flag; where entitlement is and is not enforced.
Not authority for: prices, tiers, allowances or contract terms, which are commercial and non-normative; rating and invoicing, which belong to the private synedrix-cloud class; table definitions, which stay in the Agent DB Schema; tenant isolation, which stays with the Tenancy Spec; identity, which stays with the Identity and Access Spec.
Overview
ADR-OS-012 decided what Synedrix OS counts and how it is protected: the metered unit is the governed goal, every deployment writes a tamper-evident usage ledger, self-operated deployments are billed by subscription with usage reported and never enforced, and usage pricing is reserved for the managed offering. This document turns that decision into rules a schema, a service and a test can be held to, and it defines the entitlement side that the ADR left to it.
It states what must be true. It sets no price. Where the ADR does not decide a point, this document says so and lists it in §10.
What is derived and what is proposed
| Derived from the ADR and the canon | Proposed here, for decision |
|---|---|
The metered unit and the billable terminal states; one ledger row per terminal goal; append-only, idempotent by source event, hash-chained, free of business content; written by a metering service and never by an agent; the same ledger in every deployment; no enforcement of usage in a self-operated deployment; the public-core and synedrix-cloud split; the entitlement flag as a business-instance value, inactive by default | The ledger’s exact fields and hash construction, the metering service’s failure behaviour, reconciliation, the export format, and the whole entitlement model beyond the flag: the commercial entitlement record, its states, the enforcement points and their failure behaviour |
Everything in the right-hand column is a proposal in a Draft.
1. Terms
| Term | Meaning |
|---|---|
| Governed goal | A Goal Instance that reaches a terminal state having run under a validated, activated task graph (§2) |
| Metered unit | One governed goal, counted once |
| Usage ledger | The tenant-owned, append-only, hash-chained record of every terminal goal (§3) |
| Billable goal | A governed goal whose terminal state is completed or partially_completed |
| Metering service | The platform component that reads terminal goal events and writes the ledger (§4) |
| Adjustment | A ledger row that corrects an earlier row without changing it |
| Commercial entitlement | A grant from the offering to a tenant, or to one of its business instances, to use a capability (§6) |
| Entitlement flag | The business-instance value that activates a Capability Module for that instance, inactive by default (Vocabulary) |
| Enforcement point | A place where the platform checks an entitlement and can refuse an action (§7) |
2. The metered unit
The metered unit is the governed goal. It is counted once, from the terminal
goal.*event of a Goal Instance (Event Taxonomy Standard §6.7). The count does not change with retries, Executions, Task counts or replans, because a Goal Instance has exactly one terminal state and one graph identity (Goal Model Spec, Task Graph Spec).A goal is governed when its task graph reached
active(task.graph.activated), which means graph validation, policy evaluation and the declared approval gates applied to it. A goal that never reached an active graph, such as one blocked at capability resolution, has no governed execution to meter. It is still recorded in the ledger (§3), withgovernedfalse andbillablefalse. Scheduled, system-initiated, test and simulation goals are not yet classified (§10). A goal whose record does not establishgoal_sourceastenant_requestis recorded asunclassifiedwithbillablefalse, so no usage is charged under a rule that has not been decided.For a goal that is governed and not excluded by §2.2, the terminal state decides whether it is billable. Because §2.2 excludes every goal whose
goal_sourceis nottenant_request, and that classification is undecided, no goal is billable under this version until it is decided (§10):Terminal event State billablegoal.completedcompletedtrue goal.partially-completedpartially_completedtrue goal.failedfailedfalse goal.blockedblockedfalse goal.cancelledcancelledfalse A billable goal has a unit quantity of exactly 1. There is no weight in this version. A
partially_completedgoal counts in full and afailedgoal counts for nothing, as ADR-OS-012 §4 decides.The unit is attributed to the goal’s
tenant_idandbusiness_instance_id. It is never attributed from request input.
3. The usage ledger
3.1 What a row records
| Attribute | Meaning |
|---|---|
ledger_id | The row’s identifier, server-generated |
tenant_id | The owning tenant, never platform |
business_instance_id | The goal’s business instance, or null if it has none |
goal_id | The Goal Instance |
goal_type, goal_type_version | What kind of goal it was |
terminal_state | One of the five terminal states |
governed | Whether the goal’s task graph ever reached active, taken from the goal’s task.graph.activated event (§2.2) |
goal_source | tenant_request when the goal’s own record establishes it was initiated by a request from the tenant; otherwise unclassified (§2.2) |
billable | Derived from governed, goal_source and §2.3: true only when governed is true, goal_source is tenant_request and §2.3 says the terminal state is billable |
quantity | For a unit row, 1 for a billable goal and 0 otherwise. For an adjustment row, the signed correction (§3.2.3) |
source_event_id | For a unit row, the event_id of the terminal goal.* event it came from; null for an adjustment row |
chain_seq | The row’s position in the tenant’s chain: gap-free, increasing, assigned at insert (§3.3) |
occurred_at | The terminal event’s timestamp |
deployment_mode, data_plane_operator | The mode and hosting variant when the goal ended, resolved as of occurred_at (§4.7) |
row_kind | unit for an ordinary row, adjustment for a correction |
corrects_ledger_id | For an adjustment, the row it corrects; otherwise null |
adjustment_key, reason | For an adjustment, its idempotency key and the recorded reason; otherwise null |
prev_hash, row_hash | The hash chain (§3.3) |
The ledger holds identifiers, counts and times. It never holds intent_payload, task inputs, outputs, success-condition detail or any other business content.
3.2 Rules
- One row per terminal goal.
UNIQUE (tenant_id, source_event_id)overunitrows makes a replayed event write nothing new. Every goal that terminates and whose tenant and as-of values can be resolved (§4.5, §4.7) has a row, including a goal that terminates asgoal.blockedbefore acceptance (§7). A goal that cannot yet be resolved has no row, but it is held and reported, never dropped (§4.8). - Append-only. A row is never updated or deleted. The writing role has
INSERTandSELECTonly. - Corrections are adjustments. An adjustment is a new row with
row_kind = adjustment, acorrects_ledger_id, a signedquantitythat cancels or restates the original, a nullsource_event_id, areasonand anadjustment_key.UNIQUE (tenant_id, adjustment_key)makes a replayed correction write nothing new. The original row stays. A tenant’s usage total is the sum ofquantityover itsunitandadjustmentrows.- An adjustment copies
tenant_id,business_instance_id,goal_id,goal_type,goal_type_version,terminal_state,deployment_modeanddata_plane_operatorfrom the row it corrects. It also copiesgovernedandgoal_source, unless the correction is to one of those facts, in which case it carries the corrected value and itsreasonsays so. Itsbillableis true when itsquantityis non-zero, because it changes the billable total; the §2.3 derivation applies tounitrows only. Itsoccurred_atis the time of the correction. - No double correction. The writer rejects an adjustment if the sum of
quantityover the corrected row and all its adjustments would fall below 0 or rise above 1, since a goal is counted at most once. A row recorded as 0 can therefore be corrected up to 1, for example when a goal recordedunclassifiedis later established astenant_request, and a row of 1 can be cancelled to 0, but neither can be corrected twice in the same direction.
- An adjustment copies
- Tenant-owned. The table follows the tenant boundary in the Agent DB Schema: non-null
tenant_id, forced row-level security and tenant-led keys. A tenant credential reads its own rows only and writes none. - Written by the metering service only. No agent writes the ledger, and the Audit & Lineage Agent stays read-only.
- Same ledger everywhere. A self-operated deployment runs the same schema and the same metering service, in a single-tenant control plane (Tenancy Spec §6.1).
3.3 The hash chain
Each tenant has one chain, ordered by chain_seq and not by occurred_at: a row written late, on catch-up, takes the next chain_seq when it is inserted. UNIQUE (tenant_id, chain_seq) holds. chain_seq is gap-free: it is assigned from a per-tenant counter row that is updated in the same transaction as the insert, so concurrent writers serialise on it and a rolled-back insert consumes no value. The counter lives in a separate per-tenant sequence table that the writing role may update, so the ledger table itself stays INSERT and SELECT only (§3.2.2). The first row’s prev_hash is a fixed genesis value for the tenant. For every later row, prev_hash is the previous row’s row_hash, and row_hash is a cryptographic hash over the row’s canonical attributes in a fixed order together with chain_seq and prev_hash. Anyone holding the rows can recompute the chain, and an altered, removed or out-of-sequence row breaks it from that point on.
A chain alone cannot show that the newest rows were deleted, because the remaining rows still link. The service therefore periodically records a signed checkpoint of the chain head (chain_seq and row_hash) in a store the ledger writer cannot modify, and verification fails if the ledger’s highest chain_seq is below the latest checkpoint’s, or if the row at that chain_seq has a different row_hash. Rows added after the latest checkpoint are covered by chain linkage only, so deletion of a tail that has not yet been checkpointed is not detectable until the next checkpoint. The chain and its checkpoints prove alteration and removal up to the last checkpoint. They do not prove that no row was withheld (§10).
The hash function, the canonical serialisation, the checkpoint interval and where checkpoints are retained are not fixed here. They are recorded as open in §10, because they affect interoperability of the export.
4. The metering service
- The service reads terminal
goal.*events from the event layer. It trusts only thetenant_idthe event layer stamped (Event Taxonomy Standard §2), never one in a payload. - It writes under the tenant’s context, as a role with
INSERTandSELECTon the ledger and no bypass of row-level security. It is a platform component and not an agent. It holds a tenant-bound service principal per tenant (Identity and Access Spec §5), soapp.tenant_idis derived from an authenticated credential as the Tenancy Spec §4 requires, and it never sets tenant context from event content. It writes each event with the credential of the tenant the event layer stamped. In a pooled control plane that credential’stenant_idclaim is mandatory. - Metering never blocks work. A goal reaches its terminal state whether or not the ledger row exists yet. The service writes the row asynchronously. If it is down, rows are written on catch-up from the durable event log, and the idempotency key makes the catch-up safe.
- Reconciliation. The service periodically compares terminal goal events with ledger rows for a tenant. A terminal event with no row is written. A ledger row whose
source_event_idmatches no event is reported as an integrity fault, never deleted. - A terminal event whose tenant cannot be resolved is not written to any tenant’s ledger and is reported as a fault.
- Late or duplicate events are expected. The same
source_event_idnever produces two rows (§3.2.1). - Attribution as of the event. Terminal
goal.*events carry neitherbusiness_instance_idnor the deployment mode or hosting variant. The service resolves them from the goal’s own record and from a versioned configuration history valid atoccurred_at, never from current configuration, so catch-up after a change gives the same answer as writing at the time. If a value cannot be resolved as ofoccurred_at, the row is not written with a guess or a null; it is held and reported as a fault (as in point 5). Where that history or an event field comes from is open (§10). - Held goals are visible. A terminal event held under point 5 or 7 is listed in a held-goals report available to the platform operator and, when its tenant is known, to that tenant. It is written, with the next
chain_seq, as soon as it can be resolved, and reconciliation (point 4) counts it as outstanding until then.
5. Usage in each deployment
| Deployment | The ledger is | Usage is |
|---|---|---|
| Managed (Modes 1 and 2) | Written by the pooled control plane | The basis of the usage charge, rated and invoiced in synedrix-cloud |
| Self-operated (Modes 1, 2 and 3) | Written locally by the same service | Recorded for the customer’s own reporting. Reported and never enforced |
5.1 Never enforced in a self-operated deployment
A self-operated deployment never refuses, throttles or disables a goal, an agent or a capability because of its usage. A customer who runs the stack controls the meter, and enforcing by removing capability would breach Principle 11 and ADR-OS-006 §3.3. Billing in that case rests on the subscription between the parties.
5.2 Air-gapped deployments
A Mode 3 deployment writes and verifies its ledger locally and makes no outbound connection for metering. If a contract calls for usage evidence, the signed export (§5.3) is produced on the instance and carried across by the customer.
5.3 The signed export
A self-operated control plane can export a range of ledger rows together with the chain material needed to verify them and a signature, so a third party can check the rows were neither altered nor removed within the range. The export is optional. It is not the primary billing mechanism. Its format, the signature scheme and the custody and rotation of the signing key are not fixed here (§10).
6. Entitlement
6.1 Two different things
The canon already has the entitlement flag: a business-instance-owned value that activates a Capability Module for that instance, inactive by default (Taxonomy Spec). Because the instance owns the value, the flag alone cannot be a commercial control. It records which modules the instance has switched on.
This document adds a separate concept, the commercial entitlement: a grant from the offering to a tenant, or to one of its business instances, to use a capability. The two relate as follows.
| Entitlement flag | Commercial entitlement | |
|---|---|---|
| Owned by | The business instance | The offering (managed), or the contract (self-operated) |
| Answers | Which modules is this instance running? | Which modules is this tenant allowed to run? |
| Enforced | By the instance’s own configuration | Only in the managed offering (§7) |
6.2 The commercial entitlement record
A commercial entitlement is a tenant-owned record, following the tenant boundary in the Agent DB Schema. It states:
| Field | Meaning |
|---|---|
| Entitlement identifier and version | A stable identifier for the grant, and a version number that increases by one with each change |
| Tenant and optional business instance | Who it applies to |
| Capability | A Capability Module, or another commercially gated capability, by identifier |
| State | active, suspended or ended |
| Valid from, valid until | An entitlement may be time-bound and ends at its end time without further action |
| Source | The contract or plan reference that granted it |
| Granted by, granted at | The party that made it |
- A change to a commercial entitlement is an audited event. The record is append-only: a change is a new version of the same entitlement identifier, and the history stays.
- State at an instant. A version takes effect at its valid-from time and supersedes earlier versions of the same identifier from that time, so a version dated in the future changes nothing until then. Valid-from must not decrease as the version number increases, and must not be earlier than the time the version is recorded, so there is no backdating and no version issued later can slot in before one already scheduled; the writer rejects a version that breaks either rule. The version in effect at an instant is the highest version whose valid-from is not later than the instant, and it is evaluated on its state and its valid-until. An earlier
activeversion never grants anything once a later version is in effect. A capability is covered at an instant when at least one entitlement naming it, for that tenant (and instance, if scoped), has a version in effect that isactiveand not past its valid-until. No such entitlement means not covered, and a failure to determine this is handled by §7 rule 3. - A commercial entitlement never gates a capability that Principle 11 promises (ADR-OS-006 §3.3). Only Capability Modules and other commercial value-add sit behind one.
- Who may create or change one in the managed offering is a platform and commercial decision. A tenant’s own roles do not change their own commercial entitlements.
7. Enforcement
Enforcement exists only in the managed offering, where the pooled control plane operates under Synedrix. Each point below is a place the managed control plane checks a commercial entitlement. This section is a proposal in a Draft.
| Enforcement point | Check | On refusal |
|---|---|---|
| Module activation | An instance’s entitlement flag for a Capability Module may be set active only if an active commercial entitlement covers that module for that tenant or instance | The flag change is rejected |
| Goal acceptance | A tenant with no covering commercial entitlement to run governed goals does not have new goals accepted | The goal terminates as goal.blocked, per the Event Taxonomy Standard §6.7 open item for goals that cannot be accepted, and a ledger row is written with billable false. The refusal is also recorded as an audited entitlement event naming the entitlement checked, because goal.blocked alone does not mark a commercial cause (§10) |
| Capability resolution | Each Capability Module a new goal needs is covered by a commercial entitlement at that moment, even if the instance’s entitlement flag is still active. Checked once, when the goal’s graph is first being activated | The goal terminates as goal.blocked with governed and billable false, even though it was accepted, and the refusal is recorded as an audited entitlement event as above. Goals that need no uncovered module are unaffected |
Rules for enforcement:
- Suspension never destroys. A suspended or ended entitlement blocks new activations and new goals that need the capability it covers (and, for the right to run governed goals, all new goals). It does not stop a goal whose task graph has reached
active, which runs to its terminal state (a replan that adds a module is not re-checked), and it never deletes a tenant’s data. - Export is always available. A tenant can always read its own ledger rows and export its own data, whatever the entitlement state.
- Fail closed, with a limit. If the managed control plane cannot determine a tenant’s entitlement state at an enforcement point, it refuses the new activation or goal. It does not guess in the tenant’s favour. How long a cached state may be used during an outage is not decided (§10).
- No enforcement of usage volume. ADR-OS-012 sets no allowance or quota, so this version has no enforcement point on goal count. It is added only by a later decision.
- Self-operated deployments have no enforcement points. The entitlement flag there is configuration, and no commercial entitlement is checked. The contract carries the commercial relationship.
8. Open-core placement
| Public core | Private synedrix-cloud class |
|---|---|
The ledger schema and its tenant boundary, the metering service, the billable rule, hash chaining and verification, the export format, the commercial entitlement record format | Rating, invoicing and billing, the managed offering’s enforcement points, prices and plans, and the platform’s authority to create commercial entitlements |
The ledger and the format of an entitlement are inspectable by the customer, so they sit in the public core. Turning them into money and refusing actions sit in the private class, as ADR-OS-006 §3.5 places billing.
9. Conformance
| Check | Expected result |
|---|---|
| A goal reaches a terminal state and its terminal event is replayed | One ledger row exists; the replay writes nothing |
| A goal is retried, replanned and its tasks executed several times, then completes | Exactly one billable row with quantity 1 |
A goal terminates failed, blocked or cancelled | A row exists with billable false and quantity 0 |
A goal terminates partially_completed | A billable row with quantity 1 |
| A goal never reached an active graph | A row exists with governed and billable false |
| A goal is refused at goal acceptance or capability resolution | The goal terminates goal.blocked; a row exists with billable false |
| A correction row is replayed | Nothing new is written |
| Ledger rows up to or before the latest checkpoint are deleted | Verification against the checkpoint fails |
| Only rows after the latest checkpoint are deleted | Not detected until the next checkpoint (stated limit) |
| A goal’s tenant or as-of values cannot be resolved | No row yet; the goal is in the held-goals report and is written when resolved |
| Two adjustments would together correct a row below zero | The second is rejected |
A goal has no established tenant_request source | Recorded unclassified with billable false |
| A future-dated entitlement version exists | It changes nothing until its valid-from; the earlier version applies until then |
| A row is written late, on catch-up | It takes the next chain_seq, and the chain verifies |
An entitlement is suspended after an earlier active version | Only the version in effect at that instant is evaluated; the module is not covered once the suspension takes effect |
| An entitlement version has a valid-from earlier than its predecessor’s, or earlier than its recording time | Rejected |
| A module’s entitlement ends while its instance flag stays active | A new goal needing that module is blocked at capability resolution |
The ledger role attempts UPDATE or DELETE on a ledger row | Denied |
| A ledger row is altered, removed or out of sequence | Chain verification fails from that row onward |
| A correction is needed | A new adjustment row references the original; the original is unchanged |
| A tenant credential reads or writes another tenant’s ledger rows | No rows returned; write denied |
| The ledger is scanned for business content | None found |
| The metering service is stopped while goals terminate, then restarted | Every terminal event has a row after catch-up, none duplicated |
| A terminal event whose tenant cannot be resolved | Written to no tenant’s ledger; reported as a fault |
| A self-operated deployment exceeds any usage figure | Nothing is refused, throttled or disabled |
| An air-gapped instance meters usage | No outbound connection is made |
| The managed control plane receives a module activation with no active commercial entitlement | The flag change is rejected |
| An entitlement is suspended while a goal is in flight | The goal runs to its terminal state; no new goal is accepted |
| The entitlement state cannot be determined at an enforcement point | The new activation or goal is refused |
10. Open items
- Hash construction. The hash function, canonical serialisation, checkpoint interval and checkpoint store for the chain are not fixed.
- Event-time attribution source. Whether
business_instance_id, deployment mode and hosting variant come from a new terminal-event field or a versioned configuration history (§4.7) is not decided. - Signing key custody (§5.3). Who holds the export signing key in a self-operated deployment, and how it is rotated without breaking verification, is not decided.
- Evidence of absence. The chain shows alteration, not a metering service that was stopped or rows that were never written. Whether that matters under a subscription is a commercial question.
- Complexity and weights. One unit per goal treats a trivial goal and a very large one alike. Tiers or weights by Goal Type are not designed.
- What counts as governed. Scheduled, system-initiated, test and simulation goals are not classified. §2.2 records them as
unclassified, withbillablefalse, until they are. The Goal Model Spec now records a goal’s origin asrequested_by_type(user,agent,workflow,external_systemorschedule), but which of those count astenant_requestis not decided, so until it is the metering service has no basis to settenant_request; deciding that mapping is a prerequisite for billing under this spec. - Goals rejected before acceptance. The Goal Model Spec has no rejection transition. Until it gains one, such a goal terminates as
goal.blocked(Event Taxonomy Standard §6.7), is recorded withbillablefalse, and this spec adopts that. How the event distinguishes an entitlement refusal from a malformed intent or an authority refusal is not defined. A dedicatedgoal.rejectedevent would replace it. - Entitlement outage behaviour (§7.3). How long a cached entitlement state may be used during a control-plane outage is not decided.
- Allowances and quotas. None exists. If one is adopted, it needs an enforcement point and a decision on what happens at the limit.
- Periods, disputes and retention. Billing period boundaries, time zones, the dispute process and how long ledger rows and entitlement history must be kept, including after a tenant ends, are not defined.
- Who creates a commercial entitlement. The platform’s authority and the audit it carries are not specified.
- Table definitions. The ledger and the commercial entitlement tables are not yet in the Agent DB Schema.
- Event names. Entitlement and metering-fault events need names in the Event Taxonomy Standard. None is proposed here.
Versioning
- Version: 0.1
- Supersedes: none — first version
- Related: ADR-OS-012 — Metering and the Billing Unit, Tenancy Spec, Synedrix Agent DB Schema, Synedrix OS — Core Principles
Related Documents
Core Principles and Canonical Reference
- Synedrix OS - Core Principles — Principle 11 forbids enforcing usage by removing capability from a self-operated deployment.
- Synedrix OS - Consolidated Canonical Reference — Consolidated synthesis; this spec is not yet indexed there.
Decisions
- ADR-OS-012 — Metering and the Billing Unit — The decision this document implements.
- ADR-OS-008 — Tenancy Model — Places the usage ledger in the control plane.
- ADR-OS-006 — Repository Topology and Open-Core Boundary — §3.3 and §3.5 set the open-core placement in §8.
Specifications this document depends on
- Synedrix OS — Tenancy Spec — The tenant boundary, the single-tenant control plane and the hosting variants.
- Synedrix OS - Agent DB Schema — The table classes and key rules the ledger and entitlement records follow.
- Synedrix OS — Goal Model Spec and Synedrix OS — Task Graph Spec — The terminal states and the graph identity the unit relies on.
- Synedrix OS - Event Taxonomy Standard — The terminal
goal.*events and the stampedtenant_id. - Synedrix OS - Taxonomy & Platform Spec — Capability Modules and the entitlement flag.
- Synedrix OS — Identity and Access Spec — Principals, roles and the service credentials the metering service must not be confused with.
- Synedrix OS - Responsibility Matrix — Tier ownership; metering and entitlement rows are added separately.
Synedrix OS · October 2026