Skip to content

Project 42 platform v0.117.4

Choose a tag to compare

@github-actions github-actions released this 21 Sep 20:09
· 27 commits to main since this release
06c36b1

Project 42 platform v0.117.4

Installs canonical curriculum from project42-content commit
3ce0443e5996e47dfd07e6bfa17fe63c7a52f2f8: the AI Glossary, introductory
AI and language-model lessons, and evaluation-dataset guide now include supplied
worked examples, learner practice and explained answers. The language-model and
evaluation exercises include independently executed Python with documented output.

Breaking changes

None. Content IDs and public schemas are unchanged.

Migrations

None. Adopters update the platform pin, materialise and rebuild.

Known limitations

This is the first four-item content-audit repair batch. The remaining curriculum,
visual and instructor-package audit work is still open. Synthetic evaluation
fixtures demonstrate grading mechanics, not measured performance of a real model.

Rollback

Restore platform v0.117.2 and rebuild the site. No database migration is involved.

Project 42 platform v0.117.2

Repairs public information and operator documentation. The learner-data page now
recognizes an API origin supplied in site configuration, matching sign-in behavior.
Setup instructions build the content repository first and do not require Gallery.
Courses, lessons, Field Guides and model/tool/concept learning material are unchanged.

Breaking changes

None.

Migrations

None. Adopters update the platform pin, materialise and rebuild.

Known limitations

Reference self-host profiles remain evaluation profiles. Documentation does not
claim that legal review, every video, or every deployment qualification is complete.

Rollback

Restore the preceding platform pin and rebuild the site. No database change is involved.

Project 42 platform v0.117.1

Adds the Enterprise workspace layout beside Standard, Compact and Wide. Visitors can choose a layout in the account menu; the correct bundle loads and the selection survives reload. The platform-owned portal-default now uses the Warm Campus palette without changing content or selecting a different layout.

Rendered contrast correction: restore full-opacity muted labels and explicitly pair primary button fills with foreground text, including accent callouts. No content or layout changes.

Breaking changes

None. The default layout remains Standard; theme identity remains deployment-owned. Repository-owned appearance overrides retain precedence.

Migrations

None.

Adoption

Update the platform dependency, materialise the portal, and rebuild. All shipped layouts are installed unless availablePresets explicitly narrows them. Gallery must sync the released canonical bundles before presenting updated previews.

Project 42 platform v0.116.14

Updates the complete reliable-agent learner journey contract to thirteen modules, with explicit review, operations, and capstone ordering. Preserves assessment, badge, account progress, accessibility, and transcript assertions. Includes all v0.116.12 curriculum repairs and the v0.116.13 public-type correction. Canonical content: project42-content@3fb86dda79c282b4885437e017bdf0d41a1a1ce4.

Breaking changes

None.

Migrations

None.

Known limitations

Class packages remain draft text packages. Production reconciliation follows verified website deployment.

Rollback

Pin v0.116.11, the previous deployed site release. No database migration is involved.

Project 42 platform v0.116.13

Preserves the published ClassScriptPackage type at the generated catalog boundary, so class segments without visual metadata type-check in adopter on-demand pages. Includes all v0.116.12 curriculum repairs. Canonical content: project42-content@3fb86dda79c282b4885437e017bdf0d41a1a1ce4.

Breaking changes

None. Optional fields retain their declared public contract.

Migrations

None.

Known limitations

Class packages remain draft text packages. Production reconciliation follows verified website deployment.

Rollback

Pin v0.116.11, the previous deployed site release. No database migration is involved.

Project 42 platform v0.116.12

Publishes sixteen September lesson repairs, including executable-example failure handling, activity evidence, technical corrections, and caption alignment. Restores result review under its existing module ID and preserves operations instruction in a separate module, with complete class packages for both. Completes the provider decision scoring guide, clarifies source-material authority boundaries, and renders checklist resources with accessible list semantics. Canonical content: project42-content@3fb86dda79c282b4885437e017bdf0d41a1a1ce4.

Breaking changes

None. Existing review module and assessment question IDs are preserved.

Migrations

None.

Known limitations

Class packages remain draft text packages; no synthesized media or independent approval is claimed. Orchard reconciliation follows verified site deployment.

Rollback

Pin v0.116.11. No database migration is involved.

Project 42 platform v0.116.11

Publishes reviewed September corrections to prompt architecture, provider SDK patterns, and semantic caching/tracing. The canonical content preserves existing assessment question IDs, removes internal correction notes from learner-facing prose, and aligns semantic caching captions with narration. Canonical content: project42-content@2655c4ea6f8710cabf793d67f5f311c1d61ecac4.

Breaking changes

None.

Migrations

None.

Known limitations

51 modules remain outline-only. These lessons were corrected directly in canonical content; Orchard's generated Gate 2 records still require reconciliation and are not recorded as Orchard-published.

Rollback

Pin v0.116.10. No database migration is involved.

Project 42 platform v0.116.10

Removes an OpenAI announcement URL that returned HTTP 403 to the public site's link checker. The 256-dimensional MTEB example remains supported by the already-cited OpenAI embeddings guide. Canonical content: project42-content@69bcd9c040eed4a12fd4d522a8aa3944d550c919.

Breaking changes

None.

Migrations

None.

Known limitations

51 modules remain outline-only. Orchard's generated 9266 draft remains held after assessment review; this release publishes the separately reviewed canonical correction.

Rollback

Pin v0.116.9. No database migration is involved.

Project 42 platform v0.116.9

Corrects the vector embeddings and pgvector lesson after the September Orchard draft failed assessment review. The lesson distinguishes Anthropic's Voyage AI documentation from an Anthropic embedding API, accurately describes Google's Embedding 2 identifier and preview endpoint listing, records normalization and query conventions in its schema example, scopes the partial index by dimension, and includes PostgreSQL 17 ordering guidance. Canonical content: project42-content@a99d38909c6a9ef078c66a4ff0cb91c8ad231e9d.

