fix(llmo-akamai): apply Optimize-at-Edge rules via JSON Patch, not full-tree PUT#2820
fix(llmo-akamai): apply Optimize-at-Edge rules via JSON Patch, not full-tree PUT#2820ABHA61 wants to merge 7 commits into
Conversation
…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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
This PR will trigger a patch release when merged. |
There was a problem hiding this comment.
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
- [Important] OpenAPI spec not updated for new plan response fields (
validated,errors,warnings) -src/controllers/llmo/llmo-akamai.js:483(details inline) - [Important]
mergeIntoTreeandbuildRuleTreePatchare independent implementations of the same transformation; the.trim()asymmetry already manifests as a concrete divergence -src/controllers/llmo/llmo-akamai.js:437(details inline) - [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.messagebefore logging since Akamai SDK errors could theoretically include request signatures -src/controllers/llmo/llmo-akamai.js:464 - nit: Include
newVersionin 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
buildRuleTreePatchwithchildren: [](present but empty) and negativeinsertIndex-test/controllers/llmo/llmo-akamai-utils.test.js - nit: Plan dry-run test does not assert on
body.warningsbeing 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, |
There was a problem hiding this comment.
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); | ||
|
|
There was a problem hiding this comment.
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 inmergeIntoTreeand 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); |
There was a problem hiding this comment.
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.
… 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>
…nto feat/akamai-deploy-json-patch
|
Addressed the review in Must-fix
Non-blocking
Intentionally deferred
113 tests passing; lint + docs:lint + docs:build clean. |
There was a problem hiding this comment.
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,warningsfields for the plan response. mergeIntoTreenow trims rule names, matchingbuildRuleTreePatchbehavior.getRuleTreestub 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 -redactApiKeyaccepts onlytree. Dropcfgfor clarity -src/controllers/llmo/llmo-akamai.js:493 - nit: OpenAPI schema for
errors/warningsitems usesadditionalProperties: truewith no defined properties - documenting common fields (title,detail,type) as optional would improve consumer DX -docs/openapi/llmo-api.yaml - suggestion: Seed the
RULE_TREEstub with awarningsarray 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 👎.
#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>
…nto feat/akamai-deploy-json-patch
|
HOLD lifted. adobe/spacecat-shared#1813 is merged and published as |
Note
Unblocked. Depends on adobe/spacecat-shared#1813, which is merged and published as
@adobe/spacecat-shared-akamai-client@1.1.0(addspatchRuleTree). The dependency is bumped1.0.2 → 1.1.0in this PR, sodeploy/planresolve the realpatchRuleTreeat 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 formatlatest, the GET re-expands untouchedoriginbehaviors (SSL/TLS fields under "Use Platform Settings") and the PUT re-stores them, so PAPI rejects the tree withincompatible_featureson 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
createVersion→buildRuleTreePatch→patchRuleTree(guarded by the new version'setagvia If-Match). No more full-tree PUT.validated: false) if the dry-run itself can't run.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.mergeIntoTreenow matches managed rules by trimmed name too, so the plan preview agrees with deploy.validated/errors/warningsfields are declared;newVersionis surfaced on both deploy error paths.Tests
mochagreen —buildRuleTreePatchunit 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)
papiErrors.LLMO-6252
🤖 Generated with Claude Code