Activity log
Every consequential thing that happens to a piece of content on the platform is written down once, the moment it happens, and never changed afterwards. A manuscript is imported, a chapter is translated, a keynote is summarised into a short book, an AI model drafts product copy — each is recorded as an Activity: an immutable entry naming what went in, what came out, who was responsible, and the contract or legal basis that authorised it. Together these entries form the Activity log — the platform's audit record for content.
The log is not an internal engineering artefact. It is available to every platform user, scoped to their own tenant, as the tool for answering audit questions about content. Four of those questions recur constantly, and each becomes a single filtered lookup rather than a forensic exercise:
- What did an AI model touch, and under what contract?
- What was this content derived from?
- Who is owed a share of what it earns?
- Who changed this, and when?
What makes the log trustworthy is that it records fact, not reconstruction. Nothing is written after the event from memory or inferred later from timestamps: the entry is created at the moment the action is taken, by the same system that takes it, and is append-only thereafter. No entry is ever edited or deleted, so a query today and the same query in five years return the same answer.
This page is the reference for the log as an audit tool: what a single entry contains, the full taxonomy of the eighteen activity types, and worked examples of real entries. It is deliberately distinct from Provenance & attribution, which tells the lineage story — how content nodes derive from one another as a graph you can walk. The relationship between the two is explained under The log and the graph below.
What a log entry records
Every entry, whatever the activity, answers the same handful of questions in the same shape. Read plainly, an entry says: this action, of this type, happened at this time; these agents were responsible; these tools were used; it acted on this content and produced that content; and it relied on this authority to do so.
The load-bearing parts are worth understanding before the field reference:
- A stable identity. Each entry has an
activity_idwith theact_prefix and a readable slug —act_translate_mastering_x_fr— so it can be cited in a report, a ticket, or an audit finding and found again unchanged. - A type. One of eighteen activity types, which says what kind of act this was — an ingestion, a translation, an AI enrichment, and so on.
- A timestamp. When the entry was recorded (
created_at), and, where the real-world event happened earlier — a keynote captured the day before it was processed — a separateoccurred_at. - The agent, or agents, responsible. One or more
agents, each with a
kindofHUMAN,SYSTEM, orORGANISATION. The agent is the accountable party. This is the single most important audit rule on the page: an AI model is never an agent. When a model does the work, the organisation that operated it is the agent — recorded with the roleGENERATOR— and the model itself is recorded separately as tooling. - The tooling used. The instrument: the model, its version, and the
prompt, for AI-assisted work; the pipeline or automation, for a
system action. An LLM lives here, so "exactly what did an AI model
produce or touch?" is a filter over
tooling, not an investigation. - The subject and target. What content the action consumed (
used) and what it produced (generated) — each a reference to a Resource, Source, Work, or ContentUnit, version-pinnable so the exact material is unambiguous. - The rights basis. The authority the action relied on — either the governing Contract (with its version pinned at the moment of the action) or, where no grant covered the use, an asserted statutory exception. This is what turns the log from "what happened" into "what happened, and were we entitled to do it?"
The agent is the responsible party — never the model
An AI model is an instrument, like a camera or a printing press;
responsibility rests with whoever operated it. So when a model
drafts a summary, the record names the organisation as the agent
(role GENERATOR) and logs the model, version, and prompt as
tooling. This keeps "everything AI produced or touched" a filter
— by role=GENERATOR or by a specific tooling.model — rather
than an audit. The same rule is central to
Provenance & attribution.
What review is not recorded here
The log records content transformations, not the production lifecycle around them. Review, QA, and approval rounds are tracked separately — some content needs no review, some needs several rounds by different people — so there is deliberately no "reviewed-by" field on an Activity. Likewise a sale, an API read, or a push to a distribution partner is not an Activity: those are consumption and distribution events, recorded elsewhere.
Full field reference (JSON)
Every entry carries these fields regardless of type. Endpoint
attributes such as medium, language, and size are not authored
here — they are read from the version-pinned used/generated
nodes and frozen into the log's observed snapshot (below).
| Field | Type | Required | Meaning |
|---|---|---|---|
activity_id |
act_ + UUID |
Yes | Globally unique, immutable. |
type |
enum ActivityType |
Yes | One of the eighteen types. |
subtype |
string | No | Finer, open per-type label (e.g. ERRATA, NANO_BOOK). |
created_at |
ISO 8601 | Yes | When the entry was recorded. |
occurred_at |
ISO 8601 | No | When the real-world event happened, if different. |
performed_by |
array of AgentRef | Yes | The responsible agent(s); ≥1. |
used |
array of NodeRef | Conditional | Source content consumed. Required for derivations. |
generated |
array of NodeRef | Yes | Output node(s); usually one. |
creates_tier |
enum | Yes | WORK | EXPRESSION | MANIFESTATION | CONTENT_UNIT | NONE. |
work_relationship |
enum | Yes | NEW_WORK | SAME_WORK | N/A. |
fidelity |
enum | No | VERBATIM | LIGHTLY_EDITED | REWORKED | CONDENSED | TRANSFORMED. |
derivation_type |
enum | No | Classifies the lineage edge for downstream consumers. |
royalty_flow |
enum | No | INHERIT | NET_SHARE | ADDITIVE | EXCLUDED — the rule on the lineage edge. |
rights_basis |
RightsBasis | Recommended | The authorising contract (version-pinned) or asserted exception. |
parameters |
object | No | Activity-specific config (persona, target length, item count…). |
tooling |
object | No | { model, model_version, prompt, prompt_id, pipeline, automation }. An LLM lives here. |
automation |
enum | No | MANUAL | SCHEDULED | EVENT_TRIGGERED. |
supersedes |
NodeRef | No | The node this output replaces — a Resource (REVISION) or a Work (EDITION). |
status |
enum | No | COMPLETE (default) | DRAFT. |
notes |
string | No | Free text. |
The shared sub-types referenced above:
{
"AgentRef": {
"agent_id": "agt_chandra",
"kind": "HUMAN | SYSTEM | ORGANISATION",
"name": "A. Chandra | Packt Publishing Limited | Ingestion pipeline",
"role": "AUTHOR | TRANSLATOR | NARRATOR | GENERATOR | OPERATOR"
},
"NodeRef": {
"node_id": "res_mastering_x_en",
"node_type": "RESOURCE | SOURCE | WORK | CONTENT_UNIT | MANIFESTATION | PRODUCT | CONTRACT",
"version": "ver_0195aaa",
"role": "PRIMARY | SUPPORTING | BACKBONE | …"
}
}
The observed snapshot — why the log stays readable
A log entry additionally carries an observed block: a write-time,
frozen snapshot of a small set of endpoint attributes — medium,
language, size, manifestation class. It is derived once when the
entry is written, never independently authored or edited, so it
cannot drift from the nodes it describes.
Its purpose is durability of the audit trail. The main content
attributes are dereferenced from the used/generated nodes, not
copied — but if a Resource is later purged or redacted for legal
reasons, the entry's skeleton (ID, type, timestamps, agents, edges)
survives, and the observed snapshot is what keeps the entry
interpretable once the endpoint content is gone.
The rights basis on every entry
Because the log is an audit tool, the field auditors reach for most is
rights_basis: the recorded authority for an action that consumed
content. It takes one of two forms, distinguished by a basis_type:
- Contract basis (the default). A governing contract grants the use, and the basis pins the contract version that was in force when the action happened. Consequential actions validate against that pinned version, and if the contract later changes, the platform reprojects the affected rights — so "under what contract was this done?" is answerable exactly, at the version that applied.
- Statutory-exception basis. Where no grant covers a use but the law permits it directly — quotation for criticism or review, parody, incidental inclusion, text-and-data mining — the entry records the asserted exception instead: which exception, in which jurisdictions, whether the source had been made public, a written justification, and who asserted it.
The platform records assertions; it never adjudicates them. Whether
a use actually qualifies is a legal judgement. What the log
contributes is evidence and accountability: the used edges pin
exactly which content, at which version, was consumed; the observed
snapshot freezes the proportions; and the assertion names the
accountable agent. Full treatment lives in
Provenance & attribution.
The log and the graph
Every Activity is written to the log — no exception. Only the subset that carries lineage also becomes nodes and edges in the Content Lake graph. Understanding the split matters, because it tells you which audit questions the log alone answers and which need the graph.
- The log holds everything. Whatever the activity — including the three enrichment types that create no new content — it is written to the append-only, immutable log and is searchable and filterable. This is the audit substrate: "what happened, when, by whom, over what?" is always answerable here.
- The graph holds the lineage subset. An Activity also becomes part of the graph if, and only if, it creates a new content node — a Work, Expression, Manifestation, or ContentUnit — or connects existing content nodes with an edge. This is the structure that provenance and royalty traversals run over. The graph is a projection of the log, not a second write: every graph Activity is also a log entry, but the reverse is not true.
In practice the log-only set is small and specific: the three
enrichment activities — PI_GENERATION, ENTITY_EXTRACTION, and
COVER_GENERATION — which attach metadata but create no new content
node. Every origination, derivation, expression, manifestation, and
fragment activity reaches the graph as well.
flowchart LR
ACT["A content action<br/>happens"] --> LOGW["Written to the<br/>Activity log<br/>(always)"]
LOGW -->|"creates or connects<br/>a content node"| GRAPH["Also becomes a<br/>graph node + edge<br/>(lineage subset)"]
LOGW -->|"enrichment only<br/>(PI · entities · cover)"| LOGONLY["Stays in the log<br/>(no lineage)"]
Which page answers which question
Use this page and the log for audit questions of the form "what happened to this, who did it, when, and under what authority?" — the questions a filter over the log answers. Use Provenance & attribution for "what is this derived from?" and "who is owed?" — the questions a walk over the lineage graph answers. The two draw on the same recorded facts.
The activity type taxonomy
There are eighteen activity types. They group by what they do,
following the same five categories the platform uses everywhere:
origination brings new content into being; derivation makes a
new Work from existing content; expression re-realises the same
Work in another language, mode, or corrected state; fragment pulls
out a quotable piece; and enrichment attaches metadata without
creating content. The Plane column shows whether an activity also
reaches the lineage graph (GRAPH) or stays in the log only (LOG).
flowchart TD
ROOT["The 18 activity types"] --> ORI["Origination<br/>new content"]
ROOT --> DER["Derivation<br/>new Work from existing"]
ROOT --> EXP["Expression<br/>same Work, new form"]
ROOT --> FRA["Fragment<br/>a quotable piece"]
ROOT --> ENR["Enrichment<br/>metadata only · log-only"]
| Activity | Category | What it records | Plane |
|---|---|---|---|
INGESTION |
Origination | Import source content and register it as a new Work. | GRAPH |
AUTHORING |
Origination | Original creation of a new Work by an author (optionally AI-assisted). | GRAPH |
RECORDING |
Origination | Capture a live event into a fixed, sellable new Work. | GRAPH |
ADAPTATION |
Derivation (new Work) | Rework content across medium or form with creative authoring. | GRAPH |
EDITION |
Derivation (new Work) | Publish a substantial new edition that stands as its own Work. | GRAPH |
COMPILATION |
Derivation (new Work) | Assemble parts from several source Works into a new Work. | GRAPH |
ASSESSMENT_GENERATION |
Derivation (new Work) | Generate assessments (MCQs, quizzes, labs) as a new Work. | GRAPH |
SUMMARISATION |
Derivation (per instance) | Condense content; faithful → same Work, re-authored → new Work. | GRAPH |
PERSONALISATION |
Derivation (per instance) | Produce a persona/role edition; sculpted → same Work, multi-source → new Work. | GRAPH |
TRANSLATION |
Expression (same Work) | Re-present a Work in another language. | GRAPH |
NARRATION |
Expression (same Work) | Produce a faithful audio rendering of a Work. | GRAPH |
TRANSCRIPTION |
Expression (same Work) | Convert audio/video to faithful text. | GRAPH |
REVISION |
Expression (correction) | Record a same-content correction or reprint that supersedes a prior version. | GRAPH |
MANIFESTATION_GENERATION |
Manifestation | Produce a sellable embodiment (the SKU) of an Expression. | GRAPH |
EXCERPTION |
Fragment | Pull a quotable fragment out as a ContentUnit. | GRAPH |
PI_GENERATION |
Enrichment | Generate product information (title, description, tags). | LOG |
ENTITY_EXTRACTION |
Enrichment | Attach entities, topics, and tags to a Resource. | LOG |
COVER_GENERATION |
Enrichment | Create cover art or a key visual as an image Resource. | LOG |
Two types are decided per instance
SUMMARISATION and PERSONALISATION do not have a fixed tier. A
faithful condensation, or a single-source persona edition that
merely selects and re-sequences existing material, stays the same
Work as a new Expression. A re-authored digest, or a persona book
assembled from several Works with new connecting text, becomes a
new Work. The deciding factor is whether substantial new authored
content was introduced.
The taxonomy is open by design; new product lines will add types over
time (a future DUBBING, say). Every type — old or new — is always
logged and searchable, whichever plane it lands on.
Example log entries
These are real log entries in the exact shape the log serves them. They illustrate what a business user will find when they query the log as an audit tool. Business readers never have to touch the JSON; it is here because the entries are the authoritative record behind every audit answer, and an AI or no-code tool can read them verbatim.
Ingestion — a manuscript imported into the lake
A SYSTEM agent (the ingestion pipeline) brings an external
manuscript in and registers it as a new Work, authorised by the
author's contract. Origin of provenance for imported material.
{
"activity_id": "act_ingest_mastering_x",
"type": "INGESTION",
"created_at": "2026-06-20T09:12:00Z",
"performed_by": [
{
"agent_id": "agt_pipe_ingest",
"kind": "SYSTEM",
"name": "Ingestion pipeline"
}
],
"used": [
{ "node_id": "src_mastering_x_manuscript", "node_type": "SOURCE" }
],
"generated": [
{ "node_id": "wk_mastering_x", "node_type": "WORK" },
{ "node_id": "res_mastering_x", "node_type": "RESOURCE" }
],
"creates_tier": "WORK",
"work_relationship": "NEW_WORK",
"fidelity": "VERBATIM",
"rights_basis": {
"node_id": "ctr_chandra_2026",
"node_type": "CONTRACT"
},
"parameters": {
"source_uri": "sharepoint://…/mastering-x-manuscript.docx",
"importer": "sharepoint",
"transformer": "markitdown",
"source_format": "docx",
"raw_checksum": "sha256:0f3a…"
},
"automation": "EVENT_TRIGGERED"
}
AI enrichment — product information generated by a model
An AI model drafts the product copy. Note the audit rule in
action: the organisation is the agent (role GENERATOR); the
model is recorded as tooling, never as an agent. creates_tier
is NONE, so this is a log-only entry — it attaches metadata to a
Product but creates no content node, and never reaches the lineage
graph.
{
"activity_id": "act_pi_mastering_x",
"type": "PI_GENERATION",
"created_at": "2026-04-12T10:11:12Z",
"performed_by": [
{
"agent_id": "agt_packt",
"kind": "ORGANISATION",
"name": "Packt Publishing Limited",
"role": "GENERATOR"
}
],
"used": [
{ "node_id": "res_mastering_x", "node_type": "RESOURCE", "role": "PRIMARY" },
{ "node_id": "res_commissioning_42", "node_type": "RESOURCE", "role": "BRIEF" }
],
"generated": [
{ "node_id": "prod_mastering_x", "node_type": "PRODUCT" }
],
"creates_tier": "NONE",
"work_relationship": "N/A",
"parameters": {
"fields_generated": [
"description_long",
"keywords_seo",
"what_you_will_learn"
]
},
"tooling": {
"model": "claude-opus-4-6",
"prompt_id": "prompt_pi_default_v3"
}
}
Translation — same Work, new Expression
A localisation team re-presents the Work in French. This is a
same-Work derivation, so royalties INHERIT the source title's
terms. The language pair is not authored on the entry; it is read
from the source and output Resources and frozen into the
observed snapshot.
{
"activity_id": "act_translate_mastering_x_fr",
"type": "TRANSLATION",
"created_at": "2026-03-04T00:00:00Z",
"performed_by": [
{
"agent_id": "agt_loc_team",
"kind": "ORGANISATION",
"name": "Localisation",
"role": "TRANSLATOR"
}
],
"used": [
{ "node_id": "res_mastering_x_en", "node_type": "RESOURCE", "version": "ver_0195aaa" }
],
"generated": [
{ "node_id": "res_mastering_x_fr", "node_type": "RESOURCE" }
],
"creates_tier": "EXPRESSION",
"work_relationship": "SAME_WORK",
"fidelity": "VERBATIM",
"derivation_type": "TRANSLATION",
"royalty_flow": "INHERIT",
"observed": {
"source_language": "en",
"output_language": "fr",
"source_medium": "TEXT",
"output_medium": "TEXT"
}
}
Compilation — a new Work built from three source Works
A commissioning editor assembles a data-engineering course from one
chapter of each of three books. Each used entry names a source
ContentUnit — the edges that make the multi-source royalty split
computable — and royalty_flow is NET_SHARE. No model appears as
an agent; the editor is the agent.
{
"activity_id": "act_compile_data_eng",
"type": "COMPILATION",
"created_at": "2026-03-10T00:00:00Z",
"performed_by": [
{
"agent_id": "agt_ce_data",
"kind": "HUMAN",
"name": "Commissioning editor",
"role": "OPERATOR"
}
],
"used": [
{ "node_id": "cu_book_a_ch3", "node_type": "CONTENT_UNIT", "role": "PRIMARY" },
{ "node_id": "cu_book_b_ch7", "node_type": "CONTENT_UNIT" },
{ "node_id": "cu_book_c_ch2", "node_type": "CONTENT_UNIT" }
],
"generated": [
{ "node_id": "wk_data_eng_collection", "node_type": "WORK" },
{ "node_id": "res_data_eng_collection", "node_type": "RESOURCE" }
],
"creates_tier": "WORK",
"work_relationship": "NEW_WORK",
"derivation_type": "COMPILATION",
"royalty_flow": "NET_SHARE",
"parameters": { "source_works": 3 }
}
Statutory exception — a review that quotes a rival title
Where no contract covers a use but the law permits it, the entry
records the asserted exception in place of a contract. The
accountable agent is named, the jurisdiction and justification are
recorded, and royalty_flow must be EXCLUDED — no contract
governs the edge, so nothing accrues through it. See
Provenance
for the full rules.
{
"activity_id": "act_adapt_k8s_review",
"type": "ADAPTATION",
"subtype": "REVIEW",
"created_at": "2026-07-06T00:00:00Z",
"performed_by": [
{ "agent_id": "agt_packt", "kind": "ORGANISATION", "name": "Packt Publishing Limited", "role": "AUTHOR" }
],
"used": [
{ "node_id": "res_thirdparty_k8s_book", "node_type": "RESOURCE", "version": "ver_0197eee", "role": "SUPPORTING" }
],
"generated": [
{ "node_id": "wk_k8s_book_review", "node_type": "WORK" },
{ "node_id": "res_k8s_book_review", "node_type": "RESOURCE" }
],
"creates_tier": "WORK",
"work_relationship": "NEW_WORK",
"fidelity": "TRANSFORMED",
"derivation_type": "ADAPTATION",
"royalty_flow": "EXCLUDED",
"rights_basis": {
"basis_type": "STATUTORY_EXCEPTION",
"statutory_exception": {
"type": "FAIR_DEALING_CRITICISM_REVIEW",
"jurisdictions": ["GB"],
"source_made_public": true,
"justification": "Critical review; three quoted excerpts totalling 412 of 78,500 source words, each accompanied by commentary.",
"asserted_by": {
"agent_id": "agt_rights_ops",
"kind": "ORGANISATION",
"name": "Packt Rights Operations",
"role": "OPERATOR"
}
}
}
}
Reading and querying the log
The log is available to every platform user, scoped to their own tenant. It is the tool you reach for when you need to answer an audit question about content — and because every entry is append-only and immutable, an answer today and the same answer years from now are identical.
You read the log by filtering it. The filters map directly onto the audit questions:
- By content — everything that touched a given Resource, Work, or other node, to answer "what happened to this?"
- By agent — everything a given person, organisation, or system did, to answer "who did this, and what else did they do?"
- By activity type — all translations, all AI enrichments, all editions, to answer "where has this kind of thing happened?"
- By AI tooling — everything an AI model produced or touched, filtering on the model recorded in tooling, to answer "what did AI do, and under what contract?"
- By time window — everything in a date range, to bound an audit to a period.
The two questions the log is built to answer about AI
Because the responsible agent is always an organisation (never the
model) and the model is always recorded as tooling, "what did AI
touch?" is a filter by agent role GENERATOR or by a specific
model — not a hunt. And because each entry pins the authorising
contract version in its rights_basis, "under what contract?"
comes back with the exact version that applied at the time.
Querying the log over the Public API
The log is exposed on the Public API at
/v0/content-lake/activities (base
https://api.packtpub.services, prefix /v0/). Every request is
scoped to the tenant in the caller's token; entries are read-only.
| Filter | Query parameter | Answers |
|---|---|---|
| Activity type | type=TRANSLATION |
Where has this kind of act happened? |
| Performing agent | agent_id=agt_loc_team |
What did this agent do? |
| Agent kind | agent_kind=SYSTEM |
Which acts were automated? |
| AI model | tool_model=claude-opus-4-6 (* = any AI-tooled) |
What did AI touch? |
| A content node | node_id=res_mastering_x_en |
What happened to this content? |
| Time window | from=…&to=… (ISO 8601) |
What happened in this period? |
| Graph tier | creates_tier=EXPRESSION |
Which acts created this tier? |
Paging uses opaque cursors: page_size (default 20, max 100) and
page_token → next_page_token. A single entry is fetched at
/v0/content-lake/activities/{id}. Authentication is a WorkOS JWT
in Authorization: Bearer …; the tenant comes from the token's
tenant_id claim — there is no tenant header.
How the immutability holds — under the hood
The log is append-only and its entries are never mutated; that is
precisely what lets it answer questions about the past reliably.
When content must be removed for legal reasons, a purge redacts the
content of the entries that generated it — free text and the
endpoint snapshot — while keeping the skeleton (ID, type,
timestamps, agents, and the used/generated edges), so the audit
trail never dangles. Entries that merely used the purged content
keep their observed snapshot, so they stay interpretable even
after the endpoint is gone.
Related pages
- Provenance & attribution — the lineage half of the story: how content nodes derive from one another, and how "what is this derived from?" and "who is owed?" become graph walks.
- Access, labels & visibility — who can see and do what within a tenant, and how visibility is controlled.
- Enrichment & the Knowledge Graph — the three log-only enrichment activities, and how content gains context.
- Works & rights — where rights live, and how
rights_basispins the contract that authorises each Activity. - Explorer — Chapter VIII — provenance and derivation, visualised.