Parent plan: Content Scope Split — Cross-Repository Implementation & Migration Plan

<aside> 🧩

Proposed contract v1. The names below are recommendations, not existing deployed scopes. D1–D5 in the parent plan govern approval. Preserve the original sheet values and document corrections rather than silently treating display labels as OAuth strings.

</aside>

A1. Exact scope inventory from the sheet

Source: QF_API_Endpoints, tab QF_API_Endpoints.csv, rows 2–96. All 95 Content-family entries are GET/Live in this snapshot. The table counts sheet entries, not necessarily unique runtime handlers; aliases and generic script routes can represent overlapping implementations.

Exact column F label Proposed OAuth scope Original sheet rows / count Meaning and correction
Audio scope content.audio.read 2–17, 51, 56 · 18 Reciters, recitations, audio files, timing/lookup, recitation information and styles.
Quran Scope content.quran.read 18–20, 24–50, 58, 80–87 · 39 Chapters, structure, pages, verses and scripts. Recommended correction removes rows 47/48, leaving 37.
Hadith Scope content.hadith.read 21–23 · 3 Hadith references, text and range counts.
Translation Scope content.translations.read 52–53, 62–70, 88 · 12 Translation catalogs, information, verse translations and footnotes. With row 47 correction: 13.
Tafsir Scope content.tafsirs.read 54–55, 71–79 · 11 Tafsir catalogs, information and text. With row 48 correction: 12.
Content Metadata Scope content.metadata.read 57, 59 · 2 Languages and verse media. Do not collapse this into the much broader Content Sync category.
Content Sync Scope content.sync.read 60–61 · 2 Public content delta sync and resource snapshots. This is not the existing user-data sync scope.
Reflections and lessons public scope content.reflections.read 89–93 · 5 Only the five public QuranReflect reads. Existing rights are post/comment-based, not content.
Q&A Scope content.answers.read 94–96 · 3 Answers by ayah, answer detail and range counts.

Raw counts sum to 95. The two recommended corrections change category counts, not the number of source entries. Scope names are case-sensitive protocol identifiers; keep exact spelling consistent after D1 approval.

A2. Route reconciliation rules

Column A says API Family; the current runtime policy must be derived from code, not copied from A. Preserve columns A–L, one-based row number, sheet URL/tab, read date, environment, operation ID, original label and approved override in an immutable input snapshot. Generate the normalized manifest and its SHA-256 checksum from that snapshot.

The normalized key is service + HTTP method + canonical route template + environment. Keep operation IDs and all original rows as provenance. Normalize :id and {id} to one representation, while retaining parameter semantics. Account for gateway service prefixes (/content/..., /quran-reflect/...), OpenAPI server URLs, SDK base paths and upstream /api/v4 paths. Do not strip prefixes by unrestricted substring replacement.

The sheet's public QuranReflect entries also appear in the user-related surface. Merge true duplicates only after resolving service, method and public/user authentication mode. Conflicting rows are build failures requiring a recorded decision, not last-row-wins input. An alias such as /by_rub and /by_rub_el_hizb gets an explicit equivalent policy. The generic /quran/verses/{script} and its concrete script entries require deterministic specificity and allowed script values.

Source comparisons: gateway scope registry, gateway matcher, Rails v4 routes, JS normalization/generator.

A3. Exceptions that cannot be solved by prefix replacement

Rows 47/48: /quran/translations/{translation_id} and /quran/tafsirs/{tafsir_id} sit under Quran in F. Recommend translation and tafsir scopes respectively. This is a proposed correction requiring D2, not permission to edit the sheet. Keep all three possibilities explicit in tests: original mapping, approved mapping, and rejected unapproved activation.

Public reflections: rows 89–91 cover /quran-reflect/v1/posts/feed, /posts/{id} and /posts/user-posts/{id}. Their legacy narrow permission is post.read. Rows 92–93 cover /posts/{id}/comments and /posts/{id}/all-comments, with legacy comment.read. Preserve any additional currently accepted parent alternatives only where the actual route policy accepts them. A token carrying the proposed reflection scope may access these public reads only; it must not gain private posts, my-posts, private rooms, user-specific feeds, mutations or moderation. Upstream visibility filtering remains mandatory even when the route path is public.

Restricted and unlisted routes: Qiraat has explicit gateway policy and must stay outside this content migration. The Rails source also contains routes not represented in these 95 entries, including Mushafs, hadith-by-URN, resource changes and word-by-word translations, audio subroutes, search/suggest and verse filtering. T00/T01 must produce a reviewed disposition for every relevant difference: map to a named new scope, preserve legacy-only access, preserve independent permission, or keep internal/nonpublic. A Rails route existing in source does not prove public deployment. Do not drop legacy behavior merely because an operation is missing from the sheet, and do not grant all new clients undocumented capabilities without review.

Other families: the remaining User, OAuth2 and Search rows are regression inventory unless separately approved. Never turn an OAuth token/introspection/authorization endpoint into a generic “Account and preferences” permission. Do not merge existing user sync with public content.sync.read. Do not grant search, Qiraat, analytics, learning-plan, administrative or private-user permissions as content successors. Preserve each environment's current independent rules and consent categories.

A4. Minimal contract shape and generated artifacts

Proposed canonical files in qf-api-docs: contracts/content-scopes/v1.json, its JSON schema, source snapshot and decision record. File names are new implementation deliverables, not claims that files already exist. Each consumer pins the approved version/hash. A release changes that pin deliberately; runtime never downloads policy from a mutable branch.

{
  "contractVersion": "content-scopes/v1",
  "routeId": "content.v4.chapters.list",
  "service": "content",
  "method": "GET",
  "upstreamPath": "/api/v4/chapters",
  "sourceRows": [18],
  "environments": ["production"],
  "authContext": "app-compatible-public-read",
  "legacyAnyOf": ["content.read", "content"],
  "granularAnyOf": ["content.quran.read"],
  "rateLimitPolicyId": "preserve-existing-content-read-policy",
  "responseBoundary": "endpoint-family-not-data-isolation"
}

This example is illustrative. The implementation must add the verified gateway path/alias, operation ID, environment mapping and actual quota configuration identifier; do not ship the descriptive placeholder as a database bucket key. Separate display labels, token strings, default-grant policy, finite migration prerequisites and route authorization. Include explicit deprecatedForNewClients and grandfathering semantics for legacy permissions; avoid changing meanings of existing active flags in ways that invalidate old clients.