Breaking changes

None.

Migrations

None.

Known limitations

51 modules remain outline-only. Orchard's generated 9266 draft remains held after assessment review; this release publishes the separately reviewed canonical correction.

Rollback

Pin v0.116.8. No database migration is involved.

Project 42 platform v0.116.8

Removes a redundant OpenAI Models API citation that returned HTTP 403 to the public site's link checker. The OpenAI overview and models documentation remain. Canonical content: project42-content@d5754bdeca6386eafcffdc1c1cd25d5242168878.

Breaking changes

None.

Migrations

None.

Known limitations

51 modules remain outline-only. September Orchard queue reconciliation remains open.

Rollback

Pin v0.116.7. No database migration is involved.

Project 42 platform v0.116.7

Provider comparison now includes Gemini and a dated evidence exercise. OpenAI practice adds tool and availability checks. The bounded agent loop exercises all five terminal states. This release pins canonical content at project42-content@a9a5e6668186c68e8e9ba414f20fa187d976ea4b.

Breaking changes

None.

Migrations

None.

Known limitations

51 modules remain outline-only. The agent result review lesson and existing September Orchard queue items still require repair or reconciliation; this release does not mark them published by Orchard.

Rollback

Pin v0.116.6. No database migration is involved.

Project 42 platform v0.116.5

The AI Glossary, Prompt Checklist, and Provider Decision Card now include concrete definitions and examples, representative evaluation steps, and relevant dated primary sources. These edits address review findings for September Orchard items 9268–9270. This release pins canonical content at project42-content@ab57247471396fe8e4de7a18b0fca1a38577af8d.

Breaking changes

None.

Migrations

None.

Known limitations

Consumer sites must update their platform pins to v0.116.5 to serve these corrections. Orchard still holds the generated drafts without Gate 2 evidence; the other September findings remain unresolved.

Rollback

Pin v0.116.4. No database migration is involved.

Project 42 platform v0.116.4

The Agent Safety Checklist now gives concrete permission, approval, postcondition, and recovery checks grounded in OWASP guidance. This release pins canonical content at project42-content@4baa7c26223c1c8bdec5c2e911f3d413bbb628e4.

Breaking changes

None.

Migrations

None.

Known limitations

Consumer sites must update their platform pins to v0.116.4 to serve this correction. The other September Orchard findings still need editorial work and publication.

Rollback

Pin v0.116.3. No database migration is involved.

Project 42 platform v0.116.3

The vLLM and Ollama lesson now explains how to stage Ollama models before disconnecting a host, distinguishes local model storage from cloud-feature controls, and removes an unsupported vLLM memory-default claim. This release pins canonical content at project42-content@a8678408d761bd7133a694b98d8f8ff3fc2080e1.

Breaking changes

None.

Migrations

None.

Known limitations

Consumer sites must update their platform pins to v0.116.3 to serve this correction. Other September Orchard findings remain unpublished.

Rollback

Pin v0.116.2. No database migration is involved.

Project 42 platform v0.116.2

The AI mental models lesson now explains that repeated agreement measures observed output stability, not factual truth or the underlying token distribution. Its learner activity and instructor script remain attached. The catalogue no longer promises guaranteed structured output, and the Orchard and agent-orchestration diagrams reflect the current lifecycle. The platform pins the validated canonical content at project42-content@4c41c9ea71658f1810059a369792b2ca1f7ec08f.

Breaking changes

None.

Migrations

None.

Known limitations

The consumer sites must update their platform pins to v0.116.2 to serve this curriculum.

Rollback

Pin v0.116.1. No database migration is involved.

Project 42 platform v0.116.1

The hosted smoke now proves a learner keeps their place, and the Claude safety module's code example no longer teaches the unsafe pattern it warns against.

The hosted smoke never proved "continue where you left off". It only ever wrote and read back a finished module. The persistence gate now records a visit to an unfinished module exactly as the front end does, requires a fresh signed-in session to read back recentModule, and requires selectResumeTarget to send the learner back to it. A store that keeps completions but loses the learner's place now fails the gate by name.

The Claude safety module's example concatenated untrusted retrieved text into the user message. That contradicted the section's own guidance. The example now keeps policy in the system prompt and returns the document through a tool_use / tool_result exchange (content project42-content@ba31835).

Breaking changes

None.

Migrations

None. The gate change affects only operators running scripts/smoke-hosted-browser-session.mjs: it now opens one more fresh session and makes one more progress write, both cleaned up.

Known limitations

The portal's platform pin is still v0.116.0; bumping it to v0.116.1 is a follow-up to this release.

Rollback

Pin 0.116.0. Nothing in this release touches a database migration or the self-host compatibility contract beyond the version string.

Project 42 platform v0.116.0

An unhandled Worker error is now visible in Workers Logs, a sixth tracking step joins the published layout ramp, and the hosted smoke names each provider interstitial it passes.

An unhandled Worker error was invisible in Workers Logs. handleRequest's catch-all turned every unrecognized exception into a bare 500 and discarded the original error's name, message, and stack — only a generic internal_error code and the request's raw pathname were logged. That is why a D1 CHECK-constraint failure on progress_imports produced no trace an operator could act on: wrangler tail showed a request id and nothing else. The catch-all now logs a structured console.error with the request id, method, a route pattern with dynamic segments collapsed to :id, status, code, and — for unrecognized errors only — the error's name, message, and stack, redacted through redactSensitive() in case the underlying error echoes back a token, cookie, password, or email. The client-facing response is unchanged: still only a generic message and the request id, never the error's own text. scheduled()'s two retention purges had the identical failure mode one layer up — a rejected ctx.waitUntil promise was simply swallowed with no structure to correlate against — and now log the same way, independently per task, so one broken purge does not silence the other or the notification drain.

A sixth tracking step, --p42-track-6, joins the published layout ramp. T-18's first pass left 53 hardcoded radius/letter-spacing values needing a design decision; each is now mapped to the nearest step in the published ramp (mapping table and tie-break rule in docs/appearance-contract.md), except one population that was the whole population of a step the ramp did not have: display h1s and oversized glyphs carrying tracking tighter than any published step. web/scripts/appearance-debt.json's baseline is now {} — the gate fails on any hardcoded radius or tracking value, not only a regression. project42-gallery vendors these bundles and needs npm run sync:platform-layouts --ref v0.116.0 to pick up the new token.

The hosted smoke says which provider page it clicked past. Entra titles the consent page, the "stay signed in?" page and the password page alike ("Sign in to your account"), so smoke-hosted-browser-session.mjs logged the same line whether or not a learner would have been asked to consent. Each interstitial now also logs its heading, a 200-character excerpt with email addresses redacted, and consent page: yes|no. Logging only, bounded to two seconds per read, and a failed read never fails the smoke.

Breaking changes

None.

Migrations

Two operator-facing Workers Logs keys changed on the catch-all and the drain-failure line, named here for anyone with a saved query: the catch-all's path key is now route (same meaning, pattern instead of raw path), and drainOwnerAccountNotifications's account_notification_scheduled_drain_failed line dropped its plain message field in favor of the same errorName/errorMessage/errorStack shape used elsewhere in this release.

Known limitations

project42-gallery has not yet run npm run sync:platform-layouts --ref v0.116.0, and the portal's platform pin is still v0.115.1 — both are follow-ups to this release, not part of it. Owner alerts remain limited to deployments that configured ACCOUNT_NOTIFICATION_DELIVERY, unchanged from 0.115.x.

Rollback

Pin 0.115.1. Nothing in this release touches a database migration or the self-host compatibility contract beyond the version string.


Project 42 platform v0.115.1

Two test fixes on top of 0.115.0. Nothing a learner sees changed; what changed is that the gates now assert what the site actually ships.

A spec was still asserting words 0.115.0 had rewritten. The request card has always had to state one thing before anybody presses the button: asking is not signing in. 0.115.0 moved the card's bullets into web/copy/account.ts so an adopter can override them, and that sentence came out the other side reading "It does not sign you in: a pending browser holds a request receipt and nothing else." Same claim, different words — and account-registration.spec.ts was still looking for the old ones. That is the failure that turned the portal's 0.115.0 deploy red, and it is a test fix, not a product fix: the !configured, unavailable and error branches of the account dashboard are byte-identical to 0.114.2, so a visitor whose account service is unreachable sees exactly what they saw before. The assertion now matches the claim and is scoped to the request card, so a future step mentioning sign-in somewhere else on the page cannot quietly satisfy it.

It reached the release at all because a local verification tree can hold a stale front end: app/ materialised from 0.114.x while node_modules already held 0.115.0, so the test read the previous card and passed. CI installs clean, re-materialises, and finds the truth. Re-materialise before trusting a local pass on a platform bump.

The exported-artifact gate is now one a site can pass. 0.115.0 added npm run test:pages to the template's check, and the first time that line was ever executed it failed deterministically, as it would have for every adopter. The config points the whole of tests/browser at the static export, and two specs asked it for things the export deliberately does not have. Worse than the red test: the specs that navigated to /admin followed the retirement redirect off the artifact and onto the deployment's real Admin origin over the public internet, where assertions written against a locally mocked portal went on passing. tests/browser/support/surface.ts now probes for that redirect and skips with the reason stated, and tests/web-distribution.test.mjs reads the withheld routes out of the exporter itself and fails any spec that reaches one without probing first — so the gate is checked by something other than the next person to remember it. npm run test:browser still proves the portal against the live application.

web/scripts/serve-github-pages.mjs also honours the --port its Playwright config has always passed it, so test:pages can run beside anything already holding 48142.

Breaking changes

None.

Migrations

None.

Known limitations

project-42.dev's package.json is hand-maintained in a different repository and still does not run test:pages; adding that line is a separate change, not part of this pin. Owner alerts remain limited to deployments that configured ACCOUNT_NOTIFICATION_DELIVERY, and learners are still told nothing automatically, both by design and both unchanged from 0.115.0.

Rollback

Pin 0.114.2. 0.115.0 is not a rollback target: a site whose CI installs clean cannot pass its own checks on it, which is the defect this release exists to fix.


Project 42 platform v0.115.0

Asking for an account is something you can find, and the owner hears that you asked.

Requesting an account is one step from the signed-out header. The request had always existed, on /account, and nothing in the site named it. Signed out, the route was: open the profile menu, read past "Sign in" — which starts the same flow but reads as something only an account holder does — choose "Account", then scroll past the card addressed to existing learners. Four steps, none of them named for the thing being looked for. "Request access" now sits in the header and in the profile menu, both going to that same section, which the page leads with when you are signed out and which says what happens next, how long it takes and how you find out. It is not a second path; it is the path that was already there, finally named. The offer appears only when the site is certain that nobody is signed in: an unknown session, or a self-host with no account service, does not show it, because inviting an approved learner to request an account tells them their account does not exist.

An approved learner was shown the request form again. When an owner approves or declines a request, the waiting browser's receipt is revoked, so its next status check returns a 401 — the same answer a browser that never asked would get. The page resolved that toward "never asked" and offered a fresh request form to somebody whose answer had already arrived. It now remembers that this browser has an open request, in a marker holding no identity and no secret, and tells an approved learner to sign in. The marker is written by any browser that successfully reads a receipt, so it no longer matters whether the person pressed "Sign in" or "Request an account": both start the identical flow, and both leave you just as pending.

