turbo went from 1 to 2 in the version sweep and turbo.json still said
`pipeline`, which turbo 2 refuses before it runs anything: `npm run lint`
and `npm run build` both stopped at "Found `pipeline` field instead of
`tasks`". The key is renamed; nothing under it changes.
Co-authored-by: Hanzo Dev <[email protected]>
react-table went from 8 to 9 with nothing migrated. 9 has no useReactTable
and no getCoreRowModel, so the one component every list in the app renders
through could not be imported, and ColumnDef takes the table's features as
its first type argument, so all 42 column lists stopped typing their rows:
217 of the 498 errors in the app's typecheck.
DataTable now names the four features it uses — paging, row selection,
column visibility, column sizing — and calls useTable. DataTableColumnDef is
ColumnDef over those features, so a column list still says only what a row
is. The second type parameter goes: 9's table takes columns of unknown
value, and no caller ever passed one.
Two things the types turned up. `enableSorting: false` sat on two select
columns of a table that has never sorted; without the sorting feature the
option does not exist, and neither does the sorting. The organisation
insights table erased its three row types through `as unknown as` to feed
one DataTable; it renders one per view, typed, and the casts are gone. The
server rejects any other view before the component renders.
data-table.test.tsx renders the table and drives the instance: headers and
cells, a hidden column, a sized one, one-based paging read from the props
and reported back, selection by the caller's row id. Against the old file
it fails 7 of 7 at the import.
Co-authored-by: Hanzo Dev <[email protected]>
patches/ held one file: a retyping of @ai-sdk/google-vertex 3.0.81 from the
V2 provider interfaces to V3, from when the provider package had moved and
the vertex package had not. What is installed now is 5.0.86, which imports
ProviderV4 and LanguageModelV4 itself, so the patch named a file that no
longer exists in that shape and every install ended in "Failed to apply
patch" — printed, and ignored.
With nothing left to apply, patch-package and its postinstall go too.
Co-authored-by: Hanzo Dev <[email protected]>
Spellings the new majors removed, each fatal at import rather than at a call
site, so the modules that held them could not load:
- `Schema.Enum` — zod 4 dropped the capitalised alias. `.enum` is the
property in both majors.
- `z.string().ip()` — the address formats left `z.string()`; an IP is the
union of `z.ipv4()` and `z.ipv6()`, which is what it always meant.
- `invalid_type_error` — the per-cause message params folded into `error`.
- `@noble/ciphers/chacha` — noble 2 exports a subpath only with its
extension. `@noble/hashes/sha256` is `sha2.js` now. The `.js` form resolves
under the old major too, so every noble import takes it.
Two test files in packages/prisma could not load because of these; they run.
Also drops an assignment nothing reads, in the job runner's failure branch:
ESLint 10 moved no-useless-assignment into its recommended set.
Co-authored-by: Hanzo Dev <[email protected]>
The client and CLI were raised to Prisma 7 without the migration that major
requires, so `prisma generate` stopped at P1012 and nothing downstream of it
could build.
Prisma 7 takes the connection URL out of the schema and has no query engine:
the CLI reads prisma.config.ts, and the client is handed a driver adapter.
Both read the one DATABASE_URL. The adapter holds a single connection, which
is the single writer SQLite wants by construction — the `?connection_limit=1`
suffix the old engine needed is gone, and had to be: the adapter strips
`file:` and takes the rest as the file's name.
THE TRAP IS THE ADAPTER'S DEFAULT. The engine this replaces wrote every
DateTime as an epoch-millisecond INTEGER; that is what a deployed sign.db
holds and what sqlite-sql.ts divides by 1000 to read. The better-sqlite3
adapter defaults to ISO-8601 TEXT. Left alone it would write strings into
columns full of integers, SQLite would order them by storage class rather
than by time, and nothing would throw. `timestampFormat: 'unixepoch-ms'`
keeps one representation, and timestamp.test.ts reads the column's storage
class over a second connection. Checked both ways: on the default it fails
with `createdAt is stored as text: 2026-03-04T05:06:07.890+00:00`.
packages/prisma: 52 of 52.
Co-authored-by: Hanzo Dev <[email protected]>
ESLint was raised to 10, which reads eslint.config.* and nothing else. This
repo still said it all in .eslintrc.cjs and .eslintignore, so every lint run
ended at "couldn't find an eslint.config file" — and the pre-commit hook runs
eslint over staged sources, so no commit touching a .ts file could be made.
The config is the same config in the flat spelling: every rule, every ignore
and the __tests__ override carried over, and the two legacy files are gone
rather than left beside it to disagree.
The transaction rule (inside $transaction, only `tx`) keeps both its callers.
transaction.cjs is a whole flat config, so `lint:tx` still hands it to eslint
alone — with --no-config-lookup, since --no-eslintrc and --ext no longer
exist — and the shared config spreads it in. Its test now gives the linter
the array AS SHIPPED instead of re-stating the parser beside a copy of the
rules: a copy keeps passing after the original stops selecting files. 10 of
10, and `lint:tx` is clean over packages and apps.
eslint-plugin-react finds the React version by asking the rule context for a
filename through an API ESLint 10 removed, so detection threw on the first
file. The version is stated, which never takes that path.
Co-authored-by: Hanzo Dev <[email protected]>
Three majors were raised without what each one requires, and the manifests
are where that starts.
typescript is 6.0.3 for the tools that import it, and the native 7.0.2
compiler comes in as `tsgo` and does the typechecking. typescript@7 has no JS
API at all, and prisma-kysely and typescript-eslint both import `typescript`
by name and call into it — so with 7 under that name, neither could run.
ESLint 10 reads flat config, which is built from @eslint/js, globals and
typescript-eslint; the shared config package declares them. Prisma 7 has no
query engine, so the prisma package declares the better-sqlite3 driver
adapter, and its `prisma` block goes: the CLI reads prisma.config.ts now.
Co-authored-by: Hanzo Dev <[email protected]>
Moved the go directive to 1.27.1 and verified the module with go build,
go vet and the full test suite, all clean.
Co-Authored-By: Claude Opus 5 <[email protected]>
The CLI was declared ^8.0.0-rc.12 while @prisma/client is ^7.10.0, so the
installed CLI answered "No command registered for 'generate'" and the client was
never generated. 1041 of this repo's typecheck errors are @prisma/client having
no exports to import.
This aligns the majors and nothing more. Generate still refuses, now with a
reason worth reading: the schema keeps url = env("DATABASE_URL") in its
datasource and index.ts constructs PrismaClient with datasourceUrl, both of
which Prisma 7 replaced with prisma.config.ts and a constructor adapter (P1012).
That migration is real work on how this service reaches its database, and it is
not a dependency pin.
Co-authored-by: Hanzo Dev <[email protected]>
npm refused to install at all: three entries in the overrides block
contradicted the range the same package was declared with, and an
override that disagrees with a direct dependency is an EOVERRIDE, not a
tie-break. All three were left behind by "Give every package one
version", which raised the declared ranges and never revisited the pins
below them.
zod already showed the way, so ws, pdfjs-dist and typescript now use the
same $name form and follow whatever the manifests declare. The reason
pdfjs-dist was held at 5.4.296 was react-pdf, which this repo no longer
depends on; react-pdf itself now asks for the 6.3.289 we declare.
The same commit pinned @hanzo/insights-node to 6.0.0, which nobody can
install: it depends on insights-node 5.10.4, an unscoped name published
to no registry. Back to 5.26.2, the newest release that resolves, until
a 6.x ships with its scope intact.
Co-authored-by: Hanzo Dev <[email protected]>
The purpose-prefixed labels (hanzo-build-*, hanzo-deploy-*, hanzo-docs-build-*)
are retired in favour of the bare <os>-<arch> every runner already advertises.
They were not a capability, only a habit, and the habit cost builds: papers asked
for hanzo-build-linux-amd64, no online runner offered it, and two idle
linux-amd64 runners sat beside the queue while the build simply never ran. The
lux tree lost three days to lux-linux-amd64 the same way.
What a job is FOR is the workflow's name. runs-on is the machine it needs.
runs-on was a bare scalar. Jobs written that way queue and are never
picked up — link and quest have waited since 10 August, painter since the
5th, none of them with a single success — while repos writing the same
label as a sequence drain within seconds of a push on the same fleet.
Same label, same runners, different shape. It fails silently: nothing errors,
the job simply never starts, so the lane looks configured and does nothing.
certclaims flagged five lines on the compliance page and they were three
different problems:
A CLAIM. The status table and its callout both said SOC 2 was "Certification
scoped per engagement" — a certificate, conditioned. They now say compliant with
the Type II control set, which is what the section below already described:
encryption in transit and at rest, RBAC, audit logging.
TWO HEDGES. ISO 27001 and HIPAA were "Planned". A hedge reads as a claim to
anyone skimming and dates itself the day it ships. They now say controls aligned,
with a BAA on request for HIPAA — the thing a buyer can actually act on.
A FALSE POSITIVE, fixed without weakening the rule. The definition of SOC 2
tripped on "American Institute of Certified Public Accountants" — the auditor
body's own name, beside the framework name. The acronym says the same thing, so
the sentence reads "established by the AICPA" and the gate stays as strict as it
was. Narrowing the rule to let this through would have let real claims through
too.
And the sentence this shares with bootnode: "Formal certification — SOC 2 Type
II, ISO 27001, HIPAA with a BAA — is scoped per enterprise engagement" now offers
what is on the table instead of a certificate that is not.
Nothing about the security posture changed. Only the sentences describing it.
Co-authored-by: Hanzo Dev <[email protected]>
docker/Dockerfile is Node throughout and asks for no secret, and this
repo's go.mod requires no module, so GIT_AUTH_TOKEN reached a build with
nothing to fetch.
The ISO 27001 and HIPAA rows on this page say Planned, which is true. The
SOC 2 row said Compliant and the section callout linked that word to the
trust page, so a reader took it as a report that exists. It does not — no
auditor, no attestation, no Type II period. The row and the callout now say
what a buyer can act on, and the section states the controls actually in
place and where formal certification gets decided.
The two endpoints read out of it are settings of the identity provider — they
change when someone deploys a new one, not between two sign-ins a second apart.
Fetching it per request made every sign-in depend on a live round trip, so one
edge blip surfaced as `500 Failed to fetch OIDC configuration: Bad Gateway`
with nothing to fall back on.
Serve from memory while fresh; if a refresh fails, keep serving the last
document that worked. Only a process that has never reached the provider can
fail here. The fetch also gets a timeout, so a hung provider no longer hangs
the request.
A mail send survives a brief failure at either hop: three tries, backing
off threefold with jitter, on an unreachable host, a 5xx or an unreadable
token. A 4xx and a reported delivery failure are answers, and are raised
on the spot.
One shape for every empty list — documents, templates, inbox, webhooks,
groups — the mark above the reason. The signing card's pattern is drawn
in grey with the rest of the palette.
The dark page is 0%. Depth is the ramp above it: surfaces at 6-7%, menu
hover at 16%, edges at 18%, all without hue. Fields, dialogs, sheets and
toasts sit on the surface step so they read against the page.
Base SQLite serves one write connection. A call that leaves the process
does not deadlock the way a second Prisma client does, but it holds that
one connection open across the network: every other write in the process
waits on a mail server, and a slow send reaches the same P2028 timeout by
a longer road.
Six waits sat inside a transaction. Three of them wrapped a single write,
so the transaction was doing nothing — keep the order, drop it, and one
write is still atomic on its own. The fourth returns the envelope it
created and tells the template owner about it afterwards, where a bounced
notification is logged rather than rolling back a document that exists.
The rule then asks the general question instead of naming callees: is the
client called tx, does the callback name a client that is not tx, and does
it await a call it was never handed tx. Jobs and webhooks fall out of the
third, along with mail and anything else that leaves the process, and it
reads the call site's shape rather than its callee's name — so a helper
that reaches the world from its own body is no longer invisible.
packages/auth opens three transactions — session, the OAuth callback and
account deletion — and sat outside the four directories the check named.
The sweep costs three seconds over 1726 files, so it reads every package
and app instead. A package that opens a transaction is covered by being
written, not by being remembered in a list.
Base SQLite serves one write connection. `prisma.$transaction(async (tx)
=> …)` holds it for as long as the callback runs, so a call in the
callback that reaches the global client queues behind a lock that call is
itself blocking. It waits out the busy timeout and the transaction dies
with P2028 — at runtime, on a green build. That is what stopped every
document creation at 0.1.4, across nine sites; moving the side effects
after the commit fixed them but left nothing to stop the next one.
packages/eslint-config/transaction.cjs states the invariant once, as
three esquery selectors: the global client named inside a $transaction
callback, and the two side effects that reach it without naming it,
triggerJob and triggerWebhook. Two callers read it — the shared config,
so an editor says it while you type, and `npm run lint:tx`, which runs it
alone over packages/{lib,trpc,api} and apps/remix in three seconds and is
now the first thing the gate does, ahead of the build that cannot see
this. Inline comments are off there, so the invariant is not silenced by
a comment.
The rule alone would rot into a no-op unnoticed, so lint:tx first runs
the eslint-config tests, which assert each selector still fires and that
`tx` on its own, and the non-interactive batch form, stay clean.
It reads the call site, not the call graph: a helper that reaches the
global client from its own body is still invisible to it.
Three unrelated defects, one image.
The profile toast fired only after a session refresh. That refresh is a second
read: it can stall, and when it answers unauthenticated it clears the session,
which throws in useSession and takes down the subtree holding the toaster. A
save that had already persisted could therefore go unconfirmed. The save is
confirmed on the save now and the refresh follows it, so the confirmation
depends only on the thing it reports.
Document bytes are routed by NEXT_PUBLIC_UPLOAD_TRANSPORT. Three places read it
with three slightly different rules, and the two that chose a store fell through
to the database for any value they did not recognise -- so a transport named
under a variable nothing reads stored every PDF inline while the configuration
read as object storage, with nothing logged either way. One function resolves it
now: unset means the database, and an unrecognised value throws rather than
quietly picking a store. Reads still dispatch on each row's own type, so rows
written under either transport keep working.
entry.client and entry.server built different root trees -- StrictMode on the
client only, and a second child beside the router. useId numbers a component by
its position, and a parent's child count is part of that position, so every id
below the root diverged and hydration failed on the first component to ask for
one. Measured on the documents page: the header's menu trigger came back
radix-:R26j5: from the server and radix-:R8qcl: from the client. The trees are
identical now and all seven server-rendered ids survive hydration unchanged.
Also: .gitignore still named the license artifact by its pre-rebrand filename,
so the file the app actually writes was landing in git status untracked.
A document no longer has to come off this machine. Paste text or Markdown and
it is rendered to a PDF; give a link and the server fetches it, including a
GitHub file page, which it reads as the file that page shows.
Every source resolves to bytes in one place and is created by envelope.create,
the same call an upload makes, so the editor and everything past it cannot tell
them apart. The upload button and the drop zone now share that one creation
path rather than each carrying a copy.
The server dials http(s) only, and never an address that resolves inside the
cluster — checked on the hostname, on every address it resolves to, and again
on each redirect. It reads at most the upload limit and requires the bytes to
be a PDF.
State now reads through shape, weight and tone rather than hue: the
active editor step and selected file carry a filled mark and heavier
label, recipients are told apart by six neutral tones, and status icons
lean on the clock/check/file glyphs they already draw. Red stays as the
one accent, reserved for destructive and rejected.
Drops the unreferenced --new-* ramps and the dawn/water scales, points
the stock Tailwind ring at --ring, and desaturates the sign scale so the
PDF certificate, audit log and email templates match the app.
Envelope id allocation now establishes the counter row it reads, so the
first document on a database that has never allocated one gets id 1.
Nothing inside a transaction reaches the global Prisma client any more.
Base SQLite serves one write connection, so a second client waiting on
the lock the transaction already holds stalls until the busy timeout and
the transaction dies. Webhooks, job triggers and verification emails are
side effects of committed state, so they fire after the commit; the
organisation link no longer nests a second transaction inside itself; and
a recipient lookup in the v1 field API reads on its own transaction.
EXISTS subqueries name a column. Postgres accepts an empty select list,
SQLite does not, which is what stopped the seal sweep, the unsealed
document view and signing volume from running at all.
Also:
- the profile form saves a name before a signature exists, matching the
server schema, instead of failing validation with nothing to show
- every upload reads its error from one place, so a failed template
upload says template
- the documents filter strip scrolls its selected tab into view
- the inbox and support headings use the shared page header
- one settings nav item, so a click lands on the link and fires once
- the command dialog names itself for screen readers, and the mobile
search and menu buttons carry labels
- documentation links point at docs.hanzo.ai
- the organisations row links to the organisation
- the app is called Hanzo Sign throughout, the wordmark variables are
declared to turbo, and the webhook secret rides a valid header name
SuperJSON carries typed values, not bytes: a FormData serialised to `{}`, so
an upload route received no payload. The transport now reads a File into
bytes on the way out and rebuilds it on the way in, so a route parses the
same FormData it received over HTTP — one encoding for all six upload
callsites, none of their own.
The editor's add-items failure now reports itself instead of only marking
the file card.
One PageHeader owns the page title and the single gap to its description,
replacing the hand-rolled title blocks; SettingsHeader takes the same gap,
which was zero on mobile.
The team provider renders during SSR too, where one process serves every
tenant at once — a module-level team set there is one request's scope
sitting where another request can read it. Off the browser there is no
socket to scope, so the setter does nothing.
Team scope never reached the server. mint() takes it from an `x-team-id`
header on the WebSocket handshake, but a browser cannot set headers on a
WebSocket, so every session ran at teamId -1 and the team-scoped reads
answered "Team not found". The envelope already has a teamId field: the
app declares the team once, where it knows it, and every call carries it.
Handlers still resolve the team through the caller's membership, so this
selects a scope rather than granting one.
Branding: the wordmark comes from NEXT_PUBLIC_APP_NAME at runtime instead
of being drawn into the logo, so a tenant rebrands by setting env. The
mark's geometry now has one home that the icon, the wordmark and the
signin lockup all draw from.
The rate-limit sweep deletes by rowid, SQLite's row identity.
A WebSocket is dialled by absolute origin and has no relative form, but
getBaseUrl() answers '' in the browser — the convention the HTTP transport
wants — so zapUrl() built `new URL('')` and threw before the socket opened.
Read the origin off the page there; keep the configured URL off-page.
isAdmin answers false for an absent user or roles column instead of
throwing, so the app shell renders while a session refresh settles.
closeZap claims the connection slot before awaiting, so a dial that races a
logout gets a fresh connection instead of an orphaned one.
ARCHITECTURE: the internal RPC mount is /zap over WebSocket, routed by
`<router>.<procedure>` in the body; /api/trpc no longer exists.
upload-artifact@v4 carries @actions/artifact v2, which refuses any host it does
not recognise as github.com: GHESNotSupportedError. It failed the job by itself,
so a passing suite would still have reported red.
On the Base SQLite schema User.roles is a JSON string[], so no model field
references the Role enum and the generated client no longer exports it.
Importing it yields undefined; app-header's isAdmin did Role.ADMIN and took
down the whole authenticated shell with a 500. Match the role name directly
in the four value-usages (isAdmin, the admin update schema, the role
combobox, the seed). Fixes the authenticated app on 0.1.1's Base cutover.
deploy.yml, docker-deploy.yml and publish.yml were identical: one job echoing
'native pipeline is .hanzo/workflows/deploy.yml'. They reported success for
doing nothing, which reads greener than having no workflow at all.
e2e-tests.yml was the only file there doing work -- services up, prisma migrate
and seed, then the suite. Its label was warp-ubuntu-2204-x64-8x, a WarpBuild
runner this fleet does not have, and an unserved label queues instead of
failing. .github/actions/{node-install,playwright-install} stay put and still
resolve.
The authenticated header, the embed footers and the PDF audit trail all
rendered the upstream Documenso wordmark and seal; the theme accent was
Documenso green while the signin CTA was red — two brand accents in one
product. Point the two branding components at the Hanzo house mark
(mono via currentColor) so every surface reads "Hanzo Sign" from one
source, and move the theme's primary/ring/field tokens to black-on-white
/ white-on-black. Destructive keeps red for delete/error semantics only,
minus the harsh pure-red.
The completion page a recipient saw after signing offered to "claim your
account" with a name, email and password. That is the same local password
account /signup used to create, and it could not sign in either: /signin
offers the identity provider and nothing else.
Removing it also restores the build. The panel imported signupErrorMessages
from the signup form deleted in the previous commit, so the app failed to
typecheck with TS2307 on that module.
The completion page keeps the single-column layout it already rendered for
recipients who had accounts; the conditional two-column variant existed only
to make room for this panel.
Co-authored-by: Hanzo Dev <[email protected]>
/signup rendered a local name/email/password form. The account it produced
could not then sign in: /signin offers the identity provider and nothing else,
so a locally created password had nowhere to be typed. /signup now leads to
the same door as /signin, carrying returnTo through, and the provider offers
account creation on that page.
Drops the form, its bcrypt round trip, and the two end-to-end tests that drove
the local password flow — they covered a path the app no longer offers. The
NEXT_PUBLIC_DISABLE_SIGNUP switch goes with them: there is one way in, so
there is nothing left to switch off.
Co-authored-by: Hanzo Dev <[email protected]>
Adds a notify transport, selected with NEXT_PRIVATE_SMTP_TRANSPORT=notify.
Notify keeps the provider credentials in KMS and picks the provider per org,
so nothing here carries a secret and nothing here names a sender — the from
address belongs to the credential. It authenticates with the same IAM client
that already signs users in, presented as a machine token and cached until
just before expiry.
A refused delivery arrives as HTTP 200 with status "failed", so the transport
reads the outcome in the body rather than the status line; otherwise a send
that never left would be reported as sent.
Also spells the default SMTP host without a port. "127.0.0.1:2500" is not a
hostname, so DNS rejected the name and an unconfigured deployment failed as a
name-lookup error rather than a refused connection.
Co-authored-by: Hanzo Dev <[email protected]>
ESLint normalises a scoped shareable config `@scope/name` to
`@scope/eslint-config-name`. When this fork renamed `@documenso/eslint-config`
to `@hanzo/esign-eslint-config`, that normalisation started looking for
`@hanzo/eslint-config-esign-eslint-config`, which does not exist — so every
`eslint` invocation, including the lint-staged pre-commit hook, died with
"couldn't find the config to extend from" instead of linting. The gate has
been inert since the rename.
Referencing the config by resolved path keeps the package name matching the
`@hanzo/esign-*` convention and skips the normalisation entirely.
Lint runs again and reports 16 pre-existing errors across apps/remix,
packages/lib and packages/trpc (promise-function-async, ban-types,
require-await, undeclared turbo env vars) that were invisible while it was
broken. They are left for their owners; nothing in this repo's CI runs lint,
and lint-staged only checks files a commit touches.
Radix was leaking out of the primitive layer into application code. Thirty
dialog components under apps/remix imported `@radix-ui/react-dialog` purely to
spell `Omit<DialogPrimitive.DialogProps, 'children'>`, and two more imported
`TooltipArrow` directly. Each of those is a place the UI substrate has to be
swapped when it changes, for no gain — the app never touched Radix behaviour,
only a type and one arrow element.
`packages/ui/primitives/dialog.tsx` now exports `DialogProps` and
`tooltip.tsx` exports `TooltipArrow`, so callers name the primitive, not the
library behind it. Radix imports outside packages/ui: 32 -> 0.
Eleven vendored shadcn primitives had no importers at all — aspect-ratio,
auto-sized-text, calendar, collapsible, context-menu, menubar,
navigation-menu, slider, toggle, a stray document-flow dialog and a
signature-pad Canvas class shadowed by skia-canvas. Dead component code reads
as supported API in review, so it is deleted rather than carried. That
retires eight Radix packages plus react-day-picker from packages/ui and
@radix-ui/react-tabs from apps/docs, which was declared and never imported:
28 declared Radix packages -> 19.
The embedding doc taught Tailwind classes in a snippet third parties copy into
their own apps; it now uses the same inline styles as the plain-HTML example
directly above it. `.cursorrules` told every AI touching this repo to write
"Shadcn UI, Radix, and Tailwind Aria" — it now points at @hanzo/gui and says
the inherited layer is to be replaced, not extended.
Verified: apps/remix typecheck 0 errors, client+server build green,
prisma 38/38.
npm ci refused the install, so nothing after it ran:
`npm ci` can only install packages when your package.json and
package-lock.json are in sync.
Missing: [email protected] from lock file
apps/openpage-api asked for 5.6.2 while the other seven declarations — root,
ui, prisma, eslint-config, docs, remix — are all 5.9.3, and the lock only had
5.9.3. It is 5.9.3 now, which is both what the lockfile can satisfy and one
compiler for the monorepo instead of two.
Gate verified end to end from a clean node_modules: npm ci, then
turbo run build --filter=@hanzo/esign-remix, 3 of 3 tasks.
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.
Co-authored-by: Hanzo Dev <[email protected]>
1fa47d4 ended with
Error while stop job container: Cannot connect to the Docker daemon at
unix:///var/run/docker.sock
which is the git-runner fleet ending the job, not this repo failing: a rolling
restart stops act_runner and dockerd concurrently (s6 siblings), so the job
container disappears underneath a build that was fine.
Nothing here changed. This re-runs the server/* path mapping on a stable fleet
so the typecheck fix actually gets exercised.
Co-authored-by: Hanzo Dev <[email protected]>
`turbo run build --filter=@hanzo/esign-remix` fails its typecheck with
error TS2307: Cannot find module 'server/utils/get-loader-session'
error TS2307: Cannot find module 'server/context'
error TS2307: Cannot find module 'server/router'
All three files exist — apps/remix/server/context.ts, server/router.ts,
server/utils/get-loader-session.ts. The problem is that they are imported as
NON-RELATIVE specifiers, and a bare specifier has no root to resolve against
unless it is named in `paths` or `baseUrl` is set. Neither was true, so tsc
looked for an npm package called `server`.
Added `"server/*": ["./server/*"]` rather than setting baseUrl. baseUrl would
change resolution for every bare specifier in the project, letting any
top-level directory shadow a package of the same name; this maps exactly the
one prefix that needs it. paths-without-baseUrl already works in this config —
the existing `~/*` entry relies on it.
Verified by resolving each specifier through the tsconfig's own path rules:
all three land on the real files. A full tsc run is not possible in a bare
clone (no node_modules — it stops earlier on missing @types/node), so CI is
what exercises the whole typecheck.
Co-authored-by: Hanzo Dev <[email protected]>