Skip to content

Add an offline pinned FAQ adapter and exact feed projection #293

Description

@alexeygrigorev

Parent: #42

Normative authority:

Product outcome

Provide one deterministic, network-free dtc-faq adapter and five-field JSON-feed projection from an already verified immutable FAQ checkout. It must prove both the accepted checked baseline and the current green source without changing the public FAQ reader, rendering a public edit link, or introducing any direct-sync/activation behavior.

The accepted target input is exactly:

  • repository DataTalksClub/faq;
  • branch main;
  • commit 3a170c98ad6af394408595f697ce6624ffab155f;
  • tree 6b00fa01f1bc9fee4147b4f6e30f685a429f98c8;
  • source CI run 33236963075, successful;
  • selected roots _questions/ and images/ only.

The adapter receives a caller-supplied local checkout plus the expected immutable commit/tree identity. It performs no clone, fetch, remote-head lookup, provider call, hook, filter, repository-code execution, database mutation, or public-reader switch.

Evidence baseline and target census

The checked website projection on origin/main@face8e4808d65afbf0374d1ced7a88079950d663 is content/faq_projection.json SHA-256 7b6e5723b2ab0cf453254c10fb06a08175ca2bee5b9c65d98cc0534acfe8f209. It pins DataTalksClub/faq@c8da1deea9e24945922702994de101dd90a5380a, tree f880b4b63cdd1b213bd1303d8874e4ce02e63965, with:

  • 6 courses, 70 populated sections, 1,401 questions;
  • per-course counts in public order: Data Engineering 404, Stock Markets Analytics 93, Machine Learning 471, LLM 139, AI Dev Tools 41, MLOps 253;
  • 101 image declarations resolving to 99 unique referenced assets; and
  • two management-only CSS assets plus all source images copied separately by the historical projection, outside this feed-only slice.

The target commit has:

  • 6 course metadata files, 74 declared sections, 70 populated sections, 1,406 questions;
  • per-course counts in the same public order: 404, 93, 471, 144, 41, 253;
  • 1,406 non-empty questions and answers, 101 image declarations, 99 unique referenced image paths, and 136 source image files;
  • five added LLM questions: 5cc511f85b, 2fed4b176c, 920bb7ead8, 8471c98e1f, and 670737acd7;
  • answer-only changes to eae0fb50aa and 8fd8cd336d; no metadata or image change from the accepted baseline;
  • four accepted equal-sort_order groups, resolved deterministically by source filename; and
  • two YAML numeric-looking IDs, 6739977244 and 9506089527, normalized to ten-character strings as required by the fragment/feed contract.

Four declared sections currently contain no question: ai-dev-tools-zoomcamp/module-6, stock-markets-analytics-zoomcamp/misc, llm-zoomcamp/module-4-homework, and llm-zoomcamp/module-6-homework. Preserve all 74 declarations and their order in management candidate data, but the flattened feed and its populated-section count contain only records from the 70 populated sections.

Adapter and candidate contract

Add a content_sync-owned immutable FAQ contract, adapter, bounded diagnostics, and focused fixtures/tests. The result contains:

  • source stable ID, repository, branch, commit SHA, tree SHA, adapter/parser schema versions, selected-input digest, counts, and deterministic bundle/feed digests;
  • courses in this exact public order: data-engineering-zoomcamp, stock-markets-analytics-zoomcamp, machine-learning-zoomcamp, llm-zoomcamp, ai-dev-tools-zoomcamp, mlops-zoomcamp;
  • each course’s exact slug, display name, ordered declared sections, and management-only Slack/Telegram/comment metadata where present;
  • each section’s exact ID, name, declaration order, optional management comment, populated state, and ordered questions;
  • each question’s normalized ten-character string ID, course and section identity/name, positive integer sort_order, exact question, exact raw Markdown answer, normalized source path, and management-only moving-main edit URL;
  • each declared image’s ID, description, normalized source path, stable candidate public path, size, media type, and SHA-256; duplicate references to the same source asset share one asset identity; and
  • feed projections containing only {id,course,section,question,answer} in section order and then (sort_order, source filename byte order).

The courses.json projection contains only {course,course_name,path,questions_count} in the exact course order above. Its legacy path remains /json/<course>.json even though the website serves the compatibility route beneath /faq/json/.

Question/answer bytes are decoded as strict UTF-8 and preserved as source text after frontmatter separation; Jinja/Liquid/dbt-looking text is data and is never evaluated. Valid Unicode content is preserved, not normalized or rewritten. Structural identifiers and paths remain bounded ASCII.

The current two numeric YAML ID scalars are a reviewed compatibility exception: accept a non-boolean integer only when its canonical decimal representation is exactly ten characters, then normalize it to a string. Every other ID type, length, or character set fails. Do not add general scalar coercion.

