From twig
Use for routing CMS questions across Twig's content stack — which service owns a GraphQL field or REST path, how a TOCS resource reaches the client, federation/subgraph boundaries, and which repo to open for a change. Covers content-service, content-gateway-service, twig-graph, content-sync, tsc-curriculum-progression-service and -worker, standards-explorer-service, associated-standards-service (plus the abandoned Go variant), and cachinator, and how they relate to TOCS. Triggers on those repo names and on queries like getVersion, film, TSLesson, TSGrade, TSCurriculumProgression, TSStudentProgression, breadcrumbs, StandardMappings, relatedStandards, educationalStandards, ResourcePackVersion, ApplicationManifest, VersionCache, cache/refresh, or "which service serves X".
How this skill is triggered — by the user, by Claude, or both
Slash command
/twig:content-deliveryThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill is the topology map for the CMS stack. It tells you **which service
references/associated-standards-service.mdreferences/cachinator.mdreferences/content-gateway-service.mdreferences/content-service.mdreferences/content-sync.mdreferences/curriculum-progression-service.mdreferences/curriculum-progression-worker.mdreferences/routing-examples.mdreferences/standards-explorer-service.mdreferences/troubleshooting.mdreferences/twig-graph.mdThis skill is the topology map for the CMS stack. It tells you which service
owns a field, which upstream it reads from, and which repo to open for a
change. For the TOCS REST API itself (endpoints, relations, public_key,
auth), use the tocs-api skill — this one links to it rather than duplicating
it.
This skill refers to services by their GitHub name (e.g.
TwigWorld/twig-graph) rather than a hardcoded local path. Different
users clone to different roots. When you need to read source from a
repo — anything beyond the mental model in this skill — resolve the
path first:
Read ~/.claude/twig/repo-paths.json. Shape:
{
"twig-graph": "/Users/alex/code/services/twig-graph",
"content-service": "/Users/alex/src/content-service"
}
If the file doesn't exist, treat it as {}. If the repo's entry is
present and the directory exists, use it. Don't prompt.
If the entry is missing, use AskUserQuestion to ask for the
repo's local absolute path. Use this shape:
Where is your local clone of TwigWorld/<repo-name>?<repo-name> (truncated to 12 chars)I'll paste the path — user supplies an absolute path via
"Other"Skip — work from docs only — you answer from this skill's
mental model without reading source
Do not offer a "clone it" option; cloning is the user's job.Persist the answer. If the user supplied a path, write it into
~/.claude/twig/repo-paths.json (create the ~/.claude/twig/
directory if needed, pretty-print with 2-space indent, keep keys
sorted). Expand ~ to an absolute path before writing so the file
is portable.
Skip is session-scoped. If the user skipped, remember that for the rest of this conversation and don't re-prompt — but do not write anything to the config file. Next session will ask again, since the user may have cloned the repo in the meantime.
Never run git clone / gh repo clone from this skill.
The user can hand-edit ~/.claude/twig/repo-paths.json at any time if
a repo moves.
┌──────────────┐ ┌───────────────────────┐
│ cachinator │── GET V2 ─▶ │ TOCS │
│ Go CLI │ versions/ │ (Twig Online │
│ EC2 cron │ │ Content Service) │
└──────┬───────┘ │ REST, V1 + V2 │
│ └──────────┬────────────┘
│ POST cache/refresh │
│ GET cache/status (DB-only) │
│ │
│ ┌────────────────┬────────────┴────┬─────────────────┐
│ │ │ │ │
│ │ HTTP (direct, │ HTTP (batch, │ publishes │
│ │ TOCS on miss) │ on cron) │ events │
▼ ▼ ▼ ▼ ▼
┌─────────────────────┐ ┌────────────────────────────┐ ┌───────────────────────┐
│ content-service │ │ tsc-curriculum- │ │ RabbitMQ Stream │
│ Django + DRF │ │ progression-worker │ │ (Resource / Version │
│ elementary school │ │ Node CronJob, one per │ │ / ApplicationMani- │
│ (Postgres │ │ territory. Walks TOCS, │ │ fest events) │
│ VersionCache, │ │ POSTs tree + breadcrumbs. │ └──────────┬────────────┘
│ read-through │ └──────────────┬─────────────┘ │
│ on miss) │ │ HTTP POST ▼
└──────────┬──────────┘ ▼ ┌───────────────────────┐
│ ┌────────────────────────────┐ │ content-sync │
│ │ tsc-curriculum- │ │ NestJS consumer │
│ │ progression-service │ └──────────┬────────────┘
│ │ Express REST, Postgres. │ │
│ │ Read-only cache of │ ▼
│ │ elementary grade tree + │ ┌───────────────────────┐
│ │ breadcrumbs. │ │ Neptune + S3 │
│ └──────────────┬─────────────┘ │ (graph + attr blobs) │
│ │ └──────────┬────────────┘
│ │ HTTP GET │
│ ┌─────────────┘ │
▼ ▼ ▼
┌───────────────────────────────────┐ ┌───────────────────────────┐
│ twig-graph │ │ content-gateway-service │
│ Apollo Fed v1 subgraph │ │ Apollo Fed v1 subgraph │
│ elementary (40+ queries) │ │ middle school │
│ Calls content-service + ~25 other │ │ (6 queries, Neptune-read) │
│ microbusiness services, including │ │ │
│ curriculum-progression-service. │ │ │
└───────────────────────────────────┘ └───────────────────────────┘
Both subgraphs federate (`@key`) into an Apollo gateway/router that
the clients hit. They are sibling subgraphs — they don't call each
other.
Cachinator (top-left) is a small Go CLI running on an EC2 cron that
walks TOCS V2 for version IDs, reads content-service's DB-only
`cache/status/<versionId>/` to find stale entries, and POSTs
`cache/refresh/<entryId>/` for each one. The refresh endpoint is what
causes content-service to re-fetch from TOCS and overwrite its
Postgres row. Cachinator itself never touches Neptune, S3, or the
middle-school stack.
Standards services sit alongside this pipeline — two REST services
that twig-graph fans out to but that are not federated themselves.
They have very different write models, which matters for debugging:
┌──── external POSTer TOCS ──── OAuth GET
│ (no writer found in any local repo; │
│ /store-standard-entry is live but │
│ nobody is calling it) │
│ │
│ ┌── RabbitMQ `standards` stream
│ │ (standards-associations.*
│ │ topics for AMQP worker,
│ │ catch-all stream for NestJS
│ │ consumer)
▼ ▼ ▼
┌──────────────────────────────┐ ┌────────────────────────────────────┐
│ standards-explorer-service │ │ associated-standards-service │
│ Express REST, Postgres. │ │ Node monorepo, 1 Postgres, 4 procs:│
│ Grade × standard-type │ │ - REST API (server.js) │
│ registry ("Standards │ │ - CLI worker (worker.js — **single-│
│ Explorer" dashboard). │ │ tenant, territory + tocsAppName │
│ **No tenant partitioning**: │ │ HARDCODED**) │
│ flat PK (grade_id, │ │ - AMQP event worker (eventWorker.js│
│ lesson_id, │ │ via twabbit — per-tenant via │
│ standards_entry_id). │ │ message payload) │
│ Push-only — no TOCS client, │ │ - NestJS stream consumer (catch-all│
│ no scheduled import, no │ │ @MessagePattern, no tenant │
│ writer code anywhere in │ │ filter, no DLQ, offset in │
│ ~/code. │ │ Postgres) │
│ │ │ Helm chart: cronjob template has │
│ │ │ `suspend: true` BAKED IN. │
└──────────────┬───────────────┘ └──────────────┬─────────────────────┘
│ HTTP GET │ HTTP GET
└──────────────┬──────────────────┘
▼
┌──────────────────────┐
│ twig-graph │
│ StandardMappings, │
│ relatedStandards, │
│ educationalStandards │
└──────────────────────┘
go-associated-standards-service is an abandoned Go rewrite of
associated-standards-service (last commit ~2023; Node monorepo is
authoritative). Treat the Go repo as reference-only.Surprising fact (correcting a common assumption):
content-gateway-service does not call TOCS directly — it reads Neptune +
S3, written by content-sync from a RabbitMQ Stream of TOCS events. (Its
@twigeducation/ts-tocs-client import is types-only; no live TOCS calls in the
request path.)
Two independent TOCS caches for two school bands, not connected. Middle-school refresh is event-driven (TOCS → RabbitMQ → content-sync → Neptune/S3 → content-gateway-service) and lands in minutes. Elementary curriculum-tree refresh is cron-driven (TOCS → curriculum-progression-worker → Postgres → twig-graph) and waits for the next worker cronjob.
Use this to pick the right repo/service for a given question or change.
| If the question is about… | Start in… | Why |
|---|---|---|
A REST endpoint under /api/v2/<app>/… or /api/public/… | content-service | Django DRF routes |
| A JWT service-to-service call to TOCS (OAuth client_credentials) | content-service | tocs_consumer/tocs_client.py |
Caching TOCS responses in Postgres (VersionCache) | content-service | version_cache/ app |
A film, lesson, image, TSLesson, TSGrade, search GraphQL query | twig-graph | elementary subgraph |
A getVersion, getResource, getVersions federated GraphQL query | content-gateway-service | middle-school subgraph |
| Gremlin/Neptune queries over content | content-gateway-service | repositories/gremlin/ |
| USS (user subscription) blocklists / content filtering | content-gateway-service | context/index.ts |
A ResourcePackVersion or other *Version type | content-gateway-service | 40+ Version union impls |
An Activity, Film, LessonPack type (no Version suffix) | twig-graph | elementary domain types |
Relay pagination (edges, pageInfo, cursors) | content-gateway-service | Relay-compliant shape |
| Writing vertices/edges to Neptune, uploading version JSON to S3 | content-sync | NestJS consumer |
| "Middle-school Neptune is stale" — the sync pipeline | content-sync | RabbitMQ Stream → Neptune/S3 |
ApplicationManifest handling (tenant membership of resources) | content-sync | ApplicationManifestEventService |
TSCurriculumProgression, TSStudentProgression, breadcrumbs GraphQL queries | twig-graph (schema) → tsc-curriculum-progression-service (data) | Elementary grade tree + breadcrumbs |
| Serving the cached elementary grade tree / breadcrumb trails over REST | tsc-curriculum-progression-service | Postgres JSONB cache, consumed only by twig-graph |
| "Elementary curriculum tree is stale" / repopulating a territory from TOCS | tsc-curriculum-progression-worker | CronJob tree builder |
| Adding a new territory or grade mapping (PK, K, 1–6 → version ID) | Both tsc-curriculum-progression-* repos | Worker env + service GradeProvider |
StandardMappings(gradeId, standardType, filter) GraphQL query (grade × standard-type dashboard) | twig-graph (schema) → standards-explorer-service (data) | Grade + type + lesson counts |
relatedStandards(versionId) / educationalStandards(versionId, applicationId) GraphQL queries | twig-graph (schema) → associated-standards-service (data) | Per-version associated standards + counts |
| Which standards apply to a specific version (elementary & middle school) | associated-standards-service | Postgres; populated by stream consumer + AMQP event worker + CLI worker |
| Searchable standards registry for the teacher dashboard (DCI/CCC/SEP/PE) | standards-explorer-service | Postgres; external POSTs store mappings (no known writer in local repos) |
Onboarding a new TOCS public_key to associated-standards-service (standards sweep for a new variant) | associated-standards-service/apps/.../src/worker.js + charts/.../cronjob.yaml | CLI worker is single-tenant and hardcoded; chart defaults the cron to suspend: true |
| Ingesting TOCS standards events onto a RabbitMQ stream | associated-standards-service (consumer app) | BunnyMQ + offset table |
TSCurriculumStandards GraphQL query | Deprecated mock in twig-graph | Hardcoded; retire per PD-2395 |
| The abandoned Go rewrite of associated-standards-service | go-associated-standards-service (reference only) | Not deployed; stubbed auth |
The TOCS REST surface itself (auth, public_key, relations) | the tocs-api skill | TOCS contract |
A cron that refreshes content-service's VersionCache / warms stale elementary cache entries | cachinator | Go CLI that walks TOCS V2 and POSTs content-service's cache/refresh endpoint |
MAX_CACHE_STALENESS_IN_MINUTES, MAX_CONCURRENT_REQUESTS, or the cache/refresh/<id>/ caller on EC2 | cachinator | Env vars and worker pool live there |
One stub per service — role plus the single fact most likely to trip you up.
Full detail (endpoints, schemas, ports, env vars, file:line citations) lives
in the matching references/<service>.md; load it when you actually need to
open the repo (paths resolve via Resolving repo paths).
content-service (Django REST, elementary)Two-tier elementary content cache: Postgres VersionCache first, live TOCS V1
on miss (OAuth client-credentials, not the QA id_token flow). URL
/api/v2/<tocs_app_name>/…, validated against the TOCS_API dict in
settings/defaults.py (~35 variants, each with its own TOCS PUBLIC_KEY).
Gotcha: V2 is effectively unauthenticated (@permission_classes(())); V1
/api/public/* needs a user JWT.
twig-graph (Apollo Federation v1 subgraph, elementary)Schema-first GraphQL serving film, lesson, image, quiz, TSLesson,
TSGrade, search, etc. Calls content-service over REST and fans out to ~25
"microbusiness" services via serviceUrls/gateway.ts — if a query isn't about
content, it's almost certainly one of those. Source-of-truth subgraph (no
@extends/@external).
content-gateway-service (Apollo Federation v1 subgraph, middle-school)Code-first TS, @apollo/federation v0.20.7 (deliberate Fed v1 pin — don't
migrate casually). Serves the getResource/getVersion family, 40+ *Version
union types, Relay pagination. Gotcha: reads from Neptune (Gremlin) + S3
attribute blobs, never TOCS directly — content-sync is the writer. USS
filtering applies district blocklists server-side.
content-sync (NestJS RabbitMQ-Stream consumer)The TOCS → middle-school bridge: consumes a RabbitMQ Stream of TOCS events
and writes the Neptune vertices/edges + S3 version blobs that
content-gateway-service reads. Gotchas: single replica only (no consumer
groups — a second double-processes); auto-acks even failed messages (no retry/DLQ
— recovery is rewind STREAM_OFFSET and replay); ApplicationManifest events
toggle tenant visibility via a cardinality.set application property.
tsc-curriculum-progression-service (Express REST, elementary)Read-only Postgres cache of the elementary curriculum tree (TSGrade → TSModule →
TSDrivingQuestion) + breadcrumbs. Serves TSCurriculumProgression,
TSStudentProgression, breadcrumbs — consumed only by twig-graph;
written solely by the worker (#6). Gotcha: holds V1 (breadcrumbs) and V2
(breadcrumbs_v2) side by side — a missing V2 entry doesn't imply a missing V1.
tsc-curriculum-progression-worker (cron-driven batch builder)The only write path into service #5's Postgres: a K8s CronJob per territory
(concurrencyPolicy: Forbid) that walks TOCS and POSTs the flattened tree.
Gotcha: not event-driven — stale elementary curriculum after a TOCS edit is
almost always "waiting for the next cronjob" (force it with an ad-hoc Jenkins
run). Each run wholesale overwrites. Territory flow is being retired for PVA.
standards-explorer-service (Express REST, grade × standard-type registry)Backs the "Standards Explorer" dashboard (grade + standard type DCI/CCC/SEP/PE
→ grouped standards with lesson counts). Serves StandardMappings via
twig-graph. Gotchas: flat single-table Postgres, no tenant column; data
arrives only via external POST /store-standard-entry (no known writer in local
repos as of 2026-05); runtime is Node 8.11.1 (EOL) so dep bumps are a project.
associated-standards-service (REST API + NestJS RabbitMQ consumer)System of record for which standards attach to a given content version
(distinct from #7's dashboard registry). Serves relatedStandards /
educationalStandards via twig-graph. Yarn monorepo (Express REST + NestJS
consumer, shared Postgres). Gotchas: two writers (standard_source =
worker|consumer) that readers don't collapse → double-count risk;
educationalStandards(…, applicationId) takes a TOCS public key, not the
Neptune applicationId. Sibling go-associated-standards-service is abandoned.
cachinator (Go cron CLI, content-service cache warmer)External refresh driver for #1's VersionCache: lists stale TOCS V2 versions,
reads content-service's DB-only cache/status, POSTs cache/refresh. Owns no
cache itself. Gotchas: not in Kubernetes — a single-shot Go binary on an EC2
cron host (no Argo/Helm; watch the cachinator-daily-run Sentry monitor); auth
is a pre-minted JWT env var that silently kills the cron on expiry; hardcoded
URLs (no env override); refresh is keyed by integer entry ID, not version ID.
content-service: v2 is open, v1 requires JWT from Twig
auth service (IDENTITY_PUBLIC_KEY, issuer twig:auth-service). User is
a dict from app_metadata, not a Django User.twig-graph: JWT via
@twigeducation/express-authentication-middleware. Can be disabled with
ENABLE_AUTH=false for local dev. JWT plus product/territory headers
(x-product-code, x-territory, x-request-id) pass through to
downstreams.content-gateway-service: JWT from JWT_ISSUER validated
via JWKS. SERVICE_JWT used for its own USS calls.tsc-curriculum-progression-service: JWT via
@twigeducation/express-authentication-middleware + JWT_PUBLIC_KEY.
All routes except /health require auth; no role checks beyond valid
token.content-service → TOCS: OAuth2 client_credentials token cached in
TokenManager, sent as Authorization: Bearer. Plus the public_key
query param specific to the tocs_app_name the request came in on.tsc-curriculum-progression-worker → TOCS: the same OAuth2
client-credentials flow (TOCS_OAUTH_* env vars). Worker →
curriculum-progression-service uses a static SERVICE_REQUEST_TOKEN
bearer.standards-explorer-service: express-jwt with
JWT_PUBLIC_KEY. All routes gated — including the POST /store-standard-entry writer. A valid JWT can read or write any
mapping, so the POST should never be exposed publicly.associated-standards-service (REST API):
@twigeducation/express-authentication-middleware.requireAuthentication
on every non-/health route.associated-standards-service consumer → TOCS: same OAuth2
client-credentials flow (TOCS_OAUTH_*).content-service is the only service in this trio that passes public_key
directly to TOCS. The mapping lives in settings.TOCS_API; the request path
param <tocs_app_name> selects which entry to use. Those keys are TOCS
ContentAPIApplication public_keys — the authoritative list lives in the
TOCS API / admin UI (see the tocs-api skill's "Content-API applications"),
not in this dict; treat settings.TOCS_API as content-service's local subset,
not the source of truth. twig-graph doesn't
know or care about TOCS public keys — it passes an appName (e.g., twig,
preview) to content-service, which then resolves that to a TOCS
public_key under the hood. content-gateway-service applications are
scoped by applicationId, which selects the Neptune partition — TOCS
public_keys don't enter the picture.
| Service | Layer | Keying | Invalidation |
|---|---|---|---|
| content-service | Postgres VersionCache + GinIndex | tocs_uri unique | (1) cachinator cron driving POST /api/public/cache/refresh/<entryId>/, (2) AMQP cache:updated event, (3) manual POST to the same refresh endpoint |
| twig-graph | Redis (off by default), per-call TTL | explicit keys in RESTClient.getWithCaching | TTL-only |
| content-gateway-service | Neptune result cache + Redis + DataLoader + S3 | many | TTL + redeploy |
| tsc-curriculum-progression-service | Postgres JSONB rows (grades, breadcrumbs, breadcrumbs_v2) | <territoryShortCode|tocsAppId>:<versionId> | Overwrite-on-next-worker-run only. No push invalidation. |
| standards-explorer-service | Postgres standards_explorer rows | (grade_id, lesson_id, standards_entry_id) | Upsert-on-POST from external writer. No TTL, no events. |
| associated-standards-service | Postgres territorially_associated_standards, JSONB + count | (territory_shortcode, version_id, standard_source) | RabbitMQ stream (consumer) or TOCS pull (worker). Offset table in Postgres. |
When you see "stale content" bugs, check the layer nearest the client first (Redis in twig-graph / Apollo cache-control headers) then walk backwards.
twig-graph: schema-first, lots of .graphql files.content-gateway-service: code-first, GraphQLObjectType etc. in TS,
schema written out to schema.graphql at startup for tooling.
Editing the schema means editing TypeScript.This matters because "add a field" looks completely different in the two repos. Don't copy-paste between them.
Loaded on demand: a references/<service>.md per hop above (full endpoint
tables, schemas, env vars, file:line citations), plus
references/routing-examples.md — worked traces of "client fetched field X,
here's the path through the stack" — and references/troubleshooting.md —
symptom-first triage for stale-content, missing-standards, and auth bugs across
the stack.
twig-graphql — how to actually explore and run queries against the
federated supergraph this skill maps. Use it (the mcp__twig-graphql__*
tools) to search the schema, introspect a type, validate, and execute against
prod. This skill tells you which subgraph owns a field; twig-graphql is
the tool you drive to query it.twig-content — the content domain model: what each resource code
means, the Resource/Version pattern, relationship semantics, subtypes, and
the publication model. Use it when the question is "what is this content"
rather than "which service serves it". This skill (content-delivery) is the
topology map; twig-content is the data model.tocs-api — the authoritative reference for TOCS itself. Use it
when the question is about TOCS directly (resource codes, relations
param, public_key, version_id shapes) rather than the services in
front of it. The auth flow in tocs-api is the developer QA token flow;
content-service uses a completely different service-to-service OAuth
flow to reach the same API.npx claudepluginhub imaginelearning/dp-claude-plugin --plugin twigCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.