Skip to content

fix(llmo-akamai): apply Optimize-at-Edge rules via JSON Patch, not full-tree PUT#2820

Open
ABHA61 wants to merge 7 commits into
mainfrom
feat/akamai-deploy-json-patch
Open

fix(llmo-akamai): apply Optimize-at-Edge rules via JSON Patch, not full-tree PUT#2820
ABHA61 wants to merge 7 commits into
mainfrom
feat/akamai-deploy-json-patch

Conversation

@ABHA61

@ABHA61 ABHA61 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Note

Unblocked. Depends on adobe/spacecat-shared#1813, which is merged and published as @adobe/spacecat-shared-akamai-client@1.1.0 (adds patchRuleTree). The dependency is bumped 1.0.2 → 1.1.0 in this PR, so deploy/plan resolve the real patchRuleTree at runtime. Ready for review.

What

Switches the Akamai Optimize-at-Edge deploy from a full-tree PUT to a server-side JSON Patch, and makes plan dry-run that same patch.

Why

Deploy did GET rules → merge → PUT (validateRules=true). Under rule format latest, the GET re-expands untouched origin behaviors (SSL/TLS fields under "Use Platform Settings") and the PUT re-stores them, so PAPI rejects the tree with incompatible_features on behaviors we never touched. Reproduced on the POC property: the base version validated 0 errors in Property Manager, yet the wizard's deploy returned 2 errors on the default origin + a child origin. Rebasing off the clean active version still failed — confirming it's the GET→PUT round-trip, not the base version. See LLMO-6252.

A server-side PATCH applies only our deltas to the stored tree, so existing behaviors are never re-serialized and the whole class of false rejection disappears (also robust to advanced behaviors / per-rule UUIDs / frozen-vs-latest formats).

