mirror of
https://github.com/hanzoai/chat.git
synced 2026-09-30 20:51:17 +00:00
"Forge" named nothing in this estate. The product is Hanzo Git, the address is
git.hanzo.ai, and the in-cluster service is hanzo-git.hanzo.svc. A generic word
invented for it sends a reader looking for a system that does not exist.
Comments only. ("unforgeable" stays — different word.)
156 lines
8.5 KiB
YAML
156 lines
8.5 KiB
YAML
# Hanzo CI/CD — chat. All real config lives here; .hanzo/workflows/cicd.yml is
|
|
# triggers and an import of hanzoai/ci.
|
|
#
|
|
# NO `images:`, and this file used to declare one. Two lanes were already aimed
|
|
# at ghcr.io/hanzoai/chat — this block (platform.hanzo.ai builds every `images:`
|
|
# entry on a push to the default branch) and .hanzo/workflows/deploy.yml.
|
|
# Importing the reusable would have made a third. Two builders racing one tag is
|
|
# how hanzoai/app moved v1.42.225 off the bytes that had just been verified:
|
|
# nothing errors, and the pod reports one version while serving another.
|
|
#
|
|
# deploy.yml is the lane that stays, because it is the lane that has been
|
|
# building. Hanzo Git runs #90-#97 are green and #97 is this exact commit. It also
|
|
# carries the two things the image cannot ship without: PUBLISHABLE_KEY read
|
|
# from KMS (Vite inlines it at BUILD, so a runtime value cannot reach a compiled
|
|
# bundle, and the Dockerfile refuses a blank one) and the post-push check that
|
|
# the tag resolves. Folding it in here is mechanical later — `build_secrets:
|
|
# [PUBLISHABLE_KEY]` reads the same KMS path deploy.yml curls — but changing
|
|
# which lane ships hanzo.chat does not belong in the commit that gives this repo
|
|
# its first tests.
|
|
#
|
|
# What was missing was never a builder. It was a gate: deploy.yml runs no test,
|
|
# no lint, no typecheck, so hanzo.chat has shipped ungated for 97 builds.
|
|
|
|
test:
|
|
# The build the image itself runs, run before there is an image. `pnpm run
|
|
# frontend` is verbatim the Dockerfile's own build line — four workspace
|
|
# packages, then the client bundle — so a break here is a break there, reached
|
|
# in ~2 minutes instead of after a full layer stack. On a pull request it is
|
|
# the only signal that exists: deploy.yml fires on main pushes only.
|
|
- name: build
|
|
run: |
|
|
set -e
|
|
corepack enable pnpm
|
|
pnpm install --frozen-lockfile
|
|
NODE_OPTIONS=--max-old-space-size=8192 pnpm run frontend
|
|
# A vite build that emits nothing still exits 0.
|
|
test -f client/dist/index.html
|
|
|
|
# scripts/check-barrel.cjs, against the dist the gate above just produced. Two
|
|
# defects that are invisible until the process boots: an import of a package
|
|
# @hanzochat/api never declared (rollup inlines it, and an inlined native
|
|
# require throws at load), and a name api/server/** destructures from the
|
|
# barrel that the barrel does not export (`undefined` until its first call).
|
|
- name: barrel
|
|
run: |
|
|
set -e
|
|
node scripts/check-barrel.cjs
|
|
|
|
# What a user document is allowed to carry out of this process, and who is
|
|
# allowed to ask — and whether erasure erases. Forty-one tests over the two
|
|
# hops where a credential escapes: the projection every user-returning route sends through
|
|
# (services/publicUser — an allow-list, because a deny-list only covers the
|
|
# fields somebody thought of), and the schema's own default projection under
|
|
# it. Around them, the controller that does the sending, the strategy that
|
|
# decides who verifies a request, the org header Commerce authorizes on, the
|
|
# standing refusal to enable MCP while nothing filters its tools, and every
|
|
# filter in the account-deletion chain naming a field its schema actually
|
|
# declares — one of them did not, so deleting an account kept its API keys.
|
|
#
|
|
# The second factor is not here because it is not ours: IAM owns enrolment and
|
|
# verification, and chat verifies the caller's IAM token rather than issuing a
|
|
# credential of its own.
|
|
#
|
|
# These files were each written against a real defect and each mutation-tested
|
|
# — every assertion here has been made to fail on purpose. That is what earns
|
|
# them the lane; the note below on why no whole suite is gated still stands
|
|
# unchanged, and this does not weaken it. A new spec joins by being listed,
|
|
# which is a maintenance cost taken deliberately: the alternative is a
|
|
# convention in filenames, which would braid what a test proves together with
|
|
# when it runs.
|
|
#
|
|
# The names are checked before they are run. Jest reads its arguments as
|
|
# PATTERNS, so a listed file that is not there contributes nothing and the run
|
|
# is green on what is left: this lane named ten files and ran six for a while,
|
|
# reporting 29 tests where its own note claimed seventy-five. Three of the
|
|
# missing four were right to go — 70196c49a1 handed two-factor, verification,
|
|
# reset and session issuance to IAM and deleted those subjects — but the lane
|
|
# never said so, and the fourth (UserController) was still live and had simply
|
|
# lost its cover. A gate that shrinks in silence is the failure this whole
|
|
# section exists to prevent.
|
|
- name: credentials
|
|
run: |
|
|
set -e
|
|
cd api
|
|
SPECS="
|
|
server/services/publicUser.spec.js
|
|
server/services/CommerceClient.spec.js
|
|
server/controllers/UserController.spec.js
|
|
server/controllers/__tests__/deleteUserFilters.spec.js
|
|
server/controllers/agents/mcpAuthorizationGuard.spec.js
|
|
server/middleware/__tests__/requireJwtAuth.spec.js
|
|
server/services/iamUser.spec.js
|
|
"
|
|
for spec in $SPECS; do
|
|
test -f "$spec" || { echo "::error::credentials lane names $spec, which is not there"; exit 1; }
|
|
done
|
|
npx jest $SPECS
|
|
cd ../packages/data-schemas
|
|
npx jest src/schema/user.spec.ts
|
|
|
|
# One row, one box. The app's top row is one row to the eye and eight files to
|
|
# the code, and each had grown its own answer: measured on production, 44s
|
|
# beside a 40, glyphs of 24 and 18, four opaque plates beside four transparent
|
|
# glyphs, and a mark rounder than any of them. `components/chrome.ts` is the
|
|
# single answer; `chrome.spec.ts` is what stops a second one growing back, by
|
|
# reading the source of every file that renders a control in that row.
|
|
#
|
|
# `Header.spec.tsx` rides along because it is the same lesson in a worse form:
|
|
# it holds which controls belong to the header and which belong to the
|
|
# sidebar, and it had been dying at IMPORT — `Tests: 0 total`, which reads in
|
|
# CI exactly like a file with nothing to say. It sat that way long enough for
|
|
# the contract it describes to reverse underneath it. A suite that cannot run
|
|
# is not a weaker gate than one that fails; it is a green light.
|
|
#
|
|
# Both were mutation-tested — every assertion here has been made to fail on
|
|
# purpose — which is what earns a lane, same as above. ~2s, no downtime.
|
|
- name: chrome
|
|
run: |
|
|
set -e
|
|
cd client
|
|
npx jest --coverage=false \
|
|
src/components/chrome.spec.ts \
|
|
src/components/Chat/Header.spec.tsx
|
|
|
|
# NO WHOLE jest suite is gated — the `credentials` gate above names eight files,
|
|
# one at a time — and that is a measurement rather than an oversight. All five
|
|
# suites are red on this commit — cb653548, node 22, pnpm 10.27.0, one full
|
|
# local run each:
|
|
# client 515 failed / 2356 packages/api 711 / 4644
|
|
# api 403 failed / 2737 data-schemas 246 / 1489
|
|
# packages/data-provider 227 failed / 985
|
|
# The client column is exactly the baseline CLAUDE.md already records (77 suites
|
|
# failed / 125 passed), so this is the long-standing state of the repo and not
|
|
# something a run introduced. `pnpm lint` does not lint a single file either:
|
|
# ESLint 9 loads @typescript-eslint/typescript-estree, which dies against the TS7
|
|
# native compiler with "Cannot read properties of undefined (reading 'Cjs')".
|
|
#
|
|
# Gating any of that ships a lane that is red on arrival and red on every commit
|
|
# after, which is read as noise within a day and then ignored — worse than no
|
|
# lane, because it looks like coverage. Turn a suite green, then add its line.
|
|
#
|
|
# Most of the red is one shape, not one bug per failure: tests importing symbols
|
|
# the source never exported — and a second shape this file used to hide inside
|
|
# the same numbers: suites that die during IMPORT and run nothing at all, which a
|
|
# failed/total count cannot distinguish from a suite that ran and disagreed.
|
|
# Twelve of the api column were that; eleven run now.
|
|
#
|
|
# The sharpest instance of the first shape was never merely a test failure. Merge
|
|
# 49dc4f7bf6 (LibreChat sync) dropped PERMISSION_TYPE_INTERFACE_FIELDS,
|
|
# INTERFACE_PERMISSION_FIELDS and PERMISSION_SUB_KEYS out of
|
|
# packages/data-provider/src/permissions.ts while packages/api/src/admin/config.ts
|
|
# and packages/data-schemas/src/app/resolution.ts kept importing them, so the
|
|
# admin config surface called `.has()` on undefined and threw on every override
|
|
# write. FIXED: restored from e77b03ab6d, which took resolution.spec.ts to 26/26
|
|
# and admin/config.handler.spec.ts to 54/54, exactly as this note predicted.
|