Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

情熱稟議入門

tl;dr

  • 稟議書は「回覧板」である
    多様な読み手に向けて書くことを意識しよう
  • 読み手が稟議書を読むとき、あなた(起案者)は現場にいない
    意思決定に必要な情報は全て書いておく
  • 「買う理由」ではなく「実現したい未来」を明記する

稟議書とは回覧板なのだ

回覧板とは、町内会などでお知らせを“バケツリレー”式に共有する仕組みです。
例えば、お祭りの日程や町内会費の徴収、ご高齢の方へのお祝いの予定などをA4用紙にまとめて回覧します。内容によっては「読みました」の欄にサインをすることもあります。

なぜこんな手間をかけるのかというと、町内会にはいろんな人がいるからです。夜勤がある人もいれば、そもそも町内会活動に時間を割けない人もいる。そうなると、全員が同期的に集まって話をするのは難しいですよね。

そこで回覧板を使えば、会議や電話と比べて時間はかかるものの、非同期的にほぼ確実に情報を届けられるわけです。

稟議書もこれと同じです。会社の重大な意思決定にあたって、多角的な検討をしたい。でも営業は外回りで忙しいし、エンジニアはそもそもデジタルの向こう側にいて捕まりづらい……。そんなときに稟議書を回覧することで、非同期的に意見や知見を集約し、組織として意思決定できるのです。これこそが稟議書の存在意義と言えます。

議事録の読み手は様々な背景と動機を抱えている

さて、稟議書を読むのは誰でしょう?
社長? 財務? それとも上司?

まず社長と財務では稟議書を読むときの“動機”が違います。

  • 社長:事業を前進させる新しいアイデアを探しているかもしれない
  • 財務:企業の財布を守りたい。要不要を見極めたり、投資の優先順位を付けたい

財務向けの情報ばかり書いても社長には刺さらないでしょうし、その逆も同じだというのはイメージいただけるでしょうか。

あるいは社長と上司という組み合わせでも、背景が異なります。

  • 上司:あなたと同じ業務範囲を把握している(ツーカーで通じることもある)
  • 社長:現場レベルの詳細や解像度を共有していない場合が多い

社長が稟議書を読むタイミングで、起案者であるあなたはそこにいません。
チャットなどで質問が飛んでくればラッキーですが、もし憶測や推測で意思決定がなされると、本来得られたはずの機会を失ったり、予想以上のダメージを被ったりするリスクもあります。

読み手ごとの疑問に先回りして回答する

情報が足りず誤読される隙を無くすために、どんな稟議書を書くべきか。
キーワードは「業務想定」です。読み手は何を知りたがり、どんな基準で判断するのか? あなたが本当に伝えたいポイントは何か?これらを押さえるために、「この稟議書を読んで、あの人はどんな質問をするだろう?」と想像して、あらかじめ回答を用意しておきましょう。

例えば、こんな疑問が想定されます。

  • 「前に実行したあの施策とはどう違うのか?」
  • 「この投資はいつ、どのような形で回収できるのか?」
  • 「他に検討されている施策との差別化点は何か?」
  • 「顧客にどのような変化をもたらすのか?」
  • 「不調に終わったときの代替策(コンティンジェンシープラン)は?」

社長・財務・上司といった立場ごとの着眼点も整理しておくと、さらに書きやすいです。

稟議とは未来を生み出す起爆剤

とはいえ、全ての疑問を網羅的に書くと時間も紙幅もかかります。
そのため、せめて「この施策が提案された背景」と「この施策によって実現したい未来」だけは確実に書いておきましょう。

これらの情報は、あなたのアイデアと意欲に共感してもらい、仲間として一緒に動いてもらうために必要最低限の要素だからです。

稟議書は単なる「お金の使用許可申請書」ではなく、回覧板でしたよね。
回覧板で町内会を巻き込み、行動してもらうための仕組みと同じように、稟議書は社長も財務も上司も営業もエンジニアも、みんなを仲間として巻き込むための起爆剤になり得ます。

だからこそ、あなたが熱意を注いで生み出したアイデアを、そのままの勢いで書式に落とし込みましょう。数字を使ったり、成功時のイメージを具体的に描いたりすると、より強く心に訴えかけられます。

まとめ

  • 稟議書は回覧板のように非同期コミュニケーションを可能にするツール
  • 読み手の背景や判断基準を先読みし、必要な情報をしっかりと書き込む
  • 「買う理由」ではなく、「この施策で実現したい未来」を具体的に示す

稟議書は、単に「承認をもらうための形式的な書類」ではありません。
あなたの「熱意」や「未来を創りたい」という想い、情熱を伝えるためのメディアでもあるのです。
ぜひ、そのモチベーションを惜しみなく注ぎ込んで、読み手を動かす稟議書を作成してみてください。

2024年の振り返りと2025年の抱負

2024年終わりました。仕事の面では医療系スタートアップに転職してから3年目に突入、より混沌としてきています。子供も心身ともに大きくなってきていて、自らの加齢を感じることも増えてきました。人生ですね。

さて今年のトピックはだいたいこんなところ↓だと思うので、掘り下げていきます。

  1. 情報処理安全確保支援士試験に合格した
  2. 勤め先でDevelocityを導入した
  3. 技術書典に合同誌を出した
  4. OSS活動を休止した

情報処理安全確保支援士試験に合格した

こちらは独立した記事を書いたのでそちらを参照ください。昨年の医療情報技師に続けての資格取得でしたが、転職前は資格試験から距離をとっていたこともあり、とても新鮮な体験でした。

来年は新たな受験ではなく、資格を仕事に活かす方向でのキャッチアップと発信を心がけていきたいと考えています。

勤め先でDevelocityを導入した

私の勤め先である株式会社ヘンリーはサーバサイドKotlinを推しており、もちろんサービスの開発でも活用しています。この夏に私もモノレポ化について発信していました。

Kotlinプロジェクトのモノレポ化には大きな恩恵がある一方で、増えるコードやテストケースのコンパイルならびにテスト実行をどう高パフォーマンスに行うかという壁があります。この解決にはGitHub Actions workflowの最適化も有効ですが、勤め先ではそれにあわせてGradle社が展開するオンプレサービスであるDevelocityの導入と運用を手がけました。実際有効なので、今後もサーバサイドKotlinを盛り上げる一環に情報を発信していきます。

技術書典で合同誌を出した

前述のDevelocityをはじめとした各種工夫を盛り込んだ本を勤め先の有志で作成し、技術書典に出しました。Re:VIEWでの執筆は初体験でしたが、なかなか好評だったようで良かったです。

GitbookやSphinx、zennなどとはまた違った書き味で面白く、また書いてみたいと思っています。

OSS活動を休止した

ちょこちょこGradleプラグインや自作RDBMSを書き溜めたり、仕事で使っているツールの不具合修正のためのPRを送ったりはしているのですが、実質的にOSS活動は止まってました。

狙ってそうしたというよりは、他にやりたいことがあるので結果的にそうなったという感じで、まぁそういうこともあるのかなという所です。来年もおそらくこの状況が続くと思われます。

2025年の抱負

まだ決めきれてないですが、来年はこんな感じにしようと思っています:

  1. 技術書典に個人で出展する
  2. マネジメントスキルの棚卸し

技術書典の出典には、既刊のMaven本にあわせてGradle本を書き下ろしたいと考えています。Gradleの本って、商業でも同人でもほぼないんですよね。Gradle 9対応で出せれば唯一無二の日本語本ということになりそうです。ただ1人で書ける量には限度があるため、全ての概念を1から教える初心者本ではなく触ったことのある人がステップアップするための本に特化するつもりです。

 

マネジメントについては今年もProduct Managementの真似事はやっていたものの、意図的に距離をとっていました。ICとしての勘や実績、自信が欲しかったのもありますが、何よりもPeople Managementで燃え尽きてしまったのでやりたくなかったんですよね。

People Managementは正直今でもやりたくないですが、充分に休息は取れたのでそろそろ今できることの再確認と古びたスキルの手入れをする時期かなと思っています。Project ManagementやProduct Managementを中心に据えつつ、必要なことをやっていきます。

来年もよろしくお願いいたします

今年は14記事と、昨年の20記事よりは少なくなりました。年間を通じて時間を作ることが難しく、なかなかタフな1年でした。来年もこのへんはあまり変わらない気はしますが、ちょいちょい書いていければと思います。引き続きよろしくお願いいたします。

Testcontainersで単体テスト実行時に必要なデータベースを自動的に用意する

この記事は Kotlin Advent Calendar 2024 10日目の記事です。前回はtsukakeiさんのExposedでStatementInterceptorを使ってSQL文実行前後に処理を差し挟むでした。

先日出した合同誌でも触れましたが、Testcontainersを使うと単体テスト実行時に必要なデータベースを自動的に用意することができます。TestcontainersといえばSelenide (Selenium)でもお世話になりましたが、ブラウザだけではなくデータベースも対応しているんですね。

合同誌掲載ケースではDevelocityのTest Distributionで遠隔テストを実行するのに利用しましたが、手元やGitHub Actionsなどでテストをするにも便利なので紹介します。なお掲載コードはKoinとKotest、Postgresを前提としています。

Testcontainersがなぜ便利なのか

Testcontainersはテストに利用するための外部プロセスをお手軽に利用するためのライブラリです。たとえば従来だとPostgresに依存したテストを実行するには実行環境にPostgresをインストールすること(GitHub Actionsなら services を使ってPostgresを起動する、など)が必要でしたが、ポート番号を環境変数で受け渡すとか、データベース側がちゃんと起動したか確認するとかの手間があり不便です。Testcontainersを使うことでプログラムからデータベースを起動できるので諸々をテストコードとして管理できるようになり、メンテナンス可能性が上がります。

TestcontainersをKotestで使う

公式のQuickStartにはKotestに特化したものがないので説明します。前提としてデータベースに接続するための接続プールをKoinで初期化しているものとします:

// src/main/kotlin/com/example/DatabaseModule.kt
@Module
@ComponentScan
class DatabaseModule {
  @Single
  fun createDatasource(): HikariDataSource { ... }
}

テスト実行時はこれを上書きして利用することにします。java-test-fixtures プラグインを使えばテストコードではないけどテストから利用するコードをテストコードから分離して管理できますので、これを利用すると良いでしょう。

// build.gradle.kts
plugins {
  `java-test-fixtures`
  ...
}

dependencies {
  testFixturesImplementation("org.testcontainers:postgresql:1.20.4")
  ...
}

TestFixtureとテストコードは次のようになります:

// src/testFixtures/kotlin/com/example/DatabaseModuleForTest.kt
fun createTestcontainerModule() = module {
  single<HikariDataSource> {
    // TODO: ここにTestcontainers利用コードを書く
  }
}
// src/test/kotlin/com/example/MyTest.kt
class MyTest : DescribeSpec({
  beforeSpec {
    startKoin {
      modules(DatabaseModule.module + createTestcontainerModule())
    }
  }
  afterSpec {
    stopKoin()
  }
  // TODO: ここにデータベースに依存したテストを書く
})

Postgres公式イメージを使う

Testcontainersの使い方のひとつとして、Postgres公式イメージを使ってデータベースを起動できます。スキーマがなにもない状態で起動しますから、ExposedのSchemaUtilsあたりで初期化する必要はあります。

fun createTestcontainerModule() = module {
  single<HikariDataSource> {
    val container = PostgreSQLContainer("postgres:17-alpine").apply {
      withLogConsumer(Slf4jLogConsumer(logger))
      start()
    }
    val config = HikariConfig().apply {
      jdbcUrl = container.jdbcUrl
      username = container.username
      password = container.password
      driverClassName = container.driverClassName

      // TODO: 他の設定をする

      validate()
    }

    return HikariDataSource(config)
  }
}

なお Slf4jLogConsumer はTestcontainersのログをSLF4Jのロガーに渡すための設定です。SLF4Jを利用していない場合は他の LogConsumer を使うことになるでしょう。

独自のコンテナを使う

すでにコンテナレジストリにある独自のコンテナを使うこともできます。Postgres互換のイメージであることを明示するために asCompatibleSubstituteFor("postgres") の実行が必要です。

fun createTestcontainerModule() = module {
  single<HikariDataSource> {
    val imageName = DockerImageName.parse(IMAGE_NAME).asCompatibleSubstituteFor("postgres")
    val container = PostgreSQLContainer(imageName).apply {
      ...
      start()
    }
    ...
  }
}

コンテナのライフサイクルを考える

PostgreSQLContainerAutoCloseable インタフェースを実装しています。つまりコンテナのライフサイクルをプログラムから管理する必要があります。KotestはJUnit5の基盤で動いているので、Testcontainers組み込みのJUnit4対応は期待できません。オフィシャルサイトを参考に、自分で管理を考える必要があります。

ところでテストコードで Koin を使う場合、すでに KoinApplication のライフサイクルを管理しているはずです。Specごとに KoinApplication を作るとか、テストケースごとに KoinApplication を作るとかの方法がありますが、コンテナのライフサイクルもこれと同じにできるなら、Koin moduleが閉じられたタイミングでコンテナを止めるようにするのがシンプルでしょう。

fun createTestcontainerModule() = module {
  single<PostgreSQLContainer> {
    ...
  } onClose {
    it.close()
  }
  ...
}

またKoinApplication とコンテナのライフサイクルをあわせてしまうとテストの実行パフォーマンスに影響がある場合は、コンテナをSingletonとして扱うことになります。この場合はコンテナの停止はTestcontainersに任せられるので、明示的に close() を呼ぶ必要はありません。

まとめ

Testcontainersを使うとデータベースに依存したテストケースをより簡単に実行できます。ちょっと前だとH2 Databaseなどのオンメモリデータベースを使っていましたが、Testcontainersだと本番環境と同じRDBMSを使えるのでより安心ですね。Postgresに依存するコードをテストする際にお試しください!

2024年に読んでよかった本

医療関係

「看護覚え書」ナイチンゲールが書いた本ということで、初心を理解するのに良いかなと思って購入しました。超人ナイチンゲール (シリーズ ケアをひらく)と合わせて、現代看護の成り立ちみたいなものに触れる良い機会になりました。システム実装やドメイン理解の役に立ったかというとNOですが、看護と聞いて自分が連想するものがとても狭いのだということはよく理解できました。

エンジニアリング

「情報セキュリティの敗北史」セキュリティがとても人間のウェットな部分をクローズアップするテーマなんだということを再認識した本です。マイクロソフト社のかつての過ちとか、様々な立場の人間が利益を最大化するために行動した結果として脆弱性が”活用”されているとか、ぱっと見たときに良さそうな選択肢(例えばバグバウンティ)が一歩引いてみるとそうでもないとか。

自分は性善説や理想論で動きがちな人間なので、改めて情報セキュリティめんどくせぇ奥が深いなぁと思いました。今後もちょくちょく振り返って手に取る本になりそうです。

「本質から考え行動する科学技術者倫理」 この春に放送大学の講義を受講しまして、残念ながら単位取得までは行かなかったんですが、この科学技術者倫理について学ぶ良い機会となりました。倫理というと高校の授業でやるやつですが、自分の高校の教師は歳を食ってある程度フラットに物事が見られるようになった今の自分から見ても疑いなくひどかったので、倫理についてはエッセンスも何もキャッチアップできていなかったんですよね。

失敗学の繋がりで回転ドアの話とかチャレンジャー号事件の話とかは知っていたのですが、AI技術の進展を踏まえた議論などは初めて見たので、きちんと学び直したいなと思い放送大学の講義資料とは別にこの教科書を購入しました。スタートアップで社会課題の解決にもがいていると前人未踏の問題領域に踏み込むことは多く、多かれ少なかれ倫理的判断が求められるシーンはきっとあると思うので、情報システムで社会に切り込むスタートアップに在籍しているタイミングでこの本に出会えて良かったと思います。

「一級建築士矩子と考える危ないデザイン」は単行本を購読しているマンガ「一級建築士矩子の設計思考」のスピンオフ?と言いますか、マンガのキャラクタを拝借して危ないデザインを紹介するというものです。元のマンガを購読していなくても楽しめますし、建築関係なくユーザインタフェースの設計・開発をするITエンジニアには広くおすすめです。

個人的には自分の母校のデザインがかなり危なかったのだなぁということに気づき、ちょっと怖くなりました。1階の庇に柵がついてないのに2階から乗ることができたんですよね。当時はそういうものだろうと思っていましたが、改めて考えると悪いデザインだったと思います。日頃の周囲を見る目が変わる1冊でした。

その他

建築好きなのでちょくちょく間取りや設計、建築についての本や雑誌を買っているのですが、今年は商店建築に加えてこちらの「お宿図鑑」を買いました。スケッチがとても美しく細かくて、自分が現地に赴いても発見できないだろうなと確信できる内容盛りだくさんでした。

掲載されているお宿はおおむね子連れで行ける宿というか、いずれ親と行きたい宿という風情ですね。この本を読んで、家族でゆっくりとした時間を楽しめる未来がより楽しみになりました。

Kotlinからお手製Javaライブラリを呼んでる人のためのJSpecify入門

これはKotlin Advent Calendar 20242日目の記事です。前日は id:take7010 さんの「KotlinとOpenAI APIで領収書の情報を抽出する」でした。

先日Kotlin愛好会オンラインでJSpecify入門について話しました。JSpecifyについては3年前のJJUG CCCでも話しましたが、この夏にv1.0.0が出たので改めて知っていただければと思っています。

speakerdeck.com

JSpecifyの何が嬉しいか

Kotlinの世界に閉じた実装を行っていると、JSpecifyのアノテーションを使う機会はほぼないはずです。JSpecifyが嬉しいのはKotlinからJavaを呼ぶ場合です。 Javaライブラリ側の引数や戻り値をJSpecifyアノテーションで修飾しておくと、null-safetyなKotlinの引数や戻り値と同じように扱えます。 今まではKotlinコンパイラが警告を吐くだけでしたが、Kotlin 2.1.0からエラーとして扱われるようになりました。

どこから始めればいいか

nullableな戻り値を @Nullable で修飾するところから始めましょう。ランタイムの NullPointerException を防ぐために最も重要なアノテーションだと思います。

次点が List<T> のようなGenericsだと思いますが、このへんはけっこう複雑なのでドキュメントに目を通しておきましょう。

なおすべてのnullableな型を修飾したと言えるようになったら、パッケージを @NullMarked で修飾しておけば「 @Nullable が無いところは全てnot-null」だということをコンパイラに伝えられます。 nullableな値を持ち回らないようなコードを書いている場合は便利かもしれません。

Java と Kotlin を安全に橋渡ししよう

Kotlinの強みのひとつとして「Javaの資産を活用できる」のが挙げられますが、そのためにはJavaになくてKotlinにある機能をどう扱うかを検討しなければなりません。 null安全はそのうちのひとつですが、JSpecifyのv1.0.0リリースによってある程度標準的な解決策が用意されたと言ってよいでしょう。 KotlinをJavaで書かれたライブラリやフレームワークと組み合わせて使っている方は、ぜひ試してみてください。

Kotlin Advent Calendar 2024の明日の記事は、 id:tsukakei1012 さんの「Exposedについて①」の予定です。

情報処理安全確保支援士になりました

今月より晴れて、情報処理安全確保支援士(登録番号028693)を名乗れるようになりました。昨年取った医療情報技師と合わせて、医療情報システムの情報処理安全についてのプロフェッショナルであることをある程度の説得力を持って説明できるようになったと考えています。

とはいえ資格をとったら急に仕事ができるようになるかというとンなわけないので、より複雑な課題解決ができるように継続して学んでいきます。とりあえず今日は、なぜセキュリティなのかと、今後の抱負をここにまとめておこうと考えた次第です。

なぜ今セキュリティなのか

私はいままでCDワークフロー改善であるとか、ログ管理含めた保守性であるとか、エンタープライズアプリケーションの性能改善であるとか、どちらかと言えばシステム実装に寄った非機能要件を多く見てきました。

一応OSSライセンスの管理やビルドスクリプト改善から発展して、OSS脆弱性への対応も経験してはいるのですが、セキュリティについてはそこまで専門でやってこなかったというのが正直なところです。

 

セキュリティについて学ぶ起点となったのは2021年末、Log4Shellの対応です。GitHubで突如公開されたコミットをもとに修正内容の把握や自社サービスへの影響などを短期間に特定して全社的になんとかする体験をし、セキュリティが「OSSエンジニアとしての経験とベストプラクティスを現場の運用に落とし込むという自身の関心領域とが交差する場」でもあるのだという発見に繋がりました。

そして転職後、医療情報システムの安全管理に関するガイドラインについて学んだり、サイバーセキュリティが医療機関等の管理者に対する義務化されたりして、自然とセキュリティについて学ぶ時間が増えたというのが「なぜセキュリティなのか」への回答というところです。端的に言うと医療業界的にニーズが高いのと、自分のWILLやCANとマッチしたというところですね。

情報処理安全確保の第一歩は相互理解

情報処理安全確保支援士の資格試験では、自分が支援士だと仮定したうえでどう考えて行動すべきかを問う設問が出ます。そしてその場には必ず複数の関係者が出てきます。そもそも情報処理安全確保「支援士」ですから、組織に対して支援を提供してゴールを目指す立ち位置=実行者ではなくハブであり、経営者や情報システム担当に対する助言者だということなのだと私は理解しました。

同様に医療情報技師も関係者との交流を指す「3C」を重視しています

他職種との意思疎通を図るためのコミュニケーション(Communication)、他職種と協力して対応するコラボレーション(Collaboration)、部門間や職種間の調整を行うコーディネーション(Coordination)の3つの能力を必要な資質としています

医療機関で働く医療情報技師は自らの管理下にない部門システムや保守用ネットワークを業務の前提とすることも多く、事業者を含む他職種との協業が絶対必要です。だからこそより安全で患者様や職員のためになるシステム運用を実現するために、協調や調整が重要なのだと私は理解しました。

異なる資格が同様に他者とのコミュニケーションを通じての実行にピンを留めているのは面白いですね。それだけ業務遂行に必要な能力だと考えられているわけで、重要性もまた理解できます。

不安に名前をつけるのが情報処理安全確保支援の初手

さてセキュリティに明るくない方とのコミュニケーションにおいて、その出発点は多くの場合「よく知らないけど、これをしたい」「あれってセキュリティに問題あるって本当?」といった漠然とした不安であることが多いと感じます。そしてわかりやすく一網打尽にできそうな、たとえば閉域網のような選択肢が支持されます。情報技術は目に見えないものを扱うので、気持ちは理解できます。

ここで求められる3Cはその不安を具体的に表すことだと思っています。どのようなゴールを望んでいるのか、どんなリスクがあると考えているのか、保守にどれだけの手間をかけられるのかなど、いろんな視点からあるべき姿を探ります。その結果として不安に名前がつき、この不安にはこう対処する、この不安は当面どうしようもないので受け入れる、という議論ができるようになります。

もちろん相手にはよるものの、多くの場合はこうしたコミュニケーションを通じて不安や心配をリスクマネジメントという経営的な営みに近づけること、なんとなくや人から聞いたからではなくて戦略的に決定を下せるようにすること、その決定に大きな穴が無いようにすることが支援士かつ医療情報技師としてできる貢献なのかなぁ、みたいな。

まとめ

情報処理安全確保支援士になりました。いろんな立場の方との相互理解に努め、ヒアリングを通じて不安に名前をつけ、合理的かつ利便性の高い情報処理安全確保の支援をしていきたいと思います。

そのためには今までやってきたマネジメントやコーチングといった経験が活きるでしょうし、自分のWILLであるベストプラクティスを現場に落とし込むことも役立つはずです。情報セキュリティの知識を常にアップデートしつつ、課題解決に貢献できるエキスパートとして成長していきます。

Gradle用のGitHub Actions勘どころ(2024年夏)

前回に引き続き以下略。

jarファイルのビルドはコンテナの外で行う

ビルドをコンテナの中で行う場合、工夫がなければ毎回はじめからコンテナのビルドが走ります。これはパフォーマンス上好ましくないので、GitHub Actionsのキャッシュ用APIだとかRepository Cacheだとかを使ってビルド性能の改善を図れます:

docs.docker.com

ただGradleプロジェクトの場合はGradle自身が常に同じビルド結果を生成するように動作してくれますので(JVM toolchainとかreproducible buildsとかの話ね)、jarファイルのビルドはコンテナの外でやって Dockerfile では COPY するだけでも良さそうです。この場合は actions/setup-java のキャッシュ機構やRemote Gradle Build Cacheなどを簡単に使えます。

pushとpull_requestで別のワークフローを使う

PR向けのGitHub Actions workflow runが走っているタイミングでPRをマージすると、push時に走るはずのGitHub Actions workflow runが実行されない!という問題があります。ワークフローを分けることで回避できるため push.ymlpull_request.yml とを別に定義してreusable workflowをコールする方法を採ると良さそうです。

# .github/workflows/push.yml
on:
  push:
    branches:
      - main
jobs:
  build:
    uses: ./.github/workflows/reusable-build.yml
    secrets: inherit
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    secrets: inherit
    with:
      environment: production
# .github/workflows/reusable-deploy.yml
name: Deploy
run-name: Deploy ${{ github.sha }} to ${{ inputs.environments }}
on:
  workflow_call:
    inputs:
      environment: ...

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    permissions: ...
    steps: ...