The owner is told that a request exists. Creating a request enqueued a notification for the owner, but nothing drained that outbox except an owner opening the Admin console and dispatching by hand — which is almost certainly why the pending queue had never been used. The Worker's daily tick now drains owner-directed notifications, so that cadence is the longest an owner waits. Learner-directed notifications are still deliberately not sent: /account promises a learner that nothing is sent to them automatically and that signing in again is the answer, and that promise is kept.

This release also wires in two test surfaces that existed and ran nowhere — including test:pages, the only thing that exercises the service worker and the manifest against the artifact a site actually ships — and deletes two files that nothing anywhere referenced.

Breaking changes

None.

Migrations

None.

Known limitations

Owner alerts are delivered only where ACCOUNT_NOTIFICATION_DELIVERY is configured. On a self-host, and on any hosted deployment that never set delivery up, the drain logs that fact and does nothing, so the Admin console remains the way a request is noticed. Learners are still told nothing automatically, by design. The test:pages wiring reaches new adopters and re-materialised sites; project-42.dev's own package.json is hand-maintained in a different repository and needs the same line added there. web/tests/device-local-progress.test.mjs and web/tests/progress-reconciliation.test.mjs stay unwired here: both import a catalog file that only a materialised install has, and the platform deliberately does not build web/.

Rollback

Pin 0.114.2.


Project 42 platform v0.114.2

Follows 0.114.1. The device matrix carried an assertion marked as a known failure because no surface offered the last-opened module back. Resume shipped in 0.114.0, so that assertion started passing and Playwright failed it on all eighteen devices for passing — which is precisely what the annotation existed to do. The annotation is gone and the assertion now gates the feature on every device.

The one remaining annotated failure is genuine and unchanged: on a phone in landscape the open navigation fills the viewport, so the About panel opens below the fold. That is an information-architecture decision, not a defect a test should settle.

Breaking changes

None.

Migrations

None.

Rollback

Pin 0.113.0.


Project 42 platform v0.114.1

A one-word fix on top of 0.114.0: the new device-matrix spec named a local variable module, and Next's lint refuses that in any file it checks. The site materialises these specs into its own tree, so 0.114.0 could not pass the site's verify at all. Nothing else changed.

Breaking changes

None.

Migrations

None.

Rollback

Pin 0.113.0; 0.114.0 cannot be consumed by the site.


Project 42 platform v0.114.0

The site keeps your place, and keeps your work.

One learner's account could be overwritten with another's empty record. Reset progress, sign out while the save is still pending, and the next person to sign in on that browser had their account written over with an empty one. Every per-learner reference stayed armed at sign-out, so the pending save survived the change of learner and React never re-rendered to cancel it. Deterministic, not a race.

Work no longer dies on the way out. Answering a question and immediately closing the tab, navigating away, or switching apps lost whatever sat inside the 800ms save delay, because nothing flushed on unload. It flushes now, on pagehide and on the page being hidden, with a transport that survives the document. A save the server rejects for a reason worth retrying is retried with backoff and on reconnect; one it rejects on content is not.

Resume where you left off, signed in or not. The site remembers the module you were on and offers to continue it, and finishing a module offers the next one rather than the one just passed. A signed-out visitor's place lives in their browser and merges into their account when they sign in. Their account record is never written to the device.

Every learning path can be found by clicking. Six paths and twenty-two modules — including everything authored on 11 September — existed, rendered and sat in the sitemap, but the paths page grouped by a field no path set and fell back to a hardcoded list that omitted them.

The About menu opens on tablets. The header's nav row clipped its panel between 761 and 960 pixels in every browser: every tablet held upright.

This release also carries an eighteen-project device matrix — Chromium, WebKit and Firefox across phones, tablets and desktop — that asserts the menus open unclipped, nothing scrolls sideways, tap targets are reachable and the installed-app surface resolves.

Breaking changes

None.

Migrations

None.

Known limitations

Signing out inside the save delay still loses that record: the sign-out destroys the session server-side before the flush could land. A failed write followed by a reload can still lose work once the device copy has been cleared; the retry, the reconnect listener and the unload flush narrow that window rather than closing it. keepalive behaviour is held by a source assertion, not an end-to-end test — a browser harness cannot answer a request issued from a document being torn down.

Rollback

Pin 0.113.0.


Project 42 platform v0.113.0

Signing in lasts, the header stops guessing, and work done during a session renewal is kept.

The seven-day sliding window never slid. The read path updated only last_seen_at; the only thing that extended a session was a timer scheduled seven days ahead, which requires the tab to stay open for a week. So every learner was hard-expired a week after signing in no matter how often they came back. Reading the session now extends it, capped by its absolute expiry, writing only when the gain is worth a write, and re-issues the cookie. The token is not rotated, so two tabs cannot race.

The header claimed "signed out" whenever it did not know. The session lives in an HttpOnly cookie no script can read, so the front end must ask the API — and any failed read, including a 502 or a dropped connection, set an error state that the profile menu rendered as signed out. Worse, the route guard started a sign-in from that same error state, and the sign-in request forces a full credential prompt. A single failed read was enough to be thrown at a login screen while holding a perfectly valid session. The account state is three-valued now: signed in, signed out, or unknown, and unknown offers "Try again" rather than destroying the session.

Progress recorded during a renewal survives it. When a session renews mid-visit, the app re-reads the account; that response used to replace local state and cancel the pending write of whatever had just been answered. It merges now, through the same function the offline flush uses.

Breaking changes

None.

Migrations

None.

Known limitations

