Skip to content

docs: add agent-readable documentation and repository metadata - #428

Open
const-cloudinary wants to merge 3 commits into
masterfrom
docs/agent-readable-documentation
Open

docs: add agent-readable documentation and repository metadata#428
const-cloudinary wants to merge 3 commits into
masterfrom
docs/agent-readable-documentation

Conversation

@const-cloudinary

@const-cloudinary const-cloudinary commented Aug 24, 2026

Copy link
Copy Markdown
Member

Adds task-oriented documentation that ships inside the Composer package, so it always matches the installed version.

  • docs/ — index plus 13 task pages: imports, configuration, a complete runnable flow, result fields, failure modes.
  • examples/ — 12 runnable files, one per task page.
  • Restructured README, AGENTS.md, SECURITY.md.
  • .gitattributes controls what ships. Installed path: vendor/cloudinary/cloudinary_php/docs/. It also excludes tests/, tools/, samples/, apidocs/, .github/, and the lint/test configs — anything referencing vendor/cloudinary/cloudinary_php/tests/ would break on upgrade.
  • API-doc tooling moves from docs/ to apidocs/; tools/update_version.sh updated to match.

Corrections

Every snippet was executed against a live cloud, which caught real errors:

  • The API accessors are methods. The README documented $cloudinary->uploadApi->upload(...), which is a fatal error. It is $cloudinary->uploadApi()->upload(...).
  • ApiUtils is Cloudinary\Api\ApiUtils, though the file sits at src/Api/Utils/ApiUtils.php.
  • There is no uploadLarge()upload() chunks automatically above chunk_size.
  • A partial config array replaces rather than merges, so new Cloudinary(['logging' => ...]) throws by discarding credentials.
  • ConfigurationException does not extend ApiError and is in a different namespace, so catch (ApiError) alone misses it.

Also documented: resource_type defaults to image and fails loudly on video; update() returns context nested under custom; redeclaring a metadata field throws BadRequest.

Integration test fix (ddf6548)

Three integration tests asserted the exact shape of API responses and broke when the API added fields. Pre-existing on master, which last ran CI in April 2026 — unrelated to the documentation work, but fixed here so CI can go green.

  • testListFacebookImages, testFacebookImagesWidthHeight asserted Facebook assets carry no width/height. They now do. Raw assets still legitimately have none, so the cases are split: raw asserts absence, Facebook asserts integers when present.
  • testCreateDateMetadataField asserted restrictions equals exactly ['readonly_ui' => true]; the API now also returns hidden_ui and excluded_from_search. Fixed in the shared assertion loop, so every metadata test checks that expected keys are present and correct rather than that they are the only keys.

Bring the repository up to the Cloudinary agent-readable documentation standard,
following the structure shipped in cloudinary_npm (PRs #742 and #744).

Documentation, shipped inside the Composer package so it is always version-matched
to the installed code:

- docs/ — 13 task pages with an index carrying the agent-rules banner. Every
  snippet was executed against a live cloud before being committed.
- examples/ — 12 runnable files, one per task page, all verified green.

Repository files:

- README.md restructured: quick start, common tasks, when to use this SDK,
  compatibility, and a read path for coding agents.
- AGENTS.md (six sections) plus CLAUDE.md; SECURITY.md with private vulnerability
  reporting; context7.json.

Packaging:

- .gitattributes decides what ships. docs/ and examples/ reach users at
  vendor/cloudinary/cloudinary_php/; tests/, tools/, samples/, apidocs/, and the
  lint/test configs no longer do.
- The Sami API-doc tooling moves from docs/ to apidocs/ so that docs/ can hold the
  Markdown task docs, which is the path agents look for. tools/update_version.sh
  is updated to match.

Corrections found by executing the documented calls rather than reading the source:

- The API accessors are methods. The previous README documented
  $cloudinary->uploadApi->upload(...), which is a fatal error; it is
  $cloudinary->uploadApi()->upload(...).
- ApiUtils is Cloudinary\Api\ApiUtils, although the file sits in src/Api/Utils/.
- There is no uploadLarge(); upload() chunks automatically above chunk_size.
- A partial configuration array replaces rather than merges, so passing only
  'logging' discards credentials from CLOUDINARY_URL.
@const-cloudinary
const-cloudinary force-pushed the docs/agent-readable-documentation branch from c752ffa to 9bfbbdd Compare August 24, 2026 22:24
const-cloudinary and others added 2 commits August 25, 2026 01:34
Three integration tests asserted the exact shape of API responses and broke when
the API added fields:

- testListFacebookImages and testFacebookImagesWidthHeight asserted that Facebook
  assets carry no width or height. They now do. Raw assets still legitimately have
  none, so the two cases are split: raw asserts absence, Facebook asserts the values
  are integers when present.
- testCreateDateMetadataField asserted that `restrictions` equals exactly
  ['readonly_ui' => true]. The API now also returns `hidden_ui` and
  `excluded_from_search`.

The metadata fix is in the shared assertion loop, so every metadata test now checks
that the expected keys are present and correct rather than that they are the only
keys. Added fields no longer read as regressions.
Fixes found by executing the snippets, plus the review comments from the
reference PRs (cloudinary_npm#742, pycloudinary#449) that still applied here.

Corrections caught by running the code:

- transform-and-deliver-video.md showed a /v1/ segment on a non-nested
  public_id in four places. The image page documents the rule correctly —
  /v1/ appears only when the public_id contains a slash — so the two pages
  contradicted each other. All four now match generated output exactly.
- getenv() returns false when unset and fromCloudinaryUrl() requires a
  string, so the no-credentials path raised a TypeError instead of the SDK's
  ConfigurationException. troubleshoot-errors.php leaked a PHP signature
  dump; it now matches the other eleven examples.
- Documented URLs omitted the ?_a= analytics parameter the SDK adds by
  default, so actual output did not match the page. Documented what it is
  and how to disable it.

Review feedback applied:

- Exception structure: ApiError is a bare Exception, so getCode() is 0 and
  there is no structured error body. Added the HTTP status column and a
  section stating that the exception class is the only machine-readable
  signal.
- 423 (Processing) is unmapped in BaseApiClient and surfaces as GeneralError
  carrying the raw response body, so it cannot be caught by type.
  Documented with a message-matching retry.
- Moderation is not access control: whether pending, approved, or rejected
  assets are deliverable is product-environment configurable, and the Media
  Library and CDN are separate surfaces.
- Moderation add-ons need account enablement and sometimes provider terms
  acceptance — neither of which an agent can do.
- Upload size limits are per product environment; raising chunk_size does
  not raise the ceiling. Added mitigations and an upgrade path.
- Prefer asset_id for stored references; links to the search expression and
  transformation references; concrete one-hour signature lifetime; signed
  parameters must match what the client posts.

Also tagged two illustrative blocks as plain rather than php: conflicting
use statements and a bare class name are not runnable snippets and would
otherwise be false positives for a snippet parser.

Verified: 56 php blocks across docs/ parse clean, examples lint with zero
phpcs errors, all twelve exit 1 with a readable message when credentials
are absent, unit suite 375/375, all internal links and anchors resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant