Access & labels
Once content lives in the Content Lake, three separate questions decide what happens to it: how is it organised so people and applications can find the right things, who is allowed to see and change it, and what does the platform itself know about it. This page covers all three, because in practice they work together — a curated product feed is a tag filter, but it only ever returns content the caller is permitted to see and the contract permits using.
The organising layer is tags: short free-text labels you attach to a Resource to build your own taxonomies — a topic, a collection, an editorial status. Tags are yours to define and shared across your Tenant, so everyone working in the same business sees and filters on the same set.
Alongside your tags sit system labels: qualities the platform derives on its own — the detected language, whether something is trending, whether it is already live in a Product. You never set these; you read and filter on them.
Neither tags nor labels decide who may see a Resource. That is the job of the access layer — the access grants that say which people and groups may read or change a Resource, and the visibility setting that says whether the whole Tenant can see it or only its owner. And none of these decide how content may be used — territories, formats, AI retrieval, and the rest — which is settled entirely by the Contract on the Resource's Work. Keeping these concerns separate is deliberate; the section on three questions, three layers below draws the line clearly.
Tags — your organising layer
You tag content in whatever way is meaningful to you: a topic taxonomy, a curated collection, an editorial workflow status, a product affinity. The Content Lake does not interpret your tags — it stores them and indexes them so you can filter on them fast. What a tag means is entirely up to the application that sets it.
Tags are flat string values matched as exact literals. A tag of
topic/kubernetes does not match a filter for topic/ — the slash is
a convention you adopt to keep tags tidy, not a hierarchy the platform
understands. If you want both broad and narrow filtering, apply both a
broad and a narrow tag to the same Resource.
The limits are firm:
- Up to 50 tags per Resource, each up to 64 characters.
- Any characters except square brackets (
[]) and backslashes; forward slashes are the conventional separator. - Tags are tenant-shared — every user and application in the Tenant sees and filters on the same tags. There are no per-application private tags in the Content Lake (see app-private markers for the pattern to use instead).
The reason to lean on tags is composition. Rather than hard-coding
a list of content IDs into a product feature — a list that goes stale
the moment someone ingests or removes content — you describe the
feature as a filter: "show Resources tagged topic/kubernetes and
publishing/status/approved". Content then flows in and out of the
experience as tags change, with no code deployment.
Rights ride the same filter
Because a Resource's resolved rights are structured, indexed
attributes (not tags), you can add a rights predicate to the very
same query. A feed can ask for topic go, status approved, and
AI_RETRIEVAL permitted in one filter and get back only content
that satisfies all three. Rights enforcement is not a separate pass
your application has to remember — see Search.
Designing good tags
The old instinct here is a good one and it survives: when a tag models a policy, prefer the category over the instance.
When the platform team weighed up tagging AI-partner permissions, the
choice was between naming partners (allow/openai, deny/google) and
naming the property that actually mattered
(allow/zero-data-retention). Named-partner tags multiply and go
stale the moment a new partner appears or an old one changes terms; the
category tag expresses the real intent and keeps working. The same
logic applies to your own taxonomies — tag the property you will filter
on, not whichever instance happens to have it today.
Do not encode rights as tags
That example is about organising content, not governing it. Contractual permissions — territories, formats, AI usage — are not tags and must never be modelled as tags. They live on the Work's Contract and are resolved onto the Resource as structured rights. A tag is a hint for humans and filters; a right is enforced by the platform. See three questions, three layers.
Editorial status as a tag
A common, powerful pattern is to drive an editorial workflow with a single status tag whose value is the product decision. Packt's own subscription service resolves a Product to its chapter Resources, then fetches them filtered by an editorial status tag:
- Standard readers see
publishing/status/approvedcontent only — full review, author sign-off complete. - Readers who opt into early access see
publishing/status/reviewed— checked by Packt staff, ahead of final sign-off. - Further experiences can open earlier stages
(
publishing/status/draft) to reviewers, channels, or beta audiences.
The point is that how close to the author's live thinking a given audience gets becomes a product-layer decision — a tag-filter change, not an engineering change. See Building a Product Experience and Search for how the filters combine.
Filtering a Resource list by tags and a label (JSON)
Read models on business-facing surfaces use the
Public API. This lists a Work's Resources that
carry a topic tag, are approved, and have picked up the platform's
trending label:
{
"work_id": "wk_ml_go",
"filter": {
"tags": [
"topic/kubernetes",
"publishing/status/approved"
],
"labels": ["trending"]
}
}
Tags are matched as exact literals; add both broad and narrow tags if you need both to filter.
System labels — what the platform knows
System labels are qualities the Content Lake derives from platform-wide signals to help surface content for discovery, recommendation, and merchandising. You do not set them — the platform computes them and keeps them current as the underlying signals move. Because only the platform ever applies them, they need no special prefix to tell them apart from your tags; a write that tries to set a label is rejected.
| Label | Meaning | Derived from |
|---|---|---|
used-in-product |
Live in at least one Product. | Product composition |
trending |
Elevated recent engagement versus its baseline. | Consumption over a rolling window |
highly-reviewed |
Strong aggregate review/rating signal. | Reviews and ratings |
high-completion |
Finished at an above-average rate. | Completion analytics |
source/<system> |
The Source system it was ingested from. | The Source record |
Language is deliberately not a label, either: a Resource's primary language is a first-class field on the Resource itself, set at ingestion — and search filters on it directly, the way it filters on dates.
How a Resource was produced is not a label
Which tools and models processed a Resource during ingestion — the
formatters and their versions — is not a system label. That
detail is recorded on the Resource's INGESTION
Activity as its tooling. Reprocessing
discovery — when an improved formatter ships and you need every
Resource the old version touched so it can be re-ingested — is a
query over the Activity log, not a label lookup.
The Activity log is the authoritative record of how content came
to be; labels exist only for discovery and merchandising.
Labels are eventually consistent
Because labels are recomputed from live signals, treat them as
lagging slightly. A newly popular item takes a short window to
acquire trending, and a label is removed again when its signal
fades. Filter on them for discovery and merchandising; do not use
them where you need an immediate, authoritative answer.
Three everyday discovery patterns fall straight out of combining tags with labels:
- What's hot in a topic — a topic tag plus a label:
topic/gocombined withtrending. - Proven content only — filter on
highly-reviewedorhigh-completionto bias toward content that performs. - Unused inventory — filter on the absence of
used-in-productto find content that is ready to merchandise but has no home yet.
Three questions, three layers
Organising content is not the same as controlling it. Three distinct questions govern any Resource, and each is answered by a different mechanism. Conflating them is the single most common mistake, so it is worth being precise.
flowchart TD
R["A request touches a Resource"] --> S
S{"May this app do<br/>this class of thing?<br/>(OAuth scope)"} -->|yes| G
S -->|no| DENY["Refused"]
G{"May this principal<br/>see / change<br/>THIS Resource?<br/>(access grant + visibility)"} -->|yes| C
G -->|no| DENY
C{"May the content be<br/>used THIS way?<br/>(contract rights)"} -->|yes| OK["Allowed"]
C -->|no| DENY
- Scope — may this application do this class of thing at all? The broadest gate. An access token carries scopes such as reading content, writing content, or managing access. A scope is a coarse capability, checked first; it never implies a narrower one.
- Access grant & visibility — may this principal see or change this particular Resource? The per-Resource access-control layer, covered in access grants below.
- Contract rights — may the content be used in this specific way? Whether it may be shown, printed, translated, retrieved by an AI, and so on. This is settled by the Contract on the Resource's Work and has nothing to do with who is asking. See Works & rights and Contracts & Rights.
The two easy-to-confuse layers are the middle and the last. Access grants control who can interact with a Resource; contracts control how the content may be used. A reviewer can hold read access to a chapter (an access grant) and still not be permitted to use it for AI training (a contractual term) — different systems, different concerns. When you need to know whether someone can see a Resource, check access; when you need to know whether content can be used a certain way, check rights.
Access grants
Access control in the Content Lake is Zanzibar-style: a grant binds a principal — a user or a group — to a Resource with a role. Group membership is resolved at the moment of the check, so adding someone to a group immediately extends every grant that group holds.
Roles form a hierarchy, each level including everything below it:
| Role | Can |
|---|---|
| Viewer | Read the Resource. |
| Editor | Read and modify it. |
| Admin | Read, modify, and manage its access grants. |
| Owner | Everything, including the strongest control over it. |
Grants are the fine-grained answer to who may touch this one Resource. They sit beneath the coarse scope check: an application needs the access-management capability to read or change grants at all (ACL contents are sensitive, so even listing them is not covered by ordinary read access), and beneath that, an individual grant decides the specific Resource.
Editing grants safely
Every change to a Resource's grants is a compare-and-set against an
update_token returned when you read the current access state. Read
the state, make your change, send it back with that token. If
someone else changed the grants in between, your write is refused
and you re-read — so concurrent ACL edits can never silently
clobber each other. Revocation takes effect immediately.
For most applications you never touch grants directly. The platform evaluates them on every request and simply returns the content the caller is permitted to see; you only reach for the access API when you are building an admin surface that manages sharing.
Visibility and the staging lifecycle
Where a grant answers which specific principals, visibility sets the baseline for everyone in the Tenant. A Resource is either:
- PRIVATE — reachable only by principals with an explicit grant (its owner, and anyone they share it with); or
- TENANT — visible to every authenticated principal in the Tenant.
Visibility is the floor and grants layer on top: a PRIVATE draft can still carry explicit Editor grants for the editorial team, while a TENANT-visible published chapter is readable by all without any grant at all.
Crucially, visibility is not something you usually set by hand — it follows the staging lifecycle described in Works & rights. When content is first uploaded without a Work, it lands in staging: it has no Work, it is owner-only, and it is not yet usable in any Product, distribution, or AI surface. In access terms a staged Resource is PRIVATE by construction. Promoting it into a Work — the moment a Contract is in place and rights resolve onto it — defaults its visibility to TENANT, so it becomes usable across the business, unless the promotion asks for something else.
stateDiagram-v2
[*] --> Staged
Staged: Staged · PRIVATE
Staged: no Work · owner-only · not usable
Promoted: In a Work · TENANT (default)
Promoted: rights resolved · usable across the Tenant
Restricted: In a Work · PRIVATE
Restricted: rights resolved · owner + explicit grants
Staged --> Promoted: promote into a Work
Staged --> Restricted: promote, but keep it PRIVATE
Promoted --> Restricted: tighten visibility
Restricted --> Promoted: open to the Tenant
This is why the same idea — a Resource that is not yet in a Work sits in staging, owner-only and unusable — shows up on almost every Content Lake page. Staging is both a rights state (no Work, so nothing may use it) and an access state (PRIVATE, so nobody else can see it). Promotion resolves both at once.
Keeping app-private markers
Because tags are tenant-shared, they are the wrong place for a marker that only your application should see — every other application in the Tenant would see it too. If you need private per-application state against a Resource, keep it in your own storage keyed by the Resource ID, not in the Content Lake. Reach for visibility and access grants when the goal is restricting who can see a Resource; reach for your own store when the goal is private annotations that are none of anyone else's business.
Related pages
- Content Lake overview — the estate these controls apply to, and tag-based composition in context.
- Works & rights — the staging lifecycle and how rights resolve onto Resources.
- Search — how tag, label, and rights filters combine in one query.
- Activity log — the audit record of how each Resource was produced, including the tools and formatters that processed it.
- Provenance & attribution — lineage and derivation built on the same Activity records.
- Contracts & Rights — the "how may it be used?" layer, kept separate from access.
- Explorer — Chapter IV — the lake and its content, visualised.