Skip to content

feat(llmo-akamai): CUSTOM-default OAE onboarding (PUT deploy + caching/scope gates) - #2850

Merged
ABHA61 merged 12 commits into
mainfrom
feat/akamai-oae-custom-only
Jul 20, 2026
Merged

feat(llmo-akamai): CUSTOM-default OAE onboarding (PUT deploy + caching/scope gates)#2850
ABHA61 merged 12 commits into
mainfrom
feat/akamai-oae-custom-only

Conversation

@ABHA61

@ABHA61 ABHA61 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What

Reworks the Akamai "Optimize at Edge" (BYOCDN) onboarding for CUSTOM-default properties.

Rule builder (llmo-akamai-utils.js)

  • Wildcard the UA match values (*GPTBot*) so they match real agent strings, not only an exact GPTBot.
  • Add a Caching behavior (Honor origin Cache-Control + Expires) to the OAE rule only when the property's default rule has none; cacheId is always present.
  • Append the managed "Optimize at Edge" wrapper last so its origin + cacheId win (Akamai evaluates siblings top-down, last match wins).
  • OAE origin host comes from env.EDGE_OPTIMIZE_EDGE_DOMAIN (dev/stage/live.edgeoptimize.net; defaults to live); matchSan (*.edgeoptimize.net) covers all three.

Controller (llmo-akamai.js)

  • deploy/plan: JSON-Patch → full-tree PUT (updateRuleTree, pinned ruleFormat; plan uses the client's dryRun).
  • Scope gate: reject a non-CUSTOM default origin (400) before creating a version (no orphan version).
  • Optional baseVersion (copy from a specific version; rejects 0); caller email in the activation note.
  • Activation resilience: the PAPI activation POST frequently exceeds the client request timeout on large trees (and a retry then returns 422 already-activated) — in both cases Akamai has already queued the activation, so activate recovers by reporting the in-flight/active activation for the same version+network instead of a false failure.

Why

Validated end-to-end on tokowaka-aem.stage.adobe.com: an OAE-rule Caching behavior on a property that (correctly) defaults HTML to no-store overrides it and makes the optimized path cacheable, so Akamai replays a stale/passthrough copy to bots (v40 = 0/20 optimized at IAD). Conditional caching + full-tree PUT + the CUSTOM scope gate + append-last ordering fix it — AI bots receive x-edgeoptimize-request-id + no-store, humans pass through.

Design

High-Level Design: Optimize at Edge — Akamai BYOCDN Automation (HLD)

Depends on

adobe/spacecat-shared#1825 (merged, released as @adobe/spacecat-shared-akamai-client@1.2.0) — defaultRuleHasCaching, getDefaultOriginSsl, updateRuleTree({ dryRun }). package.json now pins the published 1.2.0.

Testing

Controller + utils unit tests green, lint clean, OpenAPI updated + valid.

🤖 Generated with Claude Code

…g/scope gates)

Rework the Akamai "Optimize at Edge" (BYOCDN) onboarding for CUSTOM-default properties.

Rule builder (llmo-akamai-utils.js):
- Wildcard the UA match values (*GPTBot* etc.) so they match real agent strings, not
  only an exact "GPTBot".
- Add a Caching behavior (Honor origin Cache-Control + Expires) to the OAE rule ONLY
  when the property's default rule has none (cfg.addCaching); cacheId is always present.

