Tenancy, Identity, and Metering
This page covers three interlocking operational concerns that govern who owns data, who may act on it, and how the platform accounts for work delivered: the tenancy model (control-plane pooling and data-plane isolation), the identity and access layer (principals, organisations, roles, credentials, and approval chains), and the metering unit (the governed goal, the usage ledger, and entitlement enforcement).
Governed by ADR-OS-008 — Tenancy Model (accepted), ADR-OS-012 — Metering and the Billing Unit (accepted), Tenancy Spec (Draft), Identity and Access Spec (Draft), and Metering and Entitlement Spec (Draft).
Tenancy Model
The control-plane / data-plane split
Synedrix OS divides the platform into two layers with different pooling strategies. The control plane holds all coordination and governance state — agent registry, work graph (runtime.* tables), policy, approvals, audit index, entitlement, usage ledger, and identity records — in one shared Postgres schema with a mandatory tenant_id column and row-level security (RLS) on every table. The data plane — n8n, LangGraph, Redis, Qdrant, Neo4j, QuestDB, Langfuse, PostHog, and the Grafana stack — is never pooled; each tenant has a separate deployment.
The Hookdeck Event Gateway (the inbound event path) is a proprietary managed-cloud service that neither Synedrix nor the customer deploys, so a per-tenant deployment is not feasible. Instead, it meets the never-pooled rule through dedicated per-tenant configuration: every source, connection, and destination in the gateway belongs to exactly one tenant and is never shared.
flowchart TD
subgraph CP["Pooled Control Plane\n(Shared Postgres under RLS)"]
ARP["Agent Registry"]
WGP["Work Graph\n(runtime.*)"]
POL["Policy"]
APR["Approvals"]
AUD["Audit Index"]
ENT["Entitlement"]
LED["Usage Ledger"]
IDP["Identity Records"]
end
subgraph DP1["Data Plane — Tenant A"]
N1["n8n"]
LG1["LangGraph"]
R1["Redis"]
Q1["Qdrant"]
NR1["Neo4j"]
QD1["QuestDB"]
OBS1["Observability"]
end
subgraph DP2["Data Plane — Tenant B"]
N2["n8n"]
LG2["LangGraph"]
R2["Redis"]
Q2["Qdrant"]
NR2["Neo4j"]
QD2["QuestDB"]
OBS2["Observability"]
end
N1 -->|Authenticated outbound\nwith tenant_id| CP
LG1 --> CP
N2 --> CP
LG2 --> CP
EP["Hookdeck Event Gateway\n(Vendor-managed, per-tenant config)"] -->|Inbound events| N1
EP --> N2
HO["Hookdeck Outpost\n(Per-tenant egress)"] --> CP
Every control-plane table carries a non-null tenant_id. On tenant-owned tables it is the owning tenant. On mixed tables the reserved value platform marks a shared reference row (e.g., OS agent definitions os-agt-001 to os-agt-012). A table without an applicable permissive policy denies all access, and RLS is forced — even the table owner is subject to it, because tenant-facing services connect as a non-owner application role.
The tenant hierarchy
A tenant is the contracting organisation and the unit of isolation, entitlement, and metering. A business instance belongs to exactly one tenant, and a tenant has one or more business instances. A business instance cannot be moved between tenants.
| Scope key | Meaning |
|---|---|
tenant_id | The outer scope key. Opaque, immutable once issued, and never reused. |
business_instance_id | The inner scope key. Unique within a tenant, meaningful only together with its tenant_id. |
tenant_id is the leading column of every composite index, every composite unique key, and every cache, queue, or namespace key derived from control-plane tables. business_instance_id may be null for tenant-level records; such records reference their parent through a separate (tenant_id, id) key, because Postgres MATCH SIMPLE does not enforce a composite foreign key when any referencing column is null. A composite key that includes business_instance_id must be paired with a CHECK constraint ensuring the child’s business_instance_id is non-null whenever the reference column is set.
Cross-tenant access is never a scope setting. It happens only through a path that policy approves and that writes an audit record.
Tenant-scoping rules
| Rule | Requirement |
|---|---|
tenant_id mandatory | Every control-plane table has a non-null tenant_id column. |
| Forced RLS | FORCE ROW LEVEL SECURITY is enabled on every tenant-owned table. Tenant access is checked by an AS RESTRICTIVE policy, ANDed with all role policies. |
| Credential origin | The tenant for a request comes from the authenticated credential’s tenant_id claim. It is never taken from request input, a header, or a body field. |
| Index leading | tenant_id leads every composite index and every cache/queue/namespace key. |
| No bypass role | No tenant-facing code path uses a role that bypasses row-level security. |
| Composite foreign keys | References between tenant-owned tables use (tenant_id, referenced_id) to a matching (tenant_id, id) unique key, preventing cross-tenant references. |
| Platform reference data | Shared definitions (OS agent registry entries, domain-pack agent definitions) carry the reserved value platform in tenant_id. Tenant-owned rows never contain platform. |
Cross-Plane Connectivity
A data plane may sit inside a customer’s private network, so the control plane never opens a connection into it. The data plane initiates every connection to the control plane. Work reaches the data plane by polling or by a stream the data plane opens, never by inbound call.
Every connection is authenticated, and the credential carries the tenant’s tenant_id claim. The mechanism belongs to the Identity and Access Spec.
While the control plane is unreachable, the data plane fails closed: it takes no action that has a side effect on a production system, whether a newly received request or work already in flight. Read-only work may continue. Events it produces are held locally in a durable, ordered buffer and delivered when the connection returns. An intent (not an event) is recorded before a side-effecting step; once the step succeeds, fails, or is rejected, the fact event with that outcome is persisted. A crash between the two leaves an intent without an outcome, which recovery resolves by emitting an outcome event, never by leaving a false record. Delivery is ordered per correlation_id in the order emitted. If the buffer reaches its limit, work pauses. Whether policy may be cached during an outage is undecided.
Deployment Variants
| Variant | Data plane operator | Control plane | Available modes |
|---|---|---|---|
| Self-operated | The customer | Same control-plane implementation, run locally as a single tenant with full RLS | Modes 1, 2, and 3 |
| Managed | Synedrix (dedicated per tenant) | Pooled, multi-tenant | Modes 1 and 2 |
A self-operated control plane runs the same schema, the same RLS, and the same code as the pooled one, with exactly one tenant. There is no separate single-tenant code path, so the isolation behaviour is the one that is tested. In Mode 3 (air-gapped) it makes no outbound connection to a pooled control plane.
A business instance declared deployment_mode: air-gapped must declare data_plane_operator: customer. A configuration that does not is a validation error enforced at business instance configuration time.
Identity and Access
Principals
A principal is anything the platform authenticates and records as the actor in an audit record.
| Principal | What it is | Authority comes from |
|---|---|---|
| User | A human who belongs to an organisation | The roles assigned to them |
| Service principal | A non-human credential holder (e.g., a data plane’s connection to the control plane, or an integration) | The scopes granted to it |
An agent is not an identity principal. An agent’s authority is its registry entry (decision_scope, tools_allowed, escalation_path) and the Tool Authorization Model. The identity layer governs the humans around agents and the services that connect the planes.
Organisations, users, and the organisation-tenant identity
An organisation is the tenant. Creating an organisation creates the tenant record and its tenant_id. There is no second organisation concept. A user is created inside an organisation and belongs to it. Every user record carries the organisation’s tenant_id and is protected by the same RLS as any other tenant-owned record. A user may hold roles across the organisation’s business instances. Removing a user never deletes the audit records that name them.
Authentication
Human access is authenticated through an identity provider. The platform accepts federation from an external provider and must also run with a provider hosted inside the deployment. In Mode 3 only an in-deployment provider is permitted.
The platform binds the tenant, not the provider. A token is validated for issuer, signature, audience, and expiry. The platform then maps the provider subject to a user record it holds, and derives the tenant_id and role assignments in force from its own membership and assignment records. It never accepts a tenant or role asserted by the provider’s token as authoritative. The credential the platform issues carries at least the subject, the tenant_id, and the role assignments in force. A token whose subject has no membership in the claimed organisation is rejected. Credentials are short-lived and re-validated against current assignments; removing a role or suspending a user takes effect immediately.
Service credentials
A service principal belongs to one tenant. Its credential carries that tenant’s tenant_id claim. A credential is scoped to the narrowest set of actions it needs — a data plane’s credential lets it open the outbound connection to the control plane and carry only the traffic the Tenancy Spec describes. Credentials are never shared between services and are rotatable without downtime. Secret values live in the business instance’s secret store (e.g., vault://, sm://, aws-sm://, gcp-sm://, azure-kv:// references) and never in a control-plane table; the control plane holds references and metadata only.
Control roles
The six OS-level control roles are fixed by ADR-OS-015. Each is an accountability surface, not a job title.
| Role | May act on |
|---|---|
| human operator | Agent and workflow registration, edge cases, resumption after a timeout |
| human risk owner | The active policy ruleset; governed policy changes (never reverses a Policy Guard block) |
| human domain steward | One business function’s domain-pack behaviour, KPI vocabulary, and approval points |
| human compliance owner | Audit findings requiring action, erasure initiation, retention classes |
| senior reviewer | Contested decisions and timed-out approvals |
| business instance owner | approver_role_assignments, thresholds, hitl_timeout_minutes, notification endpoints, deployment mode, entitlement flags, secrets |
Domain packs and business instances use their own role names (such as trading-operator). Which control roles such a name maps to is a business-instance decision. A role name is a label for an assignment; it grants nothing the control roles do not.
Role assignment
A role assignment is a record, owned by the business instance and changed only by a business instance owner whose own assignment covers the target scope. The assignment states the principal, the role (one of the six control roles or an instance role name), the scope (tenant, one business instance, or one business function), the valid-from and valid-until times, and who granted it and when.
Assignment rules:
- An assignment never reaches across tenants.
- At least one business instance owner must exist for every business instance. The last one cannot be removed.
- Assigning or removing a role is an audited event.
- An owner scoped to one business instance may create, change, or remove assignments only inside that instance and may not create a tenant-scoped assignment or one for another instance. An owner whose assignment is tenant-scoped may do so anywhere in the tenant. Authorisation is checked against the target scope of the change, not only the caller’s role.
- No gap in ownership. At every moment each business instance has at least one currently valid business instance owner. The platform rejects any change that would leave a period with none, including setting an end time, a suspension, or a removal on the last currently valid owner. Role mutations go only through
SECURITY DEFINERstored procedures (grant_role_assignment,end_role_assignment) that enforce this rule atomically.
Delegation
A role holder who will be unavailable may delegate their approval authority for a defined period. A delegation has an end time and can be revoked at any moment. A delegate must be a user in the same tenant; a delegation cannot cross a tenant or grant more than the delegator holds, and it cannot be re-delegated. A delegation does not create a role assignment: it lets the delegate act as the delegator for approvals only, not reassign roles or touch secrets. A decision made under delegation records both the person who decided and the person on whose behalf.
Approval Chains
A single reviewer is the simplest chain. Where a workflow needs more, its approval_requirements declare an ordered list of steps.
| Step field | Meaning |
|---|---|
reviewer_role | The role whose holders may decide this step |
| Mode | any-of: one holder decides. all-of: every holder in scope decides |
| Timeout | After which the step escalates to senior reviewer, then human operator |
The HITL Gateway Agent (os-agt-004) routes each step to holders of the step’s role in scope, captures the structured decision, enforces the timeout, and returns a binding outcome. It does not interpret or override a decision.
A chain approves only if every step approves. One rejection ends the chain as rejected. A timeout at any step escalates and does not approve. A human approval is an input to execution, never a replacement for Policy Guard: both must pass, and a human cannot approve past a Policy Guard block. The remedy is a governed policy change followed by re-proposal as a new invocation.
Separation of duties. A user who initiated a request cannot approve it, in any step and by any role or delegation. This is enforceable once the approval request carries the authenticated initiating principal (requester_principal_id), and until then it is a requirement on the HITL Gateway contract. A decision made under delegation records both the decider and the principal on whose behalf.
Approval decisions are written to the append-only approval_decisions table, with a trigger (check_approval_decision) that binds each decision to the authenticated session and validates that the deciding_user_id matches the session’s app.user_id, the user is active, and holds the required role (or valid delegation) at decided_at. All role mutations go through SECURITY DEFINER stored procedures (grant_role_assignment, end_role_assignment) that write to an audit-only role_assignment_events table, so every grant and end of a role assignment is immutably recorded with the actor and timestamps.
flowchart LR
subgraph ApprovalChain["Approval chain execution"]
A["HITL Gateway\nroutes to reviewers"] --> B{"Step 1"}
B -->|reject| E["Chain rejected"]
B -->|approve| C{"Step 2"}
C -->|reject| E
C -->|approve| D["Chain approved"]
B -->|timeout| F["Escalate to senior reviewer"]
F --> C
end
subgraph Enforcement["Enforcement checks"]
B -->|separation of duties| G["requester_principal_id\n≠ reviewer"]
C -->|separation of duties| G
G -->|violation| H["Escalates"]
D --> I["Policy Guard check\nstill required"]
I -->|block| J["Execution blocked\nPolicy Guard stands"]
I -->|allow| K["Execute"]
end
Metering: The Governed Goal
The metered unit
The metered unit is the governed goal: one Goal Instance that reaches a terminal state having run under a validated, activated task graph. It is counted once, from the terminal goal.* event. 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.
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 — for example, one blocked at capability resolution — was not executed under governance.
Billable terminal states
| Terminal event | State | billable |
|---|---|---|
goal.completed | completed | true |
goal.partially-completed | partially_completed | true |
goal.failed | failed | false |
goal.blocked | blocked | false |
goal.cancelled | cancelled | false |
A billable goal has a unit quantity of exactly 1. There is no weight in this version. The unit is attributed to the goal’s tenant_id and business_instance_id, never from request input.
Every terminal goal is written to the usage ledger so usage and outcomes can be analysed, including goals that were never governed.
No goal is billable until the goal_source classification is decided. The Metering and Entitlement Spec records that goal_source must be tenant_request for a goal to be billable, but the mapping of requested_by_type to tenant_request (and the classification of scheduled, system-initiated, test, and simulation goals) is undecided; until it is decided, no goal is billable under this specification.
The Usage Ledger
The usage ledger is a tenant-owned, append-only, hash-chained record of every terminal goal, stored in the control plane.
| Attribute | Meaning |
|---|---|
ledger_id | Server-generated row identifier |
tenant_id | The owning tenant, never platform |
business_instance_id | The goal’s business instance, or null |
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 task graph reached active |
goal_source | tenant_request or unclassified (§2.2) |
billable | Derived: true only when governed, goal_source is tenant_request, and the terminal state is billable |
quantity | 1 for a billable goal, 0 otherwise |
source_event_id | The event_id of the terminal goal.* event |
chain_seq | Gap-free, increasing position in the tenant’s chain |
occurred_at | The terminal event’s timestamp |
deployment_mode, data_plane_operator | Hosting configuration as of occurred_at |
row_kind | unit or adjustment |
corrects_ledger_id, adjustment_key, reason | For adjustments only |
prev_hash, row_hash | The hash chain |
Rules
- One row per terminal goal.
UNIQUE (tenant_id, source_event_id)over unit rows makes a replayed event idempotent. Every goal that terminates has a row, including one that terminates asgoal.blockedbefore acceptance. - 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 signedquantity, and areason.UNIQUE (tenant_id, adjustment_key)prevents double corrections. A tenant’s usage total is the sum ofquantityover its unit and adjustment rows. - Tenant-owned. Non-null
tenant_id, forced RLS, 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.
- Same ledger everywhere. A self-operated deployment runs the same schema and the same metering service, in a single-tenant control plane.
The hash chain
Each tenant has one chain, ordered by chain_seq and gap-free — assigned from a per-tenant counter row in the same transaction as the insert, so concurrent writers serialise on it. The first row’s prev_hash is a fixed genesis value. 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 together with chain_seq and prev_hash. An altered, removed, or out-of-sequence row breaks the chain from that point on.
The service periodically records a signed checkpoint of the chain head (chain_seq and row_hash) in a store the ledger writer cannot modify, so deletion of rows up to the last checkpoint is detectable. The hash function, canonical serialisation, checkpoint interval, and key custody are not fixed here.
The Metering Service
The metering service reads terminal goal.* events from the event layer and writes ledger rows. It trusts only the tenant_id the event layer stamped, never one in a payload. It holds a tenant-bound service principal per tenant, so app.tenant_id is derived from an authenticated credential as the Tenancy Spec requires, and it never sets tenant context from event content.
Key behaviors:
- Metering never blocks work. A goal reaches its terminal state whether or not the ledger row exists. If the service is down, rows are written on catch-up from the durable event log, and idempotency prevents duplicates.
- Reconciliation. The service periodically compares terminal goal events with ledger rows. An event with no row is written. A row whose
source_event_idmatches no event is reported as an integrity fault. - Attribution as of the event. Terminal events carry neither
business_instance_idnor the deployment mode or hosting variant. The service resolves these from the goal’s own record and a versioned configuration history valid atoccurred_at, never from current configuration. - Held goals are visible. A terminal event whose tenant or as-of values cannot be resolved is listed in a held-goals report and written with the next
chain_seqas soon as it can be resolved.
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 |
Usage is 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. The subscription is a contract between the parties. The per-instance entitlement flag that keeps a Capability Module inactive by default is a public-core mechanism enforced in every deployment, self-operated included.
For air-gapped Mode 3, the ledger is written and verified locally and makes no outbound connection for metering. If a contract calls for usage evidence, the signed export is produced on the instance and carried across by the customer.
Entitlement Model
Two different concepts
The canon already has the entitlement flag: a business-instance-owned value that activates a Capability Module for that instance, inactive by default. 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.
| 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 |
The commercial entitlement record
A commercial entitlement is a tenant-owned record stating the identifier, tenant and optional business instance, the capability covered, the state (active, suspended, or ended), valid-from and valid-until times, source, and grantor. A change is an audited event: the record is append-only, with a version number that increases on each change. A version dated in the future changes nothing until then.
A commercial entitlement never gates a capability that Principle 11 promises. Only Capability Modules and other commercial value-add sit behind one.
Enforcement points (managed offering only)
| 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 | The flag change is rejected |
| Goal acceptance | A tenant with no covering entitlement to run governed goals does not have new goals accepted | The goal terminates as goal.blocked; a ledger row with billable false is written |
| Capability resolution | Each Capability Module a new goal needs is covered at the moment of activation | The goal terminates as goal.blocked with governed and billable false |
Enforcement rules: a suspended or ended entitlement blocks new activations and new goals but does not stop a goal whose task graph has reached active. A tenant can always read its own ledger rows and export its own data. If the managed control plane cannot determine a tenant’s entitlement state at an enforcement point, it refuses the new activation or goal.
Self-operated deployments have no enforcement points. The entitlement flag there is configuration, and the contract carries the commercial relationship.
flowchart TD
subgraph Managed["Managed Offering — Entitlement Flow"]
A["Capability Module\nactivation request"] --> B{"Active commercial\nentitlement?"}
B -->|no| C["Flag change rejected"]
B -->|yes| D["Flag set active"]
E["Goal acceptance"] --> F{"Covered to run\nmanaged goals?"}
F -->|no| G["goal.blocked\nbillable: false"]
F -->|yes| H["Goal accepted"]
H --> I["Capability resolution\nat activation"]
I --> J{"All needed\nmodules covered?"}
J -->|no| K["goal.blocked\ngoverned: false\nbillable: false"]
J -->|yes| L["Graph activated"]
end
subgraph SelfOp["Self-Operated — No Enforcement"]
M["Capability Module\nflag set active"] --> N["Always permitted\n(configuration only)"]
O["Goal accepted"] --> P["Always permitted\n(contract only)"]
end
Open-Core Boundary
| Belongs in the public core | Belongs in the private synedrix-cloud class |
|---|---|
The control-plane schema, tenant_id, RLS, tenant-scoped query paths, single-tenant mode, data_plane_operator declaration and validation | Pooled control-plane operation, tenant provisioning and lifecycle |
| The usage ledger schema, the metering service, the eligibility rule, hash chaining, signed-export format | Rating, invoicing, billing, usage/quota enforcement for managed offering, pricing |
| The commercial entitlement record format | The platform’s authority to create commercial entitlements, prices, and plans |
Tenancy enforcement (RLS, scoping) lives in the public core; pooled operation and commercial metering live in the private class. 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.
Conformance Highlights
| Check | Expected result |
|---|---|
| Credential for tenant A queries tenant B’s rows | Zero rows returned; no confirmation of existence |
A tenant-facing role has no app.tenant_id setting | Access denied, even on mixed tables |
| A role has an RLS policy but no permissive policy for a command | Access denied |
A request body contains a tenant_id differing from the credential | The claim is used; the input is ignored |
| A data plane loses the control plane and reaches a side-effecting step | The action pauses; read-only work continues |
| A terminal event is replayed after a goal completes | One ledger row exists; the replay writes nothing |
| Two adjustments would together correct a row below zero | The second is rejected |
| A self-operated deployment exceeds any usage figure | Nothing is refused, throttled, or disabled |
| An entitlement is suspended while a goal is in flight | The goal runs to terminal; no new goal is accepted |
| The managed control plane cannot determine entitlement state | The new activation or goal is refused |
Open Items
- Hash construction. The hash function, canonical serialisation, checkpoint interval, and checkpoint store for the chain are not fixed.
- Signing key custody. Who holds the export signing key in a self-operated deployment, and how it is rotated without breaking verification, is not decided.
- 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 is not decided. - Evidence of absence. The chain shows alteration, not a metering service that was stopped or rows that were never written.
- 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; until the mapping of
requested_by_typetotenant_requestis decided, no goal is billable. - Goals rejected before acceptance. Such a goal terminates as
goal.blockedwithbillablefalse. How the event distinguishes an entitlement refusal from a malformed intent is not defined. - Entitlement outage behaviour. How long a cached entitlement state may be used during a control-plane outage is not decided.
- Allowances and quotas. None exists. If adopted, it needs an enforcement point.
- Periods, disputes, and retention. Billing period boundaries, time zones, the dispute process, and retention periods for ledger rows are not defined.
- Identity provider product. Whether Supabase auth, an external provider, or both fit, and how that squares with the pooled control plane, is not decided.
- Membership in more than one organisation. Allowing it is convenient for consultants and complicates RLS and credential claims.
- Assurance and session limits. Credential lifetime, revocation mechanism, and multi-factor requirements for approval surfaces are not set.
- Delegation limits. Maximum delegation length and whether all-of approval steps can be delegated are not set.
- Event names. Identity and approval events (assignment, delegation, authentication outcomes, support access, entitlement changes, metering faults) need names in the Event Taxonomy Standard.
- Platform reference data. The ADR requires RLS on every control-plane table; the shared agent registry uses
platformintenant_id. Confirmation that the ADR’s “every table” is read this way is needed. - Agent DB Schema Reference class. The schema’s Reference table class (absent
tenant_id) conflicts with the tenancy rule that every table carries a non-nulltenant_id. It is unused and should be removed. - Tenancy of the Hookdeck Event Gateway. Dedicated per-tenant configuration is the isolation mechanism. Whether that is sufficient is tracked.
- Outage buffer limit. How large the durable local buffer may grow, how long events may be held, and operator notifications when it fills are not set.
- Policy caching during a control-plane outage. Whether and how policy may be cached is undecided.
- Self-operated data plane with pooled control plane. A customer who owns the data plane but wants a managed control plane is not defined.
- Identifier format.
tenant_idis opaque, immutable, never reused. Its format and relationship to the Naming Convention Spec are not set. - Table definitions. The ledger and commercial entitlement tables are not yet in the Agent DB Schema.
Related Pages
- Architecture → Governance and Controls — Policy Guard, HITL Gateway, autonomy levels, and the tool authorization model.
- Architecture → Agent Runtime & Orchestration — n8n, LangGraph, and the execution model.
- Architecture → Overview — The 12 layers, 12 principles, and the ownership model.
- Operations → Tech Stack & Deployment — The 12 platform components, the three deployment modes, and the Hookdeck exception.