Skip to content

feat: domain model#34

Merged
hsc00 merged 3 commits intomainfrom
feat/domain-model
Apr 1, 2026
Merged

feat: domain model#34
hsc00 merged 3 commits intomainfrom
feat/domain-model

Conversation

@hsc00
Copy link
Copy Markdown
Owner

@hsc00 hsc00 commented Apr 1, 2026

Overview

This PR implements a domain-driven refactor of the Asset domain to enforce stronger invariants around uploads, introduces typed value objects and enums for upload targets and proofs, adds repository/storage contracts, extends test coverage for the new types and behaviors, and updates documentation and pre-commit/tooling metadata.

Key Changes

Domain Model

  • Asset refactor (src/Domain/Asset/Asset.php)
    • Asset id is now an AssetId value object.
    • fileName and mimeType are required for pending assets (trimmed, non-empty).
    • Upload state enforces completion-proof invariants: UPLOADED requires a non-null UploadCompletionProofValue; other statuses must not have a completion proof.
    • Persistence reconstitution split into reconstitute(...) and reconstituteUploaded(...).
    • markUploaded now accepts UploadCompletionProofValue (removed filename/contentType/size handling).
    • API/getters adjusted (getId(): AssetId, getFileName(), getMimeType(), getCompletionProof(), removed getSize()/getUpdatedAt()/old getters).
    • Equality compares AssetId via equals().

Value Objects & Enums

  • AssetId
    • Added generate(): self for UUIDv4 and equals(self): bool for value equality.
  • UploadTarget (new)
    • final readonly value object with URL normalization (scheme/host lowercased), HTTPS enforcement (HTTP allowed only for localhost/loopback), signed headers validation (list of UploadParameter), completionProof and expiresAt.
  • UploadParameter (new)
    • final readonly; trims and validates name; preserves value.
  • UploadCompletionProof (new)
    • final readonly; trims name and stores UploadCompletionProofSource.
  • UploadCompletionProofValue (new)
    • final readonly; trims and validates non-empty proof value.
  • Enums
    • UploadHttpMethod (PUT)
    • UploadCompletionProofSource (RESPONSE_HEADER)

Repository & Storage Contracts

  • AssetRepositoryInterface: added findByUploadId(UploadId $uploadId): ?Asset.
  • StorageAdapterInterface: new interface with createUploadTarget(Asset $asset): UploadTarget.