Changes

  • deploy: createVersionbuildRuleTreePatchpatchRuleTree (guarded by the new version's etag via If-Match). No more full-tree PUT.
  • plan: dry-runs the same patch so Review reflects the real deploy validation. Previously plan only echoed the base version's GET-time warnings — a merged tree could look clean here yet be rejected at deploy. Degrades gracefully (validated: false) if the dry-run itself can't run.
  • buildRuleTreePatch (llmo-akamai-utils.js): builds the JSON Patch; idempotent by trimmed rule name — also removes a legacy "Optimize at Edge " (trailing space) that the old merge left duplicated.
  • mergeIntoTree now matches managed rules by trimmed name too, so the plan preview agrees with deploy.
  • OpenAPI: the plan response's validated / errors / warnings fields are declared; newVersion is surfaced on both deploy error paths.

Tests

mocha green — buildRuleTreePatch unit tests + controller tests (deploy PATCH, plan dry-run + graceful degrade, version-sensitive etag, newVersion on error). Lint + docs:lint + docs:build clean.

Follow-ups (separate)

LLMO-6252

🤖 Generated with Claude Code

…ll-tree PUT

Deploy did GET rules -> merge -> full-tree PUT (validateRules=true). Under rule
format "latest", the GET re-expands untouched origin behaviors (SSL/TLS fields
under "Use Platform Settings") and the PUT re-stores them, so PAPI rejects the
tree with incompatible_features on behaviors we never touched. Switch to a
server-side JSON Patch that only adds our managed rules + PMUSER variable,
leaving existing behaviors exactly as PAPI stored them.

- deploy: createVersion -> buildRuleTreePatch -> patchRuleTree (If-Match etag)
- plan: dry-run the same patch so Review reflects the real deploy validation
- buildRuleTreePatch: idempotent by trimmed rule name (also removes a legacy
  "Optimize at Edge " with a trailing space that the old merge left duplicated)

Requires @adobe/spacecat-shared-akamai-client with patchRuleTree (hold merge
until that is published and the dependency is bumped).

LLMO-6252

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ABHA61 ABHA61 marked this pull request as ready for review July 15, 2026 11:54
@ABHA61 ABHA61 requested a review from MysticatBot July 15, 2026 11:54
@github-actions

Copy link
Copy Markdown

This PR will trigger a patch release when merged.

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey @ABHA61,

⚠ Degraded review - no spec document was found for this change (searched the PR links, the touched repos' docs, the architecture/guidelines docs, and linked Jira). This review covers code-level quality but could not validate the change against an agreed design, so confidence is reduced. Add a spec link (PR template section 4) and re-request review for a full-confidence pass.

Verdict: Request changes - three issues to address before merge.
Complexity: HIGH - medium diff; API surface risk flag.
Changes: Switches Akamai Optimize-at-Edge deploy from full-tree PUT to server-side JSON Patch and adds dry-run validation to the plan preview (4 files).
Note: Recommend a human read before merge - possible divergence from the documented design (heuristic, flagged by the conventions reviewer). The bot review is a complement to, not a replacement for, a human read here.

Must fix before merge

  1. [Important] OpenAPI spec not updated for new plan response fields (validated, errors, warnings) - src/controllers/llmo/llmo-akamai.js:483 (details inline)
  2. [Important] mergeIntoTree and buildRuleTreePatch are independent implementations of the same transformation; the .trim() asymmetry already manifests as a concrete divergence - src/controllers/llmo/llmo-akamai.js:437 (details inline)
  3. [Important] Deploy test stub returns the same etag for all versions, so it cannot distinguish correct etag sourcing from stale reuse - test/controllers/llmo/llmo-akamai.test.js:290 (details inline)
Non-blocking (5): minor issues and suggestions
  • suggestion: Add a test for etag conflict (412/409) during deploy to verify the new concurrency guard surfaces a meaningful error - test/controllers/llmo/llmo-akamai.test.js
  • suggestion: Consider sanitizing dryRunError.message before logging since Akamai SDK errors could theoretically include request signatures - src/controllers/llmo/llmo-akamai.js:464
  • nit: Include newVersion in the deploy error response when it was already created, so callers know a version was consumed - src/controllers/llmo/llmo-akamai.js
  • nit: Add a test for buildRuleTreePatch with children: [] (present but empty) and negative insertIndex - test/controllers/llmo/llmo-akamai-utils.test.js
  • nit: Plan dry-run test does not assert on body.warnings being surfaced - test/controllers/llmo/llmo-akamai.test.js

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 3m 30s | Cost: $6.53 | Commit: 7f916cb6774bd6f702b052f802032c3eeca3283b
If this code review was useful, please react with 👍. Otherwise, react with 👎.

mergedChildRules: merged.rules.children.map((c) => c.name),
// PAPI validation of the exact change deploy will make (see the dry-run above). `validated`
// is false only when the dry-run itself couldn't run.
validated,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (blocking): The plan endpoint now returns validated, errors, and warnings fields in its response. The repo CLAUDE.md requires: "Specification Sync: Keep OpenAPI specs and implementation in sync - Run npm run docs:lint after modifying specs - Run npm run docs:build before completing implementation."

These three new fields need to be declared in the OpenAPI schema for the /sites/{siteId}/llmo/cdn-onboard/akamai/plan response before merge. Run npm run docs:lint and npm run docs:build to validate.

ruleTree, ruleFormat, etag,
} = await client.getRuleTree(propertyId, version, contractId, groupId);
const merged = mergeIntoTree(ruleTree, cfg, insertIndex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (blocking): mergeIntoTree is retained here solely for the human-readable preview (mergedChildRules, redacted tree), while the actual validation and deploy now use buildRuleTreePatch. These are independent implementations of the same transformation with a concrete divergence today: buildRuleTreePatch trims rule names before matching (managed.has((child?.name ?? '').trim())), while mergeIntoTree uses exact matching. A legacy rule named "Optimize at Edge " (trailing space) will be cleaned up by deploy but survive in the plan preview, showing a duplicate.

Fix options:

  • (Minimal) Add .trim() to the name-match in mergeIntoTree and add a test asserting both functions produce equivalent results for representative inputs.
  • (Better long-term) Derive the plan preview by applying the patch ops client-side, eliminating the dual implementation entirely.

expect(mockAkamaiClient.getRuleTree).to.have.been.calledWith(PROPERTY_ID, 8);
expect(mockAkamaiClient.patchRuleTree).to.have.been.calledOnce;
const [, version, , , ops, etag] = mockAkamaiClient.patchRuleTree.firstCall.args;
expect(version).to.equal(8);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (blocking): The getRuleTree stub returns the same etag ('etag-7') regardless of which version is requested. Since deploy creates version 8 and fetches that version's tree, the test should prove the code uses the freshly-fetched etag from the new version, not a stale one from a prior call.

Fix: Make getRuleTree version-sensitive:

getRuleTree: sandbox.stub()
  .withArgs(PROPERTY_ID, 7, sinon.match.any, sinon.match.any)
    .resolves({ ruleTree: RULE_TREE, ruleFormat: 'v2024-01-01', etag: 'etag-v7' })
  .withArgs(PROPERTY_ID, 8, sinon.match.any, sinon.match.any)
    .resolves({ ruleTree: RULE_TREE, ruleFormat: 'v2024-01-01', etag: 'etag-v8' }),

Then assert deploy passes 'etag-v8' (not 'etag-v7') to patchRuleTree.

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge labels Jul 15, 2026
Akash Bhardwaj and others added 2 commits July 15, 2026 18:04
… test, newVersion on error

- mergeIntoTree matches managed rules by TRIMMED name so the plan preview agrees
  with deploy — a legacy "Optimize at Edge " (trailing space) no longer shows as
  a phantom duplicate. Added an equivalence test.
- OpenAPI: declared the plan response's validated/errors/warnings fields + docs:build.
- deploy test: version-sensitive getRuleTree etag stub proves deploy uses the
  freshly-fetched v8 etag, not a stale one.
- deploy error path: surface the created newVersion when patchRuleTree throws after
  createVersion (the 422 rejection path already returned it) + test.
- tests: buildRuleTreePatch negative insertIndex + present-empty children; plan
  dry-run asserts warnings are surfaced.

LLMO-6252

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ABHA61

ABHA61 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review in 5080fabe:

Must-fix

  1. OpenAPI sync — declared the plan response's validated / errors / warnings in docs/openapi/llmo-api.yaml; ran docs:lint + docs:build.
  2. mergeIntoTree / buildRuleTreePatch divergencemergeIntoTree now matches managed rules by trimmed name, so the plan preview agrees with deploy (a legacy "Optimize at Edge " trailing-space rule no longer shows as a phantom duplicate). Added an equivalence test.
  3. etag test — the getRuleTree stub is now version-sensitive (v7 vs v8); the deploy test asserts it passes v8's freshly-fetched etag, not a stale one.

Non-blocking

  • newVersion on error — the 422 rejection path already returned it; also surfaced it on the throw-path (createVersion succeeds, then patch throws) + test.
  • Added buildRuleTreePatch tests for a negative insertIndex and a present-but-empty children; the plan dry-run test now asserts warnings are surfaced.

Intentionally deferred

  • 412/409 concurrency-conflict mapping (currently a generic 502) — can add if desired.
  • Sanitizing dryRunError.message — the client's error string is PAPI <method> <path> -> <status>: <body> and never contains the auth signature (a request header, never echoed); it's also consistent with the existing papiErrorResponse logging. Skipped.

113 tests passing; lint + docs:lint + docs:build clean.

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey @ABHA61,

Verdict: Approve - all prior findings addressed; no new blocking issues.
Complexity: HIGH - medium diff; API surface risk flag.
Changes: Switches Akamai Optimize-at-Edge deploy from full-tree PUT to server-side JSON Patch and adds dry-run validation to the plan preview (6 files).

Previously flagged, now resolved

  • OpenAPI spec now declares validated, errors, warnings fields for the plan response.
  • mergeIntoTree now trims rule names, matching buildRuleTreePatch behavior.
  • getRuleTree stub is version-sensitive; deploy test proves v8's etag is used.
Non-blocking (3): minor issues and suggestions
  • nit: redactApiKey(merged, cfg) passes an unused second argument - redactApiKey accepts only tree. Drop cfg for clarity - src/controllers/llmo/llmo-akamai.js:493
  • nit: OpenAPI schema for errors/warnings items uses additionalProperties: true with no defined properties - documenting common fields (title, detail, type) as optional would improve consumer DX - docs/openapi/llmo-api.yaml
  • suggestion: Seed the RULE_TREE stub with a warnings array and assert it surfaces in the plan response when dry-run rejects, proving the fallback path works end-to-end - test/controllers/llmo/llmo-akamai.test.js


Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 43s | Cost: $7.93 | Commit: 57f600d115aafd4a135927276e02e3ca353cea65
If this code review was useful, please react with 👍. Otherwise, react with 👎.

Akash Bhardwaj and others added 2 commits July 15, 2026 19:02
#1813 is merged and published as 1.1.0 (adds patchRuleTree). Lifts the HOLD —
deploy now resolves the real patchRuleTree at runtime, not just in tests.

LLMO-6252

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ABHA61

ABHA61 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

HOLD lifted. adobe/spacecat-shared#1813 is merged and published as @adobe/spacecat-shared-akamai-client@1.1.0 (adds patchRuleTree). Bumped the dependency 1.0.2 → 1.1.0 here (3657791e), so deploy/plan now resolve the real patchRuleTree at runtime, not just in the mocked tests. Ready for review whenever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants