Changelog

Generated from the main-branch git history — every commit gets surfaced here as soon as it lands in production. Subscribe via the RSS feed.

July 2026 · 10 commits

Othere504d2304 Jul
Fix #19: unblock all public marketing/docs/SEO pages in the middleware
Details
The Clerk middleware 307'd anonymous visitors + crawlers to /sign-in for
every page NOT in isPublicRoute — and the allowlist was missing almost all
public pages: /docs (and /docs/interop, the interop inbound magnet just
shipped), /conformance, /compare, /catena-x, /components, /changelog, /dpp,
/battery-passport, /eu-machinery-2027, /dpa, plus /robots.txt and
/sitemap.xml. So the magnet was dead in prod (bounce to sign-in) and the
whole marketing surface was invisible to search engines.

- Extract the allowlist to lib/auth/public-routes.ts (PUBLIC_ROUTE_PATTERNS)
  so it is unit-testable; proxy.ts imports it.
- Add every public marketing/docs/SEO page + /robots.txt + /sitemap.xml.
  Authed surfaces (/studio/* sub-routes, /org, /suppliers) stay protected.
- Add app/sitemap.ts (a real sitemap; robots.ts already advertised
  /sitemap.xml which previously 404'd).
- Regression test __tests__/api/public-routes.test.ts: builds the same
  matcher and asserts every public page/API is public and every authed
  surface is protected (the matcher-regex unit cases the #19 playbook wants).

Gate: tsc 0, public-routes 3/3, hygiene green. (The connectors-register-ssrf
test fails on this machine with OR without this change — Avast DNS
interception on the egress guard's re-resolve; green in CI.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Otherdd2c8d204 Jul
Migrate canonical domain to aas-studio.eu
Details
Custom domain aas-studio.eu is live on Vercel (added to the project;
NEXT_PUBLIC_APP_URL set in prod). Point the code's canonical URL at it: the
env var is the runtime switch, this swaps the hardcoded fallbacks + all
user-facing references so the domain is canonical everywhere, not just at
runtime.

- Runtime fallbacks (openapi v1/v3 servers, robots sitemap, changelog feed,
  credential issuer/status base, embed session URL, dpp-grant, verify-source
  UA, conformance statement) now fall back to aas-studio.eu.
- Docs / README / LICENSE / SDK (py + ts) / examples / marketing / charts /
  embed.js default base URL / QA + runbook + security docs -> aas-studio.eu.
- telemetry-gate SAAS_HOSTS keeps BOTH hosts (aas-studio.eu + the .vercel.app
  alias) so telemetry fires on either; the alias stays alive so the ArtQR
  evidence + the LinkedIn post keep resolving.

Deliberately NOT changed: the a11y/e2e/smoke CI probe URLs stay on the
guaranteed-stable .vercel.app alias; dated audit docs + changelog-data.json
stay as history.

Gate: tsc 0, jest 2496 pass (same baseline; 3 red suites are the pre-existing
.mjs-under-jest ESM noise), hygiene guard green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Other040308904 Jul
Repo hygiene: consolidate Helm charts 3->1, drop dead dirs, add CI guard
Details
The repo accreted cruft from shipping across parallel efforts. Cleaned the
genuine duplicates/dead dirs (the required ~25 root config files stay) and
added a deterministic gate so it can't recur.

Cleanup:
- Helm charts 3 -> 1. Kept charts/aas-studio (the superior variant: cronjob +
  serviceaccount + README, and bring-your-own-Secret via envFrom.secretRef).
  Removed deploy/helm/aas-studio and helm/aas-studio (older forks that templated
  secrets from values.yaml; helm/ was referenced nowhere). Repointed SELF_HOST.md
  to charts/ with the correct create-secret + install commands.
- Removed .skills/ (generic experiment playbooks, superseded by .claude/skills/)
  and styles/globals.css (Dyad-scaffold leftover; the app imports app/globals.css).
  Both referenced nowhere in source/CI.

Guard (so it can't regress):
- scripts/check-repo-hygiene.mjs + .github/workflows/hygiene.yml. HARD-fails on
  >1 Helm chart dir and dead internal doc links; warns on competing lockfiles
  (blocking once CI is fully off npm ci). knip runs report-only (non-blocking)
  until its baseline is triaged. Local: pnpm hygiene / pnpm knip.
- Added a "Repo hygiene pass" section to the aas-code-reviewer skill for the
  judgment calls the deterministic checks can't make.

Gate: tsc 0, hygiene guard green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Other2af4f6304 Jul
Add /docs/interop — self-serve "test against a live AAS Studio endpoint"
Details
An inbound magnet that turns the ArtQR-style interop demo into a repeatable,
self-serve page: any AAS server, GS1 resolver, VC verifier or product-trust
layer can point at the live public surfaces (GS1 Digital Link resolver, strong
ETag content anchor, live AID datapoint, conformance gate) and run a real
byte-level interop test. Every example is a runnable anonymous request against
production, with the real strong ETag inline. Linked from the /docs index.
CTA points at Miguel's LinkedIn + the GitHub repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Other0dd4a8603 Jul
Add public /api/demo/voltage — a live AID datapoint for interop demos
Details
The demo .aasx's Asset Interface Description (IDTA-02017) declares an HTTP
interface whose datapoint points at /api/demo/voltage. An AID-aware client
(AASX Package Explorer's Asset Interface plugin, or our own runtime poller)
issues an anonymous GET and reads { value }, so tooling can demonstrate live
AID polling against a real, conformant AAS Studio endpoint.

- GET returns { value, unit, at } with a slow sine so successive Reads move
- no auth (a device datapoint carries no Bearer), no-store (fresh each read)
- /api/demo/(.*) added to the proxy.ts public allowlist so the anonymous
  poll isn't bounced to sign-in (bug class #19)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Other281080603 Jul
Close the 5 top-down review findings on the merged copilot/DPP work
Details
Post-merge review of b662469..HEAD (ESPR copilot, fix-proposer, GS1/ETag,
MLP dual-shape, models sync) found no critical/high — the recurring
high-severity classes were all handled with tests. These are the 3 MEDIUM
+ 2 LOW gaps, each fixed regression-first.

MED #1 (cost) — the two new AI copilot routes (propose-fixes,
compliance-check) were the only AI routes with no usage metering and an
IP-keyed burst limiter (NAT-collapse / rotatable).
  - key rateLimitDistributed by userId
  - debit prisma.usage (ai_fix / ai_compliance); compliance-check meters
    ONLY when the LLM classifier actually ran (caller-supplied category is
    a zero-cost deterministic path). No hard monthly cap: interactive
    editor copilot, the per-user burst limiter is the wall.

MED #2 (correctness, user-facing) — espr-categories pinned battery Part 7
to a non-existent template id (battery-due-diligence-idta-02035-7); the
registered id is battery-circularity-idta-02035-7, so the new compliance
panel reported Part 7 permanently missing even when present. Corrected.

MED #3 (data, bug class #20) — the v3 shells write surface still persisted
the app-derived shell-id fallback into the global @unique dppPrimaryId
(missing fallbackToAasId:false), the same class 49e292c fixed on the
models route. Added the flag at both v3 call sites.

LOW #4 (info) — propose-fixes debug raw-echo was reachable by any
authenticated user; admin-gate it (isAdminUserId), matching the repo's
admin-diagnostic convention.

LOW #5 (correctness) — parseFixProposals collapsed violations sharing an
identical (constraint, path) — e.g. AASd-022 duplicates at one parent
namespace — dropping all but one proposal. Consume violations by identity
so each duplicate anchors its own proposal; still never fabricates.

Regression tests: ai-copilot-metering (userId RL + meter-when-LLM-ran +
admin-gated debug), espr-compliance (Part 7 present when supplied),
v3-shells-crud (extractPrimaryId called with fallbackToAasId:false),
fix-proposer (each same-path duplicate gets its own proposal, no
over-emission).

Gate: tsc 0, jest 2496 pass, production matrix 27/27. (3 red suites are
pre-existing/pulled .mjs-under-jest ESM-interop failures, unrelated.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixaid789c0403 Jul
robust fix-proposal anchoring + authed debug echo
Details
Live test showed proposeFixes returning [] even though the LLM was reachable
(the classifier on the same client worked) — the strict parser required the
model to echo the violation path+constraint VERBATIM, which it doesn't.

parseFixProposals now ANCHORS each proposal to a REAL violation instead of
trusting the echo: exact (constraint,path) → exact path → whitespace/case-
normalised path → the sole violation of that constraint. The surviving
proposal's path/constraint always come from a known violation (still no
fabricated targets — a proposal for a constraint we never sent is dropped),
and it also accepts a single JSON object, not just an array. Added
proposeFixesVerbose + a debug flag on the route (authed-only) that returns the
raw LLM content, so an empty result is diagnosable in prod instead of guessed.

8/8; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Featureuie7c749d03 Jul
ESPR compliance panel on the library detail page (#2 copilot UI)
Details
Surfaces the compliance-check copilot: EsprCompliancePanel calls
POST /api/ai/compliance-check with the model's jsonContent + name/description
and renders the gap — category (label, mandatory/first-wave/future status,
deadline, legal ref), how the category was decided (caller / AI-inferred with
rationale+confidence / generic fallback, stated honestly), the missing required
submodels with their IDTA spec, present counts, and missing mandatory fields.
Mounted full-width below the extraction detail. Self-contained client
component; the analysis stays deterministic server-side (the LLM only picks
the category). tsc clean.

Note: #1's plain-language explainer floor already ships via the skill-backed
RuleHelpPopover in the validation dialog; the fix-proposer BUTTON (threading
document context + an accept-and-revalidate flow into the editor) is the
remaining #1 UI follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Featureeditorc9dfbb303 Jul
LLM fix proposer — propose the right value for ambiguous repairs
Details
Ceiling of the "make it valid" copilot (floor = aasd-explainer). The
deterministic fixers resolve ambiguity by injecting blind defaults and never
flag it; the AASd error text assumes metamodel expertise. proposeFixes
(lib/ai/fix-proposer.ts) takes the violations + document context and asks an
LLM for the concrete fix value per violation with a one-sentence rationale,
with the explainer's rule catalogue injected as context. PROPOSE-ONLY by
design: nothing is applied server-side — the editor presents each proposal,
the user accepts, and the normal validation gate re-runs, so a wrong proposal
cannot ship unvalidated. The parser drops any proposal that doesn't echo a
known violation (no fabricated targets) and empty values; any LLM failure →
[] and the UI falls back to the explainer. POST /api/ai/propose-fixes.

Editor UI wiring (proposal cards next to errors) is the follow-up; this ships
the tested engine + route. 8/8; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Featurecompliance8dd579303 Jul
ESPR compliance copilot — product→category + submodel/field gap report
Details
espr-categories knew what a CATEGORY requires and regulatory-completeness knew
what FIELDS a regulation mandates, but nothing connected them to a concrete
AAS. analyzeEsprCompliance (lib/compliance/espr-compliance.ts, deterministic)
now reports, per category: required/recommended submodels present vs missing
(matched against the REAL template registry — semanticId first; idShort
fallback ONLY when the env submodel carries no semanticId, otherwise the
IDTA-02035 battery parts false-match the foundationals via shared idShorts;
battery Part 3 is genuinely indistinguishable — its published semanticId IS the
CarbonFootprint 1/0 IRI) plus the field-level Annex XIII / ESPR report.

When the caller doesn't know the category, classifyEsprCategory
(lib/ai/espr-classifier.ts) asks an LLM — BOUNDED to the closed category
vocabulary (an id outside it never survives parsing; any failure → null and the
route falls back to 'generic' and says so). POST /api/ai/compliance-check wires
both: category source is caller > llm > fallback, analysis always deterministic.

19/19 across the two suites; tsc clean.

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

Command palette

Navigate + run actions