Compare commits
3 Commits
9ed4700eb4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f07da2e12 | |||
| 047125bc11 | |||
| d668d5b7b1 |
@@ -4,7 +4,9 @@ Per-feature evidence for the M6 work that landed unit-only and now needs end-to-
|
|||||||
|
|
||||||
The sweep is gated on `MX_LIVE=1` env (populate via `tools/Setup-LiveProbeEnv.ps1`). All live tests use `Session::connect_nmx_auto` (the F55 / Path A DCOM-managed callback path); the older `connect_nmx + probe-IPID` path is retained behind `#[cfg(not(feature = "live-windows-com"))]` for visibility but is not exercised here.
|
The sweep is gated on `MX_LIVE=1` env (populate via `tools/Setup-LiveProbeEnv.ps1`). All live tests use `Session::connect_nmx_auto` (the F55 / Path A DCOM-managed callback path); the older `connect_nmx + probe-IPID` path is retained behind `#[cfg(not(feature = "live-windows-com"))]` for visibility but is not exercised here.
|
||||||
|
|
||||||
## Status (2026-05-06)
|
## Status (re-run 2026-05-07)
|
||||||
|
|
||||||
|
All five steps re-run cleanly against the live AVEVA install on 2026-05-07; outputs match the 2026-05-06 baseline (no behavioural drift since the F56 fix landed). Only fixture-side change: `tools/Setup-LiveProbeEnv.ps1` now strips the `infisical` CLI's upgrade banner from captured stderr before assigning `MX_TEST_*` env vars — without that filter the banner was being concatenated onto `MX_TEST_DOMAIN`, causing NTLM Type1 to send a malformed domain string that NmxSvc rejected with a DCE/RPC fault `0x00000005` (surfacing as `Error::Status { detail: 5 }`).
|
||||||
|
|
||||||
| Step | Feature | Test | Outcome |
|
| Step | Feature | Test | Outcome |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -64,7 +66,7 @@ recover_connection returned Ok — F45 buffered replay path executed
|
|||||||
post-recovery: drained 2 NMX subscription messages
|
post-recovery: drained 2 NMX subscription messages
|
||||||
```
|
```
|
||||||
|
|
||||||
The replay branch in `recover_connection_core` (`session.rs:1428-...`) re-issues `RegisterReference` (NOT `AdviseSupervisory`) for the buffered entry, mirroring `MxNativeSession.ReAdviseSubscription` (`cs:538-569`). Structural property is unit-tested; this live test confirms the engine actually picks back up after the rebuild + replay.
|
The replay branch in `Session::recover_connection_core` re-issues `RegisterReference` (NOT `AdviseSupervisory`) for the buffered entry, mirroring `MxNativeSession.ReAdviseSubscription` (`cs:538-569`). Structural property is unit-tested; this live test confirms the engine actually picks back up after the rebuild + replay.
|
||||||
|
|
||||||
## Step 3 — F47 buffered unsubscribe skip (PASS)
|
## Step 3 — F47 buffered unsubscribe skip (PASS)
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ buffered subscribed, correlation_id = [...]
|
|||||||
buffered unsubscribe returned Ok — F47 skip path verified live
|
buffered unsubscribe returned Ok — F47 skip path verified live
|
||||||
```
|
```
|
||||||
|
|
||||||
`Session::unsubscribe` (`session.rs:2261`) probes the registry for the subscription's mode; if `Buffered { .. }`, it skips the `nmx.un_advise(...)` wire call entirely. Mirrors the .NET reference's `if (!subscription.IsBuffered)` guard at `MxNativeSession.cs:361-381`. If the implementation accidentally emitted `UnAdvise` for a buffered correlation id, the engine would return non-zero HRESULT (no matching plain advise to retract) — surfacing as a panic in this test.
|
`Session::unsubscribe` probes the registry for the subscription's mode; if `Buffered { .. }`, it skips the `nmx.un_advise(...)` wire call entirely. Mirrors the .NET reference's `if (!subscription.IsBuffered)` guard at `MxNativeSession.cs:361-381`. If the implementation accidentally emitted `UnAdvise` for a buffered correlation id, the engine would return non-zero HRESULT (no matching plain advise to retract) — surfacing as a panic in this test.
|
||||||
|
|
||||||
## Step 4 — F40 metrics live smoke (PASS)
|
## Step 4 — F40 metrics live smoke (PASS)
|
||||||
|
|
||||||
@@ -128,6 +130,8 @@ The `WriteCompleteEvent { server_handle, item_handle, statuses, is_during_recove
|
|||||||
|
|
||||||
## Reproducing locally
|
## Reproducing locally
|
||||||
|
|
||||||
|
### Live tests (require AVEVA + MX_LIVE env)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 1. Populate live env from Infisical (dot-source so vars persist).
|
# 1. Populate live env from Infisical (dot-source so vars persist).
|
||||||
. .\tools\Setup-LiveProbeEnv.ps1
|
. .\tools\Setup-LiveProbeEnv.ps1
|
||||||
@@ -155,6 +159,64 @@ cargo test -p mxaccess-compat --features live-windows-com `
|
|||||||
--test buffered_unsubscribe_skip_live -- --ignored --nocapture
|
--test buffered_unsubscribe_skip_live -- --ignored --nocapture
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Workspace gate (no live infra needed)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd rust
|
||||||
|
cargo build --workspace --all-targets
|
||||||
|
cargo test --workspace --no-fail-fast
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
cargo bench -p mxaccess-codec
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: build clean, 847 tests pass + 9 ignored (live-only), clippy `-D warnings` clean, bench under R12's < 5 allocs/write target. `cargo fmt --all -- --check` flags pre-existing workspace-wide drift unrelated to any session edit (see § "Workspace gate" below).
|
||||||
|
|
||||||
## Open work
|
## Open work
|
||||||
|
|
||||||
None. F49 sweep complete; F50 (residual Frida capture for Suspend/Activate) closed 2026-05-06 per `docs/F50-suspend-activate-evidence.md`.
|
None. F49 sweep complete; F50 (residual Frida capture for Suspend/Activate) closed 2026-05-06 per `docs/F50-suspend-activate-evidence.md`.
|
||||||
|
|
||||||
|
## Workspace gate (2026-05-07)
|
||||||
|
|
||||||
|
End-of-session sanity sweep against `master` at commit `9ed4700` plus the F56 unit-test fixture fix that this gate flagged. Run from `rust/` on Windows x64.
|
||||||
|
|
||||||
|
| Gate | Command | Result |
|
||||||
|
|---|---|---|
|
||||||
|
| Build | `cargo build --workspace --all-targets` | **Pass** (19.81 s) |
|
||||||
|
| Tests | `cargo test --workspace --no-fail-fast` | **Pass** — 847 passed, 0 failed, 9 ignored (live-only) |
|
||||||
|
| Clippy | `cargo clippy --workspace --all-targets -- -D warnings` | **Pass** |
|
||||||
|
| Bench | `cargo bench -p mxaccess-codec` | **Pass** — R12 < 5 allocs/write target met |
|
||||||
|
|
||||||
|
The `cargo fmt --all -- --check` gate flags pre-existing workspace-wide rustfmt drift across 29 files (~1000 lines, mostly machine-generated `mxaccess-asb-nettcp/src/nbfs.rs`). Drift is unrelated to any individual session's edits and is documented here as a known workspace-hygiene gap; per-file formatting is applied to edited files at edit time.
|
||||||
|
|
||||||
|
### F56 test-fixture bug surfaced + fixed by this gate
|
||||||
|
|
||||||
|
The workspace test sweep flagged 9 failing unit tests in `mxaccess::session` that had been silently failing since F56 landed (commit `5e11b30`). Root cause: F56 added `ensure_publisher_connected` (issuing `INmxService2::Connect` + `AddSubscriberEngine` before each `AdviseSupervisory`) but the in-process fake-NMX-server fixtures' `responses` vec sizes weren't bumped to absorb the two new RPCs. Symptom was `ConnectionAborted (10053)` once the fake server's response budget ran out mid-handshake.
|
||||||
|
|
||||||
|
Fix: bumped each test's `unauthenticated_server` / `recording_server` response count by 2 to cover Connect + AddSubscriberEngine. Tests touched (all in `crates/mxaccess/src/session.rs::tests`):
|
||||||
|
|
||||||
|
- `subscribe_then_unsubscribe_round_trip` (2 → 4 responses)
|
||||||
|
- `two_subscribes_produce_distinct_correlation_ids` (4 → 6; second subscribe hits the per-engine cache)
|
||||||
|
- `subscription_stream_yields_data_change_for_matching_correlation` (1 → 3)
|
||||||
|
- `subscription_stream_filters_out_mismatched_correlation_for_status` (1 → 3)
|
||||||
|
- `subscription_stream_keeps_data_update_regardless_of_correlation` (1 → 3)
|
||||||
|
- `subscribe_populates_registry_unsubscribe_clears_it` (2 → 4)
|
||||||
|
- `read_returns_first_data_change_within_timeout` (2 → 4)
|
||||||
|
- `read_returns_timeout_when_no_data_arrives` (2 → 4)
|
||||||
|
- `unsubscribe_skips_un_advise_for_buffered_subscription` (2 → 3 + mid-flow assertion bumped from `len() == 1` to `len() == 3`)
|
||||||
|
|
||||||
|
Bench numbers post-fix (release profile, Windows x64):
|
||||||
|
|
||||||
|
| scenario | allocs/op |
|
||||||
|
|---|---|
|
||||||
|
| `write_message::encode` (Int32) | 2.00 |
|
||||||
|
| `write_message::encode` (Float32) | 2.00 |
|
||||||
|
| `write_message::encode` (Float64) | 2.00 |
|
||||||
|
| `write_message::encode` (Boolean) | 1.00 |
|
||||||
|
| `write_message::encode` (String, 5 chars) | 4.00 |
|
||||||
|
| `write_message::encode_to_bytes_mut` (Int32, F52.1) | 2.00 |
|
||||||
|
| `write_message::encode_into_bytes_mut` (Int32, pooled, F52.3) | 1.00 |
|
||||||
|
| `write_message::encode_into_bytes_mut` (Boolean, pooled, F52.3) | 0.00 |
|
||||||
|
| `MxReferenceHandle::from_names` (cache, F52.2) | 0.00 |
|
||||||
|
| `NmxSubscriptionMessage::parse_inner` (DataUpdate, Int32) | 1.00 |
|
||||||
|
|
||||||
|
All numbers match `design/M6-bench-baseline.md` § F52.{1,2,3}.
|
||||||
|
|||||||
@@ -2952,8 +2952,16 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn subscribe_then_unsubscribe_round_trip() {
|
async fn subscribe_then_unsubscribe_round_trip() {
|
||||||
// Two RPCs: AdviseSupervisory + UnAdvise. Both return HRESULT 0.
|
// Four RPCs: Connect + AddSubscriberEngine (F56's
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new())]).await;
|
// ensure_publisher_connected) + AdviseSupervisory + UnAdvise.
|
||||||
|
// All return HRESULT 0.
|
||||||
|
let (addr, handle) = unauthenticated_server(vec![
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3004,12 +3012,15 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn two_subscribes_produce_distinct_correlation_ids() {
|
async fn two_subscribes_produce_distinct_correlation_ids() {
|
||||||
// Two AdviseSupervisory calls + two UnAdvise calls.
|
// Six RPCs: Connect + AddSubscriberEngine (once, cached on the
|
||||||
|
// 2nd subscribe) + 2 AdviseSupervisory + 2 UnAdvise.
|
||||||
let (addr, handle) = unauthenticated_server(vec![
|
let (addr, handle) = unauthenticated_server(vec![
|
||||||
(0, Vec::new()),
|
(0, Vec::new()),
|
||||||
(0, Vec::new()),
|
(0, Vec::new()),
|
||||||
(0, Vec::new()),
|
(0, Vec::new()),
|
||||||
(0, Vec::new()),
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
])
|
])
|
||||||
.await;
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
@@ -3242,7 +3253,9 @@ mod tests {
|
|||||||
async fn subscription_stream_yields_data_change_for_matching_correlation() {
|
async fn subscription_stream_yields_data_change_for_matching_correlation() {
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new())]).await;
|
// Three RPCs: Connect + AddSubscriberEngine (F56) + AdviseSupervisory.
|
||||||
|
let (addr, handle) =
|
||||||
|
unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new()), (0, Vec::new())]).await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3287,7 +3300,9 @@ mod tests {
|
|||||||
async fn subscription_stream_filters_out_mismatched_correlation_for_status() {
|
async fn subscription_stream_filters_out_mismatched_correlation_for_status() {
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new())]).await;
|
// Three RPCs: Connect + AddSubscriberEngine (F56) + AdviseSupervisory.
|
||||||
|
let (addr, handle) =
|
||||||
|
unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new()), (0, Vec::new())]).await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3322,8 +3337,9 @@ mod tests {
|
|||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
// 0x33 DataUpdate has no item_correlation_id; the .NET-style
|
// 0x33 DataUpdate has no item_correlation_id; the .NET-style
|
||||||
// filter passes them through to all subscriptions.
|
// filter passes them through to all subscriptions.
|
||||||
|
// Three RPCs: Connect + AddSubscriberEngine (F56) + AdviseSupervisory.
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new())]).await;
|
let (addr, handle) =
|
||||||
|
unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new()), (0, Vec::new())]).await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3472,7 +3488,15 @@ mod tests {
|
|||||||
// F16: every successful subscribe() inserts into the
|
// F16: every successful subscribe() inserts into the
|
||||||
// SubscriptionEntry registry; unsubscribe() removes it.
|
// SubscriptionEntry registry; unsubscribe() removes it.
|
||||||
// Recovery walks this registry to replay AdviseSupervisory.
|
// Recovery walks this registry to replay AdviseSupervisory.
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new())]).await;
|
// Four RPCs: Connect + AddSubscriberEngine (F56) +
|
||||||
|
// AdviseSupervisory + UnAdvise.
|
||||||
|
let (addr, handle) = unauthenticated_server(vec![
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3802,8 +3826,15 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_returns_first_data_change_within_timeout() {
|
async fn read_returns_first_data_change_within_timeout() {
|
||||||
// Server: AdviseSupervisory ack + UnAdvise ack.
|
// Server: Connect + AddSubscriberEngine (F56) +
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new())]).await;
|
// AdviseSupervisory + UnAdvise.
|
||||||
|
let (addr, handle) = unauthenticated_server(vec![
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -3851,9 +3882,16 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_returns_timeout_when_no_data_arrives() {
|
async fn read_returns_timeout_when_no_data_arrives() {
|
||||||
// Server only handles the AdviseSupervisory + UnAdvise (no data
|
// Server only handles the Connect + AddSubscriberEngine (F56) +
|
||||||
// injection). Read must hit the timeout branch.
|
// AdviseSupervisory + UnAdvise (no data injection). Read must
|
||||||
let (addr, handle) = unauthenticated_server(vec![(0, Vec::new()), (0, Vec::new())]).await;
|
// hit the timeout branch.
|
||||||
|
let (addr, handle) = unauthenticated_server(vec![
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
@@ -4407,21 +4445,29 @@ mod tests {
|
|||||||
/// the negative control; this test pins the buffered branch.
|
/// the negative control; this test pins the buffered branch.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn unsubscribe_skips_un_advise_for_buffered_subscription() {
|
async fn unsubscribe_skips_un_advise_for_buffered_subscription() {
|
||||||
let (addr, recorded, handle) =
|
// Three RPCs: Connect + AddSubscriberEngine (F56) +
|
||||||
recording_server(vec![(0, Vec::new()), (0, Vec::new())]).await;
|
// AdviseSupervisory. The buffered unsubscribe MUST NOT add a
|
||||||
|
// fourth (F47 skips UnAdvise on buffered drop).
|
||||||
|
let (addr, recorded, handle) = recording_server(vec![
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
(0, Vec::new()),
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
let resolver: Arc<dyn Resolver> = Arc::new(StaticResolver::new(&[(
|
||||||
"TestObj.TestInt",
|
"TestObj.TestInt",
|
||||||
sample_metadata(),
|
sample_metadata(),
|
||||||
)]));
|
)]));
|
||||||
let session = connect_test_session(addr, resolver).await.unwrap();
|
let session = connect_test_session(addr, resolver).await.unwrap();
|
||||||
|
|
||||||
// Issue a plain subscribe — server records AdviseSupervisory.
|
// Issue a plain subscribe — server records Connect +
|
||||||
|
// AddSubscriberEngine + AdviseSupervisory.
|
||||||
let sub = session.subscribe("TestObj.TestInt").await.unwrap();
|
let sub = session.subscribe("TestObj.TestInt").await.unwrap();
|
||||||
let cid = sub.correlation_id;
|
let cid = sub.correlation_id;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recorded.lock().unwrap().len(),
|
recorded.lock().unwrap().len(),
|
||||||
1,
|
3,
|
||||||
"subscribe should issue 1 RPC"
|
"subscribe should issue 3 RPCs (Connect + AddSubscriberEngine + AdviseSupervisory)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mutate the registry entry's mode to Buffered (synthesise the
|
// Mutate the registry entry's mode to Buffered (synthesise the
|
||||||
@@ -4438,13 +4484,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe the now-buffered entry. F47 contract: NO
|
// Unsubscribe the now-buffered entry. F47 contract: NO
|
||||||
// UnAdvise is emitted on the wire; recorded count stays at 1.
|
// UnAdvise is emitted on the wire; recorded count stays at 3
|
||||||
|
// (the Connect + AddSubscriberEngine + AdviseSupervisory from
|
||||||
|
// the original subscribe).
|
||||||
session.unsubscribe(sub).await.unwrap();
|
session.unsubscribe(sub).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recorded.lock().unwrap().len(),
|
recorded.lock().unwrap().len(),
|
||||||
1,
|
3,
|
||||||
"buffered unsubscribe must not issue UnAdvise; recorded RPC count must stay at 1 \
|
"buffered unsubscribe must not issue UnAdvise; recorded RPC count must stay at 3"
|
||||||
(the original AdviseSupervisory)"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Registry is still cleared — F47's skip applies only to the
|
// Registry is still cleared — F47's skip applies only to the
|
||||||
|
|||||||
@@ -67,12 +67,23 @@ function Set-LiveEnvVar {
|
|||||||
|
|
||||||
function Get-InfisicalSecret {
|
function Get-InfisicalSecret {
|
||||||
param([string]$Key, [string]$Env = 'infrastructure', [string]$Path = '/windows-hosts')
|
param([string]$Key, [string]$Env = 'infrastructure', [string]$Path = '/windows-hosts')
|
||||||
|
# Capture stdout only — the infisical CLI writes its
|
||||||
|
# "A new release of infisical is available" upgrade banner (and any
|
||||||
|
# transient diagnostic noise) to STDERR. We deliberately do NOT use
|
||||||
|
# `2>&1` here so that banner stays in the error stream (visible on
|
||||||
|
# the console for diagnostics) and never pollutes the secret value.
|
||||||
|
# An earlier version of this function used `2>&1` plus a regex-based
|
||||||
|
# banner filter; that approach was brittle to future banner shapes,
|
||||||
|
# so it was replaced with stream separation. If a real error needs
|
||||||
|
# to be surfaced, $LASTEXITCODE catches it below.
|
||||||
try {
|
try {
|
||||||
$value = & $GetSecret -Key $Key -Env $Env -Path $Path 2>&1
|
$value = & $GetSecret -Key $Key -Env $Env -Path $Path
|
||||||
if ($LASTEXITCODE -ne 0 -or -not $value) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "Get-Secret returned empty for $Env$Path/$Key (exit code $LASTEXITCODE)"
|
throw "Get-Secret exit code $LASTEXITCODE for $Env$Path/$Key"
|
||||||
|
}
|
||||||
|
if (-not $value) {
|
||||||
|
throw "Get-Secret returned empty stdout for $Env$Path/$Key"
|
||||||
}
|
}
|
||||||
# Trim any trailing whitespace from the CLI output
|
|
||||||
return ($value | Out-String).Trim()
|
return ($value | Out-String).Trim()
|
||||||
} catch {
|
} catch {
|
||||||
throw "Failed to fetch $Env$Path/$Key from Infisical: $_"
|
throw "Failed to fetch $Env$Path/$Key from Infisical: $_"
|
||||||
|
|||||||
Reference in New Issue
Block a user