Controller (llmo-akamai.js):
- deploy/plan: revert JSON-Patch -> full-tree PUT (updateRuleTree, pinned ruleFormat;
  plan uses the client's dryRun).
- Scope gate: reject a non-CUSTOM default origin (400) BEFORE creating a version, so a
  rejected onboarding leaves no orphan version.
- Caching decision: addCaching = !defaultRuleHasCaching(tree).
- Optional baseVersion (copy-from); caller email in the activation note.

Tests + OpenAPI updated (119 passing, lint clean).

NOTE: package.json temporarily points @adobe/spacecat-shared-akamai-client at a gist
tarball for testing. Swap it back to the published version (after adobe/spacecat-shared#1825
merges + releases) before merging this PR.

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

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

mergeIntoTree/buildRuleTreePatch defaulted insertIndex to 0 when the caller
(the wizard) sends none, inserting "Optimize at Edge" as the FIRST child. On
Akamai, sibling rules evaluate top-down and origin/caching are last-match-wins,
so a later stock rule (Offload origin, Increase availability, …) clobbered the
OAE origin override + cacheId and bots were never routed. Default now appends
after all existing children; an explicit numeric insertIndex is still honored.

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

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

…error

Akamai's activation POST regularly exceeds the shared client's 10s request
timeout on large rule trees (it re-validates the whole tree), so client.activate
throws even though Akamai already queued the activation; any retry then returns
422 already-activated. Both were surfaced as a 502 "Akamai activation failed"
(and a misleading "check credentials" in the UI), though the activation was in
flight. The activate handler now, on error, looks up the latest activation for
the exact version+network and — if it's in flight or active — reports success so
the UI polls it. Genuine failures still surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Akash Bhardwaj and others added 2 commits July 18, 2026 17:37
…rity)

- OAE origin host now comes from env.EDGE_OPTIMIZE_EDGE_DOMAIN (falls back to
  live.edgeoptimize.net), so a dev/stage deployment routes AI-bot traffic to
  dev/stage.edgeoptimize.net instead of always prod. matchSan (*.edgeoptimize.net)
  covers all three, so CUSTOM cert validation still holds.
- Restore the setVariable PMUSER_EDGE_OPTIMIZE_FAILOVER=TRUE on the Site Failover
  rule (declared FALSE, flipped on failover) for v38 parity — observability marker
  for Akamai logs; does not affect routing/caching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a test where both the activation POST and the latestActivation recovery
lookup fail, so the handler falls through to the sanitized 502 — restoring diff
coverage for the recover inner-catch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A just-queued Akamai activation appears in the activations list before its id is
assigned, serializing as "atv_null" (which passes the general id regex but is not
pollable). The activate recovery now returns activationId:null for any non-real
(non-numeric) id, so the UI polls by network until a real atv_<digits> appears
instead of polling an unpollable placeholder and 404-ing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ABHA61
ABHA61 marked this pull request as ready for review July 18, 2026 13:02
@ABHA61
ABHA61 requested a review from MysticatBot July 18, 2026 13:02

@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: Request changes - gist tarball dependency must be swapped before merge; baseVersion validation gap should be fixed.
Complexity: HIGH - medium diff with supply-chain pin, API surface changes, and multiple behavioral shifts.
Changes: Reworks Akamai OAE onboarding to use full-tree PUT with CUSTOM-default scope gate, conditional caching, activation recovery, and optional baseVersion (7 files).
Note: CI checks are currently failing - resolve before merge.

Must fix before merge

  1. [Critical] Supply chain risk - Gist tarball dependency - package.json:86

    @adobe/spacecat-shared-akamai-client resolves from a personal Gist URL rather than the npm registry. The Gist owner can modify or delete the content; there is no independent provenance verification that the tarball matches the source in spacecat-shared PR 1825. The PR description acknowledges this is temporary, but this must NOT be merged in this state. Gate merge on the upstream package publishing.

  2. [Important] baseVersion validation accepts 0 - src/controllers/llmo/llmo-akamai.js:592

    DECIMAL_INT_RE is /^[0-9]+$/ which matches "0". After Number("0"), the code passes version 0 to client.getRuleTree(propertyId, 0, ...). PAPI property versions start at 1, so this produces an opaque 404/500 rather than the intended clean 400. Fix: add if (Number(rawBaseVersion) < 1) return badRequest(...) after the regex check, mirroring the activate handler pattern.

  3. [Important] Activation recovery fires on any exception - src/controllers/llmo/llmo-akamai.js:780

    The recovery block fires on any exception from client.activate, not only timeouts or 422. If the POST fails for a genuine permission error (403) or rate-limit (429), recovery still queries latestActivation and could match an unrelated earlier activation for the same version+network (e.g. submitted manually in Property Manager), returning false recovered: true. Consider narrowing to specific error signatures (timeout string match + 422 status) or adding a recency/timestamp check on the recovered activation.

  4. [Important] Stale OpenAPI description for insertIndex - docs/openapi/llmo-api.yaml:6489

    The spec still reads "Defaults to 0 (before everything)" but the code now defaults to LAST (children.length) when no index is provided. This actively misleads consumers who rely on the spec. Fix: update to reflect the new default (appends after all existing children).

Non-blocking (4): minor issues and suggestions
  • nit: Wildcard-wrapping heuristic (String(ua).includes('*')) leaves values like GPTBot* (trailing only) untouched rather than ensuring both leading and trailing wildcards - src/controllers/llmo/llmo-akamai-utils.js:104
  • suggestion: buildRuleTreePatch is now dead code in the controller (import removed) but still exported and tested - mark deprecated or remove in a follow-up
  • nit: behaviorCaching hardcodes defaultTtl: '1d' - document this choice so operators know AI-bot responses may be edge-cached up to a day when origin omits Cache-Control - src/controllers/llmo/llmo-akamai-utils.js:200
  • suggestion: Recovery catch logs at warn level when both the activation POST and the recovery probe fail - this double-failure warrants error for alerting visibility - src/controllers/llmo/llmo-akamai.js:810

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

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH labels Jul 18, 2026
- deploy: reject baseVersion 0 (passes DECIMAL_INT_RE but PAPI versions start at
  1) with a clean 400, mirroring activate.
- activate recovery: only adopt a listed activation submitted within the last 5
  minutes (isRecentlySubmitted) so a failed POST can't falsely match an unrelated
  older activation for the same version+network; log the double-failure at error.
- behaviorCaching: document that defaultTtl 1d is the origin-omits-Cache-Control
  fallback, not the common path.
- OpenAPI: insertIndex now documents the append-last default (was "0/first").

Addresses MysticatBot/Codex review.

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

ABHA61 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up commits: reject baseVersion 0, more robust activation recovery, and docs/OpenAPI sync. Tests green, lint clean.

@ABHA61
ABHA61 requested a review from MysticatBot July 18, 2026 16:29

@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 - gist tarball dependency must be resolved before merge; the PR is no longer in draft.
Complexity: HIGH - large diff with supply-chain pin and API surface changes.
Changes: Reworks Akamai OAE onboarding to use full-tree PUT with CUSTOM-default scope gate, conditional caching, activation recovery with recency window, and optional baseVersion (7 files).
Note: Recommend a human read before merge - this change modifies a shared contract (docs/openapi/llmo-api.yaml). The bot review is a complement to, not a replacement for, a human read here.

Must fix before merge

  1. [Critical] Gist tarball dependency still in place on an open (non-draft) PR - package.json:86 (details inline)
Non-blocking (1): minor issues and suggestions
  • suggestion: Recovery tests rely on wall-clock proximity to Date.now() rather than a fake clock; boundary behavior (4:59 vs 5:01) is not directly exercisable without sinon.useFakeTimers - src/controllers/llmo/llmo-akamai.js:51

Previously flagged, now resolved

  • baseVersion validation now rejects < 1 with a clean 400 (mirrors activate handler) + regression test
  • Activation recovery narrowed with isRecentlySubmitted (5-min recency window); double-failure upgraded to log.error + tests for stale/recent/placeholder/different-version/double-failure
  • OpenAPI insertIndex description updated to document the append-last default
  • defaultTtl: '1d' documented as the origin-omits-Cache-Control fallback

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

Comment thread package.json Outdated
"@adobe/mysticat-shared-seo-client": "1.3.1",
"@adobe/spacecat-helix-content-sdk": "1.4.33",
"@adobe/spacecat-shared-akamai-client": "1.1.0",
"@adobe/spacecat-shared-akamai-client": "https://gist.githubusercontent.com/ABHA61/8b997b63f4899a50df67073df4d2c1d2/raw/0adc628ab30acd38c16179f034bf5f9dac52ee9c/adobe-spacecat-shared-akamai-client-1.1.0.tgz",

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): Supply chain risk - gist tarball dependency.

@adobe/spacecat-shared-akamai-client resolves from a personal Gist URL rather than the npm registry. The prior review flagged this as Critical; the author acknowledged it as a pre-merge blocker ("will swap once #1825 releases"). However, the PR has since moved from draft to open without resolving it - the structural safeguard preventing an accidental merge is gone.

The Gist owner can modify or delete the content at any time. There is no provenance verification, no CI, and no audit trail on the tarball. If this merges as-is, production builds fetch from a personal Gist URL.

Engagement with pushback: The author's commitment to fix before merge is understood and reasonable, but the PR is now in a mergeable state without a branch-protection rule or CI check preventing it. Either (a) move the PR back to draft until the swap is done, (b) add a CI check that fails on non-registry dependency URLs, or (c) swap to the published package now that spacecat-shared#1825 is available.

@MysticatBot MysticatBot added the needs-human-review AI reviewer recommends a human read before merge label Jul 18, 2026
Removes the 5-minute recency guard from activation recovery. Re-activating a
version that is already ACTIVE returns Akamai 422 already-activated; with the
recency guard, an activation submitted >5min ago was rejected and surfaced as a
false "Akamai activation failed", even though the version is live on the network.
The version+network + in-flight/active status match is the correct guard: it
means the desired end state exists, so report success regardless of age.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ABHA61
ABHA61 requested a review from MysticatBot July 18, 2026 18:43

@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 - gist tarball dependency must be resolved before merge; the PR is no longer in draft.
Complexity: HIGH - large diff with supply-chain pin and API surface changes.
Changes: Reworks Akamai OAE onboarding to use full-tree PUT with CUSTOM-default scope gate, conditional caching, activation recovery without recency gating, and optional baseVersion (7 files).
Note: Recommend a human read before merge - this change modifies a shared contract (docs/openapi/llmo-api.yaml). The bot review is a complement to, not a replacement for, a human read here.

Must fix before merge

  1. [Critical] Gist tarball dependency still pinned on a non-draft PR - package.json:86 (details inline)
Non-blocking (0): no minor issues or suggestions

No non-blocking findings on this increment.

Previously flagged, now resolved

  • baseVersion validation now rejects < 1 with a clean 400 + regression test
  • Activation recovery refined: recency window removed in favor of version+network+status match; rationale documented in code comments and tests updated
  • OpenAPI insertIndex description updated to document the append-last default

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

Comment thread package.json Outdated
"@adobe/mysticat-shared-seo-client": "1.3.1",
"@adobe/spacecat-helix-content-sdk": "1.4.33",
"@adobe/spacecat-shared-akamai-client": "1.1.0",
"@adobe/spacecat-shared-akamai-client": "https://gist.githubusercontent.com/ABHA61/8b997b63f4899a50df67073df4d2c1d2/raw/0adc628ab30acd38c16179f034bf5f9dac52ee9c/adobe-spacecat-shared-akamai-client-1.1.0.tgz",

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): Supply chain risk - gist tarball dependency remains unresolved.

@adobe/spacecat-shared-akamai-client still resolves from a personal Gist URL rather than the npm registry. The PR is no longer in draft, so no structural safeguard prevents an accidental merge.

The incremental code change (recency-window removal) is well-reasoned and the test coverage is adequate - this remains the sole blocker. Once spacecat-shared#1825 publishes and the dependency is swapped to the registry version, the PR is ready to merge.

