Select a course template and AWS service. The adapter converts the JSON template into a
standardized outline, then feeds it to the shared generation engine.
URL
📄
Blog Processor Input
Provide an AWS blog URL. The adapter scrapes, converts to markdown, and builds a standardized
outline for the shared generation engine.
Upload
✍
Ad Hoc -- Custom Outline
Upload a custom outline YAML. Passed directly to the shared generation engine. Flexible for
one-off courses, partner content, or custom topics.
TCG -- Template Input
Select a course template and AWS service. The TCG adapter converts the
template into a standardized outline for the unified engine.
Template defines course structure and topic layout
The AWS service this course will be built around
Stored under courses/{name}/{version}/ in S3
Default is None: Build With videos are generated after SME review,
on final scripts. Select an option only when this run's scripts are final.
⚙ Shared Generation Engine Settings
Topics below this score trigger automatic regeneration
Maximum regeneration attempts before flagging for manual
review
Parallel topic processing via Step Functions Map
Blog Processor Input
Provide an AWS blog URL. The blog adapter scrapes the content, downloads
images, converts to markdown, and builds a standardized outline for the unified engine.
Paste the full URL of the AWS blog post
Override the auto-detected title
Ad Hoc Generation
Upload a custom outline YAML. Passed directly to the shared generation
engine.
📂
Drag and drop your YAML file here, or
click to browse
YAML file with course structure, topics, and learning
objectives
Overrides the title in the YAML if provided
Generate a focused microlearning course from a simple topic description.
No YAML needed  Bedrock builds the outline automatically.
What should the course teach? Be specific.
Primary AWS service covered in this course
5
3 = ~10 min, 5 = ~15 min, 7 = ~20 min of learner content
⚙ Shared Generation Engine Settings
Workflow Progress
Input Adapter
--
Topics
--
Scoring
--
0%
Waiting to
start...
Elapsed: 0:00
Activity Log0 entries
Course Repository
All generated courses are stored in
versioned S3 with full metadata and assets. Each version is immutable
-- new runs create new versions.
20
Unique Courses
20
Total Versions
271
Total Files
32.2 MB
Repository Size
Course
Source
Generated
Files
Size
Assets
Videos
Actions
Getting Started with AWS Security Incident Response
S3 bucket: (configured at deploy time) | Manifest generated: 2026-05-14
| To refresh: run python update_dashboard_repository.py
Content Freshness Monitor - Aperture Courses
Monitors the source blog behind each
Aperture (blog-to-course) conversion and flags when the source changes. Data is the live status from
the production freshness monitor.
⚠
1 Aperture course(s) have a major source change.
These should be reviewed for an update.
Documents stored in S3. Last synced: April
16, 2026.
STC Knowledge Base
Ask questions about the STC pipeline,
documentation, processes, feedback, and infrastructure. Powered by Amazon Bedrock Knowledge Base
with 50+ indexed documents.
Searching knowledge base...
Answer
Sources
Previous Questions
Knowledge Base ID: ERZRKI9YD9 | Model: Amazon Nova Lite | Documents: 50+ | Last sync: May 4, 2026
VTT Caption Generator
Upload an MP4 video file to generate a
WebVTT caption file using AWS Transcribe. The VTT file can be used for closed captions in Content
Studio courses.
Generation data is read-only (from stc-generation-jobs). Operational fields are editable here;
click Export changes to save them, then commit the downloaded overlay file so they persist across
refreshes.
Course
Cat
Lvl
Stage
Publisher
Waiting On
Assets
Content Studio
QA
Notes
ID
Logged
Course
Source
Trigger
Priority
Description
Status
Assigned
Resolution
STC Pipeline Changelog
All notable changes to the STC generation pipeline are documented in this file.
> This is the team-facing copy embedded in the dashboard Changelog modal
Freshness monitor watch list backfilled to full published coverage: BLOG_COURSES grew from 43 to 70 entries. Audit (2026-08-18) found 125 of 151 completed blog-generation URLs unmonitored, including the June 30-blog batch and everything teammates published since 8/5; a change to any of those sources would have gone undetected. 27 courses with confirmed sheet status=published were added (approved by Andy; manifest in `clean_runs/08182026/freshness_backfill/backfill_manifest.json`). 92 generated-but-publish-unconfirmed courses deliberately deferred to the Phase 1 watchlist-as-data build (DynamoDB auto-registration at job completion; see `clean_runs/08172026/freshness_monitor_remediation_plan.md`). Deployed via direct `update-function-code` 2026-08-18 16:21 UTC; baseline-establishing invoke clean (70 checked / 27 new / 0 errors).
Context
Root cause is structural, not a missed chore: the watch list is a hardcoded constant requiring a code deploy per course, which cannot scale to 8 publishers. Phase 1 makes registration a side effect of publishing; Phase 2 adds a coverage self-check to the weekly run.
[3.12.5] — 2026-08-06
Added
QA check `check_retired_names`: flags renamed/retired AWS service names (registry: Kinesis Data Firehose → Amazon Data Firehose, Amazon Elasticsearch Service → Amazon OpenSearch Service; extensible) unless the surrounding text is explicitly historical (formerly/evolution/upgrade/migration context). From Shreya Pawaskar's OpenSearch SME review (7/30): the retired Firehose name shipped in every reference.
QA check `check_code_hygiene`: flags three code-example patterns that fail or mislead on first run: `curl -d` with `_bulk` NDJSON payloads (strips required newlines), hardcoded account ID 123456789012 in executable code (alarm dimensions match nothing, INSUFFICIENT_DATA forever), and inline credentials (shell-history exposure). Scans embedded code blocks plus `input_files/*` referenced by `code_file_name`. Both checks review-severity; both copies of qa_checks.py synced (now 17 checks).
Technical-accuracy steering rules (`bw_style_rules.txt`): current service names, exact closed enums, no invented CLI operations/waiters/metrics, documented thresholds only, credential and account-ID hygiene, NDJSON via --data-binary, parameterized versions, one consistent figure for repeated guidance. All derived from the OpenSearch review's recurring defect classes.
Context
Course-side remediation of the same review (48 findings) applied separately to the Build With OpenSearch course YAML; see `clean_runs/08062026/dj_sme_review/` (fix script, manifest, report). Requires a Fargate image rebuild to take effect in generation runs.
[3.12.4] — 2026-08-06
Added
CS-registered alias gate (temporary): Creative Services only has `biegela` registered as a video requester; requests from unregistered aliases may not resolve on the CS side. Until Peter completes roster registration with Dima's team, `lambda_handler.py` routes avatar video requests from unregistered aliases under `biegela` (`_cs_safe_alias`, allowlist via `CS_REGISTERED_ALIASES` env, default `biegela`). Applies to both the blog path and the Fargate `PUBLISHER_ALIAS` env. True attribution is unaffected; the job record keeps the real publisher. To onboard a publisher, add their alias to the env var and deploy; remove the gate once the full roster is registered. Lambda-side only; requires SAM deploy, no Fargate rebuild.
[3.12.3] — 2026-08-06
Fixed
Course Objectives in Build With metadata: `pipeline/adhoc/generate_levitate_bw.py` now extracts the terminal learning objectives from the course intro (falling back to the Welcome module's objectives block) and writes a "Course Objectives" section into `course_metadata.md`. BW/TCG metadata had never included objectives (gap present since the first BW build 7/10; blog-course metadata was unaffected, which is why it went unnoticed). Requires a Fargate image rebuild to take effect; outputs generated before then can be fixed post-hoc by re-running `_write_course_metadata`.
[3.12.2] — 2026-08-03
Added
SME feedback store (`stc-sme-feedback` DynamoDB table): structured record per feedback email (course title, SME identity, received date, channel, full body with quoted text stripped, attachment count). Backfilled with 61 historical records from the Outlook "SME Feedback" folder; publisher outreach emails (7) and SIM notifications (6) correctly excluded by classifier. Records carry `ingest_source` for provenance; ingest is idempotent (feedback_id = hash of Outlook EntryID). Table created out-of-band via CLI and added to `lambda/template.yaml` (`SmeFeedbackTable`, DeletionPolicy Retain) the same day for IaC reconciliation; needs CloudFormation resource import at next stack deploy. Ingest/classifier scripts in `clean_runs/08032026/sme_feedback/`.
Fixed
Terminal job status unified to `COMPLETE`: `pipeline/fargate_entrypoint.py` wrote `COMPLETED` while the lambda wrote `COMPLETE`, splitting the jobs table into two terminal spellings (102 vs 68 records) that any status-filtered query would silently mishandle. The Fargate writer now uses `COMPLETE`. Readers (dashboard poll loop, lambda download handler) already accepted both spellings and keep doing so for safety. Requires a Fargate image rebuild to take effect; harmless until then.
Data
One-time normalization of `stc-generation-jobs` (2026-08-03): all 68 `COMPLETED` records conditionally updated to `COMPLETE` (manifest in `clean_runs/08032026/normalize_manifest.json`). Table now reads 170 COMPLETE / 1 FAILED.
Publisher attribution backfilled on 109 historical job records by exact-URL join between `config.inputs.url` and the official blog sheet snapshot (no fuzzy matching). Backfilled records carry `publisher_source: "sheet-backfill"` to distinguish inferred attribution from the self-declared identity introduced in 3.12.0. Coverage went from 3/171 to 112/171 attributed; the remainder is structural (TCG/adhoc jobs have no blog URL; some blog URLs lack a developer on the sheet). Manifest in `clean_runs/08032026/backfill_manifest.json`.
[3.12.1] — 2026-07-29
Added
Technical-accuracy steering rules (from Makendran Gunasekaran's SME review of the Build With Kinesis Data Streams course): `bw_style_rules.txt` now forbids "exactly-once" delivery/processing claims unless the source material documents them (most AWS streaming services are at-least-once; content must mention idempotent consumers when semantics come up), requires every number in a calculation to carry its unit, and requires binary prefixes (MiB) where the official docs use them.
QA check `check_delivery_semantics`: flags any "exactly-once" claim for human verification (review-only; some services legitimately offer it in specific modes, so no auto-fix).
QA check `check_math_artifacts`: flags unit-dropped calculations like "divide your peak throughput by 1" (review-only; the missing unit cannot be inferred). Tuned against false positives ("by 1 MB/s", "by 1,000", "by 1.5", "by 1 hour" all pass).
`lambda/qa_checks.py` synced with the pipeline copy (both paths now run 15 checks).
Context
The generator asserted exactly-once semantics for Kinesis (at-least-once in reality), a fluent factual fabrication that structural QA cannot catch. The steering rule reduces recurrence; the QA flag guarantees any recurrence reaches a human. The unit-dropped artifact was a literal SME finding ("divide your peak throughput by 1").
[3.12.0] — 2026-07-28
Added
Publisher identity and attribution: The dashboard now shows a first-run "Who's publishing?" prompt (8-person roster incl. APAC), stored permanently in localStorage with a "Publishing as" badge (with "not you?" switcher). Every launch carries the publisher; the dispatcher records it as a top-level `publisher` attribute on the DynamoDB job record (old dashboard copies record as "unattributed"). Launches are gated on a registered identity, and confirmations display it.
Creative Services attribution: Avatar video requests now carry the actual publisher's alias instead of hardcoded `biegela`, in both the blog path (from the job record) and the Fargate path (`PUBLISHER_ALIAS` env, which `avatar_videos.py` already consumed). Fallback remains `biegela` for unattributed launches. NOTE: other publishers must be registered with Creative Services (Dima's team, in progress via Peter) before their aliases resolve on the CS side.
Self-declared identity is workflow attribution only, not a security control; per-user auth remains AppSec item T6.
Infrastructure
Lambda-only deploy (no Fargate image rebuild needed). Verified live: job 2de6b5a6 record carries `publisher`, task env carries `PUBLISHER_ALIAS`.
[3.11.0] — 2026-07-28
Added
Avatar videos for Build With / TCG / Ad Hoc workflows (blog parity): New `pipeline/avatar_videos.py` module generates intro/outro avatar scripts from the finished course YAML (same prompts as the blog workflow) and ships them in the output package as `intro_script.txt` / `outro_script.txt` on every Fargate run. When the dashboard "Avatars needed?" selection is anything other than "None", the container auto-submits the video requests to the Creative Services API, exactly like blog courses.
Dashboard TCG avatar selector enabled: The previously disabled "Avatars needed?" dropdown on the TCG form is live, defaulting to "None" (unlike blog's "Intro & Outro" default) per the avatar sequencing rule: Build With videos are generated after SME review on final scripts. The launch confirmation dialog calls out when a Creative Services request will fire.
Dispatcher passes `AVATARS` and `VIDEO_API_TOKEN` to the Fargate task via container environment overrides for all adhoc/tcg launches (the Ad Hoc form's existing selector now actually takes effect too; it previously collected the value and ignored it).
Changed
Creative Services token consolidated: `lambda_handler.py` now reads `VIDEO_API_TOKEN` from the environment with the pre-existing hardcoded value as fallback (single constant, used by both the blog path and the Fargate pass-through). Full Secrets Manager migration remains tracked in stc-2967.
[3.10.3] — 2026-07-27
Fixed
Module/lesson numbering stripped from all template courses: The numbering strip in `pipeline/adhoc/generate_levitate_bw.py` was gated to Getting Started courses only, letting Build With courses ship with "Module 1:" / "Lesson 2.1:" prefixes in learner-facing titles (flagged by product on the ECR BW draft). The strip now runs unconditionally for every template course, with extended patterns: Module/Topic/Unit/Part/Chapter prefixes, bare numeric prefixes ("2.1 Setting Up"), stacked prefixes, and embedded numbering in Knowledge Check titles. S2C titles carry no structural numbering by team rule (dropping a section during maintenance would leave gaps; Somesh 2026-07-27).
Added
QA check `check_title_numbering`: Regression backstop for the numbering rule. Auto-fixes numbered titles in the output YAML and flags the fix for review in the QA report.
Branding-capitalization and duplication QA checks: Previously tested checks shipped in the same deploy. Branding flags lowercase brand/service-name occurrences ("aws" -> "AWS"); duplication flags substantial passages appearing in more than one topic.
`lambda/qa_checks.py` synced with the pipeline copy so blog and template paths run identical checks.
Infrastructure
Deployed 2026-07-27: Fargate image rebuilt via CodeBuild (also cleared prior image drift); lambda stack `stc-unified-dashboard` UPDATE_COMPLETE 17:22 UTC; API health-probed after deploy.
Live verification: Kinesis Data Streams regeneration (job `70c69dee-2540`) produced 39 titles / 0 numbered (previous version had 11 numbered). EKS test generation (job `96230960`) confirmed all three new QA checks active, numbering check passing.
Branch: `fix/title-numbering` (commit 8dcff78), pushed to origin; not yet merged to mainline, CR pending.
[Operations] S2C Agent Network — 2026-07-21
Operational tooling around the pipeline; no pipeline code changed, nothing deployed to AWS.
Added
S2C Agent Network (Amazon Quick Desktop): 11 project status agents + 1 portfolio orchestrator, one per work segment (Blog to Course, Getting Started, Build With, Custom & Solution, Doc to Course, Maintenance Triage, Platform & Tooling, Security & Migration, Publishing & Integrations, LO Service, Content Freshness). Agents observe scoped sources (local files, Obsidian vault, targeted Outlook/Slack/Asana/Jira/SIM queries), emit a standardized STATUS.md per run into `agent-hub\`, and never execute changes. Orchestrator reads all STATUS files and produces a ranked daily portfolio brief with conflict/dependency/staleness detection.
agent-hub file hub: `agent-hub\-\` folders with STATUS.md + scope cards; the contract layer between Quick (tracking) and Kiro (execution). Orchestrator maintains DEPENDENCY_MAP.md.
Agent dashboard: `agent-hub\build_dashboard.py` renders health tiles, aggregated Needs Andy queue, deadline radar, and the portfolio brief to a single self-contained `dashboard.html` (refresh via Kiro or `Refresh Dashboard.bat`).
Live Jira reads: Jira Enterprise connector reads aws-tc.atlassian.net after per-user OAuth (first read of CTM-4247 resolved two open follow-ups). SIM connector works but V2186562152 is genuinely restricted (paste workflow retained).
06 Maintenance & Freshness split into S2C-06 Maintenance Triage (AWSCurrEng tickets, Nicole) and S2C-11 Content Freshness (CUMIN/Star integration) per Andy, 2026-07-21.
task-tracker\TASKS.md transitions to deep-history archive: the agent network is now the operational tracking layer (formal retirement is a Week 1 retro decision).
First-brief findings (day one value)
Unanswered stakeholder email from Smitha (7/16) with a course takedown flag; Sandra duration question (7/21)
Critical path named: zero Isengard IAM roles on team account 967880486574 (RED, blocks AppSec migration + LO Service POC)
Somesh contention flagged across 4 projects; three-way xAPI gap figure conflict (70/35/39) surfaced for resolution
Amazon Keyspaces identified as the one ungenerated course on Andy's Build With shortlist
[3.10.2] — 2026-07-08
Removed
Dashboard CloudFront and S3 static-site resources removed from `lambda/template.yaml`: Deleted `DashboardBucket`, `DashboardBucketPolicy`, `DashboardOAC`, and `DashboardDistribution`, plus the `DashboardUrl` and `DashboardBucketName` stack outputs. The dashboard HTML embedded a client-side AWS access key and was served publicly through CloudFront. A future `sam deploy` will no longer recreate a public distribution. The dashboard is distributed to teammates manually.
Changed
`deploy.ps1` decoupled from the dashboard publish (P0 #3): Removed the dashboard HTML injection, S3 upload, and CloudFront invalidation steps (old steps 4-6). The script now only builds and deploys the SAM stack and prints the API URL and output bucket. This also removes the dependency on the out-of-package path `..\..\..\clean_runs\03112026\...` that broke fresh clones.
Security
Public dashboard origin taken down: The `index.html` object was deleted from `s3://stc-dashboard-ACCOUNT_ID/`. The exposed access key belongs to the `biegela` IAM user and should be rotated (it was publicly reachable). CloudFront distribution `EBATQTGLWD8FI` should be disabled or invalidated in the console to purge any remaining edge-cached copy.
[3.10.1] — 2026-07-08
Added
Pipeline steering now version-controlled: `lambda/steering_rules.md`, `pipeline/README.md`, and `pipeline/shared/getting_started_steering.md` are now tracked. They had never been committed because a blanket `*.md` rule in `.gitignore` was excluding them.
Changed
Aperture blog descriptions: Blog course descriptions now prepend the standard TFC and accessibility disclaimer (with a support-contact link) in `lambda/lambda_handler.py` (`generator_handler`).
Removed
`archive/` removed from the repository: Deleted the committed vendored Python environment (boto3, botocore, urllib3, dateutil, jmespath, s3transfer, yaml, and `.dist-info` folders), committed `.zip` build artifacts, and `*.backup*` files. About 2,240 files and 275,000 lines. Preserved in git history under the `pre-cleanup-backup` tag.
Stale `pipeline/blog/` duplicate removed: Canonical blog and dispatch code lives in `lambda/` (deployed via `deploy.ps1`, SAM `CodeUri: .`). The `pipeline/blog/` copy was unused by the Fargate path and had drifted badly from `lambda/` (its `lambda_handler.py` was 805 lines behind).
Repo hygiene
`.gitignore` hardened: now ignores `.aws-sam/`, `*.zip`, `__pycache__`, `*.backup*`, and `*.orig`, and no longer blanket-ignores `*.md` (so docs and steering are tracked).
Dashboard intentionally NOT in the repo: the dashboard HTML embeds client-side credentials (an AWS access key and the API key), flagged by Code Defender. It is distributed manually and will not be committed until the client-side credential exposure is remediated (AppSec T1/T8). See the Isengard service-account migration.
[3.10.0] — 2026-06-09
Added
Athena analytics pipeline: On job completion, Lambda writes a structured JSON record to `s3://stc-course-output-ACCOUNT_ID/analytics/YYYY/MM/DD/{job_id}.json`. Fields: job_id, workflow_type, course_title, blog_url, level, model_id, module_count, topic_count, code_block_count, image_count, traceability_warnings, processing_seconds. Zero-maintenance partition projection table (`stc_pipeline_analytics`) auto-discovers new data.
Athena table (partition projection): Created `stc_pipeline_analytics` in Athena with partition projection (year/month/day). No MSCK REPAIR needed — partitions resolve automatically.
Historical analytics backfill: All 41 existing S3 job outputs backfilled to analytics prefix for full historical visibility.
DynamoDB full history backfill: 39 historical job records restored from S3 outputs into `stc-generation-jobs` table. Dashboard now shows complete run history.
Batch blog processing script (`batch_blog_processor.py`): Submits multiple blog URLs via API, tracks status, downloads completed ZIPs to named subdirectories, and submits avatar video requests to Creative Services. Supports resume, per-course level, and structured output.
Getting Started steering: no-numbering rule: Added "Lesson and Topic Title Rules (MANDATORY)" to `getting_started_steering.md`. TCG courses no longer include "Lesson X.X" or "Section X.X" prefixes in learner-facing titles.
Changed
Blog code generation (CRITICAL): Completely rewrote the code file generation logic in `generate_blog_course.py`. The model no longer fabricates code examples. New behavior: extracts actual `
`, ``, and fenced code blocks from blog HTML source, maps them to YAML code references in order. Any code reference exceeding the number of actual blog code blocks is stripped from the YAML. If the blog contains zero code blocks, all code references are removed. Eliminates the class of issue that caused the Smitha/joshdemu SIM ticket.
Blog prompt (code block directive): Changed from "Generate 3-8 code references per course" to "ONLY include code blocks when the blog contains actual formatted code snippets." Added explicit NO FABRICATED CODE rule.
Pipeline/blog version synced: Applied identical fidelity rules and code generation logic to `pipeline/blog/generate_blog_course.py` (Fargate version) for consistency.
Fixed
DynamoDB TTL: Disabled TTL on `stc-generation-jobs` table. Records no longer auto-expire after 7 days. Removed `ttl` field from job record creation in `lambda_handler.py`.
Infrastructure
Lambda deployed: Both `GeneratorFunction` and `DispatcherFunction` updated (2026-06-09T19:57Z) with analytics write + code fidelity fixes.
Fargate container rebuild: Triggered CodeBuild (`stc-pipeline-build:a3614c52`) to rebuild container with updated steering rules.
SAM deploy note: Full SAM deploy fails due to pre-existing CloudFront permission issue (unrelated to code). Using direct `aws lambda update-function-code` for deployments until resolved.
[3.9.0] — 2026-06-02
Added
Source Traceability Check (`traceability_check.py`): Post-generation QA module that verifies all course content can be traced back to the source blog or fetched AWS documentation. Compares each content block against source material using Bedrock, classifies blocks as SOURCED / SUPPLEMENTED / FLAGGED. Produces `traceability_report.md` per course. Warnings count stored in DynamoDB and surfaced in dashboard.
Dashboard traceability warnings: Job completion status now shows "No traceability warnings" (green badge) or "X traceability warning(s)" (orange badge) with a clickable "View Report" link to the S3-hosted report.
Ad Hoc avatars selector: Added "Avatars needed?" dropdown (Intro & Outro / Intro only / Outro only / None) to the Ad Hoc workflow form on the dashboard. Previously only available for Blog workflow.
WCAG 2.2 AA accessibility audit tool (`accessibility_audit.py`): Evaluates generated course YAMLs against 9 WCAG 2.2 Level AA content-author criteria. Produces per-course accessibility reports with pass/fail per criterion.
Amazon Quick unified dataset (`build_quicksight_data.py`): Merges course manifest + freshness data into a single JSONL file for Athena/Quick consumption. Writes to `s3://stc-course-output-ACCOUNT_ID/quicksight/courses_unified.jsonl`. Supports partial ID matching between manifest and freshness data sources.
Athena database and table: `stc_analytics.courses_unified` table created over S3 JSONL for Quick dashboard queries.
IAM inline policies: `STC-Athena-Quick-Access` (Athena + Glue permissions) added to biegela user.
Changed
Content Fidelity rules (CRITICAL): Added CODE FIDELITY and CONTENT FIDELITY rules to the blog generation prompt (`generate_blog_course.py`). Blog code snippets are now reproduced verbatim (no modifications/synthetic alternatives). All content claims must be traceable to the blog or fetched AWS documentation. Resolves issue where Bedrock was modifying blog code and embellishing written content.
`_update_job` function: Extended to support `warnings_count` parameter. Stores traceability warning count in DynamoDB for dashboard consumption.
Dashboard Content Repository: Added 6 Getting Started courses (TCG) with purple "Level 100" badges. Updated stats: 30 unique courses, 41 total versions. Added `.badge-purple` CSS class.
Dashboard manifest date: Updated to 2026-05-14.
Maintenance triage dry run HTML: Updated with live ticket statuses (May 15 refresh). Added "Ticket Status" column to both High and Medium priority tables. 2 High Priority and 6 Medium Priority tickets marked as closed.
Fixed
Ad Hoc outline format: Innovation Sandbox on AWS outline corrected to match expected `build_levitate` format (requires `course:` top-level key, `module_number`, `lesson_number`, `topic_number` fields). Removed Knowledge Check topics from outlines (engine auto-generates proper interactive `type: question` KCs when not present in template). Fixed Welcome/Learning Objectives content duplication.
Freshness monitor Lambda deployed (`stc-freshness-checker`): Intelligent blog source monitoring with content diff analysis. Checks 20 blog source URLs weekly, extracts readable text (strips HTML/scripts/nav/footer), computes SHA-256 hash on extracted text (not raw HTML — avoids false positives from ads, tracking scripts, and dynamic page elements). When changes are detected, computes a unified diff and sends it to Bedrock for analysis. Runtime: Python 3.13, 512 MB, 5 min timeout. S2C tag: `S2C=AWS Blogs`.
Bedrock-powered change analysis: When a blog source changes, the Lambda sends the text diff to Bedrock (`us.anthropic.claude-opus-4-6-v1`) which produces: change summary, change type (cosmetic/minor/moderate/major), affected sections, update recommendation (no_action/monitor/update_recommended), recommendation reason, and course impact level (none/low/medium/high).
EventBridge weekly schedule (`stc-freshness-weekly`): Triggers freshness checker every Monday at 2 PM UTC (10 AM ET). IAM inline policy `STC-EventBridge-Access` added to biegela user for EventBridge rule management.
SNS email alerting: Topic `stc-freshness-alerts` with confirmed email subscription (`aws-tc-publishing-team@amazon.com`). Alerts include Bedrock analysis: change type, recommendation, summary, and affected sections per course. IAM inline policy `SNS-Freshness-Publish` added to Lambda execution role.
Freshness summary JSON: Lambda writes `freshness-hashes/freshness-summary.json` to S3 after each run with per-course status and full Bedrock analysis, enabling dashboard consumption.
Lambda function URL (removed): Originally created for on-demand summary access, subsequently deleted after Palisade flagged it as world-accessible. Epoxy auto-mitigated. Summary access now via S3 only.
Windows Task Scheduler sync (`STC-Freshness-Dashboard-Sync`): Runs `refresh_freshness.ps1` every Monday at 10:15 AM ET (15 min after Lambda) to download summary from S3 and embed into dashboard HTML. `StartWhenAvailable` flag catches up on missed runs.
Changed
Dashboard freshness tab — rich detail view: Replaced static hardcoded table with dynamic JavaScript rendering from inline JSON data. Columns: Course, Status, Recommendation, Course Impact, Last Checked, Source link. Expandable detail rows (click ▶) show: what changed, change type, affected sections, recommendation reason. Auto-loads when tab is opened.
Dashboard freshness tab — updated legend: Status categories: Current (green), New/baseline (blue), Changed (yellow), Error (red). Recommendation badges: No Action (green), Monitor (yellow), Update Recommended (red).
Content hashing strategy: Changed from raw HTML hashing to extracted-text hashing. Raw HTML includes dynamic elements (ads, timestamps, tracking scripts) that change on every page load, causing false "Changed" alerts. Text extraction strips `