iOS 16.4 and later give a home-screen app its own cookie store, so a sign-in performed in Safari does not reach the installed app; signing in once inside the installed app is the mitigation, and nothing in this repository can change that. A 401 account_not_registered — a session that resolves against no active identity row — still presents to the client as signed out.

Rollback

Pin 0.112.3. The session extension is a Worker change: rolling back the Worker restores the old expiry behaviour without data loss.


Project 42 platform v0.112.3

The header menus open on an iPhone.

The profile menu did nothing on iOS: the button reported itself expanded and the panel never appeared. .site-header carried overflow-x: clip, and the panels hang 188 pixels below a 67-pixel header. Chromium and Gecko clip only the axis named, so every desktop journey passed; WebKit clips both, so on iOS the panel was cut to nothing. The rule had been there since the front end first shipped and bought nothing — no header content reaches the right edge at any width from 320 to 1440.

The mobile journeys now run on WebKit at iPhone SE and iPhone 14 sizes, tap with real touch events, and assert that no ancestor of a menu panel clips on either axis. The behavioural half of those tests passes even on the broken CSS, which is precisely how this reached a phone; the clip assertion is the one that fails.

Breaking changes

None.

Migrations

None.

Known limitations

Verified by emulation and by the documented WebKit behaviour, not on a physical handset. A related rule, .site-header nav { overflow-x: auto }, still clips the About menu between 761px and 960px in every engine; that nav row is a deliberate horizontal scroller and wants its own change.

Rollback

Pin 0.112.2.


Project 42 platform v0.112.2

The borders the site actually draws are visible enough to see.

portal-default is the bundle the portal serves, and eight of its border pairs failed the 3:1 non-text contrast minimum — a card edge at 1.23:1 against the card it sits on. The Gallery's seven themes were raised to 3:1 on 2026-09-11; nothing measured this one, because the Gallery's validator only scans the Gallery. The platform now runs that same validator over every bundle it ships, including the frozen 06-galactic-guide copy, which carried seven more failures of its own. Only lightness and alpha changed; no hue moved.

Breaking changes

None.

Migrations

None.

Known limitations

The @layer p42-fallback card border in globals.css is 1.68:1. It is the pre-theme last-resort value rather than a bundle, so this gate does not measure it, and it is owned by the boundary gate instead.

Rollback

Pin 0.112.1.


Project 42 platform v0.112.1

Code samples in the OpenAI and Gemini practice paths teach what each section says.

Fourteen modules had one generated sample — a chat.completions call with gpt-4o and json_object, or a single generateContent call — pasted into every section. All 63 are rewritten against documentation read on 2026-09-11: tool routing by execution owner, bounded tool loops matched by call id, strict schemas, side-effect safety, Codex sandboxing and approvals, evaluation contracts, moderation, and migration from Chat Completions to Responses. The Gemini modules move to the Interactions API, which Google made generally available in June 2026, and the Gemini API module explains when the legacy generateContent is still the right choice.

Breaking changes

None.

Migrations

None.

Known limitations

As in 0.112.0: the hosted persistence gate needs its smoke account, secrets and variables before it can pass.

Rollback

Pin 0.112.0.


Project 42 platform v0.112.0

Every module a learner can reach is real curriculum, and self-hosted installs save progress.

The last 21 template modules are written. Six learning paths — AI literacy and mental models, agentic systems and MCP, developer and practitioner AI, self-hosted AIOps, RAG and fine-tuning engineering, and AI security and governance — were made entirely of generated placeholder text. Each module now has sourced technical content, an instructor script whose captions and transcript match its cues, and at least five knowledge checks. Every citation was fetched and read on 2026-09-11 against the text that cites it. Two sources had moved on since the templates were written, and the modules follow the current versions: OWASP's 2026 Top 10 for LLM applications, and the EU AI Act dates as amended by Regulation (EU) 2026/1744.

Self-hosted PostgreSQL accepts the front end's saves. D1 migration 0020 widened the progress_imports source CHECK; PostgreSQL never got the equivalent, so every signed-in save on a self-hosted install aborted on the constraint. Migration 014 brings it level, and a parity test fails if the two stores, the Worker and the learning-event contract disagree again.

The progress-flush regression is now caught here. planUnsyncedProgressFlush holds the decision ProgressProvider makes when an account read finally succeeds, and a test replays the seven-page-load sequence that lost data in 0.110.0 and 0.111.0.

Breaking changes

None.

Migrations

PostgreSQL self-host: 014_account_backed_progress_source.sql. D1: none beyond 0020.

Known limitations

The hosted persistence gate needs a password-capable smoke account, the PROJECT42_HOSTED_SMOKE_* secrets and the PROJECT42_HOSTED_* variables. Until they exist it fails on schedule by design. Keycloak 26.7.3 carries a deferred Netty advisory that is not reachable in the shipped topologies; see self-host/.trivyignore.yaml.

Rollback

Pin 0.111.1. Do not roll back to 0.110.0 or 0.111.0. PostgreSQL 014 only widens a constraint and is safe to leave in place.


Project 42 platform v0.111.1

Progress completed while an account read is still pending is added to the learner's record instead of replacing it.

0.110.0 fixed a real defect -- one failed GET /v1/me/progress ended every later write for the session -- by buffering what the learner did until a read succeeded. The flush was wrong. When the read came back, the provider applied the buffer with setProgress(buffered.progress), and the buffer had been built on the empty progress the provider holds before its first read. The learner's hydrated record was thrown away and that partial record was written over it. Opening a module and answering before the read returns is enough: a learner who completed seven modules on seven page loads kept only the seventh.

The flush now merges with mergeLearnerProgress, the same function account merges use, so every attempt, completion and badge the read returned is kept and only the evidence recorded while the session was not yet writable is added. The Project 42 portal's provider-journey and reliable-agent browser journeys fail on 0.110.0 and 0.111.0 and pass on this release.