Tests

  • Expanded and added unit tests covering:
    • Asset behavior, creation, reconstitution, state transitions, invalid transitions, and equality.
    • AssetId generation/validation/equality.
    • UploadTarget URL validation/normalization, HTTPS rules, signed-headers validation, local dev allowances.
    • UploadParameter, UploadCompletionProof, UploadCompletionProofValue validation/trimming.
  • Tests located under tests/Unit/Domain/Asset/** (new/updated files present).

Documentation & Site

  • Added ADR: docs/adr/04-asset-domain-contracts.md (repository and storage contracts, completion-proof invariants).
  • Added implementation log: docs/logs/02-domain-model-core-objects.md.
  • mkdocs.yml updated to include new docs entries.

Tooling & Misc

  • .github/copilot-instructions.md: added "Feedback Carry-Forward" guidance for iterative agent attempts.
  • captainhook.json: removed automatic git add -A from pre-commit; now runs php-cs-fixer and phpstan only.
  • .gitignore updated to ignore .php-cs-fixer.cache; previous .phpunit cache entry removed.
  • .php-cs-fixer.cache: removed/cleared (no cached formatting state retained).

Validation

  • New/updated unit tests added for domain types (files present in repo).
  • Static/format tooling adjustments reflected in pre-commit config; cache file removed from repo.

Reviewer Guidance

  • High-priority review targets: src/Domain/Asset/Asset.php and src/Domain/Asset/ValueObject/UploadTarget.php for invariants, URL normalization, and security constraints.
  • Also review AssetRepositoryInterface and StorageAdapterInterface to ensure persistence/provider contracts meet integration needs.
  • Tests are included for the new behaviors—run unit suite and static analysis when reviewing.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

Refactors the Asset aggregate to use value objects, adds upload-target and completion-proof domain contracts, extends the repository with uploadId lookup, introduces storage adapter/upload enums, and adds comprehensive unit tests and documentation for US-02.

Changes

Cohort / File(s) Summary
Configuration & Hooks
/.gitignore, /.php-cs-fixer.cache, /captainhook.json, mkdocs.yml
Replace phpunit cache ignore with .php-cs-fixer.cache, remove cached php-cs-fixer file contents, drop git add -A from pre-commit, and add three docs entries to MkDocs nav.
Architecture & Logs
docs/adr/04-asset-domain-contracts.md, docs/logs/02-domain-model-core-objects.md
Add ADR and implementation log defining durable Asset domain contracts, storage adapter contract, typed upload-target model, and completion-proof invariants.
Domain Aggregate
src/Domain/Asset/Asset.php
Refactor Asset to use AssetId, require fileName/mimeType, introduce upload completion-proof handling, split reconstitution (reconstitute/reconstituteUploaded), replace upload transition to accept UploadCompletionProofValue, and update getters/signatures.
Repository & Adapter
src/Domain/Asset/AssetRepositoryInterface.php, src/Domain/Asset/StorageAdapterInterface.php
Add findByUploadId(UploadId): ?Asset to repository interface and introduce StorageAdapterInterface::createUploadTarget(Asset): UploadTarget.
Enums
src/Domain/Asset/UploadCompletionProofSource.php, src/Domain/Asset/UploadHttpMethod.php
Add UploadCompletionProofSource::RESPONSE_HEADER and UploadHttpMethod::PUT.
Value Objects
src/Domain/Asset/ValueObject/... (AssetId.php, UploadCompletionProof.php, UploadCompletionProofValue.php, UploadParameter.php, UploadTarget.php)
Add AssetId generation and equality, value objects for completion proof, proof value, upload parameter, and UploadTarget with URL normalization, HTTPS enforcement (loopback exceptions), signed-headers validation, method, completion proof and expiry.
Tests — Asset
tests/Unit/Domain/Asset/AssetTest.php
Update tests for required fileName/mimeType, add reconstitution and uploaded-state tests, adapt markUploaded to completion-proof, add invalid transition tests and equality checks.
Tests — Value Objects
tests/Unit/Domain/Asset/ValueObject/*
Add/expand unit tests for AssetId, UploadCompletionProof, UploadCompletionProofValue, UploadId adjustments, UploadParameter, and UploadTarget covering trimming, validation, UUID rules, URL normalization, HTTPS rules, and signed-headers validation.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant API as API Server
    participant Storage as StorageAdapter
    participant Repo as AssetRepository

    Client->>API: request upload creation (uploadId, file meta)
    API->>Repo: createPending(asset with fileName,mimeType)
    API->>Storage: createUploadTarget(asset)
    Storage-->>API: UploadTarget(url, method, signedHeaders, completionProofSpec, expiresAt)
    API-->>Client: return UploadTarget
    Client->>Storage: perform upload to UploadTarget (PUT with signed headers)
    Note right of Storage: Storage accepts upload and produces proof (e.g., response header)
    Storage-->>Client: upload response including proof
    Client->>API: notify upload completion with completionProofValue
    API->>Repo: markUploaded(assetId, completionProofValue)
    Repo-->>API: persist uploaded state (including completionProof)
    API-->>Client: confirm upload recorded
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement

Poem

🐇 I hopped through code with whiskers twitching bright,
New IDs and proofs snug in the night,
URLs polished, headers held tight,
Assets reconstitute—everything's right! ✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author; all required template sections (Summary, Changes, Testing, Reviewer Notes) are missing. Add a complete pull request description following the template, including a one-sentence summary, list of file/component changes, testing details, and any reviewer notes or known limitations.
Docstring Coverage ⚠️ Warning Docstring coverage is 24.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feat: domain model' is vague and generic, using non-descriptive terms that do not clearly convey what specific domain model changes were made. Replace with a more specific title that describes the main change, such as 'feat: add Asset domain model with upload completion proof' or similar that highlights the primary domain model addition.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/domain-model

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hsc00 hsc00 changed the title fix(domain): normalize UploadTarget URL, add AssetId::equals, remove test helpers, consolidate .gitignore, align docblock feat: domain model Apr 1, 2026
@hsc00 hsc00 linked an issue Apr 1, 2026 that may be closed by this pull request
9 tasks
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/copilot-instructions.md:
- Around line 82-89: The "Feedback Carry-Forward" section uses undefined terms
and an overly long sentence: add concise definitions for REQUEST CHANGES and
DECLINE at first mention, and either split the long sentence referencing "scoped
instruction", "skill", "workspace instructions", and "agent files" into two
sentences or move short definitions into a new "Customization Surfaces"
subsection (or inline footnote) so each surface is briefly explained and the
guidance reads clearly; update the paragraph in the "Feedback Carry-Forward"
block to reference those definitions and simplify the wording accordingly.

In `@docs/logs/02-domain-model-core-objects.md`:
- Line 49: The file ends without a trailing newline which triggers markdownlint
MD047; open the document (the last line containing "Later application and
infrastructure work should consume these contracts directly instead of
introducing parallel repository or storage shapes.") and add a single newline
character at end-of-file so the file terminates with a newline.

In `@src/Domain/Asset/ValueObject/UploadCompletionProof.php`:
- Around line 7-18: The constructor of UploadCompletionProof currently throws a
fully-qualified \InvalidArgumentException; import InvalidArgumentException at
the top and replace the FQCN usage in the __construct method with the imported
class to match project style—update the use statements and ensure the throw in
UploadCompletionProof::__construct(string $name, UploadCompletionProofSource
$source) uses the imported InvalidArgumentException.

In `@src/Domain/Asset/ValueObject/UploadTarget.php`:
- Around line 112-119: In isAllowedLocalDevelopmentUrl(string $scheme, string
$host) remove the redundant trim call and use the already-normalized $host
directly: keep the scheme check (if ($scheme !== 'http') return false;) and then
return in_array($host, ['localhost', '127.0.0.1', '::1'], true) so you rely on
the prior normalization done before this method (refer to
UploadTarget::isAllowedLocalDevelopmentUrl and the earlier host normalization
where $host is lowercased and bracket-stripped).
- Around line 1-10: The UploadTarget value object currently references
\InvalidArgumentException repeatedly; add a top-level import "use
InvalidArgumentException;" in the UploadTarget class namespace declarations and
then replace all occurrences of "\InvalidArgumentException" in the class (e.g.,
in the constructor and any validation methods) with the unqualified
"InvalidArgumentException" to match project conventions and remove repetition.

In `@tests/Unit/Domain/Asset/AssetTest.php`:
- Around line 101-129: Update the test
itReconstitutesUploadedAssetWithPersistedValues to also assert that the
completion proof was preserved: after reconstituting via
Asset::reconstituteUploaded, call $asset->getCompletionProof() and assert it
equals the proof created by $this->createCompletionProofValue() (and do the same
for the other affected test around lines 187-198); this ensures
Asset::reconstituteUploaded and Asset::markUploaded correctly retain the
completion-proof state.
- Line 32: Rename the test method
itCreatesPendingAssetWithGeneratedIdentifierAndRequiredMetadata to follow the
repository test-naming patterns—for example
itReturnsPendingAssetWhenCreatedWithGeneratedIdentifierAndRequiredMetadata—and
similarly rename the other flagged methods (lines referenced: 72, 102, 171, 188,
227) to one of the allowed shapes (itReturns...When..., itThrows...When...,
itEmits...When..., or itDoesNot...When...) while keeping their assertions and
visibility intact; update any usages (data providers, `@depends` annotations, or
test references) to the new method names so tests still run unchanged.

In `@tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php`:
- Around line 82-84: Remove the extra blank line immediately before the final
closing brace of the AssetIdTest class; edit the class AssetIdTest so the
closing "}" directly follows the preceding line (no blank line between the last
statement/method and the class closing brace) to comply with styling/formatting.

In `@tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php`:
- Around line 108-117: Update the data provider used by the
itNormalizesSchemeAndHostToLowercase test (schemeAndHostNormalizationProvider)
to include cases that exercise userinfo and bracketed IPv6 hosts — e.g. add a
row for input like "HTTPS://User:Pass@[2001:DB8::1]:8443/Some/Path?Q=1#Frag"
with an expected URL that has the scheme and host lowercased, the IPv6 brackets
preserved, the port and path/query/fragment intact, and the userinfo preserved
exactly; also add the same row to the other provider referenced around the
itNormalizesSchemeAndHostToLowercase duplicate (lines ~198-205) so both tests
cover userinfo + bracketed IPv6 branches when creating the UploadTarget via
createUploadTarget.
- Line 21: Rename the test method itStoresUploadTargetWhenInputsAreValid to
follow the mandated pattern itReturnsUploadTargetWhenInputsAreValid, and
similarly rename any other non-throw tests (e.g., methods named itStores...,
itAllows..., itNormalizes...) to the itReturns{X}When{Condition}() form; update
the method names in the test class (and any references like annotations or data
providers) so the test runner and IDE links still work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78061d0b-bca1-4bdc-bbbe-b9718d47f761

📥 Commits

Reviewing files that changed from the base of the PR and between cc9d372 and daee145.

📒 Files selected for processing (24)
  • .github/copilot-instructions.md
  • .gitignore
  • .php-cs-fixer.cache
  • captainhook.json
  • docs/adr/04-asset-domain-contracts.md
  • docs/logs/02-domain-model-core-objects.md
  • mkdocs.yml
  • src/Domain/Asset/Asset.php
  • src/Domain/Asset/AssetRepositoryInterface.php
  • src/Domain/Asset/StorageAdapterInterface.php
  • src/Domain/Asset/UploadCompletionProofSource.php
  • src/Domain/Asset/UploadHttpMethod.php
  • src/Domain/Asset/ValueObject/AssetId.php
  • src/Domain/Asset/ValueObject/UploadCompletionProof.php
  • src/Domain/Asset/ValueObject/UploadCompletionProofValue.php
  • src/Domain/Asset/ValueObject/UploadParameter.php
  • src/Domain/Asset/ValueObject/UploadTarget.php
  • tests/Unit/Domain/Asset/AssetTest.php
  • tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadCompletionProofTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadCompletionProofValueTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadIdTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadParameterTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php
💤 Files with no reviewable changes (2)
  • captainhook.json
  • .php-cs-fixer.cache

Comment thread .github/copilot-instructions.md
Comment thread docs/logs/02-domain-model-core-objects.md Outdated
Comment thread src/Domain/Asset/ValueObject/UploadCompletionProof.php Outdated
Comment thread src/Domain/Asset/ValueObject/UploadTarget.php
Comment thread src/Domain/Asset/ValueObject/UploadTarget.php
Comment thread tests/Unit/Domain/Asset/AssetTest.php Outdated
Comment thread tests/Unit/Domain/Asset/AssetTest.php
Comment thread tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php
Comment thread tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php Outdated
Comment thread tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
docs/logs/02-domain-model-core-objects.md (1)

49-49: ⚠️ Potential issue | 🟡 Minor

Add a trailing newline at EOF to satisfy MD047.

The file still appears to end without a single trailing newline character.

Proposed fix
 - Later application and infrastructure work should consume these contracts directly instead of introducing parallel repository or storage shapes.
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/logs/02-domain-model-core-objects.md` at line 49, Add a single trailing
newline character at the end of the markdown file that contains the line "Later
application and infrastructure work should consume these contracts directly
instead of introducing parallel repository or storage shapes." to satisfy MD047;
open the document (02-domain-model-core-objects.md), move the cursor to EOF
after the last character and insert one newline, then save the file so the file
ends with exactly one trailing newline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Domain/Asset/ValueObject/UploadTarget.php`:
- Around line 57-111: The normalizeUrl() method is doing parsing,
validation/policy checking, and URL reconstruction in one place; split it into
focused private helpers to reduce complexity and make testing easier. Extract
responsibilities into methods like parseAndValidateUrl(string): array (wrap
parse_url + basic checks + empty check), validateSchemeAndHost(array
$parsedUrl): void (enforce HTTPS or call isAllowedLocalDevelopmentUrl and throw
on failure), and buildNormalizedUrl(array $parsedUrl, string $scheme, string
$host): string (handle auth, IPv6 bracket logic, port, path, query, fragment and
return the reconstructed normalized URL), then have normalizeUrl() call these
helpers in sequence (use existing isAllowedLocalDevelopmentUrl where needed).

In `@tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php`:
- Around line 59-69: Add an explicit "Assert" section comment and blank line in
the test method itThrowsInvalidArgumentExceptionWhenValueIsNotUuidV4 to follow
the Arrange/Act/Assert convention: after the Act step (the new AssetId($value)
invocation) add a blank line and an inline comment like "// Assert" (keeping the
existing expectException and expectExceptionMessage calls grouped in the Assert
section) so the test is clearly separated into Arrange, Act, and Assert; locate
the method by name and update the comments accordingly.

In `@tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php`:
- Around line 73-81: The test
itThrowsInvalidArgumentExceptionWhenUrlIsNotAValidAbsoluteUrl currently uses an
inline hardcoded invalid URL; change it to use a static data provider by adding
a #[DataProvider('invalidAbsoluteUrlProvider')] attribute on the test, make the
test accept the URL as a parameter and call createUploadTarget($url, []), and
add a static method invalidAbsoluteUrlProvider(): array that returns an array of
invalid absolute URL cases (including 'https://example.test/contains space') so
the expectation (InvalidArgumentException and message) is exercised for each
input.

---

Duplicate comments:
In `@docs/logs/02-domain-model-core-objects.md`:
- Line 49: Add a single trailing newline character at the end of the markdown
file that contains the line "Later application and infrastructure work should
consume these contracts directly instead of introducing parallel repository or
storage shapes." to satisfy MD047; open the document
(02-domain-model-core-objects.md), move the cursor to EOF after the last
character and insert one newline, then save the file so the file ends with
exactly one trailing newline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98c36f1a-81af-4f9f-91b4-eac74d9b1075

📥 Commits

Reviewing files that changed from the base of the PR and between daee145 and 6f24d40.

📒 Files selected for processing (6)
  • docs/logs/02-domain-model-core-objects.md
  • src/Domain/Asset/ValueObject/UploadCompletionProof.php
  • src/Domain/Asset/ValueObject/UploadTarget.php
  • tests/Unit/Domain/Asset/AssetTest.php
  • tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php

Comment thread src/Domain/Asset/ValueObject/UploadTarget.php
Comment thread tests/Unit/Domain/Asset/ValueObject/AssetIdTest.php
Comment thread tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php
@hsc00 hsc00 merged commit a0f3b89 into main Apr 1, 2026
8 checks passed
@hsc00 hsc00 deleted the feat/domain-model branch April 1, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

US-02: Domain Model — core objects

1 participant