- ADR-062: Timestamp-based slug format (supersedes ADR-007) - Updated v1.5.0 RELEASE.md with 6-phase plan - Updated BACKLOG.md with deferred N+1 query locations - Developer questions and architect responses for Phase 1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
Architect Responses - v1.5.0 Developer Questions
Date: 2025-12-16
Architect: StarPunk Architect Agent
In Response To: docs/design/v1.5.0/2025-12-16-developer-questions.md
Response Summary
All 8 questions have been addressed. The implementation can proceed immediately after reading this document. Key decisions:
- Q1: Keep existing pattern (pass
existing_slugs: Set[str]). ADR-062 pseudocode is conceptual. - Q2: First note gets base slug, first collision gets
-1. The table in ADR-062 is correct. - Q3: Create new function in
slug_utils.py. Migration path provided. - Q4: Keep reserved slug validation for custom slugs only. Safe to skip for timestamp slugs.
- Q5: Tests should use pattern matching for timestamps. Test strategy provided.
- Q6: Phase 0 is a hard dependency. Cannot proceed with Phase 1 until all tests pass.
- Q7: Slug format is opaque. No format-aware code needed.
- Q8: Phases 2-4 can be parallelized. Commit each phase separately.
Detailed Responses
Q1: Collision handling function location and signature
Decision: Keep the existing pattern of accepting existing_slugs: Set[str] as a parameter.
Rationale:
The ADR-062 pseudocode (slug_exists(base_slug)) is conceptual illustration only. The actual implementation should:
- Follow the existing pattern in the codebase (passing pre-fetched slug sets)
- Avoid introducing database coupling into utility functions
- Prevent N+1 queries when checking multiple candidates
Implementation Specification:
Create a new function in /home/phil/Projects/starpunk/starpunk/slug_utils.py:
def generate_timestamp_slug(
created_at: datetime = None,
existing_slugs: Set[str] = None
) -> str:
"""Generate a timestamp-based slug with collision handling.
Per ADR-062: Default format is YYYYMMDDHHMMSS with sequential
suffix (-1, -2, etc.) for collisions.
Args:
created_at: Note creation timestamp (defaults to now)
existing_slugs: Set of existing slugs to check for collisions
Returns:
Unique timestamp-based slug
"""
if created_at is None:
created_at = datetime.utcnow()
if existing_slugs is None:
existing_slugs = set()
base_slug = created_at.strftime("%Y%m%d%H%M%S")
# If no collision, return base slug
if base_slug not in existing_slugs:
return base_slug
# Sequential suffix for collisions
suffix = 1
while f"{base_slug}-{suffix}" in existing_slugs:
suffix += 1
return f"{base_slug}-{suffix}"
Location: /home/phil/Projects/starpunk/starpunk/slug_utils.py
Do NOT modify: The ensure_unique_slug() function signature in ADR-062 is pseudocode. Do not create a function with database coupling.
Q2: Sequential suffix starting number
Decision: First note gets base slug (no suffix). First collision gets -1.
Clarification: The examples table in ADR-062 is correct. The text "first collision: 20251216143052-1" means the first duplicate (collision) gets -1, not the first note overall.
Example Scenario:
Note A created at 14:30:52 -> slug = "20251216143052" (base, no suffix)
Note B created at 14:30:52 -> slug = "20251216143052-1" (first collision)
Note C created at 14:30:52 -> slug = "20251216143052-2" (second collision)
Implementation: Sequential suffix loop should start at 1, not 2:
suffix = 1
while f"{base_slug}-{suffix}" in existing_slugs:
suffix += 1
return f"{base_slug}-{suffix}"
Note: This differs from the existing make_slug_unique_with_suffix() function in slug_utils.py which starts at -2. The new timestamp slug function should start at -1 per ADR-062.
Q3: Two generate_slug() functions with different signatures
Decision: Create a new function in slug_utils.py. Do not modify or remove the existing function in utils.py.
Migration Path:
-
Create new function in
/home/phil/Projects/starpunk/starpunk/slug_utils.py:- Name:
generate_timestamp_slug()(as specified in Q1 response) - Do not name it
generate_slug()to avoid confusion during migration
- Name:
-
Update
/home/phil/Projects/starpunk/starpunk/notes.py(lines 228-234):Current code (line 228-234):
else: # Generate base slug from content base_slug = generate_slug(content, created_at) # Make unique if collision slug = make_slug_unique(base_slug, existing_slugs)Replace with:
else: # Generate timestamp-based slug (ADR-062) from starpunk.slug_utils import generate_timestamp_slug slug = generate_timestamp_slug(created_at, existing_slugs) -
Keep the existing function in
utils.pyunchanged. It may be used elsewhere or for future content-based slug options. -
Update imports in
notes.py:- Remove
generate_slugfrom theutilsimport line (line 34) if no longer needed - Verify no other callers use
generate_slug()for default note creation
- Remove
Rationale:
- Creating a distinctly-named function makes the transition explicit
- Preserves backward compatibility if content-based slugs are ever needed
- Allows gradual migration with clear audit trail
Q4: Reserved slug handling in timestamp context
Decision:
- Default timestamp slugs: Safe to skip reserved slug validation (timestamps cannot collide with reserved words)
- Custom slugs: Must retain reserved slug validation
Implementation:
The new generate_timestamp_slug() function does NOT need to check reserved slugs because:
- Reserved slugs are alphabetic (
api,admin,feed, etc.) - Timestamp slugs are purely numeric (
20251216143052) - These sets are mutually exclusive by construction
However, the existing custom slug validation in validate_and_sanitize_custom_slug() must retain reserved slug checking because user-provided slugs could be anything.
Code Impact:
generate_timestamp_slug(): No reserved slug check neededvalidate_and_sanitize_custom_slug(): Unchanged (already handles reserved slugs)validate_slug()inutils.py: Unchanged (still validates reserved slugs for custom slugs)
Simplification: You may remove the defensive validate_slug() call after timestamp generation in notes.py (line 236-237) since timestamp slugs are guaranteed valid by construction:
# This check is no longer needed for timestamp slugs:
# if not validate_slug(slug):
# raise InvalidNoteDataError(...)
Q5: Test update strategy for slug format changes
Decision: Use pattern matching for timestamp assertions. Preserve content-based tests in a separate test file or clearly marked section.
Test Strategy:
-
For new timestamp slug tests, use regex pattern matching:
import re TIMESTAMP_SLUG_PATTERN = re.compile(r'^\d{14}(-\d+)?$') def test_default_slug_is_timestamp(): note = create_note("Some content here") assert TIMESTAMP_SLUG_PATTERN.match(note.slug) def test_slug_collision_adds_suffix(): # Create two notes at same timestamp fixed_time = datetime(2025, 12, 16, 14, 30, 52) note1 = create_note("First note", created_at=fixed_time) note2 = create_note("Second note", created_at=fixed_time) assert note1.slug == "20251216143052" assert note2.slug == "20251216143052-1" -
For timestamp-specific assertions, use fixed timestamps in tests:
def test_timestamp_slug_format(): """Test that timestamp slug matches exact format.""" fixed_time = datetime(2025, 12, 16, 14, 30, 52) slug = generate_timestamp_slug(fixed_time, set()) assert slug == "20251216143052" -
Content-based tests: Move to a separate section or file named
test_legacy_slug_generation.pywith a comment:""" Legacy tests for content-based slug generation. These test the generate_slug() function in utils.py which is preserved for backward compatibility. New notes use timestamp-based slugs per ADR-062. """ -
Update existing tests that assert specific slug formats:
- Replace exact string assertions (
assert slug == "hello-world") with pattern matching - OR use fixed timestamps and assert exact expected values
- Replace exact string assertions (
Affected Test Files (estimate from your question):
/home/phil/Projects/starpunk/tests/test_utils.py: ~20 tests/home/phil/Projects/starpunk/tests/test_notes.py: ~10 tests/home/phil/Projects/starpunk/tests/test_micropub.py: ~10 tests
Helper Function (add to test utilities or conftest.py):
def assert_valid_timestamp_slug(slug: str) -> None:
"""Assert slug matches timestamp format per ADR-062."""
pattern = re.compile(r'^\d{14}(-\d+)?$')
assert pattern.match(slug), f"Slug '{slug}' does not match timestamp format"
Q6: Phase 0 priority - are tests blocking?
Decision: Phase 0 is a hard dependency. Phase 1 cannot proceed until all tests pass.
Rationale:
-
Failing tests mask regressions: If you implement Phase 1 (slug changes) while 19 tests are failing, you cannot distinguish between:
- Pre-existing failures
- New failures caused by your changes
-
CI/CD integrity: The project should maintain a green build. Committing Phase 1 work with known failing tests violates this principle.
-
Debugging complexity: If slug changes interact unexpectedly with the failing test areas (e.g., feed generation, content negotiation), debugging becomes significantly harder.
Implementation Order:
Phase 0 (Test Fixes) <-- MUST COMPLETE FIRST
|
v
+-- Phase 1 (Timestamp Slugs)
|
+-- Phase 2 (Debug Files) <-- Can run in parallel
|
+-- Phase 3 (N+1 Query Fix) <-- after Phase 0
|
+-- Phase 4 (Atomic Variants)
|
v
Phase 5 (Test Coverage) <-- LAST
Exception: If fixing a Phase 0 test reveals it requires slug-related changes, document the circular dependency and bring to architect attention.
Recommendation: Start Phase 0 immediately. Many of those 19 failures may be simple test maintenance issues (timeouts, assertions that need updating, etc.).
Q7: Backwards compatibility for existing notes
Decision: Slug format is opaque. No code needs to be format-aware.
Clarification:
-
No format detection needed: Do not create an
is_timestamp_slug()helper function. The system should treat all slugs identically regardless of format. -
Code paths are format-agnostic:
- Database queries use slug as opaque string key
- URL routing uses slug as path parameter
- Feed generation includes slug verbatim
- Templates display slug without interpretation
-
Coexistence is automatic: Content-based slugs (
my-first-post) and timestamp slugs (20251216143052) are both valid slug patterns. They coexist naturally. -
Sorting behavior:
- Database ORDER BY on
created_atis unaffected - Alphabetical slug sorting would interleave formats (numbers sort before letters)
- This is acceptable; no UI sorts by slug alphabetically
- Database ORDER BY on
-
Display considerations:
- Timestamps are less readable in URLs, but URLs are rarely human-read
- UI displays note content, not slugs
- RSS feeds include full URLs; format doesn't affect feed readers
No audit required: You do not need to audit slug usages. The design ensures format-agnosticism.
Q8: Phase ordering rationale
Decision:
- Phases 2, 3, and 4 can be parallelized after Phase 0 completes
- Phase 1 (Timestamp Slugs) should be done before Phases 2-4 for cleaner testing
- Each phase should be committed separately
Recommended Order:
Day 1: Phase 0 (Test Fixes) - Get to green build
Day 2: Phase 1 (Timestamp Slugs) - Quick win, user-visible improvement
Day 3: Phase 2 (Debug Files) or Phase 3 (N+1) or Phase 4 (Atomic) - Any order
Day 4: Remaining phases from above
Day 5: Phase 5 (Test Coverage) - Must be last
Commit Strategy:
Each phase gets its own commit or small series of commits:
feat(slugs): Implement timestamp-based slugs per ADR-062
fix(tests): Resolve 19 failing tests in Phase 0
feat(media): Add debug file cleanup and configuration
perf(feed): Batch load media and tags to fix N+1 query
feat(media): Make variant generation atomic with database
test: Expand coverage to 90% for v1.5.0
Why Not Batch: Batching commits makes bisecting regressions harder and obscures the changelog.
Parallelization Note: If you have capacity to work on multiple phases simultaneously (e.g., waiting for test runs), Phases 2-4 have no code overlap and can be developed in parallel branches:
feature/v1.5.0-debug-filesfeature/v1.5.0-n+1-fixfeature/v1.5.0-atomic-variants
Then merge all into main before Phase 5.
Observations Responses
O1: Code duplication between utils.py and slug_utils.py
Acknowledged. The duplicate RESERVED_SLUGS definitions should be consolidated in a future cleanup. For v1.5.0:
- Use the more comprehensive list in
slug_utils.py(12 items) as the canonical source - Do not consolidate in this release (scope creep)
- Add to BACKLOG.md for v1.5.1 or v1.6.0
O2: Timestamp format consistency
Confirmed: The hyphen removal in ADR-062 (YYYYMMDDHHMMSS) vs existing fallback (YYYYMMDD-HHMMSS) is intentional.
- ADR-062 format:
20251216143052(14 characters, no separator) - Old fallback format:
20251216-143052(15 characters, with hyphen)
Rationale:
- One fewer character
- Consistent with ISO 8601 compact format
- No functional difference; purely aesthetic
Action: Use the unhyphenated format per ADR-062.
O3: Test coverage gap mentioned in Phase 5
Clarification: This is intentional duplication for visibility.
- BACKLOG.md lists the gap for tracking
- Phase 5 in RELEASE.md lists it for implementation planning
- Both point to the same work item
No action needed; this is not an error.
ADR-062 Updates Required
No updates to ADR-062 are required based on these questions. The ADR's pseudocode is understood to be conceptual, and all implementation details have been clarified in this response document.
Implementation Checklist
Based on these responses, the developer can now proceed with:
- Phase 0: Fix 19 failing tests
- Create
generate_timestamp_slug()inslug_utils.py - Update
notes.pyto use new timestamp slug function - Update tests with pattern matching strategy
- Proceed with Phases 2-4 (order flexible)
- Complete Phase 5 test coverage last
Architect: StarPunk Architect Agent Status: Questions Answered - Ready for Implementation Next Action: Developer to begin Phase 0 (Test Fixes)