Breaking changes

None.

Migrations

None beyond 0.111.0. Deployments coming from 0.109.0 or earlier still need D1 migration 0020.

Known limitations

The platform's own suite does not exercise this path; the regression is caught by the portal's browser journeys. A platform-level test for the provider's hydration and flush ordering is owed.

Rollback

Do not roll back to 0.110.0 or 0.111.0. Pin 0.109.0 if this release must be withdrawn.


Project 42 platform v0.111.0

The review dates the site shows are the ones the curriculum actually earned, and every one of them is current.

Served dates were ones the content repository had already disowned. On 2026-08-22 a commit set every citation's review date to 2026-08-23 with no review behind it. project42-content rolled that back on 2026-09-06, but the platform's installed copy was synced the day before, so production kept telling learners that 598 sources were checked on a day nobody checked them. The platform now installs content that carries the true dates, and content:currency compares what the platform serves against project42-content directly, so the two copies cannot quietly diverge again. A null review date is rejected as malformed rather than read as "unknown".

Every stale claim was re-read, not re-dated. Honest dates made 373 citations fail their review cadence. Each cited page was fetched and read against the text of the module or resource citing it; 390 citations were confirmed, 26 were repointed to where the same page now lives, two retired registry hosts were updated (cursor.com/docs, owasp.github.io/API-Security), and prose that a source no longer supports was corrected: MCP deprecated the sampling and logging client primitives in protocol 2026-07-28, and Anthropic no longer documents a Console Evaluation tool. content:freshness reports 599 current, 0 stale, against project42-content@d1fcc2b.

Learner progress saves. 0.110.0 accepted the front end's account-backed-v1 source in the API contract, but a CHECK constraint on progress_imports still refused it inside the same D1 batch that writes module_progress, so every save failed with a 500. Migration 0020 rebuilds progress_imports in place with a CHECK that also admits account-backed-v1, legacy-hosted-v1 and account-merge-v1, preserving existing rows, and the Worker now validates sources against the single exported PROGRESS_IMPORT_SOURCES list and names the supported values when it refuses one.

Also in this release

  • model-context-protocol-mcp is authored: sourced technical content, a full instructor script, and four knowledge checks in place of template text.
  • The six acceptance criteria are written down in docs/definition-of-done.md, and docs/decisions/ records the four architecture decisions that were previously only in session history.
  • The self-hosting theming guide describes the three-layer theme model that actually ships.
  • Hardcoded radius and tracking values in core fell from 102 to 53; the theme ramp steps core wrote by hand are published tokens.
  • Diagram step transitions keep text contrast.

Breaking changes

None.

Migrations

Apply migrations/0020_account_backed_progress_source.sql before deploying this Worker. Without it, every account-backed progress save is refused by the database. Hosted production already has 0017 through 0020 applied.

Known limitations

The progress fix is D1-only. The PostgreSQL self-host schema (self-host/postgres/001_initial.sql) carries the same two-value CHECK on progress_imports and no PostgreSQL migration widens it yet, so account-backed saves on a PostgreSQL deployment are still refused.

0.110.0 was tagged without moving self-host/compatibility.json, the changelog or these notes past 0.109.0, so its release job could not pass release:check. Its changes ship here.

A 30-day review cadence covers 31 of 61 registered sources, so the dates confirmed on 2026-09-11 begin to fall due on 2026-10-11. docs.vllm.ai serves a Cloudflare challenge to automated readers; its citation was verified from the page's source in vllm-project/vllm. 21 modules in six learning paths are still template text.

Rollback

Pin the previous platform version. The content and documentation changes revert with the package. Migration 0020 only admits additional source values and preserves every row, so it is safe to leave in place under 0.110.0.


Project 42 platform v0.109.0

The site is usable on a phone.

It had a manifest, a service worker, apple-mobile-web-app-capable and viewport-fit=cover. Every box was ticked and it was still bad on an iPhone, because the tags were never what was wrong. What follows was measured on real iPhone SE, iPhone 15 and iPhone 15 Pro Max profiles, portrait and landscape, under WebKit -- the engine Mobile Safari actually uses.

Headings could not get smaller than a desktop size. Every heading family is sized with a clamp() whose FLOOR is a desktop measurement -- clamp(3.4rem, 7vw, 7.3rem) cannot resolve below 54.4px, because on any phone the 7vw term is smaller than the floor and the floor wins. A module page rendered a 54px heading with -0.075em tracking into a 226px column: one word per line, document.scrollWidth at 344px against a 320px viewport, and the last word clipped off the right edge. Phone type ramps now sit under (max-width: 760px), and each is chosen to resolve to exactly the floor it replaces AT 760px -- so the seam is continuous and nothing at 761px or wider renders differently.

The navigation was the first screen. At phone width the primary nav wrapped into a two-column block occupying roughly 330 of the 568 visible pixels on an iPhone SE, and "Start learning" was display:none on every phone. You landed on the site and saw the site map. The nav now collapses behind a 44px disclosure with the action restored as its first item. The links stay in the served HTML and are hidden with CSS, never conditionally rendered, so the link checker, the Pages export and crawlers still see every destination.

Nothing you had to tap was big enough to tap. Nine footer links per page at 23.2px tall -- core declared the 44px minimum and portal-default reset it to 0 for a tighter desktop footer. The Field Guide card's only action at 45x18px. Module citations at 19px. Breadcrumbs at 18px. All are 44px on a phone now, and the footer minimum is restated in the bundle that gave it away.

The installed app had an invisible status bar. apple-mobile-web-app-status-bar-style: black-translucent forces the clock and battery glyphs white, and portal-default paints a white ground. It is default now, which follows the system appearance on a light bundle and on a dark one; viewport-fit=cover still lets the page fill the cutout. <body> and .site-footer were each applying safe-area-inset-bottom, stacking two insets under the home indicator; the footer applies it once.