Validation and failure behavior

  • Accept only the exact metadata/question/image schemas exercised by the target checkout. Unknown structural keys, unknown courses/sections, duplicate course/section/question identities, duplicate per-question image IDs, invalid/blank question or answer, invalid sort order, or conflicting asset identity fail closed.
  • Metadata is authoritative for course/section membership and order. A question under an undeclared course/section fails. Equal positive sort_order values are allowed and use the filename tie-break above.
  • Resolve images only below images/<same-course>/ with the accepted .png/.jpg media types. Missing, symlinked, traversal, absolute, cross-course, remote, wrong-type, oversized, or checksum-conflicting assets fail. Unreferenced source images are inventory evidence, not candidate assets.
  • Use safe, bounded YAML event scanning/construction. Reject aliases, unsafe tags/types, duplicate mapping keys, invalid UTF-8, path/control-character violations, depth/node/file-count/byte/time overruns, symlinks, gitlinks, and unexpected selected-root file types.
  • Bounds must cover the measured target (1,551 selected files, 10,472,655 selected bytes, largest file 862,721 bytes) with explicit reviewed headroom; they must not be unbounded or inferred from the checkout at runtime.
  • Failures return stable code plus bounded normalized relative path only. Do not include source content, raw YAML, exception text, absolute paths, tokens, credentials, or provider payloads.
  • A failed parse returns no partial bundle/feed and changes no checked projection, public response, model, or prior artifact.
  • Two exact runs over the same checkout produce byte-identical canonical bundle/feed evidence and digests regardless of filesystem enumeration order.

Public provenance boundary

edit_url, repository, branch, revision, tree, checksum, source path, diagnostics, and source-status evidence are management/build data only. They must not occur in FAQ HTML, either JSON feed, public structured data, sitemap/search output, or other anonymous response.

This issue must retain the current public guarantee: /faq/ and /faq/<course>.html show no “Edit on GitHub” control and no source/revision disclosure. The public five-field question feed and four-field course feed contain no provenance field. Do not add an edit-link browser scenario: public edit-link behavior is forbidden by specification 03.

Acceptance criteria

  • The network-free adapter rejects a mismatched repository/branch/commit/tree and never invokes network, repository hooks/filters, or source code.
  • Exact replay of c8da1de... produces 6/70/1,401/99 and payload-equal course/question feed arrays to the checked projection, including ten-character string IDs and exact order.
  • Exact replay of 3a170c... produces 6 courses, 74 declared/70 populated sections, 1,406 questions, 101 declarations/99 referenced assets, the exact per-course counts and seven-record delta above, and deterministic evidence on two runs.
  • Course, metadata-section, question, equal-sort tie, raw Markdown, valid Unicode, image-reference, and feed-field/order contracts are covered by focused tests.
  • The two exact numeric-ID exceptions normalize to strings; booleans, floats, nulls, sequences, mappings, other integers, malformed strings, and duplicate IDs fail.
  • Traversal/symlink/gitlink, unknown or cross-course paths, unsafe YAML, duplicate keys, missing/invalid metadata, missing/conflicting assets, invalid UTF-8/structural controls, and every resource bound fail with content-free deterministic diagnostics and no partial result.
  • Repository tests prove candidate management provenance is absent from both feed projections and existing anonymous FAQ HTML/JSON responses.
  • A versioned selective-CI plan records exact base/head, graph/plan digests, all dispositions, and fresh focused/full evidence required by the graph.

Verification scenarios

  1. Parse the accepted baseline and target immutable checkouts twice with deliberately shuffled filesystem enumeration; compare complete canonical evidence/digests and the exact census/delta.
  2. Compare baseline feed arrays against content/faq_projection.json, including the fixed course order, raw answers, four equal-sort groups, and two normalized numeric IDs.
  3. Use bounded fixtures for every schema/type/path/YAML/resource denial above and assert stable diagnostic code/path with no raw input leakage.
  4. Inspect existing anonymous FAQ HTML, course feed, and course-index feed for absence of edit URLs, repository names, revisions, checksums, and source paths.

Browser

The graph-selected backend-only browser tier is required. Run make test-playwright-smoke against the exact frozen candidate and retain plan-bound evidence. This is regression coverage for the shared/test-infrastructure impact; it does not authorize a page, route, payload, or rendering change and adds no issue-specific public browser scenario.

Screenshots

not_applicable for this child: it adds an offline adapter/evidence surface and must not change a template, public route, response payload, CSS, asset route, or checked projection. Existing request-level provenance-denial regression tests are required. Any implementation that changes public rendering or feeds is out of scope and must return to PM for a separate render-impact issue with desktop/mobile screenshots.

Dependencies and downstream work

No open implementation dependency. The accepted checked projection and immutable source checkout are the inputs; this slice deliberately does not depend on #38’s direct-sync models, #273#278, a provider credential, or a database/public-reader decision.

Downstream only:

A merge for this child uses Refs #42 and Closes #293; it does not close #42 or claim source activation.

Non-goals

No source clone/fetch/webhook/reconciliation, credentials, durable jobs, source lock, direct database upsert, ContentRelease, SyncLog, activation/rollback, Studio/admin API, search/graph indexing, public-reader or checked-projection switch, public page/template/asset/CSS change, new public route or field, public edit link, source repository mutation, provider/production access, or execution of DataTalksClub/faq code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Must-have or release-blockingcontentArea: contentenhancementNew feature or requestintegrationArea: integrationtestingArea: testing

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions