diff --git a/Spec.md b/Spec.md index 77e1c19..6ae14a2 100644 --- a/Spec.md +++ b/Spec.md @@ -134,6 +134,7 @@ 改訂133: 2026-09-23(**標準時間の見直し候補を生産性モニタリングに追加した**(`api/productivity/standard-time-review`。B-60-10-05)。標準時間の予実は作業指示ごとに並ぶだけで、工順マスタを見直すきっかけにつながっていなかった(実践的MES導入ガイド 8.3.2(4):標準時間などの業務パラメータは定期的に再設計する)。工順の工程ごとに実績の中央値をマスタの現在値と比べ、件数としきい値で候補を挙げる。マスタは書き換えない。新規エンティティは無い。3.2節・6.1節に反映) 改訂134: 2026-09-23(1.3節に**エネルギー管理を[将来拡張]とする**ことを明記した。仕様にエネルギーの記述が無く、対象かどうかが読めなかった(実践的MES導入ガイド 3.4.2・4.4.7・7.2.2 は、MES の役割を FEMS へ製造オーダ別の分母を渡すことと位置づける)。計測・総量監視・ユーティリティ制御は FEMS/FMCS の領域として持たず、実装する場合は設備×期間の使用量をCSVで受け取り稼働区間の時間比で作業指示へ按分する形とした。機能の追加・変更は無い) 改訂135: 2026-09-23(**検査項目・基準に必要スキル・資格を持たせ、検査実績の登録時に検査員と照合する**ようにした(`InspectionItem.RequiredSkillId`。F-20-30-01)。改訂130で保全を実装した際、検査の見直し(改訂125〜128)と衝突するため後回しにしていたもの。必要スキルは発行時に `InspectionOrderItem` へ写し(作業指示の必要スキルと同じ)、発行後にマスタを変えても発行済みの検査の照合条件は変わらない。写す値なので、CSV取込で変えると版数が上がる。判定は差立・着手・保全と同じ `SkillQualificationPolicy`。実績の訂正では照合しない。マスタCSVの検査項目に `RequiredSkillCode` 列を追加(列が無ければ現在値のまま)。3.6節・5.1節・5.4節に反映) +改訂146: 2026-11-03(7.8節:MSIXパッケージを自己署名付きの1種類だけ作るようにした。これまで未署名の提出用と署名済みのコピー(`_signed`)を別々に出力していたが、自己署名付きをアップロードすればMicrosoft Storeの署名に置き換わるため、未署名は不要。`Pack-Msix.ps1`の`-SelfSign`を廃止) 改訂145: 2026-10-23(7.4節:**アクセストークンを無効化・ロール変更・パスワード変更で即時に失効させる**ことを追加した。これまでリフレッシュトークンだけを失効させていたため、発行済みのアクセストークンは有効期限(既定60分)まで使えた。トークンに Identity のセキュリティスタンプを載せ、検証のたびにDBの値・有効フラグと照合する(`Api/AccessTokenValidation`)。あわせて、Identity のトークンプロバイダが未登録で管理者のパスワードリセット(API・ユーザーCSVの `InitialPassword`・初期管理者の再生成)が例外になっていたのを直した。3.8節:**CSV出力の数式インジェクション対策**を追加した) 改訂144: 2026-10-23(7.4節・3.x節保全実施・3.8節⑨:**保全実績の登録を保全担当者(`MaintenanceManage`)に限った**。単票API(`POST api/maintenance-orders/{id}/record`)と実績CSV `maintenance-records` が全ロールに開いており、物流・品質の担当者でも在庫の引落し・保全指示の完了・設備の復帰・治工具の寿命リセットができた。7.4節の「異常・使用実績の記録は絞らない」は保全依頼(起票)までで、実績は含まない。画面の実績登録フォームも同じ定数で隠す) 改訂143: 2026-09-24(3.8節:実績CSVに保全計画 `maintenance-plans` を追加した(CSV対応バックログで保留にしていたもの)。計画の登録を `MaintenancePlansController.Create` から `MaintenanceOrderService.CreatePlanAsync` へ移し、単票APIとCSV取込の両方から呼ぶ(API契約は変えない)。計画には番号が無いため、保全指示CSVに `FromPlan` 列を足し、設備と予定日が一致する未指示の計画が1件だけのときにその計画から指示を作る。サンプル `samples/actual-csv/18_maintenance-plans.csv` を追加し、以降のサンプルの番号を1つずつ繰り下げた(19〜30)) @@ -772,8 +773,8 @@ DBはバックエンド(MesApp.Api)のみが保持し、既定はSQLiteと - **二重起動の抑止**:同じSQLiteファイルを複数プロセスで奪い合わないよう、2つ目以降の起動は既存ウィンドウを前面に出して終了する(ログオンユーザー単位のミューテックス) - **起動の記録と失敗時の扱い**:WinExeはコンソール出力が誰にも見えず起動失敗が無言になるため、起動の各段階を`%LOCALAPPDATA%\ParallelFactoryMES\startup.log`に記録する。ホストの起動はUIスレッドではなくスレッドプールで実行し(UIスレッド上で待つとデッドロックし得るため)、2分を超えたら打ち切ってウィンドウにエラーを表示する。未処理例外はログに残したうえでメッセージボックスで通知する - **パッケージID**:`Identity`の`Name` / `Publisher` / `PublisherDisplayName`はPartner Centerが発行する値を使う。表示名(`Properties/DisplayName`・`VisualElements`の`DisplayName`)はPartner Centerで予約したアプリ名と完全に一致させる(マニフェストに直書きせず`build/msix-identity.json`の`DisplayName`で与える)。バージョンは`x.y.z.0`(第4桁は0) -- **署名**:ストア配布ではMicrosoft Storeが署名するため、提出用パッケージは署名しない。手元での動作確認時のみ自己署名する -- **手順**:`build/Pack-Msix.ps1`(発行→不要ファイル除去→マニフェスト生成→`makeappx pack`)。詳細は`docs-dev/MsixRelease.md` +- **署名**:パッケージは常に自己署名した1種類だけを作り、手元での動作確認にもストア提出にも使う。ストアにアップロードするとMicrosoft Storeの署名に置き換わる +- **手順**:`build/Pack-Msix.ps1`(発行→不要ファイル除去→マニフェスト生成→`makeappx pack`→自己署名)。詳細は`docs-dev/MsixRelease.md` ### 7.9 多言語対応(日本語・英語) diff --git a/build/Pack-Msix.ps1 b/build/Pack-Msix.ps1 index 3e6dd1b..b3960e3 100644 --- a/build/Pack-Msix.ps1 +++ b/build/Pack-Msix.ps1 @@ -3,17 +3,16 @@ MesApp.Desktop を発行して MSIX パッケージを作る。 .DESCRIPTION - ストア提出用は署名しない(Microsoft Store が署名するため)。 + 常に自己署名した1つのパッケージを作る。手元の動作確認にもストア提出にも同じものを使う + (ストアにアップロードすると Microsoft Store の署名に置き換わる)。 - 手元にインストールして確認するには -SelfSign を使う。フルトラストのデスクトップアプリは - Add-AppxPackage -AllowUnsigned では入らない(0x80073D2B:未署名パッケージに実行可能 - ファイルのアクティブ化を含められない)ため、開発者モードでも署名が要る。 - - -SelfSign は提出用パッケージを書き換えず、署名済みのコピーを別名で作る。 + フルトラストのデスクトップアプリは Add-AppxPackage -AllowUnsigned では入らない + (0x80073D2B:未署名パッケージに実行可能ファイルのアクティブ化を含められない)ため、 + 開発者モードでも署名が要る。 .EXAMPLE - ./build/Pack-Msix.ps1 -SelfSign # ローカル検証用(署名済みコピーを作る) - ./build/Pack-Msix.ps1 # ストア提出用(未署名) + ./build/Pack-Msix.ps1 + ./build/Pack-Msix.ps1 -Architecture arm64 #> [CmdletBinding()] param( @@ -25,9 +24,6 @@ param( # 省略時は build/msix-identity.json の Version を使う [string]$Version, - # ローカル検証用に自己署名する - [switch]$SelfSign, - [string]$OutputDirectory, # パッケージIDの定義ファイル。リポジトリ外で管理したいときに指定する @@ -73,6 +69,7 @@ function Resolve-SdkTool { } $makeappx = Resolve-SdkTool 'makeappx.exe' +$signtool = Resolve-SdkTool 'signtool.exe' # --- 3. 発行 ------------------------------------------------------------------------- $stage = Join-Path $OutputDirectory "stage-$Architecture" @@ -106,60 +103,52 @@ Write-Host "パッケージ化中: $msix" & $makeappx pack /d $stage /p $msix /o | Out-String | Write-Verbose if ($LASTEXITCODE -ne 0) { throw "makeappx pack に失敗しました。" } -# --- 7. ローカル検証用の署名 --------------------------------------------------------- -if ($SelfSign) { - $signtool = Resolve-SdkTool 'signtool.exe' - $subject = $identity.Publisher - - # 発行元が一致し、秘密鍵を持ち、コード署名用途で、期限内の証明書を使い回す。 - # 検証のたびに証明書を増やさないため、既存があれば新規作成しない。 - $cert = Get-ChildItem Cert:\CurrentUser\My | - Where-Object { - $_.Subject -eq $subject -and $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) -and - ($_.EnhancedKeyUsageList.ObjectId -contains '1.3.6.1.5.5.7.3.3') - } | - Sort-Object NotAfter -Descending | Select-Object -First 1 - - $created = $false - if (-not $cert) { - Write-Host "自己署名証明書を作成します: $subject" - $cert = New-SelfSignedCertificate -Type Custom -Subject $subject ` - -KeyUsage DigitalSignature -FriendlyName 'Parallel Factory MES (ローカル検証用)' ` - -CertStoreLocation 'Cert:\CurrentUser\My' ` - -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}') - $created = $true - } +# --- 7. 自己署名 --------------------------------------------------------------------- +$subject = $identity.Publisher + +# 発行元が一致し、秘密鍵を持ち、コード署名用途で、期限内の証明書を使い回す。 +# 作成のたびに証明書を増やさないため、既存があれば新規作成しない。 +$cert = Get-ChildItem Cert:\CurrentUser\My | + Where-Object { + $_.Subject -eq $subject -and $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) -and + ($_.EnhancedKeyUsageList.ObjectId -contains '1.3.6.1.5.5.7.3.3') + } | + Sort-Object NotAfter -Descending | Select-Object -First 1 + +$created = $false +if (-not $cert) { + Write-Host "自己署名証明書を作成します: $subject" + $cert = New-SelfSignedCertificate -Type Custom -Subject $subject ` + -KeyUsage DigitalSignature -FriendlyName 'Parallel Factory MES (ローカル検証用)' ` + -CertStoreLocation 'Cert:\CurrentUser\My' ` + -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}') + $created = $true +} - # 提出用パッケージは未署名のまま残し、署名はコピーに対して行う - $signed = [IO.Path]::ChangeExtension($msix, $null).TrimEnd('.') + '_signed.msix' - Copy-Item $msix $signed -Force - & $signtool sign /fd SHA256 /sha1 $cert.Thumbprint $signed | Out-String | Write-Verbose - if ($LASTEXITCODE -ne 0) { throw "signtool sign に失敗しました。" } +& $signtool sign /fd SHA256 /sha1 $cert.Thumbprint $msix | Out-String | Write-Verbose +if ($LASTEXITCODE -ne 0) { throw "signtool sign に失敗しました。" } - $trusted = Get-ChildItem Cert:\LocalMachine\TrustedPeople -ErrorAction SilentlyContinue | - Where-Object { $_.Thumbprint -eq $cert.Thumbprint } +$trusted = Get-ChildItem Cert:\LocalMachine\TrustedPeople -ErrorAction SilentlyContinue | + Where-Object { $_.Thumbprint -eq $cert.Thumbprint } - Write-Host "" - Write-Host "署名しました: $signed" - Write-Host " 証明書: $($cert.Thumbprint)$(if ($created) { ' (新規作成)' } else { ' (既存を再利用)' })" - - if (-not $trusted) { - Write-Host "" - Write-Host "この証明書はまだ信頼されていません。管理者権限のPowerShellで登録してください:" - Write-Host " Export-Certificate -Cert Cert:\CurrentUser\My\$($cert.Thumbprint) -FilePath `$env:TEMP\mes-test.cer" - Write-Host " Import-Certificate -FilePath `$env:TEMP\mes-test.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople" - } +Write-Host "" +Write-Host "署名しました: $msix" +Write-Host " 証明書: $($cert.Thumbprint)$(if ($created) { ' (新規作成)' } else { ' (既存を再利用)' })" +if (-not $trusted) { Write-Host "" - Write-Host "インストール:" - Write-Host " Add-AppxPackage -Path '$signed'" - Write-Host "アンインストール:" - Write-Host " Get-AppxPackage *ParallelFactoryMES* | Remove-AppxPackage" + Write-Host "この証明書はまだ信頼されていません。手元にインストールするときは管理者権限のPowerShellで登録してください:" + Write-Host " Export-Certificate -Cert Cert:\CurrentUser\My\$($cert.Thumbprint) -FilePath `$env:TEMP\mes-test.cer" + Write-Host " Import-Certificate -FilePath `$env:TEMP\mes-test.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople" } +Write-Host "" +Write-Host "インストール:" +Write-Host " Add-AppxPackage -Path '$msix'" +Write-Host "アンインストール:" +Write-Host " Get-AppxPackage *ParallelFactoryMES* | Remove-AppxPackage" + $size = [Math]::Round((Get-Item $msix).Length / 1MB, 1) Write-Host "" Write-Host "完了: $msix ($size MB)" -if (-not $SelfSign) { - Write-Host "この未署名パッケージを Partner Center にアップロードしてください(署名は Microsoft Store が行います)。" -} +Write-Host "ストアへはこのパッケージを Partner Center にアップロードしてください(署名は Microsoft Store のものに置き換わります)。" diff --git a/docs-dev/MsixRelease.md b/docs-dev/MsixRelease.md index 465201e..f2d0bf2 100644 --- a/docs-dev/MsixRelease.md +++ b/docs-dev/MsixRelease.md @@ -55,17 +55,18 @@ Partner Center でアプリ名を予約すると「製品管理 → 製品ID」 ## 3. ローカルで動作確認する ```powershell -./build/Pack-Msix.ps1 -SelfSign +./build/Pack-Msix.ps1 ``` +スクリプトは常に自己署名した1つのパッケージを作る。動作確認にも提出(4節)にも同じものを使う。 + **`-AllowUnsigned` は使えない。** フルトラストのデスクトップアプリを含むパッケージは 開発者モードが有効でも未署名ではインストールできない (`0x80073D2B`:未署名のパッケージに実行可能ファイルのアクティブ化を含めることはできません)。 署名が必須。 -`-SelfSign` は提出用パッケージを書き換えず、`..._signed.msix` を別に作る。 発行元(`CN=5E0CB4C9-...`)が一致する有効な証明書が `Cert:\CurrentUser\My` にあれば -それを再利用し、無いときだけ新規作成する(検証のたびに証明書が増えないようにするため)。 +それを再利用し、無いときだけ新規作成する(作成のたびに証明書が増えないようにするため)。 証明書が `Cert:\LocalMachine\TrustedPeople` に未登録のときだけ、スクリプトが登録コマンドを表示する。 その場合は**管理者権限の PowerShell** で実行する(MSIXのサイドロードでは @@ -74,7 +75,7 @@ Partner Center でアプリ名を予約すると「製品管理 → 製品ID」 インストール(管理者権限は不要): ```powershell -Add-AppxPackage -Path 'artifacts\msix\ParallelFactoryMES_1.0.0.0_x64_signed.msix' +Add-AppxPackage -Path 'artifacts\msix\ParallelFactoryMES_1.0.0.0_x64.msix' ``` **同じバージョンの入れ直しはブロックされる**(`0x80073CFB`:同じIDで内容が異なるパッケージ)。 @@ -149,7 +150,7 @@ Get-AppxPackage *ParallelFactoryMES* | Remove-AppxPackage ./build/Pack-Msix.ps1 -Architecture arm64 ``` -`-SelfSign` を付けない=**未署名**。署名は Microsoft Store が行うため、これで正しい。 +**自己署名付き**のパッケージをそのまま提出する。アップロードすると Microsoft Store の署名に置き換わる。 出力先は `artifacts/msix/ParallelFactoryMES_<版>_<アーキテクチャ>.msix`。 @@ -160,6 +161,7 @@ Get-AppxPackage *ParallelFactoryMES* | Remove-AppxPackage (`.br`/`.gz` は `MapStaticAssets` 向けの事前圧縮で、本構成の `UseStaticFiles` では配信されない) 3. `Package.appxmanifest` のプレースホルダーを `msix-identity.json` の値で置換して `AppxManifest.xml` を生成 4. `makeappx pack` +5. `signtool sign` で自己署名(証明書の扱いは3節) ---