The home page cost 432.7 KB on cellular. 90.6 KB of that was a decorative hero.png, fetched on the home page and on /learn/, and 99.9 KB was a /guide/ RSC prefetch on every first paint for a link most readers never tap. A phone now gets the hero plate without the picture, and no /guide/ link prefetches.

A gate so it cannot come back. web/tests/browser/mobile-viewport.spec.ts runs under WebKit on an iPhone SE, from a new mobile-webkit Playwright project. It fails on horizontal overflow, on a heading wider than its own box, on any control under 44x44 CSS px, on a missing safe-area rule for a pinned element, on a nav that is not a disclosure at phone width, and on a text field small enough to make iOS zoom on focus. The chromium project ignores that file and the new project runs nothing else, so neither suite can pass at a width where it proves nothing.

Breaking changes

None. Every rule added is scoped to (max-width: 760px) or is a new class that only renders at phone width, so no viewport of 761px or wider changes.

Adopters running the browser suite in CI must install WebKit alongside Chromium: npx playwright install --with-deps webkit.

Migrations

None. No database, API or content change.

Known limitations

The safe-area half of the gate reads the CSSOM rather than computed styles. env(safe-area-inset-*) resolves to 0px in every engine under Playwright -- there is no notch to emulate -- so a computed-style assertion would pass on a page with no safe-area handling at all. Whether the installed app genuinely clears the notch can only be confirmed on a device.

/guide/ still serves a 100 KB HTML document. That is a content-structure question, not a phone one, and is left.

The hosted Entra sign-in page reached from /profile/ carries controls below 44px. It is Microsoft's markup, not the portal's, and is excluded from the gate.

Rollback

Pin the previous platform version. Nothing outside the package changes, so a site reverts by reinstalling.


Project 42 platform v0.106.1

The default theme stops being somebody else’s brand.

v0.105.0 made the product ship its own appearance, and that was right. What it shipped as the default was 06-galactic-guide -- a Gallery theme, copied into the package. So every fresh install rendered in one operator’s livery, and "the default look" and "the Galactic look" became the same sentence. A Gallery theme is a CHOICE. It cannot also be the thing you get when you have not chosen.

The platform now ships portal-default. A white page, hairline rules, one slate-blue action colour, system type, no ornament. It is the stock theme in the sense Hugo and Jekyll mean it: what a brand-new site renders with before anybody has decided anything, complete enough that the first thing an operator sees is a finished site rather than an unstyled one. It is not in the Gallery and never will be.

It is a complete bundle, not a placeholder. All 48 tokens core reads, every component treatment the portal renders, the heading ramp bound to the layout track tokens, and the four surfaces core stopped painting in v0.105.0 -- the hero plate, the landing ornament, the footer tap target, and the primary action fill. Every foreground/background pair it declares is at or above 4.5:1.

project42-portal create selects it, with no --theme flag needed. 06-galactic-guide still ships and is still a theme anyone can select; it is simply no longer what you get by default.

One fix made the swap a one-line change. materialise resolved availableThemes and nothing else. availableThemes is the switcher’s menu; theme is what the site renders, and the two are not the same list -- a site that offers the six Gallery bundles but renders the shipped default names that default in theme alone. Such a site installed six bundles and not the one it had actually selected, then rendered on fallback values with every gate green. The selected theme is now always resolved, and tests/web-distribution.test.mjs fails if that regresses.

Verified by building a real site both ways and reading computed styles off /, /learn, /learn/paths and /about, all 200: changing the one "theme" field moves the page ground, the body and heading typefaces, every heading colour, the primary action fill and shape, the eyebrow treatment and the card surface -- and changing it back restores the Galactic rendering exactly.

Fixed after the first cut (v0.106.1)

Building the real site on portal-default found three things the first cut missed, all of them the same shape: a decision core hands the bundle, which a light-accented theme never has to make.

Core fills .cta, .pillar-reference and .pillar-ondemand with the accent, then chooses every text colour inside them for the PAGE ground, and dims the card index to 0.65 and the list to 0.75 without restoring either. On Galactic mint accent with dark text that reads fine. On any accent dark enough to need light text the whole panel falls below 4.5:1 -- 309 axe violations across three suites. portal-default now names --p42-accent-fg on those panels and takes the opacity back. Core also leaves justify-content unset on the header disclosure, so its links compute normal; the bundle sets flex-start.

And one gate: the browser conformance suite derived every colour and the heading face from the selected bundle, but still asserted toContain("Inter") for the body face -- 06-galactic-guide’s own. Any bundle naming another body face failed a required gate for no reason beyond not being that theme, which is the very defect that suite was rewritten to remove. It now reads --p42-font-body like the rest.

Breaking changes

None for an existing site: it names its theme explicitly and keeps rendering it.

A NEW scaffold now renders portal-default instead of 06-galactic-guide. Pass --theme 06-galactic-guide to project42-portal create, or set the theme field afterwards, to get the previous behaviour.

A site whose theme is not listed in availableThemes now installs that bundle, where before it silently did not. Nothing that worked stops working.

Migrations

None. No database, API or content change.

Known limitations

portal-default lands in public/themes/ on every install, like app/. A consuming repository must git-ignore it: tracking a platform-shipped bundle makes materialise read it as vendored and refuse to refresh it, freezing the default at the version first committed. The scaffolder template already ignores /public/themes/; a site predating that template needs the entry added.

The Gallery preview matrix does not yet carry portal-default, because the Gallery holds Gallery themes and this one is not one. Previewing the product default beside the themes on offer is unsolved.

Rollback

Pin v0.105.0 and set "theme": "06-galactic-guide". Nothing outside the package changes.


