Glossary
One stable anchor per term. Cite a definition by linking to its slug — docs.planetb2b.com/reference/glossary#tenant — instead of restating it.
Terms are alphabetised. Each entry also lists where the term is implemented (schema, module, package) and links to the rule, spec, or module page where it lives in detail.
audit-entry
A row in the audit schema representing a security-relevant action. Written via @constellation-platform/audit (auditCritical() for security-critical mutations — writes the row AND publishes an audit.entry.created outbox event in the same transaction) or @constellation-platform/db (auditAction() for routine entries — writes the row only). Audit entries form a hash chain — each row's hash field includes the previous row's hash, so any tamper produces a verifiable break.
- Schema:
audit - Rule:
audit-critical - Full design: Universal Audit Log Specification (split into Architecture, Tamper-evident chain, Privacy & redaction, Shared package API, Query & access control, Operations, Usage & rollout)
catalog-entry
A product or service listing in the Catalog module. Each entry has a lifecycle (draft → published → archived), a position in the configured taxonomy (see taxonomy), and may carry pgvector embeddings for semantic search.
- Schema:
catalog.entries - Module: Catalog
- Events:
catalog.entry.created,catalog.entry.updated,catalog.entry.deleted,catalog.entry.status_changed— see Domain events.
caveats
JWT claim listing data-handling caveats (e.g. NOFORN, REL TO …). Optional defence-extension claim — present only on tenants that opt into classification-aware access control. See classification and clearance.
- Source of claim: JWT issued by the auth provider.
- Type:
string[].
classification
Sensitivity level carried by a resource — a wiki page, a row on a classification-scoped table. Combined with clearance and caveats, enables row-level access control on tenants that opt in.
INTERNAL is being inserted at rank 1 across the platform, and it reaches each resource store on its own schedule. The audit surface already carries it; the columns typed by the wiki.classification domain and the Project Tracker initiative store still constrain classification to the original four (UNCLASSIFIED, RESTRICTED, CONFIDENTIAL, SECRET) until the last links of that rollout land. Check the storage constraint for the surface you are working on rather than assuming a single platform-wide vocabulary.
- Per-row:
classificationcolumn on classification-scoped tables;wiki.classificationdomain for wiki pages. - Not a JWT claim. A token carries the principal's clearance, never a classification — the resource supplies that side of the comparison.
clearance
Sensitivity level carried by a principal — the maximum classification that person may read. Compared against the resource's classification by rank, never by name: public.clearance_rank() evaluates it inside the database for RLS, and CLEARANCE_RANK in @constellation-platform/auth-core evaluates it in application code. The two are held identical by test.
The ladder is five values, ascending:
UNCLASSIFIED < INTERNAL < RESTRICTED < CONFIDENTIAL < SECRET
The two evaluators handle an unrecognised level differently, and both are safe but not equivalent:
- In SQL,
clearance_rank()returns-1, sorank(resource) <= rank(principal)is false for every resource carrying a recognised classification, and the principal reads nothing. - In application code, an unrecognised
clearanceJWT claim is discarded during token validation rather than carried through;hasSufficientClearance()then treats the absent value asUNCLASSIFIED, so the principal reads unclassified resources and nothing above them.
Neither path grants elevated access, but do not rely on "an unknown clearance sees nothing" outside the database.
An unrecognised value on the resource side fails the other way, and this is the one that matters. clearance_rank() returns -1 there too, and -1 <= rank(anything) is true — including -1 <= -1. A row whose classification the ladder cannot rank is therefore readable by every principal, UNCLASSIFIED included. Nothing in the comparison prevents it; what prevents it is that the value cannot be stored. What makes the value unstorable is a per-store constraint, and the stores do not all enforce it the same way — Wiki types its columns with the wiki.classification domain, while Project Tracker puts a plain CHECK on projects.initiatives. So there is no single place to read, and no list here would stay true. Ask the database for every enforcement point at once:
-- Schema-qualified: this database is schema-per-module, so a bare relation
-- name does not identify which module owns the constraint.
SELECT coalesce(tn.nspname || '.' || t.relname,
'DOMAIN ' || dn.nspname || '.' || ty.typname) AS enforced_on,
c.conname
FROM pg_constraint c
LEFT JOIN pg_class t ON t.oid = c.conrelid
LEFT JOIN pg_namespace tn ON tn.oid = t.relnamespace
LEFT JOIN pg_type ty ON ty.oid = c.contypid
LEFT JOIN pg_namespace dn ON dn.oid = ty.typnamespace
WHERE pg_get_constraintdef(c.oid) LIKE '%UNCLASSIFIED%'
ORDER BY 1;
That is why a new tier is taught to every evaluator for a store before that store is allowed to accept it, and never the reverse: a store that accepted a tier its evaluator did not yet know would silently publish those rows to everyone.
"Every evaluator" is deliberate — public.clearance_rank() is not the only one. Project Tracker gates initiative consults through an independent ladder in packages/platform/coordinator/src/clearance.ts, whose own documentation records the same hazard from the other side: a classification missing from that list parses to UNCLASSIFIED and is then never masked for anyone. Teaching SQL a tier while leaving that map behind fails open just as surely. Before widening a store, enumerate what reads it.
The invariant is specifically about rank-compared stores, not about every column named classification. audit.audit_entries.classification, for instance, is unconstrained text, and its RLS gates on explicit literals rather than by rank. Under the tenant, org and own audit scopes the policy admits only NULL or UNCLASSIFIED, so an unrecognised value is excluded rather than admitted — the opposite of the rank-compared case. Under the classified scope the policy returns true regardless of classification, so such a reader sees it anyway. Before relying on any of this, check how the store you are working on actually compares.
- JWT claim:
clearance(optional defence extension). - Stored:
identity.users.clearance, owned by Directory. See the Directory module reference.
correlation-id
UUID propagated across requests, audit entries, and outbox events that all derive from one user-initiated action. At the entry route handler, getCorrelationId(request) extracts the value from the incoming x-request-id header — or generates a new UUID if the header is absent — and the handler then threads it explicitly through tools, services, repositories, and into the audit + outbox helpers. Critical for forensic replay: given a correlation ID, you can find every audit row + every published event that originated from one action.
- Source:
x-request-idheader on the incoming request, or a generated UUID when the header is missing. - Constant:
CORRELATION_HEADERfrom@constellation-platform/db/correlation. - Read with:
getCorrelationId(request)from@constellation-platform/auth-next(re-exports the helper from@constellation-platform/db/correlation).
criterion
A single acceptance criterion attached to a deliverable or task. Reviewers tick criteria off when accepting; rejecting a deliverable requires citing which criteria failed.
- Schema:
projects.deliverable_criteria,projects.task_criteria.
delegate
A user who has been granted permission to act on another user's behalf within a specific scope (e.g. approve deliverables for a project while a reviewer is on leave). Implemented in the Project Tracker module.
- Schema:
projects.delegations.
deliverable
A reviewable artefact submitted within a project — typically a document, file, or marked-complete checklist. Has a status lifecycle: DRAFT → SUBMITTED → UNDER_REVIEW → ACCEPTED | REJECTED | REVISED. Each deliverable has zero or more criteria.
- Schema:
projects.deliverables,projects.deliverable_criteria. - Events:
projects.deliverable.*— see Domain events.
domain-event
A typed, append-only message published to the outbox representing a fact about something that already happened. Naming follows <schema>.<entity>.<verb-past-tense> (see event-naming). Cross-module communication goes through events, not HTTP calls (see no-cross-app-imports).
- Publisher:
publish(tx, ...)from@constellation-platform/events. - Index: Domain events.
epic
A task with issueType = EPIC that groups base-level tasks via their epicId (Jira's Epic Link / parent). The epicId edge is tenant-scoped: as of PT-446 an epic may parent children in a different project within the same tenant, so an epic can span projects. On a task the caller can read, a referenced epic that lives in a project the caller cannot read is returned redacted — sentinels + isCrossProject: true, never the real key/title (the PT-283 reference-redaction pattern). How unreadable tasks in a cross-project listing are handled depends on the endpoint: the per-project task lists and the label goal view omit them, whereas the tenant-scoped cross-project epic-children listing (GET /api/tasks/:epicId/children, PT-491 — shipped) masks each unreadable child via redactMembersOutsideProjects (opaque id + isCrossProject: true sentinels, key/title withheld) so counts stay honest. The epic edge is distinct from the same-project sub-task edge (parentTaskId, terminal at one level). The project-scoped GET /api/projects/:id/tasks?epicId= filter lists an epic's children within one project; the PT-491 route above is its tenant-scoped cross-project complement (MCP list_epic_children / pt list-epic-children). See Work hierarchy & links for the full model.
- Schema:
projects.tasks(epic_id,issue_type). - Introduced: PT-375 (same-project); widened cross-project in PT-446.
- See: Work hierarchy & links for the full Initiative → Epic → Story → Sub-task model and cross-project redaction rules.
gate
A review checkpoint between stages in a stage-gate workflow. Reviewers vote PASS, FAIL, WAIVE, or HOLD; PASS auto-progresses the stage.
- Schema:
projects.gates,projects.gate_reviews. - Events:
projects.gate.reviewed.
hash-chain
Cryptographic chain over the audit schema. Each row's hash is H(prev_hash || canonical_payload). Any tampered or out-of-order row breaks the chain at the next verification scan. See audit-entry and the Tamper-evident chain page of the audit-log spec.
idempotency
Property of an event handler that processing the same event multiple times yields the same result as processing it once. Required because the outbox dispatcher may re-deliver an event if the subscriber acknowledged late. Idempotency is achieved with a deduplication table keyed on event_id or by upserting on a natural key.
- Pattern:
events.processeddeduplication table per subscriber.
issue
A bug, defect, support ticket, or service request in the Project Tracker module. Distinct from a task — issues are unplanned (something broke), tasks are planned (something was scheduled). Issues have severity (CRITICAL | HIGH | MEDIUM | LOW), an SLA-based first-response and resolution deadline, and may be promoted to a task.
- Schema:
projects.issues. - Events:
projects.issue.*— see Domain events.
locale
A BCP-47 language-and-region tag that identifies the rendering preferences for a request — UI strings, date and number formatting, currency symbol, plural rules. Constellation uses full regional tags only (en-US, en-GB, it-IT, de-DE, fr-FR); base-language tags (en, it) are not in the supported set because Intl formatters produce inconsistent output across runtimes when the region is left implicit.
- Type:
Localefrom@constellation-platform/i18n. - Source on the wire: the
constellation-localecookie, scoped to the apex domain so all three Vercel zones see it. - Tenant policy:
tenants.default_localeplustenants.enabled_locales; see tenant-locale-policy. - Full design: Multilanguage (i18n) Specification.
locale-resolution
The rule that decides which locale a given request renders in. Order: cookie (validated against the tenant's enabled_locales) → user preference → tenant default → Accept-Language (unauthenticated only) → platform default (en-US). A cookie that fails the tenant check is rejected and overwritten on the response so the bad value does not persist. Implemented in resolveLocale() (pure) and applied via the localeResolver hook on createConstellationAuthMiddleware.
- Implementation:
resolveLocale()in@constellation-platform/i18n;resolveRequestLocale()in@constellation-platform/auth-next/locale. - Forwarded to handlers as: the
x-constellation-localerequest header (gated behindtrustForwardedHeaders).
module
A self-contained vertical slice of the platform with its own database schema, API surface, and code base under apps/. The current modules are Directory, Catalog, and Project Tracker. Modules communicate only via domain events and typed service contracts — see no-cross-app-imports and module-isolation.
organisation
A company that participates in a tenant — typically a buyer, supplier, or service provider. Organisations are containers for users and have type, verification status, and credentials.
- Schema:
identity.organisations. - Module: Directory.
- Events:
directory.organisation.*— see Domain events.
outbox
Postgres table (events.outbox) that holds domain events written transactionally with the originating mutation. A polling dispatcher reads undispatched rows and delivers them to subscribers. Combined with subscriber-side idempotency, this gives at-least-once delivery without distributed transactions.
- Schema:
events.outbox. - Publisher:
@constellation-platform/events(publish()). - Dispatcher: polling cron in each consuming app.
permission
A named capability check (organisation.read, task.update, audit.read.own, …). Permissions are stored in identity.permissions and assigned to roles; the auth-core matchPermission() helper evaluates them at runtime. The .own suffix narrows a permission to records the actor created/owns.
- Schema:
identity.permissions,identity.role_permissions. - Helper:
matchPermission()from@constellation-platform/auth-core.
initiative
The top of the Project Tracker hierarchy — a long-running container of related projects (e.g. "Constellation Platform Development" is the dogfood initiative that contains the Directory, Catalog, PT, etc. projects). Supersedes the legacy programme entity (PLT-165). Two distinct link mechanisms coexist: the containment link Project.initiativeId (every project belongs to at most one initiative — this is what makes an initiative "a container of projects"), and the cross-cutting Task.initiativeId tagging introduced by PT-447/PT-495 that lets an epic (EPIC-typed tasks only) belong to an initiative independent of which project it lives in — the Initiative → Epic hierarchy. The two are separate FKs on separate tables; an initiative's project membership and its tagged-epic membership are not the same set.
- Schema:
projects.initiatives. - API:
/api/initiatives. - Events:
projects.initiative.progress_updated. - See: Work hierarchy & links for how initiatives span projects via tagged epics, and the cross-project access/redaction rules.
programme
Legacy — superseded by initiative (PLT-165). Retained only as the still-emitted legacy event projects.programme.progress_updated, published alongside projects.initiative.progress_updated during the dual-emit window (PLT-182), and as the PROGRAMME_OFFICE tenant type (a tenant classification, unrelated to the initiative hierarchy). No projects.programmes table exists — use initiative for the hierarchy concept.
- Events:
projects.programme.progress_updated(legacy, dual-emit only).
project
A bounded unit of work within an initiative, containing stages, tasks, deliverables, and issues.
- Schema:
projects.projects. - Events:
projects.project.*— see Domain events.
RLS
Row-Level Security. Postgres feature that filters rows based on a per-connection setting. Constellation uses RLS to enforce tenant isolation — every tenant-scoped table has a policy of the form USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid) (or a module helper such as identity.row_in_current_tenant(tenant_id)). The NULLIF is load-bearing: on pooled connections the GUC's reset value is '', and a bare ::uuid cast raises 22P02 (DIR-93).
- Rule:
tenant-context. - Mechanics: Tenancy & RLS.
role
A named bundle of permissions assigned to a user within a scope (tenant, organisation, project). A user can have multiple roles; the union of their permissions applies.
- Schema:
identity.roles,identity.user_roles,identity.role_permissions. - Events:
directory.role.*.
schema-per-module
Isolation pattern where each module owns one Postgres schema (identity, catalog, projects) within a single shared database, and is the only writer of that schema. Cross-module reads go through the module's API or events — never via cross-schema joins.
- Rule:
module-isolation. - Exception:
identityschema is shared-read.
shortlist
A curated selection of catalog entries in the Catalog module — the artifact a buyer produces from search results to say "these are the candidates we're taking forward". Lifecycle ACTIVE → ARCHIVED; an archived shortlist stays on record as the selection the delivery work started from (and, when the planned Procurement module ships, shortlists feed RFQ flows).
- Schema:
catalog.shortlists,catalog.shortlist_entries. - Module: Catalog.
- Events:
catalog.shortlist.created,catalog.shortlist.updated,catalog.shortlist.entry_added, ….
stage
A phase of a project, typically separated by gates. Stages contain tasks and deliverables. Stages have a status lifecycle (planned → in-progress → completed) and progress automatically when their gate passes.
- Schema:
projects.stages. - Events:
projects.stage.progressed.
subscriber
A consumer of domain events — an event handler in another module (or the same module) that runs on each event delivered from the outbox. Subscribers must be idempotent.
- Helper:
subscribe()from@constellation-platform/events.
tenant-locale-policy
The set of locale-related fields a tenant configures: default_locale, enabled_locales, time_zone, and currency. Defaults preserve English-only behaviour ('en-US', ['en-US'], 'UTC', 'USD'). A DB CHECK enforces default_locale ∈ enabled_locales plus a non-empty, no-NULL invariant on the array. Edited by tenant admins on the tenant detail page in Directory.
- Schema:
identity.tenants(columnsdefault_locale,enabled_locales,time_zone,currency). - Validation:
LocaleSchema/TimeZoneSchema/CurrencySchemainapps/directory/src/lib/schemas/tenant.schema.ts. - Read by:
localeResolveroncreateConstellationAuthMiddleware; per-app server actions on cookie write.
task
A planned unit of work in the Project Tracker module. Has an assignee, due date, status, and progress. Distinct from an issue: tasks are planned ("we will do X by Y"), issues are unplanned ("X is broken").
- Schema:
projects.tasks. - Events:
projects.task.*— see Domain events.
taxonomy
A hierarchical classification tree for catalog entries. Stored as Postgres LTREE so any prefix query is fast. Configurable per tenant — supported standards include UNSPSC (general commerce), ETIM (industrial), NATO FSC (defence), CPV (EU public procurement), or custom hierarchies.
- Schema:
catalog.taxonomy_nodes. - Events:
catalog.taxonomy.*— see Domain events.
tenant
The top-level isolation boundary in Constellation. A tenant is a single customer organisation (or in the dogfood case, "Planet B2B" itself). Every tenant-scoped table has a tenant_id column, and every query runs under app.tenant_id set from the JWT — see tenant-context.
- Schema:
identity.tenants. - JWT claim:
tenant_id. - Module: Directory.
tenant-id
The UUID identifying a tenant. Set as a Postgres connection setting (app.tenant_id) for the duration of a request, where it's read by RLS policies on every query.
user
An account in the Directory module — typically a person, but service accounts also exist. Users belong to zero or one organisation and have one or more roles.
- Schema:
identity.users. - Events:
directory.user.*.