Skip to content

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_id with the act_ 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 separate occurred_at.
  • The agent, or agents, responsible. One or more agents, each with a kind of HUMAN, SYSTEM, or ORGANISATION. 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 role GENERATOR — 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.

{
  "observed": {
    "source_medium": "TEXT",
    "output_medium": "TEXT",
    "source_language": "en",
    "output_language": "fr",
    "output_size": { "words": 78500 }
  }
}

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_tokennext_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.

GET /v0/content-lake/activities?tool_model=*&from=2026-01-01T00:00:00Z HTTP/1.1
Host: api.packtpub.services
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...

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.