Skip to content

chore(release): v0.16.0 - #324

Open
github-actions[bot] wants to merge 13 commits into
mainfrom
release
Open

chore(release): v0.16.0#324
github-actions[bot] wants to merge 13 commits into
mainfrom
release

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Release v0.16.0

Version bump: release:minor
Previous version: v0.15.0

What happens on merge

  1. Build + push all 4 images tagged 0.16.0 + latest
  2. Create git tag v0.16.0
  3. Create GitHub Release with auto-generated notes

Auto-prepared by prepare-release workflow

…sole migration (#322)

* build(logger): swap pino for logtape, add temporal subpath + jest

* fix(logger): align @logtape/pretty and @logtape/redaction to 2.2.1 peer range

* feat(logger): configureLogging with env formatter, base fields, bigint replacer

* feat(logger): OTel-compatible correlation field constants

* feat(logger): central redaction (field+pattern+deep+numeric formatter) and promoted supplier redactors

* fix(logger): cycle guard in redactObject + password/bearer value backstops

* feat(logger): pluggable client sink + minimalSink + browser configure branch

* feat(logger): logtape-native public API + correlation helpers + promote provider-workflows redactors

* fix(logger): drop getClientSink from public barrel per spec

* feat(logger): logtape-backed temporal bridge on @igniter/logger/temporal subpath

* build: inject APP_VERSION + SERVICE_NAME per app for service.version base field

* fix(ci): pass APP_VERSION build-arg in real deploy build steps

* refactor(logger): convert pocket/notifications/temporal/domain callers to native logtape shape

Flip remaining pino-style obj-first calls (info({obj}, 'msg')) to LogTape
message-first (info('msg', {obj})), and the pocket .child({service}) call to
getLogger(['pocket', 'blockchain']). Covers packages/pocket, all three
notification channels + notifier, packages/temporal (client, worker,
scheduleWatchdog), and one domain handler call whose second arg wasn't a
properties record.

* refactor(logger): convert workflow app loggers to logtape, wire configureLogging in workers

Flip obj-first calls to message-first in both apps' bootstrap.ts/worker.ts and
DAL settings, and replace logger.child({context}) with logger.getChild(name)
in both DAL.ts constructors and the ScheduleWatchdog wiring in worker.ts.

Also call configureLogging({ serviceName }) as the first statement in
setupTemporalWorker() for both provider-workflows and middleman-workflows —
without it LogTape emits nothing, so the converted workers would type-check
but produce zero log output on staging.

* refactor(temporal): convert workflowView optional logger hook to logtape shape

* refactor(pocket,commons): replace console.* with injected logger (optional, getLogger default)

* ci: path-scoped no-console grep guard + advisory eslint rule

* fix(ci): no-console guard on portable grep with loud failure modes

Replace rg (not preinstalled on ubuntu-latest) with POSIX grep -rEn.
Narrow the match pattern to call-shape only so it stops false-positiving
on prose mentions of console.* in comments. Handle grep's exit codes
explicitly instead of blanket `|| true`, so a broken invocation fails
loudly (exit 2) rather than silently reporting a clean guard. Drop the
invented, gameable __sanctioned__/** exemption -- packages/logger/src
has zero real console calls to exempt.

* fix(logger): keep AsyncLocalStorage require out of static bundler resolution

Task 11 (#219) Docker/Next.js build validation surfaced a real build
break: config.ts's top-level `import { AsyncLocalStorage } from
'node:async_hooks'` was only guarded at the usage site
(`runtime === 'node' ? { contextLocalStorage: ... } : {}`), not at the
import site. Static imports get resolved by bundlers unconditionally
regardless of runtime branching, so any 'use client' component that
transitively imports @igniter/logger (e.g. apps/provider's settings
Form.tsx -> @igniter/commons/crypto -> @igniter/logger) dragged
node:async_hooks into webpack's browser compilation, which has no
node: scheme handler -> hard UnhandledSchemeError build failure.

Fix: resolve AsyncLocalStorage lazily via a variable-indirected
require() (only reached when detectRuntime() === 'node'), typed
against LogTape's own ContextLocalStorage interface instead of Node's
type, since AsyncLocalStorage already structurally satisfies it. This
keeps the specifier out of webpack's static require analysis (same
pattern @temporalio/common's own dynamic requires already rely on)
without changing any runtime behavior.

Verified: logger unit tests (33/33) and check-types green; full
`pnpm turbo build` now succeeds for both Next.js apps (provider,
middleman); Docker builds for provider-workflows and provider succeed
with correct APP_VERSION/SERVICE_NAME at runtime; client and edge
bundles carry the loader only as dead code behind the same
runtime === 'node' guard, never invoked outside Node.

* fix(logger): lazy base-field binding so pre-configure loggers get service fields

getLogger() bound base fields via .with(getBaseFields()), which snapshots a
plain object at call time. Module-scope loggers (workers' worker.ts roots,
notification channels) are created before configureLogging() runs, so they
snapshotted {} forever and never carried service.name/service.version. Bind
each field via LogTape's lazy() instead, which resolves at record time.

Adds a regression test that creates a logger before configureLogging() and
asserts the resolved service.name reaches the log record.

* feat(apps): wire configureLogging via instrumentation.ts in provider and middleman

Neither Next.js app called configureLogging(), so no service.name/version
base fields or LogTape sinks were ever wired in either app's server runtime —
a full log blackout. Next 15 auto-detects src/instrumentation.ts; register()
calls configureLogging() with no args, since serviceName falls back to the
SERVICE_NAME env var already set per-app in each Dockerfile.

* fix(logger): node18-safe newRequestId + brace-safe temporal bridge + cycle-safe prod formatter

- newRequestId: guarded globalThis.crypto?.randomUUID with runtime-assembled
  node:crypto fallback (Node 18 CI jest has no global crypto)
- temporal bridge: escape foreign-message braces (LogTape doubled-brace escape)
  so JSON blobs render verbatim instead of undefined placeholders
- prod NDJSON: safeStringify WeakSet circular guard so cyclic props emit a
  line with [circular] instead of silently dropping the record

* fix(logger): scope bigint serialization to log formatters, drop global prototype patch

Delete installBigIntJson (global BigInt.prototype.toJSON patch) which silently
changed the Temporal payload boundary (bigint crossed as string instead of
throwing loud, which activities/index.ts relies on). bigint->string now handled
only inside prodJsonReplacer; pretty path verified not to throw. Test asserts the
global patch is gone (JSON.stringify on bigint throws again at global scope).

* fix(apps): configureLogging in seeds + move APP_VERSION to runner stage + template-literal log props

- bootstrap seeds: await configureLogging() at top of main so getLogger() calls
  hit configured sinks instead of a no-sink blackhole
- Dockerfiles (all 4): move ARG/ENV APP_VERSION to the final/runner stage so a
  per-deploy version bump stops invalidating install/build layers
- convert JSON/error/reason template-literal log messages to props (brace-safe):
  CompareSupplierServiceConfigHandler, middleman provider service, and the four
  SupplierStatus/Remediation child-workflow-failed logs

* docs: align spec/readme/env samples with logtape levels

- spec §0: @logtape/redaction 1.3.6->2.2.1; bigint row = scoped-in-formatters
  (no global patch); edge-output row documents accepted F2 console-routing deviation
- READMEs + .env.sample: LOG_LEVEL valid LogTape levels (warning not warn), default debug
- append adversarial-panel fix section to task-11 report

* feat(logger): render structured properties in dev pretty output

Dev pretty formatter now uses getPrettyFormatter({ properties: true })
so payload-carrying debug lines (drizzle query/params) are visible
locally. Constant base fields (service.*, env, runtime) are dropped
from dev rendering only — prod NDJSON keeps them for the collector.
Also drop the dangling colon from the drizzle query log message.

* feat(logger): LOG_FORMAT env knob; localnet defaults to NDJSON

LOG_FORMAT=json|pretty overrides the NODE_ENV-derived output format.
Localnet dev ConfigMaps set json for all four apps: one-line NDJSON is
agent/jq-friendly for log-driven debugging and gives format parity
with what Loki will ingest in prod. Laptop dev (env unset) stays pretty.

* fix(logger): store base fields and client sink in global symbol registry

Next.js can instantiate this package twice on the server (transpilePackages
copy + dist CJS copy via workspace deps). LogTape survives that via
Symbol.for globals, but our module-level state did not: configureLogging()
populated one copy's baseFields while @igniter/db's logger read the other
copy's empty object, so lazy base fields resolved to undefined and dropped
out of NDJSON records. Both stores now use the same Symbol.for pattern
LogTape itself uses.

* fix(apps): pass explicit serviceName in Next instrumentation

Matches the workers' bootstrap pattern; without it, localnet/dev pods
(no prod-image SERVICE_NAME env) stamped service.name=unknown.

* fix(logger): drop dynamic requires — hard build errors under Turbopack

Turbopack analyzes every require() call site and fails the build on
non-literal specifiers ('Can't resolve <dynamic>'); webpack only warned.
newRequestId now uses global Web Crypto with a pure-JS v4 fallback (no
node builtin at all — the id is a correlation token, not a credential).
loadContextLocalStorage switches to eval('require'), the escape hatch
opaque to both bundlers, still node-branch-guarded and fail-soft.

* fix(logger): static async_hooks import via dedicated module + browser-field substitution

Third and final form of the ALS loading strategy. Dynamic-specifier
require broke Turbopack ('Can't resolve <dynamic>'); eval('require')
broke the Next Edge compile ('Dynamic Code Evaluation not allowed').
context-storage.ts now imports node:async_hooks STATICALLY — the Edge
runtime supports AsyncLocalStorage natively — and package.json's
top-level browser field substitutes a no-op for client bundles, which
webpack and Turbopack both honor. No dynamic resolution anywhere.

* fix(db): redact query params for sensitive tables, truncate long values

Drizzle's logQuery receives positional params, invisible to the logger's
key-name-based redaction — an insert into keys emitted the encrypted
privateKey blob verbatim (found auditing real localnet flows). Queries
touching secret-bearing tables (keys, notification_channels,
smtp_configuration, auth tables) now log '[redacted]' params; long string
params elsewhere are truncated.

* feat(workflows): migrate remaining console + log-quality pass (#218)

Migrate the last 14 raw console.* calls in middleman-workflows (13) and
provider-workflows (1) to structured logging: @temporalio/activity log
in activities, injected Logger in the (unused) Blockchain lib class,
@temporalio/workflow log in worker.ts's own error path.

Log-quality pass from the localnet debuggability audit:
- executeTransaction now logs the broadcast result (transactionId, hash, type)
- applyVerificationDecision now logs the verify verdict (transactionId, hash,
  success, code, gasUsed) instead of leaving it silent
- ExecuteTransaction workflow logs transactionId/hash on every branch instead
  of only embedding transactionId in the workflow_id string
- ExecutePendingTransactions/VerifyPendingTransactions (both apps) log a
  single debug line on 0-pending instead of staying silent
- remediateSupplier's no-op "Bye!" line demoted info->debug (was ~1,150
  INFO/2h); SupplierRemediationByRange's Execution Ended summary gains
  keysChecked/remediated counts as the aggregation point
- indexerApiUrl-missing warns collapse to once-per-process via a module-level
  guard in the activity file (never in the workflow sandbox)

* feat(middleman): request-id correlation via edge middleware + server helper

Adds withLogging()/runWithRequestContext() (packages/middleman/src/lib/logging)
so route handlers and server actions bind request_id for the duration of the
call. middleware.ts now reads/mints x-request-id and propagates it on both the
forwarded request (edge -> node continuity) and the response, without
changing the existing NextAuth redirect-if-unauthenticated semantics.

* feat(middleman): migrate server console.* to structured logger + quality pass

Server actions, DAL, auth, API routes, and seed scripts now use
getLogger(['middleman', <area>]) instead of raw console.*. Notable quality
changes (audit items 3/6/8):

- Stake.ts/Unstake.ts had zero logs; add request/created op anchors
  ({ownerAddress, providerId, transactionId}) so app-side stake/unstake
  actions correlate with the workflow-side trace.
- provider-rpc/route.ts: replaced 8 decorative console.log/error with
  leveled logs, one debug-level redactObject() dump of the outbound
  payload, and a single info-level completion line with status+durationMs
  instead of multiline JSON dumps.
- auth.ts (SIWP authorize): dropped raw logging of signature/message/
  publicKey (secrets/auth material); logs domain + address only.
- ImportSuppliers.ts: dropped addresses from the "skipped" warn (count
  only); added create/status/complete lifecycle logs.
- Wrapped the touched action entry points (Stake, Unstake, ImportSuppliers,
  Providers) in runWithRequestContext() and the touched routes
  (health, provider-rpc) in withLogging() for request_id correlation.

* feat(middleman): migrate client component console.* to structured logger

'use client' components/forms and the browser-side provider RPC helper now
use getLogger() (browser-safe sink) instead of console.error/log for error
paths and swallowed catches, with the entity that makes the line debuggable
(ownerAddress, provider identity, attemptId) attached as structured props.
Cleanup-only per spec — client observability stays out of scope.

* feat(provider): request-id correlation via edge middleware + server helper

Mirrors apps/middleman's pattern: withLogging()/runWithRequestContext()
propagate x-request-id from the edge middleware into route handlers and
server actions. withAuth (the single chokepoint every provider server
action funnels through) binds correlation for the whole action call.

* refactor(provider): migrate signature validation + suppliers/status/health routes to logger

lib/utils/routes.ts collapses the 17-line-per-request signature validation
trace into a single debug line ({delegator, identity, verified}) — never
logging the X-Middleman-Signature/Identity header values (audit item 6).
Suppliers routes (stake/release/unstaking/suppliers/address-groups) log
{supplierAddresses, ownerAddress, delegatorIdentity} at the entities that
make a failure debuggable, wrapped with withLogging for correlation
(audit item 5). auth.ts, status and health routes get the same
console->logger swap with no control-flow changes.

* refactor(provider): migrate import-suppliers routes + add key-imported event

import-suppliers/{submit,request,status} now log {attemptId, ownerAddress,
delegatorIdentity, addressCount} at request outcomes instead of a console
line per branch, wrapped with withLogging for correlation (audit item 8).

actions/Keys.ts ImportKeys emits `key imported` per inserted key
({address, ownerAddress, state}) at the DB insert boundary — the
"3am debugger" event the audit flagged as missing (audit item 7).

* refactor(provider): migrate bootstrap-seed/seed + adr36/users to logger

bootstrap-seed.ts collapses 35 per-step console lines into per-section
info summaries with per-item debug detail (region/relay-miner/service/
address-group/delegator/channel counts) — never logging env or key
material. db/seed.ts, adr36 signature verification, and the users DAL
get the same console->logger swap.

* refactor(provider): migrate client component console.* to structured logger

Cleanup-only per spec §10.3 (category 5): every client component swaps
console.error(msg, err) for a module-scoped getLogger(['provider','ui',...])
log.error(msg, { error }). Also drops two leftover dev debug console.log
lines (ConfigureAppSettings, ConfigureDelegators) and promotes the
ImportProcess "invalid file" console.log to a proper debug-level log with
error context, since it's a real validation-failure path, not dev spew.

* fix(provider): truncate delegator identity in log props per spec §7

* ci: no-console guard repo-wide + final validation

* refactor(ui): migrate console to logger, guard packages/ui

* refactor(logs): app db categories + demote per-item cron noise to debug

- getLogger() in middleman/provider db/index.ts + middleman bootstrap-seed
  was emitting logger:"" (root category); scoped to ['<app>','db'].
- remediateSupplier/upsertSupplierStatus per-item start/query/update/done
  lines (~600 lines/30m dominant INFO source) demoted to debug in both
  provider-workflows and middleman-workflows.
- middleman-workflows SupplierStatusByRange had no per-range aggregation
  log (provider-workflows already had one); added
  "supplier sync range done" at info with keysChecked/upserted.
- transaction verified log: dropped code/gasUsed when undefined
  (degraded-RPC success path had no hash evidence) instead of logging
  null; added txn.type since it's readily available.

* fix(logger): redact snake_case secret fields; single-pass brace escape

Redaction patterns were camelCase-anchored, so snake_case secrets
(private_key, api_key, access_token, signer_private_key, seed_phrase,
set_cookie, ...) leaked through the sink — including our Temporal bridge's
snake_cased meta. Tolerate underscores between words in each pattern;
public_key still survives (locked rule). temporal escapeBraces now does a
single .replace(/[{}]/g, m => m+m) so already-doubled braces are not
re-doubled.

* fix(apps): configure browser LogTape; drop unauth-redirect to debug

instrumentation.ts register() runs server/edge only, so client-component
logs went into an unconfigured LogTape and were silently dropped. Add
src/instrumentation-client.ts (Next.js 15 client bootstrap) to both apps;
it calls configureLogging which wires the browser sinks. Also lower the
'unauthenticated request redirected' middleware log from info to debug —
bot/asset probing made it noise.

* fix(scripts): make no-console guard default-safe with exclusions list

Old guard only scanned an allowlist, so any unmigrated path nobody added
was silently ignored. Now scan every apps/*/src and packages/*/src by
default, minus an explicit prefix-exclusions list (renamed
no-console-allowlist.txt -> no-console-exclusions.txt). packages/logger/src
is not excluded (verified zero console writers). Exit-code discipline kept.

* fix(logger): harden redaction + console guard per review

Applies Miguel's PR #322 review findings (all latent, none active today):

- browser/client sinks get the same value-pattern backstop as node:
  new redactSinkByPattern scrubs SECRET_VALUE_PATTERNS from record
  message parts and string property values, since the library's
  redactByPattern only wraps TextFormatters and browser sinks have none
- Bearer free-text pattern is now case-insensitive (lowercase
  'authorization: bearer <tok>' header dumps had no backstop)
- botToken/webhookUrl/encryptionKey (+ snake_case) join the secret
  field set
- console guard: exclusions now anchor to the path field only (a
  violation whose content mentioned an excluded path was silently
  dropped) and the method alternation covers the full console surface
  (table/dir/assert/group/...), with known blind spots documented

Not applied: exports 'browser' condition (review LOW 3) — exports cannot
remap the internal relative context-storage import and a self-reference
subpath doesn't typecheck under classic moduleResolution; decision
documented in config.ts.
…UI (#323)

* fix(temporal): recreate schedules whose scheduler workflow is corrupt

A schedule whose internal scheduler workflow has a Workflow Task in
failed state rejects describe/update/trigger with FAILED_PRECONDITION,
which crash-looped the worker at bootstrap (ensureSchedule rethrow) and
made the watchdog skip the schedule forever. Delete+recreate is the only
heal for that state.

- isCorruptSchedule: gRPC code 9 + WFT-failed message, walking the cause
  chain (the SDK's ServiceError hides the code in error.cause — this
  also fixes isNotFound/isTransient missing every wrapped schedule-client
  error)
- recreateCorrupt: write-ahead recordRecreate -> delete -> create ->
  resetOnRecreate, never throws; wired into all five schedule RPC sites
  (bootstrap describe/drift-update, heal update/trigger, watchdog tick)

* feat(workflows-ui): pause/resume/recreate schedule actions

Gives operators a fast manual exit for stuck or corrupt schedules
straight from the admin Workflows > Schedules tab, in both apps.

- PauseSchedule/ResumeSchedule/RecreateSchedule server actions
  (owner-only) mirrored in provider and middleman
- Recreate is delete-only on purpose: canonical config lives in the
  worker, whose watchdog recreates the schedule within one tick and
  resets heal counters; NOT_FOUND on delete is treated as success
- pause/unpause on a corrupt schedule surfaces a hint to use Recreate
- SchedulesTab: per-row buttons behind optional WorkflowsActions
  members, confirm dialogs, schedule-scoped error reporting
- isCorruptSchedule/isNotFound re-exported via workflow-view subpath
  (apps must not import the package root: it drags @temporalio/worker)

* fix(temporal): cap+backoff+page the corrupt-schedule recreate loop

The corrupt/NOT_FOUND recreate path had no breaker, no backoff, and never
paged: an incurable corruption thrashed delete+recreate every tick forever,
wiping run history, while the UI rendered the schedule green (describe()
fails -> liveness undefined -> fell through to healthy).

- New knob maxRecreateAttempts (SCHEDULE_WATCHDOG_MAX_RECREATE_ATTEMPTS,
  default 3), distinct from recreateAfter (the ladder's stale-escalation
  knob): at the cap, setUnhealthy(true) + halt the recreate loop.
- Exponential backoff between recreates via a sibling eligibility map
  (backoffBaseMs * 2^recreations, capped), so the ladder's map and resets
  never clobber it.
- Healthy verdict clears the unhealthy flag + recreate backoff so a
  genuinely-recovered schedule recovers its status.
- Tighten the ScheduleAlreadyRunning swallow in recreateCorrupt: re-describe
  once after the swallow; only resetOnRecreate if it now describes (the
  write-ahead recordRecreate keeps the attempt counted otherwise).
- mapScheduleToHealth: no live verdict (describe() failed fallback) renders
  'unknown', not green; SchedulesTab badge maps unknown -> outline.
- LOW5: NOT_FOUND branch now passes the store through ensureSchedule.

* fix(temporal): operator+episode reset for the recreate breaker; bound verify-describe

* fix(temporal): episode breaker reset requires confirmed autonomous fire

healthy-verdict describe alone doesn't prove recreate cured a corrupt
schedule -- infancy grace lets a freshly recreated, still-broken
schedule read healthy before it ever fires. Unconditionally zeroing
`recreations` there re-opens the silent recreate-loop the breaker
exists to stop. Move both the recreate-breaker reset and the unhealthy
clear into the existing hasAutonomousFire-gated branch, alongside the
ladder reset -- only a confirmed autonomous fire is proof.
@jorgecuesta jorgecuesta added the release:minor Trigger minor version bump on merge to main label Jul 10, 2026
@github-actions github-actions Bot changed the title Release: staging (3e63ad0) chore(release): v0.16.0 Jul 10, 2026
Lock table headers while scrolling in both apps: DataTable opts in by
default, and the hand-rolled tables (activity, chain overview, provider
breakdown, workflows) get bounded scroll boxes. #317

Consolidate multi-table screens into tabs: middleman Suppliers
(Suppliers/Activity/Overview) and provider Keys (Keys/Activity), with the
active tab persisted in the URL. #317

Surface the failure reason on failed transactions in both apps’ tables,
reading the log/message columns through a shared failureReasonDisplay
helper. #317

Add server-side filtering to notification history (event type,
read/unread, channel) in both apps; the unread badge count stays
unfiltered. #317

Make the left sidebar collapsible to an icon rail with a header toggle,
hidden on the landing/auth pages. #317

Add unit tests for the notification filter conditions (both apps) and the
shared failure-reason helper. #317
- provider: restore UUID search on notifications (re-add enableSearch)
- provider/middleman: surface pending activity on the Activity tab by
lifting the pending query above the Radix tab boundary and wiring
TabsBadge — the poll was trapped in the unmounted tab, so pending
stake/unstake was invisible on the default tab
- db: extract shared buildNotificationEventFilterConditions to
@igniter/db/notifications; both DAL copies now delegate (dedup)
- commons: extract isInternalPath to @igniter/commons/utils; Sidebar
and SidebarTriggerGate share it (dedup)
Gate the interval on byKey the same way middleman's SuppliersTabs gates
its poll: only poll while there's pending activity, otherwise stop.
byKey is the badge's own source, so the poll and badge can't diverge.
-Show friendly on-chain failure reasons in transaction tables
Failed transactions previously surfaced only the raw ABCI log (or a
generic "Unknown error"), hard to read and duplicated in both the table
cell and the detail drawer. Thread the chain's own error text end to end
and map known Cosmos SDK error codes to short human-readable messages,
shown through a single copyable popover.
@github-actions github-actions Bot changed the title chore(release): v0.16.0 Release: staging (594c72d) Jul 29, 2026
The chain already defines RPCType.COMET_BFT = 5 but the app layer
whitelisted only the four original types, so operators could not
configure CometBFT endpoints for their services.

- add COMET_BFT to labelByRpcType and validRpcTypes (cascades to
server-side validation, dialogs, and table chips)

- extend the endpoint zod enum in AddOrUpdateServiceDialog to the
fifth type; the Add Protocol cap follows validRpcTypes.length

- map COMET_BFT to scheme "https" and URL token "cometbft" so the
default endpoint hostname no longer collides with JSON_RPC

- unit tests for both switch functions
@github-actions github-actions Bot changed the title Release: staging (594c72d) Release: staging (ed96a69) Jul 29, 2026
@github-actions github-actions Bot changed the title Release: staging (ed96a69) Release: staging (aaf01d5) Jul 30, 2026
* ci: run the test suite in the quality job

The quality job ran prettier, lint, build and check-types, but never
turbo test — so a green CI said nothing about the 23 test tasks in the
workspace. PR #326 was a behavioural fix backed entirely by tests that
CI never executed.

turbo test costs ~18s locally: its dependsOn is ^build (dependencies
only), so the apps' Next builds are not repeated.

* ci: use pull_request_target for the staging deploy

A pull_request event raised by a PR from a fork gets a read-only
GITHUB_TOKEN and no secrets, whatever the permissions block says. When
PR #326 (external contributor) was merged, all four build-push jobs
failed with 'denied: installation not allowed to Write organization
package' and post-deploy was skipped, leaving the release branch out of
sync until the deploy was re-run manually via workflow_dispatch.

pull_request_target runs in the base-repo context with a read/write
token, and keeps github.event.pull_request.labels so the existing
'release' label gate is unchanged.

The usual pull_request_target hazard does not apply: both checkouts pin
ref: staging, never the PR head, and the job only runs post-merge.

* chore(k8s): drop the orphaned middleman-workflows mainnet overlay

Real deployments come from pnf-ops (middleman side) and the
docker-compose example (operator side). This overlay was last written by
the release automation in v0.11.2 (2026-05-18) and has been frozen at
that image tag through six releases; deploy-production.yml no longer
references it.

Its only remaining effect was to read as authoritative production config
while declaring NODE_ENV=development and LOG_LEVEL=debug, both inherited
from base — which is wrong, and misleading to anyone auditing prod.

Tilt is unaffected: every k8s/apps/*/Tiltfile builds ./overlays/dev.

* ci: revert the duplicate test step

Reverts e0111e9. The quality job already ran `pnpm turbo test` (ci.yml
lines 79-80, right after Type check); that commit added a second,
identical step. Its premise — that CI never ran the tests — came from a
truncated read of the workflow, not from the file.

Squash-and-merge collapses this pair away.
@github-actions github-actions Bot changed the title Release: staging (aaf01d5) Release: staging (08112f5) Jul 30, 2026
@jorgecuesta jorgecuesta added release:minor Trigger minor version bump on merge to main and removed release:minor Trigger minor version bump on merge to main labels Jul 30, 2026
@github-actions github-actions Bot changed the title Release: staging (08112f5) chore(release): v0.16.0 Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:minor Trigger minor version bump on merge to main

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants