diff --git a/README.md b/README.md index a27a3bc7..32176375 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ routes each area to its guide under [`docs/`](./docs/). | Guide | Use it when | | --- | --- | +| [Maintainer handover](./docs/HANDOVER.md) | Taking over project operations, access, maintenance, or AI-assisted support | | [Architecture](./docs/ARCHITECTURE.md) | Tracing the page lifecycle, production request path, or code ownership boundaries | | [Content authoring](./docs/CONTENT_AUTHORING.md) | Editing or debugging pages, metadata, fragments, preview, and publish behavior | | [Blocks](./docs/BLOCKS.md) | Adding or changing an EDS block | diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md new file mode 100644 index 00000000..603c2881 --- /dev/null +++ b/docs/HANDOVER.md @@ -0,0 +1,301 @@ +# Spectrum Hub maintainer handover + +Last reviewed: 2026-11-10. + +This is the starting point for a maintainer taking over Spectrum Hub and for +an AI assistant helping them. It maps the system, operational responsibilities, +and safety boundaries. Follow the linked guides for detailed procedures. +Current source, workflow files, and verified service configuration take +precedence over a dated description. + +This document is not deployment authorization, an access grant, or a complete +inventory of work in progress. Do not put credentials, session cookies, +private page content, or personal account details in it. + +## Start here + +Read [the repository overview](../README.md), +[Contributing](../CONTRIBUTING.md), and [Architecture](./ARCHITECTURE.md). +An AI assistant must first read [AGENTS.md](../AGENTS.md) and +[.ai/README.md](../.ai/README.md), then load the skills matching its task. +`.ai/` is the canonical agent guidance; `.github/skills` is a discovery adapter. + +For orientation, ask the assistant to use `contributor-onboarding`. It can +explain the core lifecycle one checkpoint at a time without making changes. +For a specific maintenance task, route directly to its subsystem guide. + +## What this project runs + +Spectrum Hub is the multi-platform documentation and discovery site for +Adobe's Spectrum design system. + +| Layer | Responsibility | Source of truth | +| --- | --- | --- | +| Document Authoring (DA) | Page content, metadata, authored block tables, shared fragments | DA workspace; [content guide](./CONTENT_AUTHORING.md) | +| AEM Edge Delivery Services (EDS) | Combines repository code with previewed or published content | AEM configuration and authored documents | +| CloudFront | Public hosts, routing, cache separation, direct media/assets, preview network restriction through WAF | AWS configuration; [edge guide](../workers/website-lambda/README.md) | +| AWS Lambda | Session handling, page access, anonymous content/index/sitemap filtering | `workers/website-lambda/` | +| Browser runtime | Page setup, EDS decoration, blocks, styles, deferred work | `scripts/`, `blocks/`, `styles/` | +| Component data | Combines RSP, SWC, and Figma data with authored mappings | `deps/`; [data guide](./DEPS.md) | +| Search | Builds section-level records and replaces the Algolia index | `tools/indexer/`; [indexer guide](../tools/indexer/README.md) | + +These are separate release paths. Publishing a document is not a Lambda +deployment. Merging site code is not a Lambda deployment either: the checked-in +GitHub workflows do not provide an automatic Lambda deployment pipeline. +Use the explicit runbooks below. + +`workers/website/` is the legacy Cloudflare Worker, not the current production +implementation. Do not fix live edge behavior there. + +## Environments and access + +| Environment | Purpose and boundary | +| --- | --- | +| `http://localhost:3000` | Local code through AEM CLI; does not reproduce Lambda access controls or CloudFront caching | +| `--spectrum-hub--adobe.aem.live` | Published-origin branch-code review; not a substitute for public-host edge checks | +| `--spectrum-hub--adobe.aem.page` | AEM preview origin; access requires the relevant authoring authorization | +| `preview.spectrum.adobe.com` | CloudFront and stage Lambda over `aem.page`; Adobe VPN required; documented as uncached | +| `spectrum.adobe.com` | Public production host over `aem.live`; viewer-aware filtering and shared anonymous caching | + +GitHub, DA editing, EDS publishing, AWS, IMS/service configuration, Algolia, +and Figma access are separate permissions. Have the outgoing maintainer or +service administrator confirm each required access path. Do not assume GitHub +write access grants publishing or production access. + +For AWS login, use your organization's approved AWS login portal, then run +`aws login --profile default`. Obtain the portal URL from the team's internal +access instructions; keep internal login URLs out of this public repository. +The deployment runbooks include the macOS browser-opening command and AWS +identity checks. `default` is a local profile name, not proof of the correct +account. Verify its account and role before any service writes. + +Branch preview sign-in has a documented IMS host-pattern limitation for branch +segments longer than eight characters. See +[the engineering workflow](../CONTRIBUTING.md#your-first-engineering-change) +before interpreting branch-only sign-in/CORS failures as a code regression. + +## Credential and service-ownership handover + +Transfer ownership and authorized access, not secret values in this document. +The inventory below describes credentials referenced by the repository; +confirm the actual configuration separately for stage and production. +Do not retrieve secret values simply to complete a handover checklist. + +| Credential or configuration | Documented storage and consumers | What the successor needs | +| --- | --- | --- | +| Lambda session-signing secret | Secrets Manager `spectrum-hub//session-secret`, referenced by `SESSION_SECRET_ID`; signs and validates `spectrum_session` cookies | Authorized management access and the rotation procedure; rotating it invalidates sessions signed with the old key | +| IMS service client secret | Secrets Manager `spectrum-hub//ims-client-secret`, referenced by `IMS_CLIENT_SECRET_ID`; Lambda service authentication for DA allowlist access | Ownership/access to the corresponding Adobe Developer Console integration, plus coordinated secret replacement | +| AEM origin-auth token, where configured | Secrets Manager `spectrum-hub//origin-auth`, referenced by `ORIGIN_AUTHENTICATION_ID`; also an `Authorization` custom origin header on the CloudFront direct-media path | Origin-auth administration and a coordinated update of both Lambda and direct-media consumers | +| Algolia indexing credentials | GitHub Actions secrets `ALGOLIA_APP_ID` and `ALGOLIA_WRITE_API_KEY`; local indexer credentials may be in an untracked `.env` | Algolia application/key administration and GitHub secret-management permission; the write key must stay out of browser code | +| Algolia index selection | GitHub repository variable `ALGOLIA_INDEX_NAME`; local indexer configuration | Confirmed production and scratch-index names; an index-name mistake can replace the wrong index | +| Figma API token | GitHub Actions secret `FIGMA_TOKEN`, used by `validate-figma-discovery.yml` | Authorized Figma access and a supported replacement/renewal path, especially if the existing token belongs to a departing account | +| AEM publish-driven CloudFront invalidation credential | The edge guide documents `accessKeyId` and `secretAccessKey` in AEM's `cdn.prod` configuration for IAM user `invalidator` | Confirmed IAM and AEM configuration ownership, approved recovery/rotation process, and identification of all shared consumers | +| AWS operator access | Individual KLAM/federated login and the operator's AWS profile | The successor's own approved account/role access; do not transfer temporary AWS credentials, login caches, or personal credentials files | +| GitHub Actions `GITHUB_TOKEN` | Issued automatically for workflow jobs; extraction workflows use it through `GH_TOKEN` | Repository/workflow administration and appropriate job permissions, not a copied token | +| GitHub, DA/EDS, Adobe Developer Console, Algolia, and Figma administration | Each service's own access controls | The successor's own membership and required role; do not share passwords or browser sessions | + +`` is `stage` or `prod`. Secret IDs, IMS client IDs/scopes, public +hosts, distribution IDs, and index names are configuration rather than secret +values. Record the required identifiers in the approved configuration record. +An Algolia application ID is an identifier even though this workflow stores +it as a GitHub secret. Never substitute a privileged indexing key for a +browser-facing search credential. + +For each managed credential, record the following in the approved team +vault/service record, linking to it from the handoff record where appropriate: + +- Responsible service owner and backup role, plus the access-request path. +- Environment, secret name or vault reference, and every consumer that must + change together. +- Issuer/admin console, expiry or renewal requirement, and rotation/recovery + procedure. +- Last verified access/rotation date and non-sensitive evidence that consumers + work after a change. + +GitHub Actions secret values cannot be read back through GitHub. If a value +is not available through an approved vault, arrange replacement with the +service owner instead of trying to extract it from a running job. Do not copy +the outgoing maintainer's `.env`, `deploy.env`, AWS credential files, or browser +storage as a substitute for provisioning the successor. + +Use the [Lambda secrets guide](../workers/website-lambda/SECRETS.md) for +rotation and runtime resolution. Granting access, changing IAM, rotating a +secret, changing a CloudFront origin header, or updating AEM configuration +requires explicit approval; an inventory exercise does not authorize them. +Session-key rotation affects signed-in users, and origin-token rotation must +not leave the direct-media path using the old token. + +The [edge guide's push-invalidation section](../workers/website-lambda/README.md#push-invalidation) +notes that `invalidator` is also used by `adobe.design`. Coordinate with the +shared credential owner before changing it, and verify a publish produces +the intended CloudFront invalidation afterward. Do not rotate or revoke +shared keys solely because one maintainer is leaving. + +## Where to make changes + +| Task | Start here | Important boundary | +| --- | --- | --- | +| Copy, metadata, navigation, fragments | [Content authoring](./CONTENT_AUTHORING.md) | Content lives in DA; shared fragments affect multiple pages | +| Block behavior or markup | [Blocks](./BLOCKS.md) | Keep block-specific logic in its `init(el)` implementation | +| Global page lifecycle | [Architecture](./ARCHITECTURE.md) | Avoid adding block behavior to `scripts.js` or `ak.js`; load the lifecycle/performance skill | +| CSS and tokens | [Styles](./STYLES.md) | Use existing tokens, scheme handling, and block scoping | +| Audience rules, auth, cache headers | [Lambda guide](../workers/website-lambda/README.md) | Change the live Lambda source, not the legacy Worker | +| Status data or component links | [Dependencies](./DEPS.md) | Edit the generator or authored mappings, then regenerate | +| Search records or DA tools | [Tools](./TOOLS.md) | A publishing indexer run replaces the entire remote index | +| Test selection | [Tests](./TESTS.md) | Root `npm test` is not the full repository suite | + +The browser lifecycle is `scripts/scripts.js` for project setup, then +`scripts/ak.js` for area decoration and section/block loading, then +`scripts/lazy.js` for deferred work. `scripts/postlcp.js` exists but is not +wired into that lifecycle. Read the current source before changing load order. + +Never hand-edit generated status/property output to fix a symptom. The +[data guide](./DEPS.md) identifies authored aliases/overrides and the generators +that own each output. + +## Common maintenance operations + +| Operation | Procedure | +| --- | --- | +| Set up local development | [Contributing setup](../CONTRIBUTING.md#set-up-the-repository); use `.nvmrc` and the package engine constraint | +| Preview or publish content | [Author workflow](../CONTRIBUTING.md#author-workflow) | +| Deploy preview Lambda | [Preview runbook](../workers/website-lambda/DEPLOY-PREVIEW.md) | +| Deploy production Lambda | [Production runbook](../workers/website-lambda/DEPLOY-PROD.md) | +| Manage or rotate secrets | [Secrets guide](../workers/website-lambda/SECRETS.md); separate authorization required | +| Investigate cache/routing/VPN configuration | [Edge guide](../workers/website-lambda/README.md); inspect before running infrastructure scripts | +| Build search records without publishing | `node tools/indexer/index.js --dry-run`; see [indexer guide](../tools/indexer/README.md) | +| Update component mappings/status | [Data guide](./DEPS.md); regenerate and inspect the resulting diff | + +The indexer dry run writes `tools/indexer/out/records.json` without publishing. +It still contacts configured services. Get permission before running it through +an assistant. **`--limit` alone is not safe:** without `--dry-run`, a limited +run can replace the full live index with only those records. + +For deployment, scripts default to the production target and the `spectrumHub` +profile unless overridden. The runbooks explicitly select the target and +`default` profile, stop on unreviewed local configuration, and preserve existing +Lambda environment variables. Do not bypass these safeguards. + +## Validation and scheduled jobs + +Use the smallest relevant suite from [Tests](./TESTS.md), plus manual checks +that exercise the changed behavior. New blocks/shared custom elements need +matching accessibility coverage under `test/a11y/`. Semantic or interactive +changes also need keyboard and assistive-technology review. + +`npm test` covers the root unit and supporting Node suites. Accessibility, +Pattern Library smoke tests, full link crawls, lint, and Lambda tests are +separate. The Lambda package has no `test` script; follow the +[edge-test instructions](./TESTS.md#edge-tests) rather than assuming root CI +covers edge behavior. + +As of the review date, these recurring jobs are configured in +[GitHub Actions](https://github.com/adobe/spectrum-hub/actions): + +| Workflow file under `.github/workflows/` | Configured schedule (UTC) | +| --- | --- | +| `index-algolia.yml` | Every 12 hours | +| `extract-rsp-properties.yml` | Daily at 07:00 | +| `extract-swc-properties.yml` | Daily at 06:00 | +| `validate-figma-discovery.yml` | Daily at 08:15 | +| `link-check.yml` | Daily at 06:00 | +| `e2e-deployed.yml` | Daily at 07:00, also on pushes to `main` | + +These are configured schedules, not evidence that the latest runs succeeded. +Check the workflow source and recent run results before relying on generated +data or automation. Algolia indexing uses repository secrets +`ALGOLIA_APP_ID` and `ALGOLIA_WRITE_API_KEY`, plus variable +`ALGOLIA_INDEX_NAME`; Figma validation uses `FIGMA_TOKEN`. +Verify configuration and ownership without retrieving or displaying values. + +## Critical operational safeguards + +Private data must be removed from anonymous responses at the edge, not merely +hidden by browser code. Check document responses, page variants, filtered +indexes, and sitemap behavior on the public-like host. Localhost is not proof +that an anonymous visitor cannot access content. + +Production anonymous content can stay in CloudFront for a day. Filtering +changes need the appropriate gate ETag version and, when existing shared +copies must be removed, an approved invalidation. An ETag bump or Lambda +redeploy alone does not clear fresh cached copies. Authenticated responses +must remain private/no-store and must not contaminate the anonymous cache. + +Secrets belong in AWS Secrets Manager, referenced by IDs. On cold-start +resolution failure, authorization fails closed; public pages may still work +while sign-in fails. The resolver caches successful values for ten minutes +and may retain a last-known-good value after a later transient failure. +Read [the secrets guide](../workers/website-lambda/SECRETS.md) before rotation. + +Some media/static assets bypass Lambda. A non-HTML asset under a private-looking +path is not automatically protected by page audience rules. Preview's origin +token also has a direct-media CloudFront header path; do not rotate only one +side or print distribution origin headers into an AI transcript. + +## Troubleshooting entry points + +| Symptom | First investigation | +| --- | --- | +| Preview returns `403` | Adobe VPN and WAF, then authoring/origin access; do not disable the gate | +| Public pages load but sign-in fails | IMS/session flow, allowed origins, secret-resolution errors, and execution-role permissions | +| Private data is visible anonymously | Capture the affected URL and response type without sensitive payloads; escalate immediately to the edge/deployment owner | +| Old behavior after a Lambda deployment | Confirm source SHA, target, update status, edge invalidation, and browser TTL | +| One page has incorrect structure | DA metadata/block markup and shared fragments before global loader changes | +| Status or component links are wrong | Authored mappings, upstream extraction, and generated status model | +| Search results are stale or incomplete | Latest indexer run, intended source/index, and full-replacement safeguards | +| A branch alone fails IMS sign-in | Branch host length restriction documented in Contributing | +| Direct media fails while HTML works | CloudFront media behavior and origin authorization; avoid exposing custom header values | + +Use GitHub Actions logs for repository jobs and the function's CloudWatch logs +for Lambda failures, subject to access approval. Read-only inspection is not +permission to publish, invalidate, redeploy, rotate secrets, or change IAM. +For a production incident, involve the confirmed incident/deployment owner; +do not improvise a rollback that could restore an older data leak. + +## Known gaps and transition checklist + +The edge guide documents anonymous browser copies that can persist after +sign-in until their TTL expires, sitemap privacy depending on indexed page +audience data, and unsupported sitemap indexes. These are known constraints, +not instructions to change behavior during handover. + +As of 2026-11-10, the open GitHub issue list returned only the +[Dependency Dashboard (#19)](https://github.com/adobe/spectrum-hub/issues/19). +That is not a complete backlog: confirm Jira work, unmerged pull requests, +active branches, incidents, and outstanding decisions with the outgoing +maintainer. No live AWS configuration, access grants, alarm coverage, or +deployment results were verified when writing this document. + +Complete these handoff items with the outgoing maintainer; owners and +organization-specific request paths are not established by this repository: + +| Confirm before takeover | Evidence to retain in the approved team location | +| --- | --- | +| Primary/backup maintainer and incident escalation | Current role owners and escalation channel | +| GitHub, DA, EDS, AWS, IMS, Algolia, and Figma access | Successful access checks and approved request paths, not credentials | +| Last deployed preview/prod revisions | Source SHA, deployment record, verification results, known-good rollback SHA | +| Operational visibility | Log locations, dashboards, alarms, alert recipients, retention, and a recent successful run | +| Service credential lifecycle | Responsible role, rotation process, expiry reminders, and recovery procedure | +| Work still in progress | Linked issues/PRs/Jira tickets, decisions, dependencies, and release constraints | +| Routine operations | A supervised preview deploy and anonymous/authorized verification by the successor | + +Update the review date when these facts are reconfirmed. Prefer links to +maintained service records over copying a credential inventory or temporary +task list into the repository. + +## Copyable AI onboarding prompt + +> Read `AGENTS.md`, `.ai/README.md`, and `docs/HANDOVER.md` in your assigned +> workspace. I am taking over Spectrum Hub. Use the contributor-onboarding +> skill for orientation, then the task-specific guides and skills. Explain +> content versus repository ownership, the browser lifecycle, and the current +> Lambda/CloudFront request path without changing anything. Treat source and +> verified configuration as authoritative; distinguish documented facts from +> unverified operational state. Never inspect another worktree or discard +> existing changes. Do not retrieve, print, or request secrets or session +> cookies. Ask before installing dependencies, starting services, contacting +> external systems, editing files, or publishing. Require explicit +> operation-specific approval for deployments, invalidations, secret/IAM +> changes, production changes, and rollback. Use the preview/prod runbooks +> rather than inventing deployment commands. Identify missing access, +> ownership, test evidence, and handoff decisions without assuming them. diff --git a/workers/website-lambda/DEPLOY-PREVIEW.md b/workers/website-lambda/DEPLOY-PREVIEW.md new file mode 100644 index 00000000..1f8514ee --- /dev/null +++ b/workers/website-lambda/DEPLOY-PREVIEW.md @@ -0,0 +1,265 @@ +# Deploy the preview Lambda + +Use this runbook to deploy the latest `main` Lambda code to +. It updates an existing Lambda, not the +site's AEM content, CloudFront routing, WAF rules, or secrets. + +## Deployment target + +| Setting | Value | +| --- | --- | +| Repository | `adobe/spectrum-hub` | +| Source directory | `workers/website-lambda/` | +| Deploy target | `stage` | +| Lambda function | `spectrum-stage-lambda-proxy` | +| AWS region | `us-east-2` | +| AWS CLI profile | `default` | +| CloudFront distribution | `E2RMZZGQ0O3SJ1` | +| AEM origin tier | `aem.page` | + +**Always set `TARGET=stage`.** The deploy script defaults to production when +`TARGET` is omitted. It also defaults to the `spectrumHub` AWS profile, so pass +`AWS_PROFILE=default` explicitly. Profile names are local: `default` must +resolve to the authorized Spectrum Hub AWS account, not another account. + +## 1. Check prerequisites and source + +You need Git, Bash, `zip`, AWS CLI v2, and authorized AWS credentials. Use the +team's Adobe federated-login process to refresh the `default` profile. Do not +paste credentials, secret values, or session cookies into chat or this file. + +Connect to Adobe VPN before checking the preview site. Its WAF rejects traffic +outside the corporate network. + +From your repository checkout: + +```bash +git status --short +git fetch origin main +git rev-parse HEAD +git rev-parse FETCH_HEAD +``` + +For a latest-main deployment, use a clean checkout whose `HEAD` matches the +fetched main SHA. Record that SHA. The deploy script packages local files, not +an automatically downloaded copy of `main`, and untracked files can enter the +bundle too. + +If the checkout is dirty or the SHAs differ, stop and select a clean checkout +of the fetched commit. Do not discard changes or switch a shared checkout +without its owner's approval. An AI-managed worktree must stay within its +assigned workspace and follow its repository instructions. + +Before checking AWS access, use your organization's approved AWS login portal +and complete sign-in for the intended Spectrum Hub AWS account. Obtain the +portal URL through the team's internal access instructions, not this public +repository. On macOS, replace the placeholder below with that URL before +running: + +```bash +open 'https:///' +``` + +On other operating systems, open the approved portal directly in your browser. +After completing portal sign-in, run: + +```bash +aws login --profile default +``` + +Complete the browser-based AWS login prompts for the intended account and +role. An AI assistant must leave interactive sign-in to the operator and +must not request or capture credentials. + +Then check AWS access: + +```bash +aws --version +aws sts get-caller-identity --profile default --no-cli-pager +``` + +Confirm the returned account and role are the intended deployment identity. +Do not proceed on an expired login, unexpected account, or access-denied error. + +## 2. Check the existing preview function and local configuration + +```bash +cd workers/website-lambda + +aws lambda get-function-configuration \ + --function-name spectrum-stage-lambda-proxy \ + --profile default --region us-east-2 \ + --query '{FunctionName:FunctionName,Role:Role,Runtime:Runtime,MemorySize:MemorySize,Timeout:Timeout,State:State,LastUpdateStatus:LastUpdateStatus,LastModified:LastModified,CodeSha256:CodeSha256,OriginTier:Environment.Variables.AEM_HOST_SUFFIX,PublicHost:Environment.Variables.PUBLIC_HOST}' \ + --no-cli-pager +``` + +Confirm this is the existing preview function, with `OriginTier` set to +`aem.page` and `PublicHost` set to `preview.spectrum.adobe.com`. Record the +pre-deploy code hash and modification time. If the function is missing, the +configuration differs, or an update is in progress, stop and investigate. +Do not let this routine redeployment become first-time infrastructure setup. + +Two gitignored local files can affect deployment: + +- `deploy.env` is sourced by `deploy.sh` and can override values passed through + the shell, including profile, function, target, region, and environment file. +- `env.stage.json`, if present, replaces the Lambda's environment-variable + configuration. If absent, an update preserves the existing variables. + +For a routine code redeploy, **leave the deployed environment unchanged**. +The deployment block below deliberately stops if either local file exists, or +if `ENV_FILE` is already set. Have a maintainer review any such configuration +locally before using another deployment procedure. Do not print entire +environment files or AWS environment-variable responses into AI transcripts. +Do not copy configuration from production or set `ALLOW_ENV_MISMATCH=1`. + +No npm install or build is needed to package this Lambda: `deploy.sh` includes +the source and vendored modules. For Lambda code changes, use the separate +Lambda suite described in +[Tests](https://github.com/adobe/spectrum-hub/blob/main/docs/TESTS.md#edge-tests); +the root `npm test` command does not cover it. Running `npx vitest run` in this +directory can download Vitest, so approve that network/install step separately. + +## 3. Deploy + +This step changes AWS resources. Obtain explicit deployment approval before +running it through an AI assistant. Do not run simultaneous deployments to +the same function. + +From `workers/website-lambda/`, run the whole block: + +```bash +( + set -euo pipefail + export AWS_PAGER="" + + if [ -f deploy.env ] || [ -f env.stage.json ] || [ -n "${ENV_FILE:-}" ]; then + echo "STOP: local deployment configuration requires maintainer review." >&2 + exit 1 + fi + + export LAMBDA_ROLE_ARN="$( + aws lambda get-function-configuration \ + --function-name spectrum-stage-lambda-proxy \ + --profile default --region us-east-2 \ + --query Role --output text + )" + if [ -z "$LAMBDA_ROLE_ARN" ] || [ "$LAMBDA_ROLE_ARN" = "None" ]; then + echo "STOP: could not read the existing preview execution role." >&2 + exit 1 + fi + + AWS_PROFILE=default AWS_REGION=us-east-2 \ + TARGET=stage FUNCTION_NAME=spectrum-stage-lambda-proxy \ + FUNCTION_MEMORY=1024 FUNCTION_TIMEOUT=30 \ + ./deploy.sh + + aws lambda wait function-updated \ + --function-name spectrum-stage-lambda-proxy \ + --profile default --region us-east-2 + + aws lambda get-function-configuration \ + --function-name spectrum-stage-lambda-proxy \ + --profile default --region us-east-2 \ + --query '{FunctionName:FunctionName,State:State,LastUpdateStatus:LastUpdateStatus,LastModified:LastModified,CodeSha256:CodeSha256,MemorySize:MemorySize,Timeout:Timeout}' +) +``` + +The script requires the role ARN even for updates; this reads the existing +role rather than inventing a new one. It packages the code, updates the +function, waits for the code update, and then updates memory and timeout. +The final waiter above also waits for that configuration update. + +Expect a warning that `env.stage.json` is missing. In this procedure that is +intentional: the existing environment variables are preserved. The displayed +`AEM_HOST_SUFFIX: aem.live>` describes the absent local file, not the +deployed environment; the read-only check in step 2 establishes the actual tier. + +The script sets memory to **1024 MB** and timeout to **30 seconds**. If step 2 +shows different values that must be retained, stop and agree on those values +before deploying. + +## 4. Remove old shared cached responses when required + +Preview is documented as uncached. A routine redeploy may not need a cache +purge, but filtering or access-control changes must not leave older shared +responses available if caching was enabled. + +The metadata-filtering rollout in +[#209](https://github.com/adobe/spectrum-hub/pull/209) uses gate ETag version 3 +and explicitly calls for invalidating existing shared copies. An ETag version +bump does not evict fresh cached responses. + +After the Lambda update succeeds, obtain separate approval for invalidation, +then run: + +```bash +aws cloudfront create-invalidation \ + --distribution-id E2RMZZGQ0O3SJ1 \ + --paths "/*" --profile default --no-cli-pager +``` + +Use the returned invalidation ID to wait for completion: + +```bash +aws cloudfront wait invalidation-completed \ + --distribution-id E2RMZZGQ0O3SJ1 \ + --id \ + --profile default --no-cli-pager +``` + +Replace `` before running the command. Do not use the +production distribution ID. CloudFront invalidation does not clear browser +caches; use a fresh browsing session for verification. + +## 5. Verify the deployment + +Confirm `State` is `Active` and `LastUpdateStatus` is `Successful`, and record +the post-deploy modification time and code hash. If a redeploy packages +identical code, the hash may remain unchanged. + +On Adobe VPN, check in a fresh anonymous +session and a signed-in session. Inspect document and JSON responses, not just +the decorated DOM: + +| Check | Anonymous | Signed in and authorized | +| --- | --- | --- | +| Known public page | Loads without private audience blocks or private card metadata | Retains the intended private content | +| Known private page | Returns `404` | Loads the intended content | +| `/query-index.json` and affected filtered indexes | No private rows or private metadata columns | Retains the intended private data | +| A public page's `.md` variant with private metadata | Private metadata rows are absent | Private metadata rows remain | +| `/sitemap.xml` | No indexed private entries; URLs use the preview host | URLs use the preview host | + +Authenticated responses must remain `private, no-store` and must not be served +as shared-cache hits. Preview responses should follow its no-cache configuration. + +If validation fails, stop and report the source SHA, target, update status, and +observed behavior without cookies or secrets. Do not change routing, WAF, +authentication, secrets, or production settings to make the check pass. +Agree on a known-good source revision and obtain approval before any rollback +redeployment; the deploy script does not create a rollback release for you. + +## Instructions for an AI assistant + +Copy this prompt into an assistant with access to the repository: + +> Read `AGENTS.md`, `.ai/README.md`, and +> `workers/website-lambda/DEPLOY-PREVIEW.md`. Help deploy the latest fetched +> `main` Lambda code to `preview.spectrum.adobe.com` using AWS profile `default`, +> region `us-east-2`, and function `spectrum-stage-lambda-proxy`. Start with +> read-only checks in your assigned workspace. Confirm a clean latest-main +> source revision, the AWS identity, and the existing preview configuration. +> Explain any local configuration overrides without revealing their contents. +> Ask for explicit approval before deploying, invalidating caches, installing +> tools, or changing files. Preserve deployed environment variables. Never +> default to production, bypass an environment guard, or change secrets, +> authentication, WAF, or routing. After approved deployment, wait for updates +> to finish and help verify anonymous and authorized behavior. Report the source +> SHA, target, update result, any approved invalidation, and verification gaps. + +## References + +- [Lambda architecture, environment, and caching](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/README.md) +- [Deployment script](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/deploy.sh) +- [Local configuration example](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/deploy.env.example) +- [Secrets management](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/SECRETS.md) diff --git a/workers/website-lambda/DEPLOY-PROD.md b/workers/website-lambda/DEPLOY-PROD.md new file mode 100644 index 00000000..d81d2f38 --- /dev/null +++ b/workers/website-lambda/DEPLOY-PROD.md @@ -0,0 +1,308 @@ +# Deploy the production Lambda + +Use this runbook to deploy the latest `main` Lambda code to +. This changes the live site's edge application. +It does not publish AEM content or change CloudFront routing, WAF rules, +authentication configuration, or secrets. + +For preview, use the +[preview deployment runbook](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/DEPLOY-PREVIEW.md) +instead. Creating or reading this document does not authorize a deployment. + +## Deployment target + +| Setting | Value | +| --- | --- | +| Repository | `adobe/spectrum-hub` | +| Source directory | `workers/website-lambda/` | +| Deploy target | `prod` | +| Lambda function | `spectrum-prod-lambda-proxy` | +| AWS region | `us-east-2` | +| AWS CLI profile | `default` | +| CloudFront distribution | `E3VFWCMFUVXVV` | +| Public host | `spectrum.adobe.com` | +| AEM origin tier | `aem.live` | + +Set `TARGET=prod` explicitly even though it is the script's default. Pass +`AWS_PROFILE=default` explicitly: the script otherwise uses `spectrumHub`. +Profile names are local, so verify that `default` resolves to the authorized +Spectrum Hub AWS account. + +## 1. Confirm source, readiness, and AWS access + +You need Git, Bash, `zip`, AWS CLI v2, and authorized AWS credentials. Refresh +the `default` profile through the team's Adobe federated-login process. +Never paste credentials, secret values, or session cookies into chat or +documentation. + +From the repository checkout: + +```bash +git status --short +git fetch origin main +git rev-parse HEAD +git rev-parse FETCH_HEAD +``` + +Use a clean checkout whose `HEAD` matches the fetched main SHA. Record the SHA +and confirm that this same revision has been verified on preview. Preview is +behind Adobe VPN and uses `aem.page`, so its content and cache behavior differ +from production. + +The script packages local files, including untracked files that match its +packaging rules; it does not fetch `main` for you. If the checkout is dirty or +the SHAs differ, stop and select a clean checkout of the intended revision. +Do not discard changes or switch a shared checkout without its owner's +approval. AI-managed worktrees must follow their assigned workspace boundaries. + +For Lambda code changes, obtain the relevant Lambda test results using +[the edge-test instructions](https://github.com/adobe/spectrum-hub/blob/main/docs/TESTS.md#edge-tests). +The root `npm test` command does not cover this Lambda. The documented +`npx vitest run` command may download Vitest; approve any network or install +step separately. Packaging itself needs no npm install or build. + +Before checking AWS access, use your organization's approved AWS login portal +and complete sign-in for the intended Spectrum Hub AWS account. Obtain the +portal URL through the team's internal access instructions, not this public +repository. On macOS, replace the placeholder below with that URL before +running: + +```bash +open 'https:///' +``` + +On other operating systems, open the approved portal directly in your browser. +After completing portal sign-in, run: + +```bash +aws login --profile default +``` + +Complete the browser-based AWS login prompts for the intended account and +role. An AI assistant must leave interactive sign-in to the operator and +must not request or capture credentials. + +Then check AWS access: + +```bash +aws --version +aws sts get-caller-identity --profile default --no-cli-pager +``` + +Confirm the account and role with the deployment owner. Stop on an unexpected +identity, expired login, or access-denied error. + +Before deploying, agree on the source SHA, a known-good rollback revision, +who will verify anonymous and authorized responses, and whether cache +invalidation is required. Obtain explicit production deployment approval. +Obtain separate approval for invalidation when needed. + +## 2. Check the existing production function and distribution + +```bash +cd workers/website-lambda + +aws lambda get-function-configuration \ + --function-name spectrum-prod-lambda-proxy \ + --profile default --region us-east-2 \ + --query '{FunctionName:FunctionName,Role:Role,Runtime:Runtime,MemorySize:MemorySize,Timeout:Timeout,State:State,LastUpdateStatus:LastUpdateStatus,LastModified:LastModified,CodeSha256:CodeSha256,OriginTier:Environment.Variables.AEM_HOST_SUFFIX,PublicHost:Environment.Variables.PUBLIC_HOST,AllowedOrigins:Environment.Variables.ALLOWED_ORIGINS,AnonymousBrowserTTL:Environment.Variables.ANON_CACHE_MAX_AGE,AnonymousEdgeTTL:Environment.Variables.ANON_EDGE_MAX_AGE}' \ + --no-cli-pager + +aws cloudfront get-distribution \ + --id E3VFWCMFUVXVV --profile default \ + --query 'Distribution.{Id:Id,Status:Status,Aliases:DistributionConfig.Aliases.Items,Enabled:DistributionConfig.Enabled}' \ + --no-cli-pager +``` + +Confirm the existing function is the production function. `OriginTier` should +be unset (`null`) or `aem.live`, never `aem.page`; unset defaults to `aem.live`. +Confirm `PublicHost` is `spectrum.adobe.com` and `AllowedOrigins` is explicitly +configured for the intended production origins. + +The distribution must be enabled and include `spectrum.adobe.com` in its +aliases. Record the function's pre-deploy code hash and modification time. +If resources are missing, configuration differs from the intended deployment, +or an update is in progress, stop and investigate. Do not turn a redeploy into +first-time infrastructure setup. + +Production is documented with an anonymous edge TTL of `86400` seconds. +Inspect the actual values above rather than assuming them. Do not change +cache settings as part of this procedure. + +## 3. Preserve environment configuration and deploy + +`deploy.sh` sources the gitignored `deploy.env`, which can override shell +settings. Its production environment file, `env.json`, replaces the Lambda's +environment-variable configuration if present. If that file is absent, an +update preserves existing environment variables. + +This routine redeploy deliberately stops if either local file exists or +`ENV_FILE` is set. Have a maintainer review any such configuration locally +before choosing another procedure. Do not print complete environment files +or AWS environment-variable responses into AI transcripts. Never copy a +preview environment into production or set `ALLOW_ENV_MISMATCH=1`. + +The script reapplies **1024 MB memory** and a **30-second timeout**. If the +values inspected in step 2 differ and must be retained, stop and agree on +the deployment settings first. Do not run simultaneous deployments to this +function. + +After explicit production deployment approval, run this whole block from +`workers/website-lambda/`: + +```bash +( + set -euo pipefail + export AWS_PAGER="" + + if [ -f deploy.env ] || [ -f env.json ] || [ -n "${ENV_FILE:-}" ]; then + echo "STOP: local deployment configuration requires maintainer review." >&2 + exit 1 + fi + + LAMBDA_ROLE_ARN="$( + aws lambda get-function-configuration \ + --function-name spectrum-prod-lambda-proxy \ + --profile default --region us-east-2 \ + --query Role --output text + )" + if [ -z "$LAMBDA_ROLE_ARN" ] || [ "$LAMBDA_ROLE_ARN" = "None" ]; then + echo "STOP: could not read the existing production execution role." >&2 + exit 1 + fi + export LAMBDA_ROLE_ARN + + AWS_PROFILE=default AWS_REGION=us-east-2 \ + TARGET=prod FUNCTION_NAME=spectrum-prod-lambda-proxy \ + FUNCTION_MEMORY=1024 FUNCTION_TIMEOUT=30 \ + ./deploy.sh + + aws lambda wait function-updated \ + --function-name spectrum-prod-lambda-proxy \ + --profile default --region us-east-2 + + aws lambda get-function-configuration \ + --function-name spectrum-prod-lambda-proxy \ + --profile default --region us-east-2 \ + --query '{FunctionName:FunctionName,State:State,LastUpdateStatus:LastUpdateStatus,LastModified:LastModified,CodeSha256:CodeSha256,MemorySize:MemorySize,Timeout:Timeout}' +) +``` + +The existing execution role is read because the script requires its ARN even +for updates. Expect a warning that `env.json` is missing: that is intentional, +so existing environment variables remain unchanged. + +The script updates code, waits, then updates configuration. The final waiter +above waits for the configuration update too. Confirm `State` is `Active` and +`LastUpdateStatus` is `Successful` before proceeding. Record the post-deploy +code hash and modification time; identical packaged code may retain its hash. + +## 4. Invalidate old shared responses when required + +Production caches anonymous content at CloudFront. A Lambda code deployment +does not purge it. AEM publish-driven invalidation is not a substitute for +invalidating responses affected by a Lambda filtering change. + +For changes to filtering, access control, or private metadata, confirm that +the source revision has the appropriate `GATE_ETAG_VERSION` bump in +`lib/etag.js`. That bump forces older browser validators to be re-filtered +when they revalidate, but does not evict fresh browser or edge copies. +Do not edit the version ad hoc in the deployment checkout. + +The metadata-filtering rollout in +[#209](https://github.com/adobe/spectrum-hub/pull/209) uses gate ETag version 3 +and requires invalidating existing shared copies. For that rollout, do not +declare deployment complete until the approved invalidation has finished. + +After the function update succeeds and invalidation is explicitly approved: + +```bash +aws cloudfront create-invalidation \ + --distribution-id E3VFWCMFUVXVV \ + --paths "/*" --profile default --no-cli-pager +``` + +Use the returned invalidation ID to wait for completion: + +```bash +aws cloudfront wait invalidation-completed \ + --distribution-id E3VFWCMFUVXVV \ + --id \ + --profile default --no-cli-pager +``` + +Replace `` before running the command. This invalidates the +production distribution, not preview. A full purge temporarily increases +origin traffic. It does not clear browser caches; use fresh browsing sessions +for verification. + +## 5. Verify production behavior + +Check using separate fresh anonymous and +authorized sessions. Inspect actual document and JSON responses as well as +the decorated DOM. Do not copy session cookies into commands or AI chat. + +| Check | Anonymous | Signed in and authorized | +| --- | --- | --- | +| Known public page | Loads without private audience blocks or private card metadata | Retains the intended private content | +| Known private page | Returns `404` | Loads the intended content | +| `/query-index.json` and affected filtered indexes | No private rows or private metadata columns | Retains the intended private data | +| A public page's `.md` variant with private metadata | Private metadata rows are absent | Private metadata rows remain | +| `/sitemap.xml` | No indexed private entries; URLs use the production host | URLs use the production host | + +Confirm cache separation with repeat requests: cacheable anonymous pages and +`/query-index.json` should eventually return a CloudFront hit with private data +still absent. Authenticated HTML and filtered JSON must remain +`private, no-store` and never be shared-cache hits. Private-page anonymous +`404` responses must be `no-store`. + +Use the same URLs across the two sessions to check that an authorized response +cannot contaminate an anonymous response. A browser may retain anonymous +subresources after sign-in until their browser TTL expires; test with fresh +sessions rather than changing cache behavior to work around that known gap. + +Record the source SHA, target, deployment result, invalidation ID and status +if applicable, and verification outcomes. Clearly identify any checks that +could not be completed. + +## If deployment or verification fails + +Stop and report the failing command or observed behavior without credentials, +cookies, or secret values. Do not keep retrying writes blindly or change +secrets, authentication, WAF, routing, or caching to make a check pass. + +If rollback is needed, obtain explicit approval to redeploy the agreed +known-good revision using the production target and preserved environment. +The deploy script does not publish a versioned rollback release or switch an +alias for you. Do not reset a shared checkout. A rollback of filtering logic +can reintroduce a data leak, so involve the deployment owner before proceeding. +Reassess cache invalidation and repeat verification after any approved rollback. + +## Instructions for an AI assistant + +Copy this prompt into an assistant with access to the repository: + +> Read `AGENTS.md`, `.ai/README.md`, and +> `workers/website-lambda/DEPLOY-PROD.md`. Help prepare a production Lambda +> deployment to `spectrum.adobe.com` using AWS profile `default`, region +> `us-east-2`, function `spectrum-prod-lambda-proxy`, and distribution +> `E3VFWCMFUVXVV`. Start with read-only checks in your assigned workspace. +> Confirm a clean latest-main source revision, preview verification of that +> revision, relevant test evidence, the AWS identity, and existing production +> configuration. Identify a known-good rollback revision with the operator. +> Do not treat this prompt as deployment authorization. Ask for explicit +> production deployment approval and separate cache-invalidation approval +> before writes. Ask before installing tools or changing files. Preserve +> deployed environment variables. Never bypass environment guards or change +> secrets, authentication, WAF, or routing. After approved deployment, wait for +> updates and any required approved invalidation, then help verify anonymous +> filtering, authorized access, and cache separation. Report the source SHA, +> target, update result, invalidation status, and verification gaps. Stop and +> report failures; obtain approval before any rollback. + +## References + +- [Lambda architecture, environment, and caching](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/README.md) +- [Deployment script](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/deploy.sh) +- [Local configuration example](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/deploy.env.example) +- [Secrets management](https://github.com/adobe/spectrum-hub/blob/main/workers/website-lambda/SECRETS.md) diff --git a/workers/website-lambda/README.md b/workers/website-lambda/README.md index cee7d6e5..c60ef434 100644 --- a/workers/website-lambda/README.md +++ b/workers/website-lambda/README.md @@ -6,6 +6,12 @@ access rules: gating private pages, stripping `audience-*` content blocks, and filtering `query-index.json` and `sitemap.xml`. See [`index.js`](./index.js) for the pipeline and [`lib/gate.js`](./lib/gate.js) for the access policy. +For an existing Lambda redeployment, use the +[preview deployment runbook](./DEPLOY-PREVIEW.md) or +[production deployment runbook](./DEPLOY-PROD.md). They cover latest-main source +checks, the `default` AWS profile, preserving environment variables, cache +invalidation, and human or AI-assisted verification. + ## Runtime environment Set these on the Lambda (they are read from `process.env`):