> ## Documentation Index
> Fetch the complete documentation index at: https://docs.framelane.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Brand kit release

# Brand kit release checks

The API and console are deployed to production; acceptance is partial as recorded
below. Signed-in management and MCP read/edit checks pass; deployed font/media,
provider and export checks remain incomplete.
The local browser fixture uses real API routes, auth, database, validation and
translation, with simulated storage, font cooking and extraction. Its successful
callbacks and queued render payloads do **not** prove a deployed farm render,
CDN access, TasteLabs timing or a commercial licence.

Defaults: `BRAND_KIT_MIN_PLAN` is unset, `BRAND_EXTRACTION_PROVIDER=framelane`,
and PDF import is deferred. The user has confirmed the signed embedding Order is
obtained and filed; no contract or filing location was independently inspected.
TasteLabs remains disabled in production pending its deployed acceptance checks
and an explicitly authorized provider rollout.
Do not treat the plan's historical farm commit or deployment document as proof of
the currently deployed image.

## Current production status (2026-10-30)

The user explicitly requested **merge, deploy and test again**. That authorization
superseded the earlier manual-GCP restriction for this release. API
[PR 59](https://github.com/ITIShq/framelane-api/pull/59) and console
[PR 18](https://github.com/ITIShq/framelane-homepage/pull/18) were merged and
deployed. The [deployment evidence](evidence/brand-kit-production-release-2026-10-30.json)
records:

* Initial API merge commit `65958eb803846aa5019b4507de6e6384ad61535d`; Cloud Build
  `945b251b-e8c1-4ee2-8c01-f2d3ce867e55` succeeded with image digest
  `sha256:304b8d2649ed1555194ada525a0042b3a95dcfa72528bc0c3e31fcc4da111b84`.
* Migration execution `framelane-migrate-hdqpd` succeeded from `0022` through
  `0025`. Initial Cloud Run revision `framelane-api-00115-5l7` was deployed before
  the hotfix below.
* API hotfix [PR 61](https://github.com/ITIShq/framelane-api/pull/61) merged as
  `06ec2578808c8781e62e4fd71e1608b95f01fa62`. Cloud Build
  `2229c81b-dc97-4c1d-9f08-65e1cc246c1a` succeeded with image digest
  `sha256:e96913109507cc35a3cb02ead1745871c3dc8456d600eaf4853e6651e480d8d5`.
  Revision `framelane-api-00116-k8t` now serves 100% of traffic. No further
  migration was required beyond `0025`.
* Console merge commit `53007cde3162d9ee76b2480fd64e6cea6c2f2f5f`; Vercel deployment
  `dpl_BY6oUDaM4wH8PHztdfa2EsiuTsgf` was promoted to `https://framelane.io`.
* API health and readiness returned HTTP 200, with database and Redis healthy.
  Live OpenAPI exactly matches the committed schema's 59 paths. Three checked
  new feature routes exist and reject unauthenticated requests with HTTP 401.
* An HTML request to `https://framelane.io/brand-kit` returned HTTP 307 to
  `/login`, confirming the signed-out Clerk boundary. A non-HTML unauthenticated
  request can return 404 as authentication behavior; it is not evidence that the
  deployed page is absent.

The initial signed-in test saved a name and colour, then exposed a reload HTTP
500: the parameterized `ON CONFLICT` predicate could no longer infer the partial
unique index when PostgreSQL reused a generic prepared plan. The failure was
reproduced against a disposable real PostgreSQL database. The hotfix uses the
same static predicate as the migration, while keeping user data parameterized.
Its 31 focused checks pass, including four real PostgreSQL checks and 21 generic
plan insert/reload executions that preserve one kit and its manual edits. Main
CI run `41035828066` passes 4,190 tests with 18 skips and all 10 upload-panel tests;
the image spec check confirms 59 OpenAPI paths.

Signed-in browser acceptance passed at **09:58 UTC** after the hotfix:

* Eight concurrent workspace-kit reads returned one kit. An unsupported
  `font/woff2` upload request returned `422 unsupported_content_type` without
  creating a font row or starting work.
* Rename/reload/restore and colour add/remove/reload/re-add/cleanup passed.
  The empty font UI rendered, and an empty project's Brand panel showed the
  persisted swatch and management link without changing its scene.
* There were no page errors or forbidden request attempts. Render and task lists
  were empty before and after; extraction usage stayed zero. No media/font bytes,
  cooking, extraction, preview, thumbnail or render submission was exercised.

The live MCP endpoint initialized with 31 tools. `get_brand_kit` and
`edit_brand_kit` succeeded, REST independently verified persistence, and 20
repeated MCP reads succeeded. Initial values were restored. No MCP upload,
extraction or render calls were made.

A generated Clerk identity using a reserved `example.com` address with Null MX
`0 .` completed authentication and workspace sync; the earlier `.invalid`
rejection was not a blocker. The generated workspace and Clerk user were deleted,
and the test API key returned HTTP 401 afterward. The existing real user account
was untouched. Temporary credentials and browser-state files were removed.

At **09:59:16 UTC**, the final bounded log checks returned zero error entries for
API revision `framelane-api-00116-k8t` and zero console errors after the fix at
09:52:02 UTC; the console log query exited successfully. The renderer group was
stable at target zero and the instance list was empty. This records the checked
revision and time window, not an assertion about all historical or future logs.

**Licence evidence is user confirmation:** the user instructed us to consider the
signed embedding Order obtained and filed. No contract or filing location has
been independently inspected.
The historical three-site live captures below remain complete.

**Capacity and spending constraint:** the user instructed us not to restore GPU
capacity because of cost. Capacity was briefly requested at one and immediately
returned to zero; the stable target is zero and the instance list was confirmed
empty. No font-processing, render or paid-provider jobs were submitted during
this deployment verification. Keep `BRAND_EXTRACTION_PROVIDER=framelane`, do not
restore capacity, and do not repeat paid captures. Deployed font cooking, farm
image inspection, new cooked-font CDN/browser checks and completed video exports
remain unproved and must wait for renewed authorization and available capacity.
The completed management and cleanup checks do not establish uploaded-object
cleanup, retained-font rendering, provider flow or alert delivery. Those checks
remain outstanding; deployment alone does not prove them.

## Historical production authorization and preflight (2026-10-29)

This section records the state before the 30 October deployment. Its deployment
and permission limitations are superseded by the current status above.

The user authorized production for the remaining acceptance checks. Use
`https://api.framelane.io` and a dedicated disposable acceptance workspace once
the feature is deployed. This replaces the earlier staging-only requirement for
kit, font, CDN and export checks. It does not enable TasteLabs for customers or
change the render-node instruction that the user runs every GCP command.

The [read-only production probe](evidence/brand-kit-production-preflight-2026-10-29.json)
found API commit `d0508d459335c6b8d9b75aee0544829eb89cfd40`. Its OpenAPI schema
has only the older `/v1/brand-kits` and `/v1/brand-kits/{brand_kit_id}` routes;
workspace-kit and font routes are absent. `https://framelane.io/brand-kit`
returns 404. The `app.framelane.io` and `console.framelane.io` example hosts do
not resolve from this executor. Both feature PRs remain open drafts. Deploying
the reviewed API and console is the next prerequisite; no production writes
were performed during this preflight.

The version response reports renderer SHA
`5bc7a6f5a2cb17092a69d775dcbd1699eda34d5a`, taken from the latest completed or
cancelled render callback. This is useful provenance for that callback, not
proof of the currently running image or all active farm versions.

After deployment, use a new controlled acceptance email for `POST /v1/signup`
and `/v1/signup/verify`, or a separate Clerk test identity. **Do not call signup
with an existing customer's email:** it rotates their keys and resets email
verification. A Clerk identity with the same verified test email can adopt that
workspace. Store its API key privately as `FRAMELANE_API_KEY`; record and verify
the dedicated workspace ID with `GET /v1/workspace` before mutations and cleanup.
Do not use the existing connected workspace for destructive acceptance tests.

## Live provider evidence (2026-10-29)

A user-supplied test key completed the three planned provider captures with one
submission per site: framelane.io in 180.019 seconds, stripe.com in 6.180 seconds,
and linear.app in 6.158 seconds. Linear's page and a fingerprinted Next CSS asset
were independently fetched. All three reached `completed` within five minutes.
TasteLabs reported two credits for each submission, including the Stripe and
Linear cache hits: six credits total. Free list/result reads recovered only these
submissions for asset verification; no additional extraction was submitted.

The [live report](evidence/brand-kit-tastelabs-2026-10-29.json) links the sanitized
envelope sequences, elapsed times, response status counts and stylesheet proof.
The real shapes exposed nested `typography.*.variants`, semantic button-radius
labels, `Primary` logo roles and a brand colour also used for text. Mapper
regressions now preserve those values. Offline API persistence tests replay each
recorded shape, preserve manual name/heading edits, store the detected body font
and reuse the same URL without a second submission.

All three sites returned mirrored logos on `sb.engine.thetaste.ai`. One logo from
each was fetched through the actual DNS-pinned, byte-limited logo proxy: a PNG for
FrameLane and sanitized SVGs for Stripe and Linear. That exact host is the tested
storage allowlist candidate. Stripe also returned two direct URLs on
`images.stripeassets.com`; those are source-site assets and were not added to the
provider-storage allowlist. No signed URL, raw provider asset path or credential
is included in the evidence. The key was used only for this local test run.

These are live provider and proxy checks from the local executor. They do not
prove a deployed provider API/console flow, concurrent deployed pollers, alert
delivery, a completed video, or the signed embedding Order. The Order's current
status is the user confirmation recorded above; the remaining deployed checks
are listed below, and the production provider default remains `framelane`.

## Local cooker evidence (2026-10-28)

The [machine-readable proof](evidence/brand-font-cooker-2026-10-28.json) pins the
cached amd64 image, cooker source and binary hashes, generated input hashes,
metadata hashes and actual chunk hashes. This used image
`sha256:6f7fb604936a93df5cdf36a4c18136fedb3f3926db3e739e60671c1e7ab37b7c`
(tagged `git-sha-90682fd1ccdee625324359c10e2e49e7feaab288`; that tag does not
establish the currently deployed farm revision).

A generated TTF with a nonempty `glyf` outline and a generated OTF with a nonempty
CFF outline both ran through the image's real `process_user_font.py` and legacy
`slugfont` binary. They completed in 1.340 s and 1.311 s respectively, each writing
V3 metadata and one 2,364-byte `.slug` chunk. The API's actual `validate_font` and
`verify_font_objects` accepted both; the latter read the real output files through
a local signer shim, with no GCS requests. These tiny synthetic inputs establish
format and output-contract compatibility, not a full-font latency guarantee.

The containers used the explicit font-tool Python entrypoint, `--network none`,
`--read-only`, `--cpus 1`, `--memory 512m`, `--pids-limit 64`, `--cap-drop ALL` and
`no-new-privileges`. Only generated inputs and a temporary output directory were
mounted. No renderer worker, GPU, credentials or cloud connection was used.
The deployed callback, CDN and rendered-frame gates below remain outstanding.

## Local Claude Code evidence (2026-10-28)

The [S4 client proof](evidence/brand-kit-claude-code-2026-10-28.json) records a real
Claude Code 2.1.312 session over initialized Streamable HTTP on a separate local
fixture at `127.0.0.1:8039`. Its strict MCP configuration connected only that
server; built-in tools and hooks were disabled, and only the four acceptance
tools were allowed. All four calls succeeded: `get_brand_kit("workspace")`,
`edit_brand_kit`, `extract_brand_kit` into the same kit, and inline font
`create_upload` using a generated TTF. An unauthenticated MCP request returned 401.

Independent REST and read-only SQLite checks confirmed one workspace kit, one
completed extraction job, preserved manual name/color/heading, filled body font
and logo suggestion, and the uploaded font ready after one attempt. Its stored
source bytes matched the generated input. The schema-budget suite passed all
16 tests. [Reproduction steps](../../tests/browser/README.md#claude-code-acceptance)
use a temporary directory and do not modify the user's MCP configuration.

This closes the plan's local Claude Code round-trip check. The fixture simulates
storage, font cooking, website DNS and TasteLabs; this evidence does not establish
Clerk OAuth, deployed callbacks, CDN access, paid extraction or rendered output.
The independent real CPU cooker proof above covers actual local cooking.

## 1. Deployment procedure (completed for the 2026-10-30 release)

The 30 October deployment was executed under the user's explicit merge/deploy/test
authorization; its results are recorded above. The commands below remain the
reference procedure, not a request to repeat the completed deployment. The names
come from the repository's production deployment docs;
verify them against the existing resources first. Preserve their database,
network, service-account and secret configuration. Run from the reviewed, clean
API feature checkout, after resolving any review changes. Cloud Build updates
`:latest` as well as the commit tag; the migration and service use the commit tag.
Keep `BRAND_EXTRACTION_PROVIDER=framelane` and `BRAND_KIT_MIN_PLAN` unset.

```bash theme={null}
export BRAND_PROJECT='framelane'
export BRAND_REGION='us-east1'
export BRAND_API_SERVICE='framelane-api'
export BRAND_MIGRATE_JOB='framelane-migrate'
export BRAND_API_IMAGE='eu.gcr.io/framelane/api'
export BRAND_API_URL='https://api.framelane.io'
export BRAND_GIT_SHA="$(git rev-parse HEAD)"

gcloud run services describe "$BRAND_API_SERVICE" --project="$BRAND_PROJECT" \
  --region="$BRAND_REGION" \
  --format='yaml(metadata.name,status.url,status.latestReadyRevisionName,status.traffic)'
gcloud run jobs describe "$BRAND_MIGRATE_JOB" --project="$BRAND_PROJECT" \
  --region="$BRAND_REGION" --format='value(metadata.name)'
```

Record the current revision before proceeding. Then build, migrate and deploy:

```bash theme={null}
gcloud builds submit --project="$BRAND_PROJECT" --config=cloudbuild.yaml \
  --substitutions="_IMAGE=$BRAND_API_IMAGE,_GIT_SHA=$BRAND_GIT_SHA" .
gcloud run jobs update "$BRAND_MIGRATE_JOB" --project="$BRAND_PROJECT" \
  --region="$BRAND_REGION" --image="$BRAND_API_IMAGE:$BRAND_GIT_SHA"
gcloud run jobs execute "$BRAND_MIGRATE_JOB" --project="$BRAND_PROJECT" \
  --region="$BRAND_REGION" --wait
gcloud run deploy "$BRAND_API_SERVICE" --project="$BRAND_PROJECT" \
  --region="$BRAND_REGION" --image="$BRAND_API_IMAGE:$BRAND_GIT_SHA"
curl --fail --silent --show-error "$BRAND_API_URL/v1/version"
```

Save the commit, immutable image digest, successful migration revision `0025`,
and `/v1/version` response in the release evidence. Migration order is build,
migrate, deploy; do not execute the old migration job image. The existing README
has infrastructure setup instructions, but do not recreate production resources
to resolve a name mismatch. Deploy the console's exact packed editor rc.24 after
the API is ready, then run the farm/browser checks below. Confirm the actual
console origin from that deployment before checking CORS.

## 2. Operator: farm, subscription and CDN evidence (S0/S2)

These checks remain incomplete. The current user instruction is to keep GPU
capacity at zero; do not start a worker, upload a font that dispatches processing,
or submit a preview/render job to advance them. Read-only subscription metadata
can be inspected without restoring capacity. Running-container and new cooked-font
checks must wait until the user authorizes capacity again.

Use the operator-verified production names. The documented farm MIG below is a
starting point, not live deployment evidence. Supply the actual push-subscription
names; these read-only commands identify the template and filters:

```bash theme={null}
export BRAND_FARM_MIG='gpu-render-node-instance-group-use1-regional'
export BRAND_RESULTS_SUB='your-render-results-push-subscription'
export BRAND_UPLOADS_SUB='your-upload-finalize-push-subscription'
gcloud compute instance-groups managed describe "$BRAND_FARM_MIG" \
  --project="$BRAND_PROJECT" --region="$BRAND_REGION" \
  --format='yaml(instanceTemplate,versions)'
gcloud compute instance-groups managed list-instances "$BRAND_FARM_MIG" \
  --project="$BRAND_PROJECT" --region="$BRAND_REGION" \
  --format='table(instance,instanceStatus,currentAction)'
gcloud pubsub subscriptions describe "$BRAND_RESULTS_SUB" \
  --project="$BRAND_PROJECT" --format='yaml(name,topic,filter)'
gcloud pubsub subscriptions describe "$BRAND_UPLOADS_SUB" \
  --project="$BRAND_PROJECT" --format='yaml(name,topic,filter)'
```

For the Docker-based farm documented in render-node's `deployment_plan.md`, use
an instance and its zone returned by the preceding list. The following commands
are **for the operator to run**, not evidence that an inspection has happened:

```bash theme={null}
export BRAND_FARM_INSTANCE='instance-returned-by-list-instances'
export BRAND_FARM_ZONE='that-instances-zone'
gcloud compute ssh "$BRAND_FARM_INSTANCE" --tunnel-through-iap \
  --project="$BRAND_PROJECT" --zone="$BRAND_FARM_ZONE"
```

Inside that VM, list the running containers and select the actual renderer name
(`render-node` in the documented deployment). Inspect only these fields; a full
container inspection would also disclose its environment:

```bash theme={null}
sudo docker ps --format 'id={{.ID}} name={{.Names}} image={{.Image}}'
export BRAND_FARM_CONTAINER='render-node'
sudo docker inspect "$BRAND_FARM_CONTAINER" \
  --format 'id={{.Id}} imageId={{.Image}} configuredImage={{.Config.Image}} started={{.State.StartedAt}}'
BRAND_FARM_IMAGE_ID="$(sudo docker inspect "$BRAND_FARM_CONTAINER" --format '{{.Image}}')"
sudo docker image inspect "$BRAND_FARM_IMAGE_ID" \
  --format 'imageId={{.Id}} repoDigests={{json .RepoDigests}}'
sudo docker logs "$BRAND_FARM_CONTAINER" 2>&1 \
  | grep -F 'Render node build: gitSha=' | tail -1
```

Save the instance/zone, container ID/start time, image ID, immutable repository
digest and startup `gitSha` together. The startup marker is emitted by the
running binary (`src/offlineRenderer/main.cpp`), unlike a mutable image tag.
If the logging driver does not expose `docker logs`, retrieve that same marker
from Cloud Logging for this instance and container start. If `RepoDigests` is
empty or the source marker is `unknown`, reconcile the inspected image ID with
the build/deployment provenance before declaring this gate passed. Repeat for
every active version during a rolling deployment; do not start another worker
or substitute the locally cached cooker image for a running instance.

Confirm the identified source contains `processUserFont`
(request field 107, response field 108). `/v1/version` exposes the latest render
completion/cancellation callback SHA; it does not identify the current image.
Confirm the result
push reaches `/internal/metadata/complete` and its filter permits font results.
Confirm object-finalize events reach `/internal/gcs/notify`. The API's 60-second
font sweep is a fallback for a missing finalize event; it cannot replace a missing
farm result subscription. Fix any infrastructure filter using the operator's
normal reviewed change process; do not guess a replacement filter.

Upload one licensed TTF and one **CFF-outline OTF** to the dedicated acceptance
workspace using the console. Both must reach `ready` within two minutes, return a
source URL plus chunk metadata, and render a frame showing the chosen face. Save
request/attempt IDs, timestamps, the output frame, and farm logs. There must be no
`Can't process brandkit font ranges` error. The API preserves canonical `.json`
and `.slug` objects under its exact first-party `/fonts/` CDN prefix, so metadata
and relative chunks retain their public CDN base. Raw `/uploads/` font sources
still rewrite to `storage.googleapis.com` for service-account reads. The current
farm downloader may itself convert CDN URLs to authenticated storage URLs and
fall back to CDN reads; verify both metadata and relative chunks on the deployed
farm. If a fetch fails, investigate its credentials/rewrite/CDN configuration;
the local fixture cannot validate this path. If CFF fails, restrict upload acceptance to
TTF and adjust the UI/docs before shipping rather than advertising unproven OTF.

Using URLs from that acceptance upload, test the console's actual Origin/Referer and
a public chunk URL. The response must permit the console origin for sources and
public CORS reads for `/fonts/`:

```bash theme={null}
export BRAND_CONSOLE_ORIGIN='https://your-deployed-console-origin'
export BRAND_SOURCE_URL='source-url-returned-by-the-acceptance-upload'
export BRAND_CHUNK_URL='public-chunk-url-returned-by-the-acceptance-upload'
curl --fail --silent --show-error --head "$BRAND_SOURCE_URL" \
  -H "Origin: $BRAND_CONSOLE_ORIGIN" -H "Referer: $BRAND_CONSOLE_ORIGIN/"
curl --fail --silent --show-error --head "$BRAND_CHUNK_URL" \
  -H "Origin: $BRAND_CONSOLE_ORIGIN"
```

Also verify a real browser `FontFace` load; a successful curl is insufficient for
browser CORS. Compare preview/export text metrics side by side. Tombstone the
font, reload the existing project, and confirm preview and export still use it;
it must disappear from the library/picker. A new live face with the same canonical
family must take precedence. In the disposable workspace, confirm simultaneous
first kit reads return one ID, upload membership appears, and asset/kit/workspace
deletes leave no owned rows. Workspace deletion removes source files and the
`fonts/{workspace}/` prefix; check storage and cleanup logs, including late farm
callbacks. Copies owned by another workspace retain their document/history and
source kind, with the deleted source reference cleared.

Before deleting the test workspace, finish or cancel its outstanding render jobs,
save the accepted output and re-check `GET /v1/workspace` against the recorded
test ID. `DELETE /v1/workspace` removes all its rows, keys and owned files; never
run it with an existing customer workspace's key. Keep this cleanup separate
from tombstone testing, which must retain the workspace and saved project until
the second export completes.

## 3. Operator: TasteLabs gates (S3, still separate from production core checks)

Keep trial credentials out of source control and use a separate production key
after licensing. The signed Order must cover embedded use, stored mapped
results, copying selected logos, rate/concurrency limits, DPA/subprocessor terms,
separate environment keys and future PDF support. The plan contains a licence
request draft; sending it is a separate user action. A test key was supplied for
the 29 October local live checks above. On 30 October the user confirmed that the
signed embedding Order is obtained and filed; this is user confirmation, not an
independent review of the contract or its filing location. Deployed provider
acceptance remains incomplete. Production test authorization does not make a
global provider flag workspace-scoped. Keep the shared production API on
`framelane`; no new paid checks are authorized under the current spending
constraint. Any later provider acceptance or rollout requires renewed scope for
that work. Core browser checks can proceed without changing the provider; font
and export checks also require the GPU constraint to be lifted.

Run the reviewed spec check and one deliberate fixture capture per approved test
site if another capture is required. The three 29 October captures already exist
and must not be repeated merely to regenerate fixtures. Each capture command
consumes a provider submission; never retry it
blindly after an unknown outcome. The script reads `TASTELABS_API_KEY` from the
operator's environment without printing it and requires a new output directory.

```bash theme={null}
uv run python scripts/check_tastelabs_spec.py
uv run python scripts/capture_tastelabs_fixture.py --url https://framelane.io \
  --output tests/fixtures/tastelabs/recorded-framelane --confirm-staging-charge
uv run python scripts/capture_tastelabs_fixture.py --url https://stripe.com \
  --output tests/fixtures/tastelabs/recorded-stripe --confirm-staging-charge
# Repeat once for an approved site with a hashed stylesheet and a new directory.
```

Review new captures before committing them. The 29 October recorded sequences
are sanitized real trial captures; older fixtures also cover synthetic cases. The capture
anonymizer replaces every HTTP URL with `fixture.example`, so its saved sequence
cannot establish the real storage allowlist. During each authorized capture,
inspect the logo URLs in the provider's response viewer or the controlled test
response inspection, and separately save **only their non-secret hostnames**,
the UTC observation time and capture-folder name in the release evidence. Do not
save signed URL paths/query strings, headers, keys or raw provider responses.
Confirm these are the exact HTTPS hosts serving the returned logos; use the
hostname without a scheme, path, port or wildcard in `TASTELABS_STORAGE_HOSTS`.
Do not infer a host from synthetic fixtures or allow all provider subdomains.

On an isolated internal deployment, enable `tastelabs` with
the trial key and require all three sites to reach ready within five minutes
through the API and console. Verify one apply under concurrent polls, one paid
submit per running kit, quota rejection, 24-hour reuse without a new paid submit,
usage totals and the provider flag switched back to `framelane`. Verify logo
preview/save produces an owned asset and never injects provider SVG as markup.
Only then, with the signed Order filed, approve production provider enablement.

Before that enablement, verify delivery of the dispatch-failure alert required by
the plan. The application emits `brand_extraction.submit_failed` at ERROR with
`job_id` and `status_code`, then attempts the heuristic fallback. A successful
fallback must not hide exhausted provider credits. Configure the existing
operator-owned log alert using this filter, replacing the service placeholder
with the actual isolated test service (and the production service on rollout):

```text theme={null}
resource.type="cloud_run_revision"
resource.labels.service_name="your-isolated-test-api-service"
severity>=ERROR
jsonPayload.event="brand_extraction.submit_failed"
```

Use the operator's approved fault-injection method on a dedicated isolated
instance to return a controlled dispatch 401, 402 and 5xx from a provider stub;
these checks need no real TasteLabs key or paid extraction. For each case confirm
the matching structured event, the heuristic fallback result, and delivery to
the alert policy's actual notification destination within its configured
evaluation window. In particular, 402 must reach the credit-exhaustion operator.
Record the policy ID, destination name, test job/status code, event timestamp and
notification receipt timestamp; redact contact addresses where evidence is shared.
A matching log entry or a notification-channel test alone is insufficient.
Never inject provider failures into the shared production service. Restore the
isolated provider configuration afterward. If no alert policy or
notification destination exists, record that operator setup as an open release
gate; the application log by itself is not delivered alerting.

## 4. Final acceptance evidence

Current status: deployment, signed-out authentication, signed-in kit management,
MCP read/edit persistence and empty-workspace cleanup checks pass. Font/media,
provider and export acceptance remains incomplete; farm-dependent checks are
pending capacity authorization. The following is the remaining procedure, not
evidence that those checks ran. Do not dispatch farm or paid-provider work under
the current constraint.

Keep API/console/editor commits, generated SDK versions, test reports and the
deployment artifacts together. The local Claude Code check above covers workspace
read, `edit_brand_kit`, extraction into that kit and font upload. The deployed MCP
read/edit subset now also passes; only exercise its remaining external-service
calls when separately authorized. Then complete the console acceptance flow to
add/delete/re-add image, video, audio and font,
then insert media, apply a colour, set a font, reload and export the composition.
A locally queued job is not an exported video. Confirm the upload licence notice
and Terms sentence are present. PDF import remains out of this release.

## Review packaging

S1 core, S2 fonts, S3 extraction and S4 MCP share the service, schemas, routers,
worker and generated SDKs. The implementation is packaged as one coherent local
feature commit so each reviewed revision builds and tests; this deliberately
replaces the plan's separate API slice commits. The console/editor remain their
own changes. No production deployment or provider enablement is implied by this
packaging.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.