Changelog
Notable changes, per release, in Keep a Changelog format.
All notable changes to APIMaster Integration Workbench are documented here.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
Architecture note: releases remain local-first — application-managed workspace state uses
browser localStorage (core key apibox.state.v5, plus versioned awp.* keys). The capability
manifest separately records same-origin loads, explicit remote operations, configured browser
behavior, navigation and unresolved host boundaries; local persistence is not a zero-network claim.
[Unreleased]
Fixed — v0.10.1-rc.3 A workspace bundle now carries its spec text
Three documents said the bundle embedded SwaggerMaster spec text; the storage manifest saidexcluded-until-bundle-carries-artifacts on the same three prefixes. The manifest was right —workspace-bundle.ts had zero references to the artifact store. The metadata slices rode the
bundle through the persisted-state map, but the text lives in IndexedDB, so an exported workspace
imported as projects and versions pointing at artifacts that were not in the file.
The bundle gains an optional specArtifacts map keyed by the artifact store's own logical key — the
addressing scheme its own doc comment already named for this purpose. Export reads the three
SwaggerMaster-owned kinds; import writes them back in both the replace and merge paths and reports
the count. The manifest now says included.
Where the collection sits relative to the credential guard is the whole safety question. The
export refuses when a redacted bundle still contains something credential-shaped, and that guard's
comment already claimed it covered "spec text alike" — aspirational while the text was not in the
scanned payload. Collecting after the guard would have produced a bundle labelled redacted that
carries an API key inside an OpenAPI example. The artifacts are collected first, into the scan input,
and a test now pins that ordering.
Four failure modes were chosen rather than defaulted: an unavailable store still exports (carrying no
artifacts), an unreadable artifact is skipped and counted, a refused write on import is skipped
rather than aborting a restore whose rows are already applied, and a bundle written without the field
still imports. awp.eventintel.specs.v1. is left alone — it is marked plainly excluded, a settled
decision rather than pending work.
Also fixed: two no-control-regex ESLint errors in test files committed earlier the same day, where
a comment-stripping regex carried a literal tab instead of . They shipped because only the
changed files had been linted.
Added — v0.10.1-rc.3 Henris Labs attribution, and the one-pager made readable
"Made by Henris Labs" now sits at the bottom of the sidebar, under a divider, in the official
lockup: the emblem, HENRIS LABS and the AI FOR A BRIGHTER TOMORROW tagline. It scrolls with the
panel and hides when the panel collapses, so it cannot cover a control the way a fixed corner badge
would.
It is reproduced, not iframed. henrislabs.com/brand/embed is a full HTML page written for an<iframe>, and its emblem path only resolves on that origin. Embedding it would put an undeclared
third-party request on every screen of a product whose route contract promises none, and would needframe-src opened in the CSP. Every value — the 21 px wordmark at 0.105em, the 7.2 px tagline at0.19em in #2588ff, the 66 × 47.52 emblem, the dark-mode inversion — is copied from that
stylesheet, and the emblem is vendored at 39 KB (down from 1.6 MB). Identical rendering, no network.
The trade-off, stated: a rebrand will not propagate by itself, so a test pins the published values.
The ContractRadar one-pager was never removed. git log -S on its marker finds one commit, the
one that added it, and none that took it away; the panel has been mounted unconditionally and
production serves the artwork. What made it read as missing is that a 1024 × 1536 portrait sheet was
rendered in a 288 px box with object-cover object-top — the top 19%, as a strip, at the bottom
of a long page. It is now object-contain in a fixed-height box: the whole sheet, click-through
unchanged. The fixed height also stops the anchor collapsing to its caption bar while the image loads
(37 px before, 549 px after).
Fixed — v0.10.1-rc.3 Every 404 rendered the whole application twice
__root.tsx wraps <Outlet /> in one AppShell, and the router renders notFoundComponent inside
that same tree — but NotFoundScreen wrapped itself in a second one, nesting a whole application
inside the running one. Measured in a browser: two <header> landmarks, two "Search navigation"
inputs and two <main> elements, on a page that may have at most one. That is an accessibility
defect, not a cosmetic one.
It affected every unknown URL plus the three routes that render the screen directly when their entity
is absent rather than throwing notFound() — so a missing API, a missing endpoint or a missing spec
each produced it. Now: one header, one navigation, one main.
ErrorComponent wraps itself the same way and was deliberately left alone. It is the root route's
error component, where an error thrown by the root cannot be caught inside that same component, so it
may legitimately render outside the shell. That was not proved either way, and removing the wrapper on
the strength of an analogy would risk shipping a shell-less error screen.
Found by loading URLs and counting landmarks in the DOM. It was invisible to every static check in the
repository and to 1909 unit tests — the second time in two days that executing the product found what
reading it could not.
Added — v0.10.1-rc.3 Three things the plans specified and the code never had
The audit recorded twenty-five items that documentation asks for and the product never implemented.
These are not deletions — nothing was removed — so this is building them, not restoring them.
Entitlement now re-checks dependencies. The authorization engine ran six steps and had no
dependency step: a feature could be entitled while a hard prerequisite was switched off by an
override or lifted out of reach by a runtime re-tier. A seventh step denies with reason dependency
and names the prerequisite that is holding it. Measured before it was written, because a new denial
can only take capability away: across all four tiers and all 94 catalogue features it denies
nothing, and that measurement is now a test rather than a sentence. A cycle terminates by
construction — prerequisites are judged without re-entering the dependency step.
A locked notice offers admins the matrix. Owners and admins now get a "view in the access matrix"
link, and when the reason is dependency the notice names the capability that is off. Everyone else
still sees only what happened: a link to a table you cannot edit is an invitation to a dead end.
The API portfolio register filters by lifecycle and classification, and sorts. Its plan asked for
five filters and a sort; three of them had silently dropped out of the acceptance ledger. The
lifecycle list carries six values rather than the four of LifecycleStatus, because the register
also holds published and retired specs and a filter built from the narrower type would have
hidden every one of them. Sorting by readiness puts "not assessable" last — null is not a zero, and
a health-sorted register that leads with the unmeasured reads as the opposite of the truth.
One specified item was deliberately not built: the self-demotion confirmation dialog. This
product has a reversible session-role simulator, not a tenant member plane, so the dialog would warn
about a lockout that cannot happen. It is blocked on the tenant plane and recorded as such.
Fixed — v0.10.1-rc.3 The hosted Contract Radar was throwing, and the strip did it again
Found by the regression audit the owner asked for. ff98e18 moved the public-mode strip's end anchor
from inventory to contractRadar, which restored the Contract Radar tab — and deletedradarLvlBadge, radarShort and radarEntryHtml, which are Contract Radar's own renderers and are
defined between connections and contractRadar. On the hosted deployment the tab dispatched,
painted its four summary cards, and then threw ReferenceError: radarShort is not defined: no radar
table, no detail pane, no change feed.
Every check was one level too high. The tab list was right, the dispatch map was right, every dispatch
target resolved to a defined function, the sandbox security self-test passed at 72 checks and the root
suite was green at 1901/0. A missing callee sits below all of them.
The end anchor is now the first surviving definition after the removed section, and the rule is
written into the file: **an end anchor is bound to the first thing the public build still needs, never
to whatever happens to follow the section today.** The durable half isstrippedCalleesStillReferenced, asserted in the root suite so it runs in the pre-commit gate. It
uses the local build as its reference, which is what makes it exact — a naive "called but never
defined" scan reports about seventy names in the healthy build alone. Mutation-tested: restoring the
broken anchor turns it red.
The wider audit found no other regression. All 52 routes in FEATURE_PRESERVATION_MATRIX.md still
exist, are in the route baseline and are in the navigation registry; the documented navigation
contract lands exactly at 46 primary / 47 All Tools / 49 command palette; no source file was deleted
in 60 commits and no commit in 80 carried a large net deletion. Eleven items are documented but were
never built — recorded in versioning/260908_02_…, not silently dropped.
Six iterations had also shipped without the paired versioning/ record VERSIONING_CONTROL.md
requires. All six were backfilled, each marked as backfilled rather than presented as contemporaneous.
Added — v0.10.1-rc.3 Code-intelligence tooling: map, edit, prove
Three tools, each with one job, per codegraph_install_direction.md: codebase-memory-mcp 0.10.8
answers "what does this change touch" before an edit, Serena v1.7.0 does symbol-level search and
editing, and @playwright/test 1.63.0 provides the working proof afterwards. Every version is
pinned — no @latest in .mcp.json or package.json — so a tool upgrade stays a separate, reviewed
change.
Deliberately not installed, and recorded so nobody re-derives it: Trailmark, GitNexus,
CodeGraphContext, Sourcegraph MCP, and the Playwright MCP. A browser an agent clicks through is
exploration; a version-controlled, re-runnable test is evidence, and the second does not follow from
the first.
The codebase-memory-mcp installer writes MCP configuration into 43 detected client surfaces and can
touch agent hooks, so it was run with --skip-config after a dry run, and both servers were declared
by hand in a project-scoped .mcp.json. Indexes stay local and unversioned: persistence off, the
graph in ~/.cache, and .gitignore covering Serena's cache and Playwright's reports.
Both tools are scoped to this repository's real shape. Companion-sandbox/ is excluded because it is
the generated, byte-identical copy of sandbox/ with identical line numbers — indexing both is
how a symbol search returns two hits and the edit lands in the copy that gets overwritten on the next
sync. With the vendored trees excluded the graph went from 92 164 nodes to 44 139: half of it was
other people's code. It answers what it was installed for — FactsheetCard has 120 callers,
which is the blast radius of the HZ-016 component and was not cheaply knowable before.
e2e/marker-reachability.spec.ts closes HZ-016 end to end. One test proves the component forwards
the attribute, another proves something reads it; only a browser can prove the route renders that
component at all. data-access-admin-state — the marker whose silent absence started this work — now
provably reaches the DOM.
Two real defects surfaced during the install. The first Playwright run tested a different product:
port 8080 was held by another repository's dev server and reuseExistingServer could not tell the
difference, so the assertions failed against a stranger's 404 page; the suite now owns port 8091 with--strictPort. And npm install was the wrong installer — this repository's SBOM is built frombun.lock and production builds with bunx bun@1.2.22, so the dependency was re-added with that
exact Bun rather than the newer local one.
Fixed — v0.10.1-rc.3 The data-* marker inventory was overstating the dead surface
The inventory published on 2026-09-06 reported 180 markers read by something and 374 read by nothing.
The corrected figures are 194 and 360. Fifteen markers moved from unread to read; none moved the
other way. This matters because the next pass of that workstream was going to delete from the larger
number.
Three defects, all in the scanner, all pointing the same way. A file was not credited for reading
what it emits, so run-heat-strip.tsx — which calls querySelectorAll("[data-bar-index]") eight
lines from where it writes the attribute — reported no consumer. A Tailwind variant names a marker asdata-[collapsible, never as data-collapsible, so a substring test missed the entire styling
mechanism of the sidebar and the calendar: nine markers looked dead, and deleting them would have
broken both surfaces with no failing test. And a selector such as [data-radar-action=run] was
counted as an emission, which disqualified its own file from being a consumer of it.
Fixing the first two naively made it worse — stripping emissions everywhere removed thirty-one
genuine consumers, because a test asserting toContain('data-academy="…"') writes the attribute form
inside a string literal. Emissions are now stripped only in a file that actually emits the marker,
and the result was measured both ways: no real consumer was lost.
One further correction is worth stating on its own. The generator holds the review document's prose
as string literals and was in its own haystack, so it counted as a consumer of the five markers it
explains — and for data-portfolio-edit it was the only consumer there had ever been. That marker is
emitted once and read by nothing, while the plan cited it as load-bearing and a test asserted it had
a consumer. Both were true only because the generator described it.
Four new tests guard the corrected behaviour, and each was mutation-tested: reverting any one of the
three fixes turns the matching test red.
Fixed — v0.10.1-rc.3 The hosted Companion Sandbox got its Cockpit and Contract Radar back
On a hosted deployment the dashboard strips its admin and mutation implementations out of the served
HTML, and the comment above that strip names exactly what it removes: connections, logs, API keys. It
was removing far more than that. The first pattern ran from async function connections all the way
to async function inventory, and both contractRadar and cockpit sit between those two in the
file — so it deleted roughly eight hundred lines and took the change radar and the entire cockpit
with them. That is why the public sandbox lost its data modeller, its save-as-contract editor, its
enum/min/max enforcement table and its change radar on 2026-08-16, while every older deployment still
showed them. The pattern now stops where its own comment always claimed it stopped.
Contract Radar is back unconditionally: it only reads fingerprints and change history.
The Cockpit is back behind SANDBOX_PUBLIC_COCKPIT, and with the flag off its implementation is
stripped rather than merely hidden, so the default build cannot be talked into rendering it.
The security self-test that had been read as the reason the cockpit could not be public turns out not
to be the protection at all. It bans the substring method:"PUT" from the public HTML — a useful
tripwire, kept unchanged for the default build, but a substring is not a guarantee. What actually
stops a visitor writing a contract is the control-plane gate, which sends every non-GET under/scenario-state through the control scope, and the provisioning rule that grants the public demo
credential only demo-read and demo-write-limited. Confirmed against the live deployment, not
inferred: an unauthenticated read of /scenario-state/contracts returns 401, and so does one
carrying an invented key.
So the cockpit build is now asserted by that property instead of by a banned substring — every
mutating call site in it must target /scenario-state/, it must still ship no external script, nosessionStorage and no key-minting UI, and the public demo credential must provably not carrycontrol or admin. Both new assertions were mutation-tested: rendering the build with the flag off,
and pointing one cockpit PUT at /admin/contracts/, each fail the suite. The self-tests now run 72
checks, up from 64.
Measured against that build rather than argued: with no key GET /scenario-state/contracts is 401;
with the demo key it is 200 and the corresponding PUT is 403; with the admin key both succeed. So on
the public URL the Cockpit is a read-only window until someone pastes a key into the Overview box, and
a savable editor only for whoever holds the admin credential. With no key at all the service cards
render but will not open, because the detail behind them is guarded and the 401 is not surfaced in the
UI — worth knowing before reading that as a second regression.
Added — v0.10.1-rc.3 Two APIs claiming the same address
Every readiness pack in this product judges one API against its own definition. That is the right
scope for almost everything, and it is exactly why nothing could see the one class of problem a
workspace holding many definitions is uniquely able to find: two APIs that would answer to the same
URL, or generate the same Kubernetes object.
detectEstateCollisions reports two things, both decided from facts the workspace already holds and
both unambiguous. A host-path collision is two APIs declaring the same route shape on the same
hostname — high when they claim the same method, because a gateway then has two backends and no way
to choose, and medium when they divide the methods, because that routes but leaves one path on one
host owned by two definitions. A slug collision is two APIs sharing a slug: every generated
artefact takes its name from it, so both would produce the same Service, HTTPRoute and NetworkPolicy
and the second applied would overwrite the first.
Parameter names are normalised away before comparing — /orders/{id} and /orders/{orderId} are one
route to a gateway, and comparing raw strings would miss every real case, because two teams never
pick the same parameter name.
What it deliberately does not report is the part that keeps it useful. A shared hostname on its
own is the normal arrangement — one gateway fronting many APIs — and flagging it would bury the real
finding in noise. Nor is a nested route a collision: /orders and /orders/{} are distinct routes a
gateway serves without ambiguity.
It surfaces on the estate register, above the table, computed over the whole estate rather than
the filtered view: a collision does not stop existing because the reader narrowed the list.
Added — v0.10.1-rc.3 One credential table, and an export claim it can contradict
The spec editor has recognised six credential shapes since it shipped — private keys, AWS access key
ids, JWTs, bearer credentials, credential-shaped assignments, credentials in URLs — and nothing else
in the product could see them. The workspace export redacts by header name
(authorization, proxy-authorization and the rest), which cannot reach a credential sitting in a
response body, a declared example, a mock artifact or a scenario data pack. So `includesSecrets:
false` was the user's checkbox rather than a measured fact, and a bundle could state something untrue
about itself to whoever it was shared with.
The pattern table and its placeholder guard moved into src/lib/credential-shapes.ts and both
callers read it. Writing the six patterns a second time is how two scanners start disagreeing; the
spec editor keeps its own advisory wording and the export keeps its own refusal, but the recognition
is shared and a test asserts neither file declares a pattern of its own.
The export now measures the claim. Before serialising, a redacting export scans the whole payload
— the one place every surface meets — and refuses if anything credential-shaped is in it, naming the
rule and the count and never the value, with an audit line to match. The message states both ways
out: remove the values, or export with secrets included so the bundle says what it holds. Refusing is
fail-closed; the alternative is a file that lies about itself.
Two consequences worth stating. A placeholder is still not a credential — <YOUR_API_KEY>,{{ACCESS_TOKEN}} and changeme do not block an export, because a scanner people learn to ignore is
worse than none. And the pre-replace rollback bundle now exports with secrets: it never leaves
the machine, a redacted copy could not restore the workspace it exists to restore, and it would have
been refused by this very guard precisely when a rollback matters most.
The repository's own secrets gate caught the first version of the tests, whose fixtures were literal
PEM headers and an AKIA-prefixed key. The rule here is never to teach that gate an exception, so the
fixtures are assembled at runtime instead: the scanner under test still sees the real shape, and the
repository contains no secret-shaped literal.
Added — v0.10.1-rc.3 The data-* marker inventory, generated and checked
Two thirds of this product's instrumentation is read by nothing. scripts/generate-marker-inventory.mjs
now records that in the same generated-and-checked form as every other truth here: **552 distinct
markers emitted from src/, 178 read by something outside their emit site, 374 read by nothing at
all** — 115 of those in components/swaggermaster/ alone.
This deletes none of them, on purpose. A marker is a selector somebody may need later, and removing
374 of them is a judgement about what future tests and browser proofs will want, not a cleanup. Pass
1 makes the number visible and reviewable; the review document states the question pass 2 has to
answer first — is a marker an instrumentation contract or a convenience? — and proposes a rule to
accept or reject: a marker is justified when the element it names has no stable accessible name, role
or test-visible text.
Getting the count right took three corrections, each of which had been silently inflating or
deflating it:
- Tailwind variants are not attributes.
data-[state=open]:bg-accentand the arbitrary form - Comments are not emitters.
sidebar.tsxexplainsdata-state="expanded"in prose two hundred - Nothing that describes the inventory may count as a consumer. The generated JSON lists every
sandbox/ and Companion-sandbox/ are byte-identical duplicated trees with their own marker
namespace, and Swaggermaster/swagger-ui-main/ is vendored upstream code; all three are excluded and
the inventory says so. The scan cannot see a marker used only inside a browser transcript, which is
stated in the review document and is the main reason pass 1 stops at counting.
node scripts/generate-marker-inventory.mjs --check fails on drift and is asserted bytests/marker-inventory.test.ts. There is deliberately no npm alias: adding one changespackage.json, which invalidates the reviewed Knip baseline provenance, and a shortcut is not
worth touching a reviewed artefact for.
Fixed — v0.10.1-rc.3 A timing test that failed three runs in four, and blocked commits it had nothing to do with
tests/productization-swaggermaster-analysis.test.ts asserted the product's p95 ≤ 150 ms budget as
the p95 of 20 in-process runs. Measured at clean HEAD with the dev server stopped, three consecutive
runs gave 169 ms, 357 ms and 464 ms — three failures in four — and it had already failed one
pre-commit gate for a change that could not affect it.
The cause was the method, not the code. The p95 of 20 samples is the second-worst value, which on a
shared machine measures a garbage-collection pause. The median of the same work is 50–72 ms, a
third of the budget, and quadrupling the document multiplies the time by 3.5–4.9 — clean linear
behaviour with no sign of trouble.
So the test now asserts the property it is named after, and only that: the analysis is bounded by
work (4× the document costs well under 8× the time, where quadratic would show ~16×), the result
is complete when the call returns so the budget cannot be met by deferring, and a blow-up ceiling
roughly fifteen times the measured median catches an accidental O(n²) that scaling alone would miss.
Five consecutive runs pass; tightening the ratio ceiling to 2 fails it, so the assertion is live.
The absolute budget is not dropped — it is **moved to where this repository already says such
numbers belong.** browser-performance-baselines.v1.json carries the rule "Source code cannot
self-approve a baseline; a GitHub issue or pull-request review URL is required", and its entries
still await reviewed Linux and Windows measurements. performance-baseline-review.md now records the
doc 03 §5 budget, the measured medians and ratios, and states plainly that this is an unreviewed
measurement from one un-fingerprinted machine — AWAITING_REVIEWED_MEASUREMENT, not a PASS.
Added — v0.10.1-rc.3 Approval integrity: self-approval, and approvals a later edit invalidated
MappingReview has carried requestedBy, decidedBy, decidedAt, locked and productionLock
since the review workflow shipped, and the governance pack read none of them. A review one person
both raised and approved printed exactly like a properly reviewed one, and an approved mapping edited
afterwards kept a green production lock over a change nobody signed off.
Two rules in a new approval category: gov.approval.self-approved when the requester is also the
decider, and gov.approval.stale when a mapping's updatedAt is later than its approval'sdecidedAt. Both rise from medium to high under a production lock. The category is registered, which
matters more than it sounds: the route groups findings by reading the second segment ofgov.<category>.<rule>, so an unregistered category would have put every finding in no group at all,
silently.
ReadinessContext gained mappings and mappingReviews, as required fields. It carried nine
and none of them was a mapping, so there was nothing to judge. Required rather than optional
deliberately: an optional field would let every construction site keep compiling while the rules
quietly judged nothing. The compiler named five sites in src/, two more than a grep had found.
Where the pack cannot judge, it says nothing: FieldMapping.updatedAt is optional, so a mapping with
no edit timestamp is never called stale, and an unparseable timestamp is not a guess.
Added — v0.10.1-rc.3 Replace: what it would do, and something to go back to
The Replace path asked the user to type REPLACE against a dialog showing a filename and the words
"cannot be undone" — no counts, no per-collection delta, nothing about what was about to be
discarded — then called importWorkspaceBundle with no prior inspection and no rollback point.
The dialog now reads the bundle without applying any of it and shows the real scope: one row per
collection with what is held now, what the bundle carries, and what Replace would discard. Confirm
stays disabled until that has been read, so it is no longer possible to confirm blind. Before the
first mutation a rollback bundle download is requested.
Three obstacles, each of which had to be decided rather than coded around:
exportWorkspaceBundlestamped the last-backup timestamp on every call, so an automatic safety- Import is gated on
import.commit, export ongateway.export, so a role that may restore can be - A browser download is a request. The UI says a rollback bundle will be requested and that a
Proven in the browser against a real 322 KB bundle of this workspace: 32 collection rows, the
confirm button disabled until the read completes, and the rollback sentence rendered as written.
Fixed — v0.10.1-rc.3 data-* markers that never reached the DOM
A JSX attribute name containing a hyphen bypasses TypeScript's excess-property check on a
component. <FactsheetCard data-access-admin-state="members"> therefore compiled cleanly, was
destructured into a fixed prop list that did not mention it, and never reached the browser —document.querySelectorAll("[data-access-admin-state]") returned an empty list on the page that
"sets" it. Nothing failed, and a test actively said the opposite: it asserted the marker by reading
the route's source text, which cannot tell a marker that works from one that is swallowed.
An audit of all 54 data-* attributes written on components — twelve distinct components, each
classified to its definition and every suspect refuted three ways — found ten forwarding correctly
and two dropping. The two needed different fixes, and the rule that separates them is what the
component renders.
FactsheetCard is ours and renders a root <section>, so it now declares a DataAttributes index
signature keyed ` data-${string} ` and spreads it. A caller's marker arrives; a component
without the declaration rejects the attribute at compile time instead of swallowing it.
MenubarPrimitive.Sub renders no DOM element at all — it is a state-and-context wrapper, and one
level down MenuSub renders only providers — so data-slot="menubar-sub" had nowhere to land and
was deleted. Its four sibling wrappers in the same file never carried one, so the file is now
consistent. This one was not in the original report and would not have been found by reading src/.
The guard is the durable half. tests/data-attribute-passthrough.test.tsx renders the primitive
and reads the markup, then re-runs the whole audit as an assertion: every locally-defined component
receiving a marker must spread a rest object inside its own body. It was hardened twice by its own
failures — it first reported the doc comment describing the bug as a broken call site, and it first
accepted a file-level spread as proof, which stayed green when FactsheetCard's own spread was
removed. Removing only the JSX spread now turns 7 of its 13 tests red.
Recorded and deliberately not acted on: roughly 373 of the ~536 data-* markers emitted from src/
have no consumer anywhere. That is instrumentation debt, a separate judgement, and unrelated to
markers that cannot work.
Fixed — v0.10.1-rc.3 Version compatibility: the pack could not see a schema, and said everything was fine
Building a new rule on top of the compatibility pack turned up something worse than the gap being
filled. The pack compared two captured versions by wrapping each as{ req: "<schema text>", res: { "200": "<schema text>" } } and handing that to diffSchemas, which
walks a schema's properties. The wrapper has no properties key, so the walker found nothing to
compare — in every case, for every input. Verified against the shipped function: a removed response
property, a changed request schema and a dropped status code all came back breaking: false.
The consequence is the part that matters. compatibility.breaking_transition could never fire, and
the pack's summary read "All N version transitions are backward-compatible" whatever had actually
changed. A provider checking before shipping was told their breaking change was safe.
Each schema is now compared as a schema: the request schemas against each other, and each response
status's schema against its counterpart. A status the older version declared and the newer one does
not is breaking on its own — a consumer written against that response no longer has a contract for
it — and the finding names it. An unparseable capture still says nothing, because failing to read a
schema is not evidence that it changed.
Added — v0.10.1-rc.3 Version compatibility: a breaking change the version number does not admit to
compatibility.semver.understated-bump. The pack decided whether a transition breaks consumers and
never read the version STRING a provider put on it. A breaking change shipped as a patch or a minor
bump is the most common way a consumer's pin stops meaning anything: the contract changed and the
signal that it changed did not. The rule fires only when both version strings parse as semver and
the major component did not increase, and it is deliberately quiet everywhere else — a version
string that is not semver (a date, v2, a build number) is never judged, and 0.x is exempt
because semver's own rule is that anything may change before 1.0.0. A breaking change under an
unchanged version string is the worse case and is reported at high severity: two different
contracts then answer to one number.
Added — v0.10.1-rc.3 Readiness: whether a write can be lost, and who picks the tenant
Five rules in the platform pack, all judged from the definition alone.
Lost updates had no home anywhere. The resilience pack owns idempotency keys, Retry-After and
pagination; nothing asked whether two clients can read, edit and write the same resource with the
second silently discarding the first. An API could pass all eleven packs with no lost-update
protection at all. platform.concurrency.no-precondition names a PUT or PATCH that replaces a
resource the same path also exposes for reading and declares no If-Match, noIf-Unmodified-Since and no version field in the request body. That scoping matters: a write to a
path nothing exposes for reading cannot be a read-modify-write race, and demanding a precondition
there would be this pack inventing a requirement. platform.concurrency.no-conflict-status catches
the other half — a conditional write that declares neither 409 nor 412, so the contract never says
what a caller gets when the precondition fails. The two never fire together on one endpoint.platform.delete.no-precondition is the same question for a delete, at low severity.
The inverse of a rule we already had. platform.probe.auth-gated catches a probe that wrongly
demands authentication, and nothing caught a diagnostic surface that demands none.platform.ops-surface.unauthenticated names an unauthenticated /metrics, /actuator/*,/debug/pprof/*, /heapdump or /env — matched as whole path segments, so /metricsummary is not
one. The privileged-path family (/admin, /internal, /system) is deliberately absent: OWASP
API5:2023 already owns it, and two packs on one fact is how a finding gets fixed twice and waived
once. A test asserts the three sets stay disjoint.
A scope the caller picks is a scope the caller can change. platform.tenancy.client-asserted
names an authenticated endpoint whose tenant, organization or account discriminator arrives as a
query parameter or a body property with no tenant header or path parameter beside it. A deliberate
cross-tenant administrative endpoint is a legitimate exception, and the remediation says so.
The generated HTTPRoute now also names any declared diagnostic path it is publishing. It still
routes them — a route is derived from what the definition declares, and silently dropping an
endpoint would make the file disagree with the API it describes — but which of its rules puts a
metrics endpoint on a public hostname is no longer something the reader has to notice for
themselves.
Added — v0.10.1-rc.3 Platform hand-off: the runbook the alerts always linked to, and an index that grades the placeholders
Every alert rule the hand-off writes carries runbook_url: <YOUR_RUNBOOK_URL>, and no part of this
product produced a runbook. The alert fired, the responder clicked, and there was nothing there —
the same defect as the Gateway the route pointed at and the namespaces the overlays deployed into.
docs/runbook.md is that document, derived like everything else. It names the alerts that link to
it and what each one means, the declared probe endpoints as the first thing to call, the approved
upstreams for the "is it us or them" branch, and a triage table of the error definitions **this
API's own endpoints reference** — ErrorDefinition is workspace-scoped and carries no apiId, so
the only honest link is behavior.perStatus[].errorDefinitionId, and that is the one used. Where
the definition declares nothing, the section says so rather than going quiet. No readiness findings
are embedded: a snapshot of what was wrong on generation day would keep claiming it long after the
definition changed. The alert rules now also carry apimaster_runbook_file: docs/runbook.md, so
the file that answers an alert travels with it even while the URL is still the operator's to fill.
README.md is the index, and it exists because HandoffBundle.placeholders was one flat list. An
operator could not tell <CIDR_OF_billing.example.com>, which makes a file invalid, from<YOUR_RUNBOOK_URL>, which lets everything apply and leaves an alert linking nowhere. The README
splits them into blocks apply and applies and quietly does nothing — naming, for the second
group, exactly what is silently missing — lists every file, gives the apply order, and repeats the
environment-slug table the overlays, namespaces, Applications and gateway listeners all share. An
unrecognised placeholder is graded as blocking, because assuming a stranger is harmless is the
wrong default for a file about to be applied to a cluster.
One consequence, handled rather than hidden: markdown is not YAML, and "Download all" is a single
YAML stream. The two markdown files ride in it as comment lines, so the stream still parses, the
text is still readable, and stripping the leading # recovers them.
Three defects in the generated markdown were caught by its own tests before landing: table data
rows were emitted without their leading and trailing pipes so no table rendered, underscores were
escaped inside code spans where markdown does not interpret them, and the README omitted itself
from its own file list.
Added — v0.10.1-rc.3 Access review: the report the tier matrix was already selling
access.review — "Access review report" — has been in feature-catalog.ts and priced pro intier-matrix.ts since rc.3, and no component, route or FeatureGate rendered it. The product was
selling a governance instrument it did not have. It exists now, as a fifth tab on Access control,
built only from facts the workspace already holds: the member assignments, the roles-v2 permission
matrix and the audit ledger. Per member it counts the features their role can edit, the features
they can only read, and how many of those carry an admin, security-sensitive or billing-sensitive
action. It names roles nobody holds, members whose role no longer exists at all, and the recent
access-plane changes. It exports as CSV, and the export is audited.
Two things keep it honest and are pinned by tests. Nothing is scored — a review report that
grades a workspace invites the reader to fix the grade, and the judgement is the reviewer's job.
And the ledger column is never a claim about a person: the audit ledger is bounded twice, so an
empty cell means nothing retained names that member as the actor. The bound sits beside the column
in the UI and in the CSV's own preamble, which says in as many words that an empty value is not
evidence that a member has not acted.
Fixed — v0.10.1-rc.3 Audit ledger: the cap was quietly discarding pinned entries
pushAudit capped the ledger at its most recent 500 entries with a plain slice, which took the
oldest entry whichever it was. pruneAudit deliberately keeps pinned entries past the retention
window, and the reference documentation states that pinned entries survive pruning — so the one
record a person had marked as the one that mattered was being dropped by a second limit nobody had
been told about. The cap now counts only unpinned entries: pinned ones are kept wherever they sit,
and since pinning is itself capped at 50 the ledger stays bounded. The Settings retention card now
states the entry cap beside the day counts, because retention in days was never the whole story.
A test caught the first attempt at this fix trimming from the wrong end and dropping the newest
entries; the ordering is now pinned explicitly.
Added — v0.10.1-rc.3 Platform hand-off: the references it made and did not keep
A second, adversarial re-read of the container-orchestration packs — eight readers over all sixteen
files, then a critic that checked every claim against the source — found that the bundle referred to
two objects it never produced, and silently ignored four operational facts the workspace already
holds. Both are fixed, and the hand-off is now twelve to fourteen documents depending on the
definition.
The Gateway and the namespaces. The HTTPRoute has always pointed parentRefs at<YOUR_GATEWAY> in <YOUR_GATEWAY_NAMESPACE>, and every Kustomize overlay has always deployed
into a namespace — and the folder contained neither object, so the first kubectl apply -k on a
fresh cluster failed on something nothing in the bundle described. optional/gateway.yaml now
carries a Gateway with one listener per distinct environment hostname, using the **same two
placeholders** the route uses so a single rename resolves both files, and admitting routes only
from that environment's namespace. optional/namespaces.yaml carries those namespaces with the
Pod Security Standard the generated workload demonstrably meets (enforce: restricted) and
deliberately without this API's governance labels, because a namespace may hold other teams'
workloads. Both live outside base/ and are referenced by no kustomization: most clusters already
run a shared gateway, and an overlay's namespace: field also rewrites Namespace objects among its
resources in a Kustomize-version-dependent way. The notes say all of that.
Four declared facts the hand-off had been ignoring. ApiEnvironment carries timeoutMs,retries, authMode and apiKeyHeaderName, and the generator read none of them. Now: the
HTTPRoute takes its timeouts.request from the environment's declared timeout, so the gateway
stops outliving the caller's own patience; the workload takes terminationGracePeriodSeconds from
the longest declared timeout plus headroom, with the Kubernetes default of 30s as the floor, so a
rollout stops cutting requests that were still allowed to be running; a declared retry count is
stated and not emitted, because HTTPRouteRule retries are in Gateway API's experimental channel
and writing one would claim a CRD the cluster may not have; and a declared auth mode becomes a
sentence naming what the operator must mount, never a fabricated secretKeyRef pointing at a
Secret nobody was told to create. Where a fact is absent, the note says that too, so silence is
never mistaken for a green light.
Smaller, and load-bearing. The workload gets its own ServiceAccount with the token unmounted
on both objects, so a future RoleBinding has something to bind to that is not default. The
NetworkPolicy now names the addresses that must go into an except list before any ipBlock is
widened — 169.254.169.254 is the cloud metadata endpoint, and a workload that can reach it can read
the node's credentials. And the environment-name sanitiser that resolves collisions ("Prod" and
"PROD " both want prod) moved into one shared function, because four artefacts now depend on
agreeing about it and a divergence would point a manifest at a directory or a namespace that does
not exist. A test asserts all four agree.
Added — v0.10.1-rc.3 Readiness: a routable host is not the same as a safe one
platform.handoff.insecure-host. routableHost accepts http:// and https:// alike, so a
plaintext environment produced exactly the same HTTPRoute, the same Gateway listener and the same
green platform score as a TLS one, and the only place it showed up was a note in a generated file.
It is now a high-severity finding naming the environments, with local development addresses
(localhost, 127.0.0.0/8, *.localhost) deliberately excluded. The existingplatform.handoff.no-host stays silent for these, because a plaintext URL is routable — the two
rules judge different things.
Added — v0.10.1-rc.3 Platform hand-off: three artefacts a re-read of the pack found missing
Re-reading the sixteen container-orchestration prompt files against what the hand-off actually
emits turned up three gaps. Each was checked against the rest of the product first — the
deprecation and sunset material the packs also ask for is already covered by the governance
module, so it is not repeated here — and each new artefact derives only from facts this workspace
already holds. The document order in the bundle is unchanged apart from the two new entries, and
the existing eleven files are byte-identical to before.
The admission policy that makes the baseline real (pack 05 §8). The generated workload declares a security
baseline, but a cluster only has one if something refuses what does not meet it. The hand-off now
also emits policy/workload-baseline.yaml — a Kyverno ClusterPolicy whose rules are the same
facts the workload already carries: non-root, read-only root filesystem, no privilege escalation,
no host namespaces, digest-pinned images, CPU/memory requests and a memory limit, and the
governance labels (owner, criticality, data classification) this workspace supplies. It matches
Pods by the workload's own label, in the namespaces the Kustomize overlays create.
Two things keep it honest. It ships as validationFailureAction: Audit, because the pack's own
rollout order is Audit → canary → Deny (05 L124) and a policy that starts by refusing deployments
is a policy an operator turns off. And the readiness-probe rule is written only for an API
that declares a probe — requiring one from a service whose contract has none would be this
product inventing a requirement instead of carrying one; when there is none, a note says so.
Exceptions are named as belonging in a Kyverno PolicyException (scoped, owned, ticketed,
expiring, per 05 §8) and none is generated, because there is no exception to declare.
The GitOps Applications that point at the overlays (pack 06 §8). The hand-off has written
Kustomize overlays since the first slice, and nothing pointed at them — a desired state with no
reconciler is a directory, not a deployment. gitops/applications.yaml now carries one Argo CDApplication per environment, each aimed at that environment's own overlay path, with the
repository, revision, Argo namespace and project left as named placeholders because this product
cannot know them. Sync is deliberately manual: there is no syncPolicy.automated block. The
pack's own rollout order is inventory → read-only → shadow → canary before any controller writes,
and a controller that prunes on its first run is how a desired state deletes production; the note
beside the file says exactly what to add once that order has been walked, and names the Flux
equivalent for teams that do not run Argo CD.
A disruption budget, where criticality earns one (packs 02 §7 and 05 §8). A node drain is a
voluntary disruption, and without a budget it may take every replica at once. Whether that matters
is a criticality question, and criticality is a governance fact the workspace already records, sobase/workload.yaml now carries a PodDisruptionBudget with minAvailable: 1 for a tier-1 or
tier-2 API and none at all for tier-3 or sandbox — a budget on a workload nobody depends on
only blocks the drain. Both branches are stated in the notes rather than left silent, including
the caveat that minAvailable: 1 against the two default replicas still lets a drain halve the
capacity.
Fixed — v0.10.1-rc.3 Platform hand-off: overlay collisions, a keyboard-unreachable preview, and a frozen findings snapshot
Three defects from the QA pass over the container-orchestration slices — the qa-and-checks
data-integrity, accessibility and integration-resilience lenses, applied by hand and by probing
the running page rather than by reading the source.
- Two environments that sanitise to one Kubernetes name silently collided.
Prodand - The artefact preview could not be scrolled from the keyboard. A scrollable region that is
- The hand-off readiness findings were computed from a store snapshot read during render.
Also checked and found sound: an API slug that sanitises to nothing falls back to api; an
over-long slug is cut to a 63-character DNS label and the version to a valid label value; an
empty and a bare root path collapse to one Exact / rule while a templated root becomes a
prefix; a repeated upstream host yields one egress rule; and the download-all stream stays
parseable when a document body contains a --- line.
Added — v0.10.1-rc.3 Settings: recovery readiness for the workspace itself (container-orchestration fit map, slice 5)
The pack's disaster-recovery chapter asks a cluster four questions — when was the last good
backup, does its age meet the objective, what does the backup cover, and has a restore ever been
rehearsed. APIMaster runs no cluster, but it holds a workspace whose **only copy is a file the
user exported**, so the same questions are worth asking about that file. A new card in Settings
answers all four, each from something measured:
- Age against an objective you choose. The last export time is the backup reminder's own
- What a bundle carries, and what it leaves behind. Derived, not written down: the carried
- A restore drill that never restores.
inspectWorkspaceBundleis the read-only sibling of
Fixed — v0.10.1-rc.3 Platform hand-off: a definition's own title could inject a document into a generated file
Found in the self-review of the hand-off generator, applying the qa-and-checks security and
data-integrity lenses by hand. Every generated file opens with a # comment carrying the API's
name and version, and a raw newline inside a comment ends the comment — the rest of the value
is then read as YAML. Reproduced before fixing: an API named `Evil\n---\napiVersion: v1\nkind:
Secret\n… produced a base/workload.yaml` whose second document was a Secret. This is
reachable from ordinary input, because an imported OpenAPI info.title is free text.commentSafe now collapses every line break and control character (U+2028/U+2029 included) to a
space and bounds the value, and every interpolated value in a comment passes through it — the
header's name, version, timestamp, derived-from list and placeholder list, and the NetworkPolicy's
FQDN block. The JSON dashboard was never affected; JSON.stringify escapes.
Added — v0.10.1-rc.3 Platform Hand-off: the artefacts a team hands to its cluster (container-orchestration fit map, slices 3 and 4)
/platform-handoff — a new page under Readiness & Governance. Pick an API and one of its
environments; the page derives, from nothing but the definition, environments, governance record
and approved consumer relationships the workspace already holds, the platform-side artefacts a team
hands to its cluster: a Gateway API HTTPRoute (one rule per distinct method + path match,
hostname from the environment; templated paths reduce to PathPrefix because Gateway API has no
segment wildcard), a Deployment + Service skeleton with readiness/liveness/startup probes taken
from the declared probe endpoints, labels from portfolio governance (apimaster.dev/owner,criticality, data-classification) and the 05 §8 workload baseline (non-root, read-only root
filesystem, no privilege escalation, dropped capabilities, seccomp), an OTLP exporter stanza only
when the definition declares traceparent; a NetworkPolicy whose ingress is the gateway
namespace and whose egress is cluster DNS plus one rule per upstream host — the upstreams are the
source APIs of approved field mappings into this API, the workspace's declared consumer
relationships; Kustomize base + one overlay per environment (namespace named after the
environment, hostname patched where the environment is routable); OpenSLO v1 availability and
latency SLOs whose latency threshold defaults to the largest declared latency and whose availability
objective is the operator's choice on the page (empty = placeholder, never an invented number);
PrometheusRule alerts with the 07 §10 burn-rate windows (1h/14.4 page, 6h/6 ticket) and a
per-route 5xx alert, each carrying owner, severity, for, a runbook placeholder and a dashboard
reference; a Grafana RED dashboard per route template; a GitHub Actions supply-chain job
(refuse a floating tag, CycloneDX SBOM, cosign verify) reusing the contract gate's checkout and
upload-artifact pins and leaving every third-party action as <PIN_TO_COMMIT_SHA>; and, opt-in,
the ContractRadar runner as a CronJob (watch --once, uid 1000 from the runner's Dockerfile)
badged under the self-hosted-runner-agent roadmap capability with a placeholder image, because the
runner is source-prepared and not a container release.
Every file opens with the same sentence — _generated by APIMaster from the API definition … — not
from a cluster_ — followed by what it was derived from and what to fill before use; the page lists
each document's derivations, placeholders and notes beside the preview, and the hand-off readiness
card shows the platform pack's findings for the chosen API with the blockers (no routable host →
no HTTPRoute). Equal inputs and an equal clock give equal bytes. Downloads sit under the export
right (gateway.export) and land in the audit ledger as platform.handoff.export. The capabilityplatform-handoff-artefacts is registered as local, presentation-only; the route joins the
four-way route contract (67 → 68) with en/de/fr labels; the feature catalog gainsreadiness.platform-handoff (tier pro, like the CI gate whose export right it rides on) and the
four commercial plan descriptions name the capability; the A5 menu freeze lists the route among its
specified extensions; and the platform readiness category now drills down here.
Added — v0.10.1-rc.3 Readiness: the platform hand-off pack (container-orchestration fit map, slice 2)
An eleventh readiness dimension, Platform hand-off (platform, weight 6): what a container
platform needs to know about an API before it can run it well, judged from the definition alone.
Five rules, each with the standard it points at: platform.probe.missing — no GET /healthz,/readyz, /livez, /health, /ready, /live, /status or health-tagged endpoint, so a
kubelet cannot know when the service may receive traffic (medium); platform.probe.auth-gated — a
probe that demands credentials reads as down while the service is up (low);platform.error.no-schema — 4xx/5xx responses declared with no schema at all, so no error
envelope exists for gateways and SDKs (RFC 9457 problem+json named as the shape, medium);platform.tenancy.inconsistent — a tenant/organization/account marker on some endpoints and not
others, and the definition does not say whether the rest are platform-scoped (medium);platform.slo.no-latency-declared — no declared latency anywhere, so an SLO could only be
placeholders (low); platform.handoff.no-host — no environment with a routable http(s) base URL,
so no ingress hostname and no egress target can be derived (medium without environments, high
with unroutable ones). Overlap is deliberately avoided: idempotency, pagination, rate-limit
signalling and Retry-After stay in the resilience pack, auth on mutating methods in security.
Nothing observes a cluster. Because a new dimension joins the weighted average, overall readiness
scores move for every API — the scorecard shows the dimension by name. Its drill-down stays on the
scorecard until the hand-off surface lands.
Added — v0.10.1-rc.3 Overview: the attention queue as an inbox (fit-map slice 9)
The workspace attention queue listed its six highest-priority items and linked out; a reader could
not narrow it, could not see what was overdue, and had to travel to the source screen to
acknowledge something the product already knew how to acknowledge. It now carries four filters —
All · Blockers · Unacknowledged · Overdue, each with its count over every item the engine
produced, not only the displayed digest — an overdue mark on items past their due instant (a
schedule's missed run, a waiver's expiry), and per-item commands the store can persist:
Acknowledge for findings, change records and ContractRadar changes, Resolve for findings,
and Waive in Governance → as a link for findings and breaking changes, because exact-scope
waivers are the governance panel's to grant. A button appears only where the engine attached a
mutating action for the current role and the origin has a persisting store action — a CI run, a
smoke run or a schedule has no acknowledgement the product would remember, so no button pretends
otherwise. The store still decides: a refused command surfaces its reason, it is never masked and
never pre-empted by a role check in the component.
Added — v0.10.1-rc.3 Schedules: a webhook a receiver can verify, and a contract that describes it (fit-map slice 8)
The envelope. Schedule and change-watch webhooks posted an ad-hoc JSON blob a receiver could
neither authenticate nor de-duplicate, and nothing described it. Every delivery is now oneapimaster.schedule.webhook.v1 envelope: a top-level text line (the field chat-style receivers
conventionally render), the previous summary (kept for receivers built against it), the typedevent (schedule.run or change.detected), and an integrity block — idempotencyKey,timestamp, signed, algorithm, signature.
Body-embedded HMAC. The signature is HMAC-SHA256 over the canonical JSON of event (sorted
keys, compact — the same cr-canonical-1 form every fingerprint in this product uses), keyed with a
per-schedule secret, delivered as integrity.signature = sha256=<hex>. It rides in the body,
not a header, for a reason the runner does not have: a browser-originated webhook is sent withmode: "no-cors" so receivers that answer no CORS preflight still get the POST, and that mode
silently drops custom request headers. A receiver recomputes the HMAC from event and compares.
Without a secret the envelope says signed: false, algorithm: null — never a fake signature — and
the unsigned path stays synchronous, exactly as the network probe has always observed it. The
signed bytes replace the logged payload, so the notification log never shows a body the receiver
did not get. Delivery status remains unverified under no-cors, as before.
Where the secret lives. A new storage key, awp.schedules.webhook-secrets.v1, outsideapibox.state.v5 — so it is outside the workspace bundle and outside any support screenshot of
the store. Registered in all three homes (erasure allowlist, storage baseline, storage manifest;
sensitivity credential, export excluded); the exact-shape storage pins move 49 → 50 (manifest) and 25 → 26 (localStorage baseline), and the generated inventories and release overlay are regenerated with them. Entered
in the schedule editor's Webhook signing secret field, kept only in this browser.
The contract. The payload is described as an AsyncAPI 3.0 document (both messages, the
integrity schema, the sha256= pattern) and can be registered in Event Intelligence from the
Schedules page — Register payload contract, under the eventapi.write right — where it is
versioned like every other declared contract; the store refuses an unchanged re-registration.
Fixed — Security posture: short name tokens matched inside longer words
Found while importing the observability pack's own OpenAPI document (fit-map slice 1): itsTrace.spans array was reported as a leaked primary account number because spans containspan. The same substring rule made paid and valid bind a server-controlled id, notes an
object id (no), keyword a key. Tokens of three characters or fewer now match a whole name
segment only — card_pan, cardPan, user_id, orderNo still fire; spans, spinner,paid, notes no longer do. Longer tokens keep substring matching (passwordHash still carriespassword), and reference joins the id tokens so {reference} keeps counting as an object id.
Added — v0.10.1-rc.3 Insights: a report you can hand over, and deviation flags that print their formula (fit-map slices 6 and 7)
Download report. /insights computed its series entirely in the browser and offered no way to
take them out. It now freezes exactly what the page shows — workspace health with its components
and live weights, movers, the 90-day rollups, the pass-rate and latency series for the chosen
window, weakness mix, readiness drift, coverage rings, audit anomaly flags, notification streaks
and the deviation flags — into one canonical payload, stamps it with SHA-256, prints the hash on
the PDF cover, and downloads three artefacts: a sectioned PDF, a JSON manifest wrapping the exact
hashed bytes, and a date-joined CSV of the daily series (`date, runs, passed, failed,
passsratepct, lowwsample, p50ms, p95_ms`). The ring-buffer caps the page discloses travel with
the report, so a reader knows what the series could not see. A number in the report is a number
on the page; nothing is recomputed on a different basis for print. It is the evidence bundle's
sibling — same artefact class, same plan feature row (evidence.export, whose declaration already
read "report PDFs"), so the same right (audit.view); the export is written to the audit ledger
as insights.export with its hash prefix. Notification targets pass through the slice-5 redaction
(a mailbox is configuration; the report needs the streak); audit-anomaly text is deliberately left
whole, because "actor X wrote 8 entries in 5 minutes" is the finding and, unlike the evidence
bundle, an anomaly has no separate actor column for the identity to survive in.
Deviation flags. A third card in the execution row, over the workspace's own live runs —
a mock run's duration is whatever its scenario declares, so it is evidence of nothing. Three
rules, each printed with the numbers it fired on so a reader can recompute it from the executions
list: p95(24h) ≥ 2 × p95(7d) and Δ ≥ 100 ms (latency shift, medium), `pass(24h) ≤ pass(7d) −
20 pts (pass-rate drop, high), ≥ 5 HTTP 5xx inside 15 min` (server-error burst, high). The two
window rules need five runs in both windows — the same low-sample floor the daily series already
draws. Not a model, not a score, not labelled anomaly detection.
drift.spec-impl.latency-over-declared. The drift pack judged status codes and response
fields against the contract, never time. It now compares the p95 of at least five live runs per
status against what the definition declares — the endpoint's per-status latency, then its
default, then the scenario declared for that status; nothing declared, nothing exceeded. Both
bounds must hold (p95 > 1.5 × declared and > declared + 50 ms): a ratio alone would fire
on 8 ms against 5, a slack alone on 2050 against 2000. Medium; the implementation axis reads
drifting. The remediation names the consumer's stake: timeouts are planned from the declaration.
Added — v0.10.1-rc.3 Security posture: values that look like a person, and an evidence bundle that will not repeat them (fit-map slice 5)
sec.exposure.sensitive-value-literal. The security pack judged field names only — a
definition whose example carried a real customer's e-mail under a property called contact passed
clean. It now scans what the definition and the workspace actually hold: parameter examples,
response examples, schema literals (example, examples, default, enum, const, with the
property pointer) and the bodies of captured runs (the latest fifty per endpoint, first 64 KiB
each). Four shapes, each with a structural check so a hit means "this would validate": IBAN by ISO
7064 mod 97-10; card number by Luhn and a payment-network major industry identifier (2–6) — the
rule that keeps a thirteen-digit millisecond epoch, which passes Luhn one time in ten, out of the
findings; e-mail excluding RFC 2606 / 6761 reserved domains (example.com, .test, .invalid,localhost), because those exist so documentation can carry an address that is nobody's; phone in
international + form only, since local formats are indistinguishable from identifiers. Card or
IBAN makes the finding high; e-mail or phone, medium. The status code does not excuse a value — a
404 example is as much an artefact as a 200's. The finding prints the masked form and where it
sits (a…@corp-mail.hu (email, response 200 example "created")); the value itself never enters a
finding.
Evidence bundle redaction. The same four shapes are replaced with [redacted <kind>] in the
free text the bundle ships — audit summaries and targets, schema-version labels and notes — in one
pass inside collectPayload, so the PDF, the JSON manifest and the audit CSV inherit it and the
integrity hash covers the redacted text. Actor names and entity ids are not redacted:
accountability needs who did what. counts.redactedValues states how many values were removed, so
an auditor sees that redaction happened rather than wondering what is missing.
Added — v0.10.1-rc.3 Trace Readiness: a tracing contract you can hand over, and a gateway that forwards it (fit-map slices 3 and 4)
Download tracing contract. Trace Readiness can now export what a tracing backend would need to
know about an API — derived from the definition the way Kong and CI config already are: generated
from what the author declared, labelled generated, never a claim that anything is running. One entry
per operation with the OpenTelemetry HTTP server span name (GET /orders/{id} — the route template,
never a URL), the stable http.request.method / http.route attributes, exactly the status codes the
contract declares (empty means it declares none — nothing is invented), and the propagation posture
per operation including the traceparent example verdict. It sits under the export right, not the
editing right, so a viewer who may export may take it. The observability pack this answers contained
no OpenTelemetry semantic conventions at all; the standard is supplied here.
Kong opentelemetry plugin. The gateway export emits an opentelemetry plugin stanza **only
when** the definition declares W3C traceparent on at least one endpoint — a gateway that forwarded
trace context for an API whose contract never declared it would be asserting propagation the
contract does not promise. The collector endpoint is a placeholder the operator fills in: this
product has no collector, runs none, and would be inventing a destination if it wrote one.header_type: w3c names the format the contract declared.
Added — v0.10.1-rc.3 SwaggerMaster: a Go client target (fit-map slice 10)
Code generation gains a fourth client: a dependency-free Go net/http client, beside the
TypeScript, Python and Postman targets and offered from the same Mock & export panel. One exported
method per operation over a small Client struct, path parameters escaped with url.PathEscape,
query parameters as url.Values, a JSON body encoded only where the operation declares one.
Two rules decide the output's honesty. Every import is used — Go refuses to compile an unused
one, and no Go toolchain runs in this repository's gates, so bytes and encoding/json appear only
when some operation carries a body and fmt never appears at all. And identifiers are exported
CamelCase derived from the operationId, so a definition's own names survive into the client
instead of flattening to Op1, Op2. Stated plainly: the client cannot be compile-checked here the
way the Python one is parsed; what the tests hold are the properties a compiler would refuse — an
unused import, an unbalanced brace — and the shape a reader relies on. Byte-identical for the same
clock, like every other target.
Also in this change: the previous commit left one exported type (TraceparentExampleStatus) with no
consumer outside its module. The knip gate is not part of the pre-commit profile, so it passed the
hook; it is module-private now and knip is back to baseline.
Added — v0.10.1-rc.3 Observability prompt pack: the fit map, and the first slice
A ten-prompt pack describing a hosted, multi-tenant observability SaaS (ingestion gateways,
ClickHouse/TimescaleDB/Elasticsearch/Redis/Kafka, ML anomaly services, a served REST/GraphQL API,
Helm charts, per-GB billing) was assessed against this product, one reader per prompt, every claim
checked against the prompt text and the repository. The verdict and its evidence are recorded indocs/productization/observability-pack/FIT_MAP.md.
All ten prompts are a separate product. Building the pack as specified would break four
invariants at once: local-first (no server, no database), the 11-record outbound-only Trust
manifest (the pack needs inbound listeners and dozens of new outbound flows), the GDPR posture (RUM
capture, third-party analytics, cross-region replication), and INV-009 (an ingestion engine with
anomaly detection, service maps and dashboards is a monitoring platform). The "Embeddable Mode"
in every prompt does not remove the backend — it relocates it into a host's server and database
(apiBase="/observability/api", obs_-prefixed tables, KafkaProducer(bootstrap_servers=…)); the
only embed target this product possesses is "frontend: react". The direction that does work is the
reverse: an observability product consuming what APIMaster already exports.
Where the two genuinely meet is the contract side of observability, and the map names eleven
slices there that touch none of the four invariants. The first has shipped:
Trace Readiness v2. The pack's readiness check knew only whether a header named traceparent
was declared; it never read the example value the author wrote — which is what gets pasted into
clients, mocks and docs — and it never looked at what the recorded runs actually did. Now: a declared
example that is not a W3C traceparent (00-<32 hex>-<16 hex>-<2 hex>, lowercase, neither id all
zeros, version not ff) is a high finding, because a tracer handed such a value starts a new
trace instead of joining and silently orphans the very call the header was meant to stitch; and
where the operator has fired runs with a traceparent and no response ever carried it or a
correlation id back, that is a medium finding — runtime evidence that the correlation is one-way
in practice, which no reading of the contract can see. The propagation matrix gains an example
column drawn as a tri-state: "no example declared" is an absence and is drawn as one, never as a
failure. The trace pack had never had a test; it has thirteen. The pack itself contains no W3C or
OpenTelemetry content at all — the standard is supplied by APIMaster.
Two more packs arrived beside this one; container-orchestration/ (six files) was not requested and
is not assessed. Both live outside the repository because untracked paths block every commit and a
truncated SAML example key in prompt 08 trips the secrets gate — a placeholder, not a credential,
and the gate must not be taught to ignore it.
Fixed — v0.10.1-rc.3 Compare looks inside a schema
compareOpenApi is the one engine ContractRadar and SwaggerMaster share, so the two products can
never disagree about whether a change breaks. For a schema present on both sides it compared enum
values and declared renames — and nothing else. Deleting a property, adding a property torequired and retyping a property from number to string all produced an empty change list and
the verdict "Identical contracts". Reproduced by running the shipped function before touching it.
The property-level judgement now comes from classifyRadar, the classifier ContractRadar's field
contracts already run through, by way of a projector that maps a document's schema index onto the
classifier's canonical shape. Five new change kinds — property-added, property-removed,property-required-changed, property-type-changed, property-range-changed — carry the
classifier's own verdicts: a removed property, a newly required one, a retyped one and a narrowed
numeric range break; an added optional property and a widened range do not. A $ref retargeted to
a different schema is a type change even though both sides say "object". Swagger 2.0 definitions
are inspected at the same depth.
Nothing is counted twice. Schema add/remove and enum values stay with the walkers that already
reported them; a declared rename (x-apimaster-renamed-from) is one change, not a removal plus an
addition; description edits are not reported, because this report never carried them. The projector
skips a bare $ref, an array root or a primitive rather than projecting it as an empty object and
letting the classifier report "no change" with confidence it has not earned.
The shipped sample ladder still compares as fully additive at property depth — which is what the
samples test has always pinned, and now means more than it did.
Fixed — v0.10.1-rc.3 SwaggerMaster: the reference tab shows the document the version actually serves
A published snapshot freezes a resolved artifact at publish — that is what makes it immutable
while the shared component domains it drew from keep moving. Mock, code generation and export were
moved onto that frozen artifact; the reference tab and Compare were left reading the authored bytes,
under a label that already said "frozen snapshot". So a published definition using domain://
references showed the reader this product's own scheme where a schema belongs, while its own mock
and generated client were seeing the inlined document. Same version, same instant, two documents —
and the comment in the code claimed the opposite of what the code did.
A guarded readServedSpecText now serves the frozen document to the read-only surfaces. The editor
is deliberately untouched and still reads the raw bytes: showing an author inlined components they
never typed would be a different kind of lie. Compare reads both sides as served, because two
versions differing only in the domain revision they froze against are genuinely different
contracts, and comparing authored bytes called them identical.
Added — v0.10.1-rc.3 SwaggerMaster: the workspace style guide governs the document it is for
Stage 7 of the analyzer — the style rule pack — has existed the whole time, runs only when the
caller supplies rules, and no shipping caller ever did. The guide was editable on the governance
screen, the Problems panel said style rules "arrive with the style-guide surface" long after that
surface shipped, and the guide card claimed findings and scores were visible to every member. None
of it ran.
The workbench now passes its workspace's default guide into the debounced analysis, so style
findings join the same list the Problems panel and the gutter already render, and the score appears
beside the lint counts. Editing the guide re-lints the open buffer, so the two screens cannot
disagree about one document. A workspace with no default guide degrades to exactly today's
behaviour: style: null, which is more honest than a perfect score computed over zero rules.
Deliberately not bundled: domain resolution in the editor (a fragment needs each domain's bytes
read back and hash-verified, which is an async load a per-keystroke analysis cannot take), and
writing standardizationScore onto version rows (a published row is immutable, so the number would
freeze against whatever the guide said that day, and it moves portfolio readiness and the executive
brief at once). Both are recorded, not forgotten.
Fixed — v0.10.1-rc.3 SwaggerMaster: a version string carries at most one frozen contract
A version string can hold several draft revisions while it is being worked on — r1, r2, r3 —
and saving refuses a new draft once the head is published. Publishing looked only at the row it was
handed. So r1 and r2 of 1.0.0 could both become published, each freezing a different resolved
artifact under the same string, and two consumers pinned to 1.0.0 would be reading two different
contracts. Publishing a superseded revision was possible the same way: it froze older text under a
string whose visible latest draft was a different document.
Publishing now refuses both — SM_IMMUTABLE when a sibling is already frozen, SM_CONFLICT when a
newer draft exists — and the sibling set is a required input to the transition builder rather
than an optional one, so the compiler names every call site instead of letting a future one forget.
Deprecating and retiring are untouched: that is the same contract later in its life.
Fixed — v0.10.1-rc.3 SwaggerMaster UX-6: the try-out surface is perceivable, not only visible
The response region already carried tabIndex={-1} and an aria-label — built to be focused, and
nothing ever focused it. A request that finished below the fold was silent: no announcement, and no
way to reach the answer but hunting for it. It is now focused from the send path only (never on
render, which would take the caret away from somebody still filling in a field) and announced in a
polite live region, because those serve two different readers.
A required parameter was marked by a red asterisk with no accessible text, and the input carried norequired of any kind — so a screen-reader user was never told which parameters the request needs.
The asterisk is now decoration the reader cannot see (aria-hidden), and aria-required plus a
spoken "(required)" carry the fact. The documentation filter had a placeholder and no name: a
placeholder is missing from some assistive technology and disappears the moment the field has a
value, leaving an unlabelled box.
Added — v0.10.1-rc.3 SwaggerMaster UX-8: the registry card opens what it names
The version chips on a definition card were the only place in the registry that named a specific
snapshot, and the only place that could not open one. UX-3 put tab and version in the URL, which
made each chip one navigate call away from being a door — they now open that snapshot's
documentation, with an accessible name that says so out of context. The card also gains a reader's
entrance beside the author's: Open workbench lands on the overview, View docs on the
reference.
Added — v0.10.1-rc.3 SwaggerMaster UX-5: the definition itself can leave the workspace
The swaggermaster.export feature has been called "Definition export and cURL copy" since it
was declared, and only the cURL copy existed. A reader could take a generated Python client out of
the workspace but not the OpenAPI document those clients were generated from — a strange hole in a
definition workspace, and the tab has been called "Mock & export" the whole time.
Five shapes, and the bar rises with the claim each one makes. Source hands back the stored text
byte for byte, comments and key order intact, and refuses nothing but a missing artifact —
withholding somebody's own text because it no longer parses would be hostile. JSON and YAML
re-serialise the parsed document, so they need it to parse, and they say on the screen that
comments and key order are not preserved rather than letting it look like a round trip.
Resolved JSON and Resolved YAML claim a document any standard tool can read, so they are
held to the same bar as code generation: a supported dialect, zero structural errors, and everydomain:// reference inlined — that scheme is APIMaster's own and no OpenAPI tool resolves it.
It reuses what was already proven rather than building beside it: the same permission the feature
was declared with, the frozen resolved artifact publish already writes, the serialisers the catalog
export already uses, and the filename sanitiser code generation already trusts. A refusal reaches
the reader with its code and detail; both outcomes reach the audit trail.
Fixed — v0.10.1-rc.3 SwaggerMaster: one workspace's cleanup could delete another's definitions
The artifact store is one database for the whole browser. The health sweep built its expected-key
set from the current workspace only, so every other workspace's specification text came back as
an orphan candidate — with a cleanup button beside it. The guard that should have caught this had
three faults at once: it tested row.id === ref.id twice, it only looked at rows in the caller's
own workspace, and for domains it compared an artifact id of <id>.<revision> against a bare
domain id, so it never matched at all. Switching workspace, sweeping and clicking cleanup would
delete the neighbour's only copy; a live domain fragment that published versions depend on was
deletable the same way. The comment above that guard read "never delete unknown/foreign keys" —
the intent was right and the code did not reach it.
Both the sweep and the guard now derive keys from one shared function, using the builders the
artifacts were written with, across every workspace.
Fixed — v0.10.1-rc.3 an erasure receipt that had not checked the erasure
"Erase all application data" fired the SwaggerMaster artifact-database delete and discarded the
promise, then issued a signed PDF deletion receipt on the strength of the Web Storage result alone.
That promise resolves false when another tab still holds the database open — an ordinary thing
for a person with two tabs to hit. The delete is now awaited and its answer believed: a failure
takes the existing incomplete-erase path, names the likely cause, and issues no receipt.
Two overclaims went with it. The storage manifest recorded the three artifact kinds asincluded-via-bundle while workspace bundles have never carried specification text, and the health
panel told a person whose snapshot failed hash verification to "restore from a bundle export" —
advice pointing at a path that does not exist. The manifest now says what is true, and the panel
names the recovery routes that work, including the export added above.
Fixed — v0.10.1-rc.3 positioning: the four surfaces the repositioning had not reached
An audit of the repositioning against its own brief found four places where the old, narrow framing
survived. None of them were the Overview hero, which is where everyone had been looking.
The compact hero was the worst of them, and three independent reviews found it separately.
Collapsing the hero collapsed the positioning with it: the returning-user bar showed the brand name,
one ContractRadar button, and nothing that said what the product is — the exact equation this
programme removed from the expanded view, restored through the back door for the users who come
back most often. The choice is persisted, so it stayed their first impression. The bar now carries a
product line of its own.
The site-wide meta description was half-fixed. The public title was broadened and the
description beside it was not, leaving the sentence search engines and link previews quote —
"Design, mock, map, test and govern APIs in one workbench" — describing the lifecycle of one
artifact type. It now names event contracts, scenarios and dependencies too, and a test holds it to
the same breadth as the hero, because the positioning invariant names it a product-level surface and
nothing had been checking it.
The Design & Integration navigation group described one of the four domains it holds. Its line
read "Write and edit API definitions, then send real requests against them" while the group carries
eighteen entries including SwaggerMaster, Event Intelligence, the estate catalogue and scenarios —
and it is collapsed on first load, so that line is all a reader gets. Rewritten in English, German
and French.
Switching hero mode dropped keyboard focus on the floor. Each toggle unmounts itself, so the
browser reset focus to <body> and a keyboard user was silently returned to the top of the
document. Focus now moves to the heading of the mode they landed in, with a visible ring.
Two claims were also narrowed to what is true. The domain grid said "a change in one is visible in
the others" — which is not true of the event domain the same grid introduces, since AsyncAPI
contracts are registered in the workspace but are not joined to the API dependency graph. It now
claims what the five domains genuinely share: one estate, one role model, one audit trail. And the
event card names Kafka, AMQP and MQTT, which invites a reader to assume broker connectivity, so it
now carries the boundary the README and the shipped docs already state — the declared contract, not
a broker connection.
The hero's visible actions were three for three Contract Intelligence or commercial. The one
workbench-level action was defined beside them and rendered nowhere a sighted reader could reach —
it existed only as the target of the screen-reader skip link — and bringing a definition in, the
first thing a new reader wants to do, had no entry point in this layer at all. The hero now offers
Open full APIMaster Workbench and Import an API definition alongside the demo and the
version compare. They take the slot the pilot link held, which was a verbatim duplicate of the one
the pilot section renders below at primary weight with a heading and an explanation around it;
keeping both put four equally weighted secondary actions in one row and said the same thing twice.
Each of the three areas now carries exactly one primary action, and a test holds that.
The sidebar's capability disclosure was dead code. Its condition readcapabilityIds.length === 0 — the one case in which that badge renders nothing by design, because
an entry declaring no capability makes no claim. So the branch never displayed anything for any of
the 67 entries, and the six that actually hold were precisely the ones it excluded: /schedules and/tmf showed no capability indication at all, and the other four were covered only incidentally by
the execution-mode chip. Six holds now show, 61 entries stay quiet, and "a held capability is shown
as held, everywhere" is finally true of the primary navigation.
Five links in the shipped help centre pointed at documents that have never existed. The
Documentation Center treats any .md href as an internal doc link, resolves the click through the
manifest, gets nothing back, and does nothing — a user clicking got silence. ./DATA_FLOW.md (four
places) and ./TECHNICAL.md now point at the documents that exist. Four stale repo-path citations
were corrected in the same pass, and the security contact link now uses the path it is actually
served from rather than a relative path that resolved to nowhere inside the app.
Measured rather than asserted: every text element of the reworked Overview was checked against WCAG
AA with OKLCH and OKLAB converted to sRGB and alpha-blended against the real backdrop — 24 elements
in dark, 25 in light, zero failures, lowest 5.47:1 against a 4.5 requirement. Long-content overflow
and layout were checked at 375, 768, 1200 and 1440 px with a 66-character unbreakable word in every
card: no element overflowed and the page never scrolled horizontally.
Changed — v0.10.1-rc.3 product positioning: APIMaster is the workbench, Contract Intelligence is one domain
The Overview led with "Catch breaking API changes before they break consumers." — a true, sharp
sentence about one capability domain, standing as the definition of the whole product. Under it,
the six-step detect → evidence flow was also Contract Intelligence, so the first screen argued that
APIMaster is a contract-diff tool with accessories. The navigation had said otherwise for a long
time (Contract Intelligence is one of seven peer groups) and src/config/brand.ts already recorded
the decision in a comment — the hero simply never caught up. Commercially the gap mattered: a
product positioned as a breaking-change detector is compared against diff tools, and priced like one.
The hero is now product-level, and a capability-domain grid sits directly beneath it naming the
five domains the workbench is made of — integration landscape, scenario design, definition workspace
(SwaggerMaster), contract intelligence, and event & async contracts (Event Intelligence) — each
linking to a route that exists today. The breaking-change sentence is preserved, on the Contract
Intelligence card where it is accurate, and the six-step flow keeps its six steps under a heading
that names it as one domain's detail.
Nothing claims more than the app does. Every card's badge is resolved from the navigation registry
through the existing throw-on-unknown mechanism, so a card cannot assert availability the registry
has not declared; a route that declares nothing shows no badge at all. Where the two truth layers
disagree the more restrictive one wins: /contract-radar reads local in the V1 vocabulary but
the V2 disclosure holds it at "Needs Agent", and the card shows the hold. The older Overview
surfaces still prefer the softer V1 status — noted, not silently changed here.
The same narrowing was corrected in the site-wide public title, public/llms.txt (what AI crawlers
read), docs/architecture/ARCHITECTURE.md, and the curated docs/context/knowledge.v1.jsonproject.purpose that every agent reads first. The README gains an explicit product definition, a
capability-domain table, a "Contract Intelligence is a domain, not the product" section, a **product
language guardrail** for future contributors, a Current scope and direction section stating the
boundaries plainly (event contracts are definition-side — no broker connection; there is no hosted
multi-tenant service), and the seven major modules its route table had been missing, SwaggerMaster
and Event Intelligence among them.
The rule is now an invariant (INV-009) and a test: promoting domain outcome copy back into the
hero fails the build, with the reason.
Deliberately not changed: the social card artwork (public/apimaster-social-card.svg/.png) still
reads "ContractRadar by APIMaster", which is what every link preview shows. The .png thatog:image actually serves has no generator in the repo and no rasterizer available, so editing only
the SVG would leave the two disagreeing — worse than a consistent, narrow card. It needs a
deliberate design pass.
Added — v0.10.1-rc.3 SwaggerMaster UX-1..UX-4: the definition workbench becomes legible
Four changes against the finding that SwaggerMaster is feature-rich and presentation-poor, made
without removing anything. UX-1: the empty registry was a dead end; it is now a guided launcher
offering three ways in and a one-click restore of the sample definitions, with a read-only message
for a viewer who cannot create. UX-2: signal colours that failed contrast in light mode are
replaced by named utilities that pick up the readable override, and the operation list in the docs
rail is colour-coded by method — the badge always carries the method text, so colour is never the
only signal. UX-3: tab, version and compare moved out of component state and into the
router, so a pasted link reopens the exact view, the back button steps through views, a compare
selection survives a reload, and one Copy link in the header serves all nine tabs by copying
the real address rather than rebuilding a string that can drift from it. UX-4: the catalog gets
a search box, a recent/name sort and a live "M of N definitions" count, with a distinct no-matches
state.
The redesign plan behind these (Swaggermaster/UX_00..UX_08) records the remaining workstreams —
spec export, try-out accessibility, the unified design studio and the four-lane layout — and the
rule that the structural ones ship behind a shared layout flag so the nine-tab workbench stays
intact and switchable.
Added — v0.10.1-rc.3 Event Intelligence WP-3.1: the AsyncAPI diff and a Compare tab
Two event snapshots can now be compared. The comparison runs over the normalized model, not the
documents, so a 2.x and a 3.x description of the same contract compare as equal and a real change is
caught whichever dialect each side is written in. "Breaking" is judged from the consumer's side:
what disappears, moves, or changes shape under an application that is already listening — a removed
channel, operation, message or schema, a moved channel address, a flipped operation direction, a
changed content type or payload, a removed transport binding, a changed server protocol. Additions
never break; a sensitivity change is reported but does not. The report mirrors the OpenAPI diff's
shape, so both kinds of contract change can be presented and routed the same way.
Fixed — v0.10.1-rc.3 Commerce Estate: an event version ladder with something on it
The blueprint saved its second AsyncAPI snapshot from the same asset it created the project from, so
both rungs carried identical bytes. The ladder existed but the diff was empty, the compatibility
verdict had nothing to weigh and publishing moved a document that had never changed. Each event
definition now gets a real next document — orders.amended.v1 at 2.4.0 and payments.refunded.v1
at 2.2.0 — each purely additive, so a subscriber on the previous rung is untouched. Version labels
now match the documents' own info.version. No bundled blueprint may save a version whose bytes
equal the asset its project was created from, and the event rungs are held to zero breaking and zero
removed changes: a blueprint whose happy-path demo shipped a breaking change would teach the wrong
lesson.
Added — v0.10.1-rc.3 Workspace Blueprints WP-B3.1: the apply wizard
Provisioning is reachable from the product rather than from a test. Access → Tenant carries a
wizard that lists the catalogue from an eager summary registry (no specification text is pulled into
the entry chunk to draw a picker), takes a workspace name and mode, and runs the guarded command.
The tier is shown as the organisation's own and is never offered as a choice. Every refusal the
command can return has a human answer, an unfamiliar code is still shown rather than swallowed, and
the receipt is reported in full — including the parts that are not good news: steps run, duration,
verified counts, an incomplete run, a pending cleanup, a truncated audit, and any declared screen
that stayed empty with the reason why. The run is announced to assistive technology, progress is a
progressbar, and focus follows the step.
Added — v0.10.1-rc.3 Workspace Blueprints WP-B2b: the Commerce Estate blueprint
One command fills a workspace with Northwind Commerce: eight commerce APIs that reference each
other (orders, payments, catalogue, shipping, returns, customers, notifications, pricing — 50
endpoints and 88 schemas), filed under four business domains, each with a Mock/Dev/Staging ladder,
staffed by twelve colleagues across every role, designed from three OpenAPI definitions with a real
published version ladder and four shared component fragments, and exchanging events over Kafka,
AMQP and MQTT in both AsyncAPI dialects. The tier ladder shrinks the estate rather than changing
it, and every declared screen is alive on all four tiers — measured at 12 commands in 0.2 s on solo
and 66 commands in 1.6 s on enterprise. A blueprint can now also fill an existing workspace,
but only an empty one and only when the caller confirms it, and cleanupBlueprint removes
exactly what a blueprint put there — holding, and saying so, when something has been changed since.
A manifest may no longer advertise an estate its steps do not build.
Deliberately absent from the estate: runs, radar findings, change records, mocks, snapshots and
governance reviews. Those would either record work nobody did or could not be undone after a failed
run, so they are named in the change record with the work package that has to earn them first.
Fixed — v0.10.1-rc.3 Workspace Blueprints WP-B2a: engine corrections
Two reviews ran against the WP-B1 engine and everything they confirmed is fixed. A run whose
workspace changed under it now compensates in the run's own workspace instead of removing
nothing and calling it "cleanup pending"; aborting a run whose workspace cannot be reached
refuses and keeps the record, instead of deleting the only trace of what was created; and a
receipt no longer offers a resume it has already made impossible. A screen counts as alive only
when every collection it needs has rows — a catalog page with APIs but no endpoints is
reported as partial, not as working. A plan limit (the tenant's API cap) now yields a smaller
estate with the reason named, instead of aborting and rolling back work the plan was entitled to
create. Blueprint environments no longer describe themselves as Companion Sandbox targets, a
blueprint cannot seed an owner, and a business domain can finally be removed, so an aborted run
leaves the workspace as it found it. Manifests are checked rather than trusted: only IANA-reserved
hosts, no credential-shaped asset text, and a bounded expanded plan.
Added — v0.10.1-rc.3 Workspace Blueprints WP-B1: the provisioning engine
A workspace can be provisioned from a blueprint in one command. A blueprint is a **sequence of
store commands**, not a state dump: every step names one of six allowlisted commands and carries
its input with symbolic references, so the engine creates nothing a person could not create
through the same guarded, audited path. The plan is deterministic and hashed; the run is written
ahead to a tab-scoped journal, so an interrupted run can be resumed — but only while the plan is
provably the same one — or aborted with reverse compensation. The receipt is **verified against
the store's own counts** before it is believed, and everything compensation could not remove is
named rather than left behind. The tier is the tenant's own and only shrinks the estate; one
active demo per organisation; nothing reaches the network while provisioning. One bundled
blueprint ships with the engine (api-estate-starter: two APIs with their endpoints, schemas,
environments and members, one OpenAPI definition and one AsyncAPI definition, every host
synthetic). A new architecture rule keeps the engine pure: src/lib/blueprints/** may not import
the domain.
Added — v0.10.1-rc.3 Event Intelligence WP-2: the event definition workbench
Every registered AsyncAPI definition now opens in a workbench: Docs rendered from the
normalized model with user-centred verbs (Publishes = the application sends, Consumes = it
receives — the 2.x inversion handled once, never on screen), Editor on the shared CodeMirror
surface with the validation findings (rule, pointer, remediation) beside it, Versions with
publish, deprecate and retire — publish freezes the snapshot and refuses a contract that still has
validation errors; the default version hands over exactly as in SwaggerMaster — and Export
of the byte-exact raw document and the normalized model. The registry gained Open workbench,
navigate-after-create and Import from URL under the platform's outbound target policy
(loopback, private ranges, cloud metadata and credentialed URLs are refused before any request;
redirects are not followed). Two additive actions (eventapi.publish, eventexport.download);
editors and operators export, owners and admins publish.
Added — v0.10.1-rc.3 Workspace Blueprints WP-B0: foundation (actions, feature, provenance home, engine commands)
The first slice of the Workspace Blueprints programme — few-click, tier-sized, fully populated
demo/template workspaces, planned from the Effectime study. Nothing provisions a workspace yet;
this slice gives the engine proven seams: two additive actions owned by one preview feature bound
to the existing Get Started route (no new route), a validated provenance record per provisioned
workspace with a one-active-demo-per-tenant marker (both keys with their three storage homes), and
three guarded, audited commands the engine will stand on — recording provenance, upserting
business domains, and removing an imported API only while nothing references it. Provisioned
workspaces show a blueprint badge in Organizations & workspaces.
Added — v0.10.1-rc.3 Event Intelligence WP-1: AsyncAPI core library (normalize, validate, hostile fixtures)
The registry now understands what it imports. Every AsyncAPI document is normalized into one
canonical model (3.x semantics; the 2.x publish/subscribe inversion is handled in exactly one
place and rendered user-centred), gets a dialect-independent fingerprint, and is validated below
the governance layer: core structure, reference policy (remote and file references are refused,
never fetched), protocol bindings (Kafka/AMQP/MQTT shapes), schema references, a secret advisory,
and the two metadata profiles the registry reads (x-sensitivity, CloudEvents). The import
preview shows error/warning counts and the version row keeps them. Fourteen synthetic fixtures
join the pack, including hostile ones — and one of them found a real hole: a YAML alias bomb
passed the node limit because only unique nodes were counted. The shared limiter now budgets the
expanded size, which protects OpenAPI import as well.
Added — v0.10.1-rc.3 Event Intelligence E0: AsyncAPI registry foundation (WP-0)
The first slice of the Event Intelligence programme (docs/async-event-platform): a new
Event Intelligence screen next to SwaggerMaster where an AsyncAPI 2.x or 3.x definition can
be created from a blank 3.0 template or imported, versioned as immutable draft revisions, read
back hash-verified and deleted — every command guarded by the new eventapi.read /eventapi.write actions, audited, and scoped to the workspace. The definition text lives in the
shared artifact store under its own awp.eventintel.specs.v1. family; the registry rows live in
one new eventIntelligence envelope of the persisted state, backfilled for every returning
profile. Owners, admins and editors author; operators and viewers read. Nothing on the screen
implies a broker connection: what is registered here is the declared contract, and discovered
broker topology arrives later as its own, separately labelled layer. Both features ship atpreview maturity.
Added — v0.10.1-rc.3 account menu and local sign in / sign out (A8)
The visible account layer the B2B flow was missing: a topbar account avatar menu (who am I,
which role, which organization; jump to Members & roles or the Get Started wizard; Sign out) and
a persisted local session — signed out, the tool shell shows a Sign-in screen where you pick
one of this browser's local profiles (identity, role and workspace switch together) or register
a new organization. Registration stays reachable while signed out; public pages are unaffected;
existing users remain signed in. No passwords and nothing is sent anywhere — hosted credentials
arrive with the hosted phase, and the UI says so.
Added — v0.10.1-rc.3 local multitenancy: registration, organizations, workspaces, colleagues (A7)
The B2B backbone at the local plane (owner mandate, 2026-08-25): the Get Started wizard
(/onboarding) registers your profile, creates your organization and its first workspace, makes
you its Owner and lets you add colleagues — all stored honestly in this browser (hosted sign-in
and e-mail invitations arrive with the hosted phase, and the copy says so). The topbar workspace
switcher is now LIVE: switch between the organization's workspaces and between organizations, or
jump to creating a new workspace. The Access screen's tenant tab gained the Organizations &
workspaces manager (switch/rename/create/archive with lockout guards), and its Members tab is
scoped to the current workspace. Existing profiles are upgraded in place: the implicit
organization is derived from the existing tenant envelope and every workspace is assigned to it —
nothing is lost, nothing changes until you create a second organization. A returning-user bug
class was caught live and fixed: growing the feature catalog used to leave persisted role rows
without cells for the new features (fail-closed locking them); the roles backfill now reconciles
missing cells from the seed while preserving admin edits.
Added — v0.10.1-rc.3 access programme Phase A complete (A4–A6: /access, menu binding, tier preview)
The user-facing half of the multitenancy/RBAC programme (commits after b7ad70a, 2026-08-25),
completing Phase A on top of the A0–A3 foundation below. The frozen truth-table AND the menu of
the four legacy roles at the enterprise default remain bit-identical — proven, not assumed.
/access— Access Control home (route 56, "Access Control" under Governance): the- Published matrix edits are authoritative everywhere: the store guards, the workspace
- One menu-visibility pipeline drives the sidebar, mobile drawer, command palette, All-Tools
- Tier preview instrument (browser-only, session storage, export-excluded, never authority):
- Measured gates: full suite 749 tests green on the candidate; tsc clean; knip at baseline;
Added — v0.10.1-rc.3 multitenancy & RBAC access programme foundation (A0–A3)
Foundation iterations of the owner-approved multitenancy/RBAC programme (authority:APIMaster_Multitenancy_RBAC_Plans_Completed_2026-08-24/; commits 9c736f3 → 9054dba,
2026-08-24 → 2026-08-25). No user-visible behaviour change by proof, not by hope: the legacy
4-role × 21-action truth table was frozen BEFORE the work and both new projections reproduce it
bit-identically.
- A0 authority freeze: the measured legacy truth exported as machine-readable authorities
- A1 feature catalog: 65 features FK-typed to the capability manifest (27 capabilities) and
- A2 tenant envelope + entitlement resolver: implicit single tenant persisted inside
- A3 roles v2 +
can()facade swap (highest-blast-radius iteration, landed alone): five - Measured gates per iteration: suites grew 687 → 724 tests (14 439 assertions, 122 files, all
Fixed — v0.10.1-rc.3 browser-proof engineering wave, accessibility and residual launch mandates
Twenty-two commits on 2026-08-24 (6199bb5 → 48b9b19) took the browser release-proof from its
first-ever clean full execution to the point where every defect it can surface in code is closed,
and closed the last two code-closable mandates of the production-launch plan.
- Fixed the returning-user hydration mismatch (React #418): the workspace store never hydrates
- Fixed thirty serious/critical accessibility violations found across eight screens: muted-text
- Fixed the error boundary's console noise for a by-design state: store-backed entity routes now
- Fixed a long-standing syntax error in the proof harness's sidebar-search performance sampler that
- Added redacted, session-only diagnostics for support intake: application error boundaries record
- Added a machine-readable app/runner/schema compatibility matrix
Measured on 48b9b19: root tests 687 pass / 0 fail (12,626 assertions, 119 files); tsc --noEmit
exit 0 for root, CodeGraph tools and the runner; eslint . reports zero problems; Knip 690 equals
its reviewed baseline with zero additions; architecture baseline zero violations; CodeGraph
byte-stable at 4,297 nodes and 13,183 edges with 31 explicit unknowns; generated inventories,
route/feature baseline (55/55), workstream status, documentation context and public-brand checks
all PASS; production build green for client and SSR; live post-deploy verification PASS with 46
checks against this exact SHA; ContractRadar runner 41 pass / 0 fail / 3 explicitENVIRONMENT-HOLD skips.
Unchanged holds, stated honestly: the canonical-origin check still FAILS live because the apex
domain redirects to www instead of serving — the Vercel primary-domain flip is an owner action.
The browser proof itself still ends FAIL because 270 visual screenshot candidates and one browser
performance candidate await owner review before they can become baselines; source may not
self-approve them. Every supported-environment row therefore remains declared-untested, and the
TMF public export remains owner-held, so its golden-journey suite records a by-design HOLD.
Detailed outcome and continuation controls:versioning/260824_01_v0.10.1-rc.3_browser-proof-and-launch-mandates.md andversioning/260824_01_v0.10.1-rc.3_ai-dev-prompt.md.
Added — v0.10.1-rc.3 development-efficiency and deterministic CodeGraph controls
- Added exact-version, baseline-governed Knip and dependency-cruiser checks. The root Knip snapshot
- Added a repository-owned TypeScript Compiler API CodeGraph for root application source, the
- Added bounded structured development gates, an active-source formatting boundary, a source-only
- Kept the store/API Explorer decomposition out of this mixed tooling round because its plan
CodeGraph phases 1, 2 and 4 and CLI retrieval are source-prepared. The orphan baseline still
requires owner review; native MCP registration and a measured with/without-graph AI ROI comparison
remain HOLD/NOT RUN. The Windows runner result is 41 PASS plus three explicitENVIRONMENT-HOLD cases; clean Linux 44/44 provider proof is NOT RUN.
This documentation slice was prepared before the final repository-wide sequential verification.
Root tests, typecheck, lint, build, deterministic drift checks and candidate freeze remain
PENDING FINAL ROOT RECEIPT; this entry does not claim their PASS. Existing provider, hosted
browser, history-secret, legal, TMF, signing and release-owner holds remain unchanged.
Detailed outcome and continuation controls:versioning/260816_06_v0.10.1-rc.3_dev-efficiency-codegraph.md andversioning/260816_06_v0.10.1-rc.3_ai-dev-prompt.md.
Added — v0.10.1-rc.3 production-readiness source closure
- Implemented the dated production-readiness programme as a fail-closed source hardening round
- Replaced implicit or bypassable production paths with an exact-candidate release proof and a
- Pinned Bun
1.2.22and Node22.18.0, moved Vite/Nitro configuration under first-party control, - Added a never-throw, content-minimising error boundary; request-scoped CSP nonces; enforced HSTS,
- Hardened both sandbox trees in lockstep: cryptographically random digest-at-rest credentials,
- Added explicit Ed25519 execution-attestation policy and V2 evidence import. Only a verified issuer,
- Added support, incident, uptime, recovery, security-disclosure, privacy/DPA/legal draft and TMF
Local source gates for the affected slices are green, including the exact Bun 1.2.22 focused
readiness matrix (43/43, 346 expectations), the current-tree credential scan and the pinned
Vercel build/output contract. The final repository-wide sequential test count is recorded inversioning/260816_05_v0.10.1-rc.3_production-readiness-outcome.md after the commit-candidate freeze.
Production promotion remains HOLD. Full Git history still contains one known historical sandbox
credential fingerprint and requires provider-side revoke/rotate plus owner-approved remediation.
GitHub provider billing blocks candidate jobs; main is not protected and no protectedproduction-release environment exists. Vercel is currently configured for Node 24 and redirects
the canonical apex to www; the live deployment therefore does not prove the rc.3 source policy.
Trusted positive CI attestation, exact-clean browser/accessibility/visual evidence, vulnerability
audit, hosted sandbox proof, legal/TMF/runner-distribution/signing approval and release-owner
promotion remain HOLD/NOT RUN.
Detailed outcome and continuation controls:versioning/260816_05_v0.10.1-rc.3_production-readiness-outcome.md andversioning/260816_05_v0.10.1-rc.3_ai-dev-prompt.md.
Fixed — exact-SHA Settings hydration stabilization and production receipt
- Stabilized
/settingsserver/client hydration. Gateway YAML is no longer generated with a fresh - Made snapshot timestamps locale/time-zone independent and resolved plan, stored licence and
- Added Settings hydration contracts for render-clock isolation, input-identity invalidation,
Final local evidence after the repair: **518/518 root tests passed with 11,096 expectations across
89 files**; typecheck and repository-wide ESLint passed; the production build completed with 3,674
client, 532 SSR and 3,336 Nitro modules. A real local browser run exercised Trial, a stored
evaluation licence and an expired stored licence with zero console errors; this is interactive
evidence, not an automated hydrateRoot unit test.
Operational receipt: implementation commit 717365ee2f4d7cabb5559b65efea5d38e2608d9e was followed by
hydration repair commit b24f28c08c34f1b61fbf1eabab340c126899bbf5, both pushed directly toorigin/main. The final source candidate deployed from Git to Vercel project apimaster asdpl_AY26nbTVVRp1A6HUP5xt1ST6utbN / bld_cjqmi49er, targeted Production, reached READY and
received aliases including https://apimaster.dev and https://www.apimaster.dev.https://apimaster.dev/settings returned a path-preserving 308 tohttps://www.apimaster.dev/settings; the exact-SHA post-deploy verifier passed all six checks
against that effective serving origin. Fresh live browser tabs showed the exact SHA on Settings,
correct gateway copy, both ContractRadar entry markers and zero console errors.
GitHub Actions run 31954404344, bound to b24f, assigned no runner and executed zero steps on either
platform; GitHub reported account payment/spending-limit admission. Provider candidate proof,
trusted positive CI authority, the clean full browser/visual matrix, legal, signing, human review
and global promotion therefore remain HOLD.
Added — next-round productization closure with fail-closed execution truth
- Enabled the intended APIMaster productization presentation in the Vercel Production environment
- Connected imported release-evidence decisions to workspace Attention. Blocked/warning receipts
- Closed three independently reviewed false-clear paths: schema drift behind identical
- Added a typed store-to-workspace dependency-graph projection with explicit provenance,
- Added a bounded exact-origin/exact-SHA post-deploy verifier for root identity, ContractRadar
- Added deterministic P01–P13 workstream verification and generated status: 13 workstreams, 52
- Added the full 16-fixture migration matrix through real readers, including supported round-trip,
Measured local outcome: 514/514 root tests passed with 11,081 expectations across 88 files;
root and runner typecheck, repository-wide ESLint (28.0 seconds), the 3,673-client / 531-SSR /
3,336-Nitro-module production build, public-brand verification, 37-entry storage verification,
TMF integrity and the 13/52/37/43 workstream structural gate passed. The final R2 matrix passed
65/65 with 363 expectations and no open reviewer P1/P2. The Windows runner completed 40/43 with
the same three symlink-security EPERM environment holds; an isolated fresh Linux run passed 43/43
on immutable imagenode@sha256:752ea8a2f758c34002a0461bd9f1cee4f9a3c36d48494586f60ffce1fc708e0e.
The R4.7 dirty browser diagnostic reached HTTP readiness but timed out after 2,700,035 ms without a
consumer result, PNG or visual candidate. Teardown passed and the visual-baseline SHA remained
unchanged; this is TIMEOUT / HOLD, not browser proof. Exact-SHA hosted verification, trusted
positive CI execution authority, GitHub/GitLab provider admission, visual/human review,
legal/trademark/redistribution, signing/key custody and overall promotion remain HOLD / NOT RUN.
Detailed outcome and continuation controls:versioning/260816_03_v0.10.1-rc.2_next-round-completion.md andversioning/260816_03_v0.10.1-rc.2_ai-dev-prompt.md.
Added — Pack2 wiring-gap closure and fail-closed release truth
- Wired the canonical Attention engine into the existing-workspace Overview and reconciled the
- Added fail-closed capability disclosure across navigation, the sidebar, All Tools and Settings:
- Added guided GitHub/GitLab CI onboarding with deterministic workflow/config exports. GitHub uses
- Added a versioned semantic-hash dependency manifest, workspace mapping projection and headless
- Added complete V2 release-evidence JSON review on the CI Gate. Shape, checksums, manifest, policy,
- Added canonical governance lifecycle persistence and UI actions with exact-scope waivers,
- Unified workspace, pilot, canonical-governance and runner verdicts on the shared release-decision
- Added the TMF622-to-TMF641 release-assurance panel with six independent layer counts,
- Made release-proof source reachable on pushes to
main; no hosted GitHub/Vercel run is inferred.
Measured local outcome: 473/473 root tests passed with 10,797 assertions; root typecheck, the
3,647-client / 529-SSR-module production build, public-brand verification, 37-entry storage
verification and TMF verification passed. Runner typecheck and the focused config/V2 command suite
passed (11/11). The runner full
suite and packaging suite were NOT RUN in this round; earlier evidence retains three Windows
symlink EPERM environmental HOLD cases, and prepack was not allowed to overwrite protected
pre-existing dist files. In the explicit 98-file intended set, 96 non-generated files passed
Prettier, the two generated route/feature baselines passed their deterministic generator check, and
scoped ESLint passed with zero errors/warnings (runner paths are governed separately). Repository-wide eslint . is
NOT COMPLETED and is not represented as a global lint pass. Browser, hosted-provider, legal,
signing and production gates remain HOLD / NOT RUN.
Detailed outcome and continuation controls:versioning/260816_02_v0.10.1-rc.2_pack2-wiring-gap-closure.md andversioning/260816_02_v0.10.1-rc.2_ai-dev-prompt.md.
Post-freeze integration receipt: implementation commitbe488988ef1f381f2b8c9eff6fb024b08b062b4a was pushed directly to origin/main and deployed from
that exact Git source to Vercel project apimaster asdpl_2NLSYEr4jwxDab8huTdCKbhmr4jM (READY / PROMOTED). Both https://apimaster.dev andhttps://www.apimaster.dev returned HTTP 200, and the Settings SSR response contained the full
commit SHA. GitHub candidate jobs did not start: GitHub reported an account payment/spending-limit
provider-admission failure before runner assignment or any workflow step. That external HOLD is not
a source-test failure and is separate from Codex usage limits.
Fixed — Pack2 gap closure, ContractRadar regression repair, brand and TMF integrity
- Corrected the previous completion claim. A measured baseline was 395 pass / 9 fail across 404
- Repaired a regression introduced by
9edee05, which removed 93 lines from the ContractRadar route - Restored TMF asset byte provenance. 55 bundled specs were checked out CRLF against an LF-hashed
- Reclassified the
spdx-expression-parse"registry TLS rejection" recorded on 2026-08-15. The - Closed public brand drift from 316 findings to zero. The artifact-generator identity, receipt copy
- Scoped the root test script to
bun test testsand addedtest:runner, so the runner's
Version is intentionally held at 0.10.1-rc.2: this repairs defects inside the existing candidate.
The Pack2 wiring gaps remain open and are listed inversioning/260816_01_v0.10.1-rc.2_pack2-gap-closure.md — most importantly that the headless runner
decides absent dependency knowledge as fully-known-and-safe, that release bundles still ship emptyfindings.json, and that the attention and capability engines reach almost no surfaces. Hosted,
provider, legal, signing and human gates are unchanged and remain HOLD.
Fixed — Pack2 cross-platform runtime hardening
- Normalized productization smoke-proof script identities to POSIX separators while
- Preserved POSIX-rooted runner configuration paths when parsing portable manifests on Windows;
- Re-ran the complete Pack2 P00–P13 focused suite: 84 tests passed, 0 failed, 730 assertions.
The Pack2 feature surface is implemented in the existing mainline modules. Hosted/browser,
legal dependency installation, and external promotion evidence remain explicit HOLD states;
they are not represented as local test passes.
Fixed — Pack1 scheduler integration and ContractRadar source contracts
- Mounted the existing guarded browser scheduler from the root shell so enabled
- Restored the ContractRadar public index exports used by the intake and pilot
- Regenerated the 52-route/52-feature productization baseline after the source
- Added fail-closed release performance evidence: exact previous/candidate SHA-bound bundle reports,
- Removed runtime Google Fonts requests; the application shell now uses local system font stacks so
- Added five exhaustive, fail-closed semantic status families for risk, capability, run, lifecycle
Changed — APIMaster public identity completion
- Centralized public page titles, root metadata, artifact filename stems and ContractRadar golden
- Replaced legacy public product copy and artifact prefixes while preserving repository names,
- Added a fail-closed legacy-brand drift scanner as a critical release-proof gate. The public copy
Added — P11 release evidence lifecycle and P12 scoped pilot
- Added candidate-only Linux/Windows release proof with owned Vite/Chrome lifecycle, axe, keyboard,
- Added a protected exact-SHA promotion workflow that recomputes candidate, hosted, provider,
- Added critical root and isolated-runner high-severity dependency-audit gates. The lockfiles now
- Added the
/pilotroute with a backward-compatible V2 discovery worksheet, explicit - Regenerated
src/routeTree.gen.tswith the TanStack generator. All 51 original file routes remain;
Changed — owner-approved actions and production licence issuance
- Recorded the repository owner's exact
radar.*role policy, bounded pilot proposal, explicit - Hardened production licence date parsing and signature/tamper verification. Private signing
- Classified the self-contained local ContractRadar CLI as beta/evaluation. A built, scanned and
Added — P09 runner and P10 TMF provenance implementation
- Added a private, source-prepared one-shot ContractRadar Node 22 runner with shared OpenAPI
- Added a deterministic 55-asset TMF repository inventory (54 TMF API identifiers), raw-byte hashes,
- Hardened the TMF promotion boundary with a strict source-registry schema, parseable SPDX
- Added versioned workspace-scoped TMF install receipts, a pure no-overwrite stable-entity planner,
- This is implementation evidence, not an npm/container publication, TM Forum certification or
Added — P05/P06 ContractRadar policy, workspace journey and golden proof
- Added the exact
radar.scan.run,radar.change.acknowledgeandradar.routing.writerole - Scoped targets, feed, routing and scan metadata to workspaces through an additive, idempotent
- Added the flag-controlled eight-step guided journey and deterministic, in-memory golden demo over
- Added proof-build-only role-matrix and rollback probes, stable browser selectors, an additive
Changed — P8B manifest-backed commercial truth consumers
- Migrated Pricing and the Welcome pricing teaser to the typed commercial-plan manifest. Recorded
- Bound Pro, Team, Enterprise, pilot and support contact actions to the repository owner's recorded
- Reframed Welcome, Enterprise and Help around browser-local-by-default behavior, the complete
- Removed the unaudited
56TMF inventory count, exact API identifiers and related speed claim from - Split Enterprise into manifest-derived available-now and roadmap lists. Team sync, authenticated
- Limited SoftwareApplication JSON-LD to the currently actionable Solo €0 offer. Held paid-plan
- Completed the scoped current README/LLM/docs/Settings/module consistency pass: public material now
- This is source-verified P8B scope, not a release PASS. Mailbox delivery, key custody and exact-SHA
Changed — P8A manifest-backed Trust disclosure
- Added a pure, fail-closed public truth adapter. Unknown capabilities resolve to
- Replaced the Trust page's hardcoded three-flow list with all 11 canonical outbound records in
- Rebuilt the Data Protection Annex from the same shared disclosure. The PDF is page-break aware and
- Aligned Trust FAQ/JSON-LD and EN/DE/FR Trust copy with the manifest-backed boundary. This P8A
Added — APIMaster productization: owned runtime-proof lifecycle
- Added a programmatic Vite proof runner that owns a temporary exact
127.0.0.1server, bounded - Added explicit package commands for route proof with the productization UI separately off and on,
- Added isolated contract tests for ephemeral-port selection, Vite 7 normalized bind retries, exact
- Recorded the current change authority: this repository has no
versioning/directory; root
Added — Scenario Lab (Wave 1): business-scenario orchestration + layered validation
The first wave of the Scenario Platform (design in design/scenario-platform/) — a strictly additive
foundation that turns the workbench from a single-endpoint inspector into a scenario-driven proving
ground. Multi-step business flows run over the EXISTING runEndpoint engine (mock mode today; the
companion sandbox is a later wave), with per-step, per-layer validation. Zero regression: the only
existing-type change is one optional ApiEnvironment.kind field; everything else is new modules +
additive store slices. apibox.state.v5 is preserved (old profiles hydrate the new arrays to []).
- Scenario Lab sidebar section (between Modeling and Runtime) with four routes:
- Scenario Catalog (
/scenario-catalog) — flows by sector, new/clone/delete/run. - Scenario Builder (
/scenario-builder) — three-pane flow editor: ordered steps, step inspector - Run Monitor (
/run-monitor) — run list + step timeline and swimlane cross-system view, with a - Business States (
/business-states) — a Business-State model registry + Enum Dictionary that
- Scenario Catalog (
- Scenario engine (
src/lib/scenario-engine/run-flow.ts) — orders steps, interpolates variables, - Layered validation (
src/lib/scenario-validation/) — an 8-layer model (contract, status, enum, - Store: additive
scenarioFlows/scenarioRunsslices +upsertScenarioFlow/deleteScenarioFlow
Fixed / Changed — Documentation now ships in the app bundle, and is interactive
- The documentation now reliably appears in the deployed app. The generated docs were moved from
- Interactive docs. Each document now renders an "Open in the app" band that turns its
- Documentation refreshed to v0.9.1. New
READINESS_PLATFORM.mdguide (the 10-dimension Readiness
Added — Readiness engine: three more dimensions + localized shell
- Unified Readiness Scorecard now spans ten dimensions. The four standalone readiness pages added
- Localized application shell (DE / FR). The sidebar (section titles + ~40 nav labels), command
Added — Documentation system
- In-app Documentation Center (
/docs): a desktop-class, three-pane help browser - Generated documentation suite (
docs/): a complete, business- and user-focused documentation - Help Menu masterfile (
docs/HELP_MENU_MASTERFILE.json): a machine-readable manifest
Source candidate — 0.10.0-rc.1 task-pack integration (2026-08-12)
- Integrated the preserved Pack 1 foundation with every Pack 2 workstream, P00–P13. The candidate
- Added bounded local runner and CI workflows, downstream impact/provenance graphs, canonical
- Added six presentation-only packaging records and the Contract Release Assurance Pilot flow. The
- Added the TMF622-to-TMF641 release-assurance vertical while retaining raw-byte provenance and
- Preserved the original Pack 1 route, storage, permission, domain, compatibility and local-first
- This entry records a source candidate, not a shipped release. Its base authority is
Consolidation outcome — main (2026-08-15)
- The
0.10.0-rc.1source candidate was consolidated ontomainas one squash commit; the - The implementation outcome remains SOURCE COMPLETE / PROMOTION HOLD: local candidate
- GitHub candidate jobs did not enter repository steps and Vercel checks were provider-admission /
- Production SHA/deployment, hosted runtime, package publication, legal/licence/TMF clearance,
ContractRadar pilot candidate — 0.10.1-rc.2 (local outcome)
- Added the local, unsubmitted ContractRadar intake receipt, deterministic pilot analysis, receipt-bound
- Added hydration-safe display guards and role-aware ContractRadar controls; no hosted runner, account,
- The package and source slice are versioned as
0.10.1-rc.2, but browser proof, visual/accessibility
[0.9.0] - 2026-06-13
The "governance & readiness platform" wave. API Workbench Pro grows from an API workbench into an
API governance + contract intelligence + scenario validation + trace readiness platform, driven
by a 50-item enterprise pain-point analysis. Two multi-agent waves (a completion wave finishing the
EU-leader roadmap, then a readiness-platform wave) with a shared check-engine and integrator QA gate
(build + type-check + lint green, zero regression — apibox.state.v5 untouched, no new deps).
Added — Readiness & governance platform
- Unified Readiness Scorecard (
/readiness): the keystone — a single "can I ship this API - Governance & Lint (
/governance): a Spectral-style, configurable ruleset (naming, date-time, - Security Posture (
/security-posture): OWASP-API-style static analysis — BOLA, excessive data - Trace & Observability Readiness (
/trace-readiness): trace-context / correlation-id - Drift & Breaking-Change Radar (
/drift-radar): 4-way drift (spec ↔ implementation ↔ contract ↔ - Validation Engine (
/validation-engine): scenario-driven validation beyond schema — a business- - Check engine (
src/lib/checks/): a shared, pure, contract —Finding/CheckResult/
Added — Completion wave (EU-leader roadmap)
- API Change Intelligence (
/change-intelligence): spec watches + change feed closing the - CI/CD Contract Gate (
/ci-gate): anapibox-ci.jsonbundle plus generated GitHub Actions and - TMF Telco Vertical (
/tmf): curated TMF packs (Order Cascade, Customer 360, Assurance) and an - GraphQL (
/graphql) and Realtime (/realtime) consoles: GraphQL introspection + query console - Enterprise & Teams roadmap (
/enterprise): honest, future-tense team-sync / SSO direction with - Instrumentation & entitlements:
ValidationIssue.resolvedAt, mapping timestamps and an entity - Internationalisation: a dependency-free i18n library (EN / DE / FR) with a topbar language
- Repository provenance docs:
README.md, thisCHANGELOG.md,docs/ARCHITECTURE.md,docs/GDPR.md,
Guarantees
- Zero regression: every module is additive; no existing route, store action or persisted key changed
- No new npm dependencies were introduced in that wave. Analytics calculations stayed local, while
[0.8.0] - 2026-06-12
The "EU-leader" upgrade wave. A multi-phase release driven by six parallel strategy analyses, a
synthesis pass, seven parallel build workstreams with disjoint file ownership, and an integrator QA
gate (build + type-check + lint green, light/dark visual verification).
Added
- Shell & navigation: command palette (
Ctrl/Cmd+ K),g-key navigation chords, a light/dark - Insights BI module (
/insights): workspace health score, KPI tiles with sparklines, pass-rate - Overview dashboard: health banner, a 48-bucket event-density Pulse Rail, a "since you left"
- API Explorer power pack: cURL import, copy-as (cURL / fetch / axios / Python requests /
- Mapping Studio: a fully local Mapping Copilot (field-pairing suggestions; field names never
- Runtime: Run Heat-Strip (last 60 runs), Gate Waterfall trace from real trace steps, scenario
- Portability, trust & monetisation base: encrypted workspace export/import (optional AES-GCM via
- Public marketing surfaces:
/welcome,/pricing,/trust,/help, plusrobots.txt, - API Change Intelligence foundation: persisted
SpecWatchandChangeRecorddomain types and
Changed
- Rebranded the internal codename ApiBox to the public name API Workbench Pro across route
- Branded, additive metadata on gateway JSON/Kong YAML and PDF exports — existing parsers remain
- Repo-wide Prettier / line-ending normalisation.
Fixed
ReadinessBarnever painted — invalid nestedoklch(oklch(...))replaced with direct- Build-blocking duplicate
resolveRef/validateAgainstSchemadeclaration in the API Explorer, - Command palette accessibility error (Radix
DialogTitle) and Mapping Studio rendering only in dark - Hard-coded relative timestamps and environment health chips on Overview replaced with computed
Compatibility
- The
apibox.state.v5persist key is untouched; the persist migration is non-destructive, so - All existing store action signatures are unchanged; new features call existing actions.
- New persisted data lives in separate, versioned
awp.*keys with try/catch fallbacks. - Entitlements are soft — premium surfaces show a plan chip; nothing is hard-blocked, and trial mode
[0.7.0] - 2026-05
Baseline before the upgrade wave: the core offline-first workbench.
Added
- The 20-module API lifecycle: API Catalog & Import (OpenAPI/Swagger with a weakness classifier),
- The role-based governance model (owner / editor / operator / viewer) with a permission matrix and
- 56 preloaded TM Forum (TMF) Open API specifications and four public no-auth test connections.
- Zustand v5 persistence under
apibox.state.v5; no backend.
[Unreleased]: https://github.com/HenrikFaul/api-workbench-pro/compare/v0.8.0...HEAD
[0.8.0]: https://github.com/HenrikFaul/api-workbench-pro/releases/tag/v0.8.0
[0.7.0]: https://github.com/HenrikFaul/api-workbench-pro/releases/tag/v0.7.0