Project 42 platform v0.105.0

The product ships its own look, and core stops painting one.

A Project 42 site could not have an appearance without reaching for the Gallery. The theme and layout bundles lived only in project42-gallery, arrived through a sync script, and were pinned by a hash lock the build verified -- so a fresh scaffold rendered unstyled until someone checked the Gallery out, and a running site could change appearance underneath its owner when a bundle was re-synced. At the same time core CSS was painting things a theme is supposed to own, so a theme could not actually change them.

Both halves are the same defect: appearance had no single owner.

The product now ships a complete appearance. A theme bundle and the three layout bundles live in the package. Installing the platform installs a finished look, with no Gallery checkout, no sync step, no lock file and no network.

A theme is a folder. Download one, drop it in at themes/<id>/ in your own repository, and name it in the single "theme" field of project42.config.json. Nothing else changes -- not the code, not a build script, not a manifest, not a lock entry. Resolution is: your own folder, then a bundle you already pulled from the Gallery, then the platform default. A theme nothing provides fails the install and says which folder to create.

Core declares no brand. Measured over web/app/globals.css: 4,106 declarations, 2,667 structure, 1,338 appearance. The appearance that was pinned to a value core had no business choosing has moved to tokens the bundle owns -- 27 places an accent or brand fill was read as text, 31 typefaces named inline, 28 pill radii, nine ramp tracking values, nine white fills. --p42-text-accent and --p42-text-emphasis are the substantive pair: an accent is a fill colour, and read as body copy it can fail contrast while passing its own contract -- which is how near-black text landed on a near-black panel at 1.04:1 in production. Both default to the fill in use today.

A gate keeps it out. npm run theme:boundary fails on a colour literal, a brand colour used as text, a typeface named inline, a token pinned to a literal outside the fallback layer, and any new hardcoded radius or letter-spacing. Run against core as it stood before this release it reports 65 violations; against core as it stands now, none.

Verified unchanged: 121,231 computed style values across 15 routes, captured from real builds before and after, differ in one place -- an in-flight transition opacity sampled microseconds apart.

Breaking changes

None for a site that vendors its Gallery bundles: those keep resolving from where they already are.

A site that names a theme no folder provides now fails npm install instead of building an unstyled page. Drop the theme folder in at themes/<id>/, or name one the platform ships.

Theme bundles must declare seven further tokens -- --p42-text-accent, --p42-text-emphasis, --p42-font-body, --p42-font-mono, --p42-font-mono-display, --p42-font-serif and --p42-surface-inverse -- or tokens:check fails for them. The bundle the platform ships declares all seven; a Gallery bundle must be republished with them.

Migrations

None. No database, API or content change.

Known limitations

Core still carries 102 hardcoded radius and letter-spacing values, 38 distinct. None matches a step in any published layout ramp, so collapsing them would change the rendered page; they are recorded in web/scripts/appearance-debt.json and the gate lets the list shrink but never grow.

The Google Fonts @import at the top of web/app/globals.css still loads the brand typefaces from core, and is the one network dependency the build has. Moving it into the bundle risks a visible change in how type loads, so it is left.

Rollback

Pin the previous platform version. Nothing outside the package changes, so a site reverts by reinstalling.


Project 42 platform v0.104.3

The curriculum stops being downloaded by browsers that never render it.

Three client components track a learner's progress — ProgressProvider, ProfileDashboard, ProgressSnapshot — and each was handed the whole catalogue. The progress API reads eight fields from it: contentVersion, a path's id, title, moduleIds and badge, and a module's id, title and capstone. Every module body, knowledge check and source list travelled with them into the browser as the single largest chunk in the bundle.

It had been that way as long as those components have existed. Splitting the catalogue into its own module in v0.104.0 made it visible rather than causing it, by putting the same 1.3 MB in two chunks at once.

Projecting at run time would not have helped: a projection computed from the full object still has the full object in the module graph. So materialise generates the projection — the curriculum's shape without its content — into lib/progressCatalog.generated.ts, which imports nothing, and lib/progressCatalog is what a client component reads.

Measured on the generated template, same build, same content:

before after
client total 3,535,749 bytes 993,821 bytes
largest chunk 1,306,327 bytes 199,967 bytes
projection — 48 KiB of the catalogue's 1,625 KiB

A gate in tests/web-distribution.test.mjs fails any "use client" module that imports lib/catalog directly. It is a direct-import check; the performance budget in a consuming repository is what catches a client component reaching the catalogue through an intermediate module.

Adding a field

The projection is one function in bin/project42-portal.mjs. A progress feature that needs a field the projection does not carry must add it there, and the type cast on the generated value is why that is a deliberate act rather than something that happens by accident.

Migrations

No file under migrations/ was added or changed since v0.104.2.

Breaking changes

None.

Known limitations

Unchanged from v0.104.0: publishing a content change to a site is three commands, and a generated site's browser suite passes on 06-galactic-guide but not on 05-open-orbit or 07-quiet-lantern.

Rollback

Revert consuming sites to v0.104.2. They regain 2.5 MB of client payload and lose nothing else.

Project 42 platform v0.116.6

The Anthropic in Practice lesson now teaches and assesses how to choose among chat, the Console playground, the Messages API, Claude Code, and Managed Agents. It adds a practical exercise, context and tool boundaries, and a dated availability check. This release pins canonical content at project42-content@fbdc401d7548aa985d1e99b19b768e3c36e1286b and addresses September Orchard item 9271 through a reviewed editorial correction.

Breaking changes

None.

Migrations

None.

Known limitations

Consumer sites must update their platform pins to v0.116.6 to serve this lesson. Orchard still holds its generated draft without Gate 2 evidence; the other September findings remain unresolved.

Rollback

Pin v0.116.5. No database migration is involved.