DataTalksClub / DataTalksClub/website
Restore path-only podcast guest links after the shared Wiki URL change
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Product outcome
Restore the accepted #223 podcast guest-link boundary after 912f60fff9aed1dfd306fe9eba9d465674976dcc deliberately broadened the shared Wiki/graph URL helper. Podcast guest links remain safe, root-relative path-only values; Wiki graph, search, and hash-deep-link URLs retain their separately accepted bounded query/fragment behavior.
This is a reopened regression of the contract delivered in 2f64787b5a0c31825ddf9a16b1f7858ac28f3b35, not a redesign of either feature.
Normative authority and exact evidence
- URL, link, and SEO compatibility: exact podcast/Person detail paths; Wiki search, graph, and hash deep links; unsafe-link and internal-link preservation gates.
- GitHub content and Person ownership: podcast/Person projection and Podwiki resolution contracts.
- Verification strategy: podcast, Person, Wiki, URL/link, and browser coverage.
- Accepted #223 implementation:
2f64787b5a0c31825ddf9a16b1f7858ac28f3b35. - Regression source:
912f60fff9aed1dfd306fe9eba9d465674976dcc, which correctly allowed the sharedsafe_public_graph_urlto retain the one bounded Wiki search query and safe fragments, but left the podcast adapter calling that widened contract directly. - Exact reproduction anchor: current main
face8e4808d65afbf0374d1ced7a88079950d663renders a synthetic direct guest path/people/safe-guest.html#bioinstead of omitting the link. - The checked current projection has 205 episodes, 208 guest profiles, 207 non-empty guest paths, and zero guest paths containing
?or#; all 438 checked Person paths also contain zero?or#. Therefore no data migration, projection rewrite, or current-page copy change is required.
Scope — podcast adapter only
1. Add one path-only podcast boundary
In content/podcast_content.py, add a private podcast-owned validator/wrapper used by _guests for both:
guest_profiles[*].public_pathread directly from the podcast record; andpeople_by_slug[key].public_pathread from the resolved public Person.
The wrapper may reuse safe_public_graph_url for its shared safety checks, but it additionally returns a non-empty value only when URL parsing proves that the accepted root-relative value has an empty query and empty fragment. It must not add a flag/mode to, narrow, duplicate, or otherwise change the public behavior of the shared Wiki/graph helper.
Accepted non-empty podcast guest values:
- are strings beginning with exactly one
/; - remain on this site with no scheme, authority, credentials, backslash, control character, malformed percent escape, or decoded traversal segment;
- have no query component and no fragment component;
- are returned unchanged rather than normalized, decoded, joined, redirected, fetched, or guessed.
None, an empty string, non-string values, protocol-relative/absolute/credential-bearing URLs, query-bearing values, fragment-bearing values, query-plus-fragment values, raw or percent-decoded traversal/control forms, backslashes, and malformed encodings become the existing empty-path fallback. Do not broaden this correction into a new /people/-prefix rule: #223's accepted safe root-relative path-only contract remains authoritative.
2. Preserve the existing resolution and mismatch matrix
Validation happens independently before the current direct-record/resolved-Person comparison:
| Direct record path | Resolved Person | Result |
|---|---|---|
| valid path-only | no matching Person | direct path is linked |
| absent/invalid | no matching Person | guest name remains visible as plain text, with no guest anchor |
| absent/invalid | valid resolved path-only | resolved Person path is linked |
| valid path-only | same valid resolved path-only | that one path is linked |
| valid path-only | different, absent, or invalid resolved path | existing ImproperlyConfigured("Public podcast guest profile path does not match.") failure remains |
| absent/invalid | absent/invalid resolved path | guest name remains visible as plain text |
Do not silently prefer one of two independently valid different paths. Do not turn an invalid direct value into a mismatch when a valid resolved Person supplies the existing fallback. A fail-closed mismatch exposes no unsafe href, source value, credentials, or Person/profile data in the exception or ordinary logs.
The template already renders Guest.public_path conditionally and does not change. Names, images, media availability, summaries, external profile links, guest order, episode routes, canonical/SEO data, cache behavior, and related episode/navigation behavior remain unchanged.
3. Preserve Wiki behavior exactly
content.public_data.safe_public_graph_url and its current consumers are not implementation targets. The following accepted behavior must remain green:
/wiki/search?q=data%20engineeringis retained as the sole bounded search-query shape under its existing count, key, length, decoding, and control-character rules;/wiki/topic#section-2and other already accepted safe page fragments are retained;- unsafe protocol-relative, external, credential, traversal, malformed-percent, control, multi/unknown-query, and unsafe-fragment cases remain rejected.
No rollback of 912f60f URL localization, Wiki graph/search/hash links, sponsor/article work, or generated projection is allowed.
Allowed files
- Runtime:
content/podcast_content.pyonly. - Focused regression tests:
content/tests/test_podcast_catalog.pyonly.
Existing shared-helper/Wiki tests in content/tests/test_sponsor_article_charts.py and content/tests/test_wiki_design.py must run unchanged. If a runtime fix requires any other production file or changes the shared helper, stop and return to PM.
Dependencies and ordered combined delivery
No issue supplies a new product model, interface, decision, or infrastructure prerequisite. #223 and #270 remain separate product scopes, but their delivery is serialized because both own distinct changes in content/podcast_content.py and content/tests/test_podcast_catalog.py, and current-main podcast smoke cannot terminate green until #270 restores the three localized resources.
After the shared current-main recovery blockers are repaired and accepted, use one exact repaired base and this protocol:
- The #223 engineer rebases/reconstructs and freezes only the two allowed #223 paths/hunks, records its exact patch fingerprint, and runs #223-focused checks. It does not implement, test, or accept #270.
- The #270 engineer prepares only the #270 resource-policy/runtime/render delta against that repaired base with the frozen #223 patch treated as ordered input. It must not modify #223's guest wrapper, grammar, fallback/mismatch matrix, or tests, and it does not test or accept #223.
- A named integration owner—not either independent tester—constructs one uncommitted candidate from the repaired base by applying the exact #223 patch first and the exact #270 delta second. Any non-mechanical conflict or changed issue fingerprint returns to the owning engineer/PM; the integration owner may not invent cross-scope behavior.
- Generate one versioned full plan/envelope for the exact combined source fingerprint. The independent tester validates both patch identities and the ordered final tree, runs the complete shared envelope once, and posts separate tester-final reports to #223 and #270. #223's screenshot disposition is recorded separately (expected
not_applicableonly if its recomputed issue impact and zero-suffix census still support it); #270's required route screenshots remain under.tmp/screenshots/issue-270/and cannot be credited as #223 product evidence. - PM posts separate acceptance decisions. Only after both PASS, create focused commits from the accepted patch boundaries in exact order:
Closes #223, thenCloses #270. Verify that the resulting two-commit tip is byte-identical to the accepted combined tree, then merge/push once and dispatch one exact-SHA on-call observation.
The old requirement that #223 independently reach a green full smoke result and land before #270 starts is superseded. Old face8e4 red/partial reports remain diagnostic only; no engineer report, focused run, or cross-engineer assertion is tester evidence.
Acceptance criteria
- Both direct podcast-record and resolved-Person public paths pass through one podcast-owned path-only boundary before comparison or rendering.
- A safe root-relative path-only value is preserved byte-for-byte; query or fragment is rejected even though the shared Wiki helper correctly accepts its bounded Wiki forms.
- Every invalid type/security shape above produces an empty guest path without builtin parsing failure, unsafe
href, value reflection, external request, redirect, or logging. - The exact direct/resolved fallback and fail-closed mismatch table above is preserved, including a visible unlinked guest name for empty/invalid final paths.
- The current 205-episode/208-guest-profile projection renders the same guest names, order, and 207 non-empty destinations; its zero query/fragment census remains zero and no checked projection changes.
- Shared Wiki query/fragment acceptance and rejection tests pass unchanged;
safe_public_graph_url, Wiki routes/graph/search, and912f60fbehavior are unchanged. - No template, route, projection, source pin, manifest/hash, SEO/canonical, cache, CSS, JavaScript, copy, image, Person record, or external profile-link change occurs.
- #223-focused checks pass on its frozen patch, and the graph-selected format/lint/type, Django/content, migration-drift, browser, container, and versioned verification gates terminate green on the exact ordered combined #223-then-#270 candidate. No retry, skip, xfail, quarantine, assertion weakening, timeout increase, partial run, or cross-engineer result is acceptance evidence.
- Engineer and independent tester record exact base/candidate/diff/plan/run/report digests and terminal counts; the previous 2026-08-24 tester evidence proves the historical implementation only and is not relabelled as current-candidate evidence.
Required regression scenarios
- Direct record only: valid path; empty/None; bool/integer/list/mapping;
//host; absolute and credential-bearing URL;?query;#fragment; combined suffix; backslash; raw control; malformed percent; raw and percent-decoded..segment. - Resolved Person only: the same accepted/rejected matrix, proving query/fragment values cannot bypass the direct-record check.
- Direct/resolved combinations: same valid path, different valid paths, valid direct plus empty/invalid resolved, invalid direct plus valid resolved, and both invalid. Assert the exact link/fallback/mismatch result in the table.
- Rendered episode response: safe direct and resolved paths appear once as guest anchors; invalid final paths keep escaped visible guest names without an anchor or unsafe value; mismatch fails before a response can contain either candidate path.
- Current checked projection: all 205 episodes compose, all 208 guests retain name/order, the current 207 destinations are unchanged path-only values, and no query/fragment link appears.
- Shared regression: existing Wiki search-query, safe-fragment, graph/link localization, and unsafe-value tests remain unchanged and green.
Verification and screenshots
Use only uv-backed commands and Make targets. At minimum run:
- focused
content.tests.test_podcast_catalog,content.tests.test_sponsor_article_charts, andcontent.tests.test_wiki_design; - the affected content suite, locked format/lint/type checks, migration drift check, and current content/projection invariants;
- the versioned
verification-planandverification-run, accepting a safer/full selection on unknown graph or evidence history; - the graph-selected Playwright tier, with a focused representative podcast episode/linked Person navigation and Wiki search/hash-deep-link scenario. A partial or timed-out browser run is red.
Expected dispositions:
- Migrations: no model/migration change; new migration execution is
not_applicable, while migration drift remains required. - Render behavior: focused rendered Django coverage is required for safe link and plain-text fallback. The checked projection has no affected value, so existing public pages have no expected DOM/copy/visual delta.
- Screenshots: expected
not_applicablebecause no template/static/current-projection output changes. The engineer recordspending_independent_tester; only the independent tester may confirmnot_applicableafter recomputing the frozen graph and the zero-suffix projection census. If the actual diff or graph shows render impact, the selected podcast route and linked Person/fallback states require inspected desktop/mobile screenshots instead. - JavaScript enabled/disabled comparison:
not_applicable; no script behavior changes. If actual scope reaches JavaScript, return to PM.
Non-goals
- No change to
safe_public_graph_url, Wiki query/fragment grammar, URL localization, Wiki graph/search/deep links, or any912f60fproduct behavior. - No template, CSS, JavaScript, copy, visual design, route, redirect, canonical, metadata, sitemap, cache, or Person-page change.
- No generated projection, source repository/pin, adoption ledger, manifest/tree/hash, migration, or data cleanup; current data already satisfies the boundary.
- No new Person-path family restriction, URL normalization, redirect following, network validation, source guessing, or mismatch fallback.
- No external guest profile-link policy change; those remain separately validated external URLs.
- No broad URL-helper, podcast adapter, projection, test, or security refactor; no unrelated gate recovery.
Lifecycle
The #223 engineer owns only its frozen two-path guest-boundary patch and focused report. After the repaired-base ordered integration described above, one independent tester validates the combined fingerprint/full envelope but posts a distinct #223 report and screenshot disposition; PM accepts #223 separately. After both #223 and #270 pass tester and PM gates, create the exact focused Closes #223 commit first and Closes #270 second, prove the two-commit tip matches the accepted tree, then merge/push once. On-call alone observes the resulting exact-SHA CI/deployment. Neither engineer is the other issue's tester, and neither issue's acceptance is inferred from the other's report.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with content/podcast_content.py, focusing on _guests and the existing safe_public_graph_url usage, then read the accepted #223 implementation and content/tests/test_podcast_catalog.py. Run the focused podcast, sponsor/article, and Wiki test modules. Done means the direct/resolved path matrix passes, path-only values remain unchanged, invalid values produce safe fallbacks, and shared Wiki behavior stays green.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, security, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100