The variable was declared and set on failover but never read — the cache key
doesn't reference it, no rule consumes it, and the EO worker never sees PMUSER
vars. It was only there for POC parity. Removing the setVariable + its
declaration keeps the managed rule tree minimal; the Site Failover rule keeps
just the GA failAction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/controllers/llmo/llmo-akamai-utils.js
Akash Bhardwaj and others added 2 commits July 20, 2026 18:12
adobe/spacecat-shared#1825 merged and released the client as 1.2.0. Swaps the
temporary gist tarball for the published version and regenerates the lockfile —
resolving the merge blocker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ABHA61
ABHA61 requested a review from MysticatBot July 20, 2026 17:56

@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 blockers resolved, code is well-structured with comprehensive test coverage.
Complexity: HIGH - large diff with dependency upgrade, API surface changes, and multiple behavioral shifts.
Changes: Reworks Akamai OAE onboarding to use full-tree PUT with CUSTOM-default scope gate, conditional caching, activation recovery, and optional baseVersion (7 files).
Note: Recommend a human read before merge - this change modifies a shared contract (docs/openapi/llmo-api.yaml). The bot review is a complement to, not a replacement for, a human read here.

Non-blocking (6): minor issues and suggestions
  • suggestion: OpenAPI response schema for baseVersion still reads "The latest version the new version was created from" but it may now be an explicit caller-supplied version - update to "The version the new version was created from" - docs/openapi/llmo-api.yaml
  • suggestion: buildRuleTreePatch JSDoc still says "0 = before everything = default" but the default is now LAST (append) - stale comment in dead code - src/controllers/llmo/llmo-akamai-utils.js:493
  • nit: Recovery logic does not explicitly assert recovered.network === network - the latestActivation call is already scoped by network, but an explicit check would make the contract visible - src/controllers/llmo/llmo-akamai.js:789
  • suggestion: Add a direct unit test for buildRoutingRule with cfg.addCaching = true to verify the Caching behavior is positioned before cacheId - currently only tested through the integration-level controller test - test/controllers/llmo/llmo-akamai-utils.test.js
  • suggestion: Add a test for the scope gate when getDefaultOriginSsl returns null/undefined (tree with no origin behavior) to exercise the "an unknown mode" error path - test/controllers/llmo/llmo-akamai.test.js
  • nit: Wildcard wrapping produces ** for an empty-string UA value - not exploitable since the defaults list is service-controlled, but a .filter(ua => String(ua).trim()) before .map(...) would harden it - src/controllers/llmo/llmo-akamai-utils.js:99

Previously flagged, now resolved

  • Gist tarball dependency swapped to @adobe/spacecat-shared-akamai-client@1.2.0 from npm registry
  • baseVersion validation now rejects < 1 with a clean 400 + regression test
  • Activation recovery narrowed with version+network+status match; double-failure upgraded to log.error + tests for stale/recent/placeholder/different-version/double-failure
  • OpenAPI insertIndex description updated to document the append-last default

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

@ABHA61
ABHA61 merged commit e67e9c5 into main Jul 20, 2026
21 checks passed
@ABHA61
ABHA61 deleted the feat/akamai-oae-custom-only branch July 20, 2026 18:08
solaris007 pushed a commit that referenced this pull request Jul 20, 2026
# [1.669.0](v1.668.1...v1.669.0) (2026-07-20)

### Features

* **llmo-akamai:** CUSTOM-default OAE onboarding (PUT deploy + caching/scope gates) ([#2850](#2850)) ([e67e9c5](e67e9c5)), closes [Hi#Level](https://github.com/Hi/issues/Level) [adobe/spacecat-shared#1825](adobe/spacecat-shared#1825)
@solaris007

Copy link
Copy Markdown
Member

🎉 This PR is included in version 1.669.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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 released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants