diff --git a/.agents/skills/publishing-and-deployment/SKILL.md b/.agents/skills/publishing-and-deployment/SKILL.md
index 16e970665..a3dc9ba23 100644
--- a/.agents/skills/publishing-and-deployment/SKILL.md
+++ b/.agents/skills/publishing-and-deployment/SKILL.md
@@ -35,7 +35,7 @@ Registry release inventory: 18 package-bearing contracts across 9 producers; 16
| `audio` | `fs-gg-audio` | `0.6.0` | `0.6.0` | `audio:0.6.0` | FS.GG.Audio.Core/.Host/.Engine/.Elmish/.WebBrowser public .fsi surfaces — AudioEffect vocabulary + IAudioBackend/IMixingBackend seam + mixing Engine + Audio.Cmd Elmish bridge + browser Web Audio host |
| `net` | `fs-gg-net` | `0.6.0` | `0.6.0` | `net:0.6.0` | FS.GG.Net.Core/.WebSocket/.WebSocket.Server/.Protobuf/.Grpc/.Elmish public .fsi surfaces — ITransport/IMessageChannel seam + Sequential/Multiplexed correlation + serve/ServerEcho + WebSocket client/server transport + Google.Protobuf/protobuf-net codecs + gRPC lifecycle bridge + Elmish Cmd/Sub |
| `github` | `coord-engine` | `0.113.0` | `0.112.0` | `github:0.112.0` | the `fsgg-coord-engine` CLI verb surface (claim/take/batch/who/widen/set-paths/say/landable/done/release/flush/…) + its exit-code contract, emitted from src/FS.GG.Coord.Core/Protocol.fs; shipped as the FS.GG.Coord.Cli dotnet tool |
-| `github` | `telemetry-host` | `0.8.0` | `0.8.0` | `github:0.8.0` | optional Linux x64 FS.GG.Telemetry.Host dotnet tool — authenticated workspace-scoped ingestion, private dashboard, protected native capture and bounded learning export; independently versioned from the github coordination coherent set |
+| `github` | `telemetry-host` | `0.9.0` | `0.8.0` | `github:0.8.0` | optional Linux x64 FS.GG.Telemetry.Host dotnet tool — authenticated workspace-scoped ingestion, private dashboard, protected native capture and bounded learning export; independently versioned from the github coordination coherent set |
| `github` | `new-sdd-workspace` | `0.22.0` | `0.21.0` | `github:0.21.0` | the `new-sdd-workspace` scaffolder CLI (package FS.GG.NewSddWorkspace) — one-command full-stack SDD workspace creation, wrapping the FS.GG.Templates `rendering` provider (ADR-0016); shipped as a dotnet tool |
| `templates` | `fs-gg-workspace-template` | `0.19.3` | `0.19.3` | `templates:0.19.3` | FS.GG.Workspace.Template package, eight template configurations and seven `dotnet new` selectors: workspace-provider identities — `fs-gg-console` (provider `console` — minimal F# executable, no npm lane, ADR-0072 §2); `fs-gg-web` (provider `web` — F# ASP.NET Core + plain TypeScript/Vite website, ADR-0071 §3); `fs-gg-fable-game` (provider `fable-game` — F# ASP.NET Core + Fable/Elmish game client, SignalR for connection-oriented real-time traffic + plain HTTP endpoints with explicit versioned DTOs for typed request/response per ADR-0073, consuming the published `fs-gg-game-core-fable-lockstep-v1` profile per ADR-0069); `fs-gg-fable-bindings` (provider `fable-bindings` — Fable interop library over an exactly pinned npm package/declaration closure, ADR-0072 §3, first proved by the unreleased EHotwagner/babylonjsBindings reference) — plus `fs-gg-governance`, the separately registered pre-existing governance overlay template folded into this package at the same rename; `fs-gg-project-knowledge` and `fs-gg-python` are also already packaged. The current and legacy fable-game configurations share one selector. |
| `game` | `game-skills` | `0.9.0` | `0.9.0` | `game:0.9.0` | FS.GG.Game.Skills 0.9.0 package — owner-sourced game, Fable, replay and rules guidance with schema-v2 per-file digests and workspace bundle predicates; embedded by the SDD 1.8 receiver |
diff --git a/.claude/skills/publishing-and-deployment/SKILL.md b/.claude/skills/publishing-and-deployment/SKILL.md
index 16e970665..a3dc9ba23 100644
--- a/.claude/skills/publishing-and-deployment/SKILL.md
+++ b/.claude/skills/publishing-and-deployment/SKILL.md
@@ -35,7 +35,7 @@ Registry release inventory: 18 package-bearing contracts across 9 producers; 16
| `audio` | `fs-gg-audio` | `0.6.0` | `0.6.0` | `audio:0.6.0` | FS.GG.Audio.Core/.Host/.Engine/.Elmish/.WebBrowser public .fsi surfaces — AudioEffect vocabulary + IAudioBackend/IMixingBackend seam + mixing Engine + Audio.Cmd Elmish bridge + browser Web Audio host |
| `net` | `fs-gg-net` | `0.6.0` | `0.6.0` | `net:0.6.0` | FS.GG.Net.Core/.WebSocket/.WebSocket.Server/.Protobuf/.Grpc/.Elmish public .fsi surfaces — ITransport/IMessageChannel seam + Sequential/Multiplexed correlation + serve/ServerEcho + WebSocket client/server transport + Google.Protobuf/protobuf-net codecs + gRPC lifecycle bridge + Elmish Cmd/Sub |
| `github` | `coord-engine` | `0.113.0` | `0.112.0` | `github:0.112.0` | the `fsgg-coord-engine` CLI verb surface (claim/take/batch/who/widen/set-paths/say/landable/done/release/flush/…) + its exit-code contract, emitted from src/FS.GG.Coord.Core/Protocol.fs; shipped as the FS.GG.Coord.Cli dotnet tool |
-| `github` | `telemetry-host` | `0.8.0` | `0.8.0` | `github:0.8.0` | optional Linux x64 FS.GG.Telemetry.Host dotnet tool — authenticated workspace-scoped ingestion, private dashboard, protected native capture and bounded learning export; independently versioned from the github coordination coherent set |
+| `github` | `telemetry-host` | `0.9.0` | `0.8.0` | `github:0.8.0` | optional Linux x64 FS.GG.Telemetry.Host dotnet tool — authenticated workspace-scoped ingestion, private dashboard, protected native capture and bounded learning export; independently versioned from the github coordination coherent set |
| `github` | `new-sdd-workspace` | `0.22.0` | `0.21.0` | `github:0.21.0` | the `new-sdd-workspace` scaffolder CLI (package FS.GG.NewSddWorkspace) — one-command full-stack SDD workspace creation, wrapping the FS.GG.Templates `rendering` provider (ADR-0016); shipped as a dotnet tool |
| `templates` | `fs-gg-workspace-template` | `0.19.3` | `0.19.3` | `templates:0.19.3` | FS.GG.Workspace.Template package, eight template configurations and seven `dotnet new` selectors: workspace-provider identities — `fs-gg-console` (provider `console` — minimal F# executable, no npm lane, ADR-0072 §2); `fs-gg-web` (provider `web` — F# ASP.NET Core + plain TypeScript/Vite website, ADR-0071 §3); `fs-gg-fable-game` (provider `fable-game` — F# ASP.NET Core + Fable/Elmish game client, SignalR for connection-oriented real-time traffic + plain HTTP endpoints with explicit versioned DTOs for typed request/response per ADR-0073, consuming the published `fs-gg-game-core-fable-lockstep-v1` profile per ADR-0069); `fs-gg-fable-bindings` (provider `fable-bindings` — Fable interop library over an exactly pinned npm package/declaration closure, ADR-0072 §3, first proved by the unreleased EHotwagner/babylonjsBindings reference) — plus `fs-gg-governance`, the separately registered pre-existing governance overlay template folded into this package at the same rename; `fs-gg-project-knowledge` and `fs-gg-python` are also already packaged. The current and legacy fable-game configurations share one selector. |
| `game` | `game-skills` | `0.9.0` | `0.9.0` | `game:0.9.0` | FS.GG.Game.Skills 0.9.0 package — owner-sourced game, Fable, replay and rules guidance with schema-v2 per-file digests and workspace bundle predicates; embedded by the SDD 1.8 receiver |
diff --git a/.github/workflows/release-telemetry-host-successor-candidate.yml b/.github/workflows/release-telemetry-host-successor-candidate.yml
index 7bfca8acb..97a70208f 100644
--- a/.github/workflows/release-telemetry-host-successor-candidate.yml
+++ b/.github/workflows/release-telemetry-host-successor-candidate.yml
@@ -1,10 +1,10 @@
name: release-telemetry-host-successor-candidate
-# Published predecessor is immutable Host0.7.0 (utel-host-rel-13).
+# Published predecessor is immutable Host0.8.0 (utel-host-rel-14).
# Prospective source coordinates only. Separately admitted invocation may establish
# feed absence in its mandatory first gate; no candidate work precedes that PASS.
# Root still supplies fresh tag/release/journal/protection and source admission.
-# Independent Host 0.8.0 source and byte qualification only. This workflow has
+# Independent Host 0.9.0 source and byte qualification only. This workflow has
# no package-feed, tag, release, journal, OIDC or promotion write capability.
on:
workflow_dispatch:
@@ -19,7 +19,7 @@ concurrency:
jobs:
candidate:
- name: telemetry-host-080-candidate
+ name: telemetry-host-090-candidate
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 45
@@ -54,14 +54,14 @@ jobs:
test "$(git rev-parse HEAD)" = "$SOURCE_SHA"
test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$SOURCE_SHA"
version="$(dotnet msbuild src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj -getProperty:Version | tr -d '[:space:]')"
- test "$version" = 0.8.0
+ test "$version" = 0.9.0
- name: Test passive Host HTTP authority before browser qualification
timeout-minutes: 5
run: |
set -euo pipefail
dotnet restore tests/FS.GG.Telemetry.Tests/FS.GG.Telemetry.Tests.fsproj --locked-mode
dotnet test tests/FS.GG.Telemetry.Tests/FS.GG.Telemetry.Tests.fsproj -c Release --no-restore --filter "FullyQualifiedName~HostPassiveReceiptTests|FullyQualifiedName~HostSchemaProfileTests|FullyQualifiedName~RemoteTelemetryTests.protected native collector resolves durable dispatch and replays retained envelope" --blame --logger "console;verbosity=normal" --logger "trx;LogFileName=host-passive.trx" --results-directory "$RUNNER_TEMP/host-passive"
- python3 scripts/read-trx-count.py "$RUNNER_TEMP/host-passive/host-passive.trx" --minimum 4 --label "passive Host HTTP" --summary-file "$GITHUB_STEP_SUMMARY"
+ python3 scripts/read-trx-count.py "$RUNNER_TEMP/host-passive/host-passive.trx" --minimum 5 --label "passive Host HTTP" --summary-file "$GITHUB_STEP_SUMMARY"
- name: Retain passive Host HTTP diagnostics
if: ${{ always() && steps.read-first.outcome == 'success' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -95,13 +95,13 @@ jobs:
mkdir -p artifacts/telemetry-host
dotnet pack src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj -c Release --no-restore \
-p:RepositoryCommit="$SOURCE_SHA" -o artifacts/telemetry-host
- package=artifacts/telemetry-host/FS.GG.Telemetry.Host.0.8.0.nupkg
+ package=artifacts/telemetry-host/FS.GG.Telemetry.Host.0.9.0.nupkg
manifest=artifacts/telemetry-host/manifest.json
test -f "$package"
python3 scripts/telemetry-host-release.py prepare --package "$package" \
--lock src/FS.GG.Telemetry.Host/packages.lock.json \
--assets src/FS.GG.Telemetry.Dashboard/Assets \
- --source-sha "$SOURCE_SHA" --version 0.8.0 --tag telemetry-host/v0.8.0 \
+ --source-sha "$SOURCE_SHA" --version 0.9.0 --tag telemetry-host/v0.9.0 \
--output "$manifest"
python3 scripts/telemetry-host-release.py verify --manifest "$manifest" --package "$package"
bash tests/standalone-telemetry-host-package/run.sh "$package" artifacts/telemetry-host/package-evidence.json
@@ -115,7 +115,7 @@ jobs:
with:
name: telemetry-host-successor-candidate-${{ github.sha }}-${{ github.run_id }}
path: |
- artifacts/telemetry-host/FS.GG.Telemetry.Host.0.8.0.nupkg
+ artifacts/telemetry-host/FS.GG.Telemetry.Host.0.9.0.nupkg
artifacts/telemetry-host/manifest.json
artifacts/telemetry-host/package-evidence.json
if-no-files-found: error
diff --git a/.github/workflows/release-telemetry-host-successor-publish.yml b/.github/workflows/release-telemetry-host-successor-publish.yml
index eeb7aed09..0276a48a8 100644
--- a/.github/workflows/release-telemetry-host-successor-publish.yml
+++ b/.github/workflows/release-telemetry-host-successor-publish.yml
@@ -19,7 +19,7 @@ on:
required: true
type: string
continue_failed_original:
- description: Retired Host 0.6 continuation flag; current Host 0.8 refuses true
+ description: Retired Host 0.6 continuation flag; current Host 0.9 refuses true
required: true
default: false
type: boolean
@@ -41,7 +41,7 @@ concurrency:
jobs:
publish:
- name: telemetry-host-080-publisher
+ name: telemetry-host-090-publisher
if: github.ref == 'refs/heads/main' && github.actor == 'EHotwagner'
runs-on: ubuntu-latest
environment: release-successor
diff --git a/.github/workflows/telemetry-host-package.yml b/.github/workflows/telemetry-host-package.yml
index 426aae7b0..f716575ab 100644
--- a/.github/workflows/telemetry-host-package.yml
+++ b/.github/workflows/telemetry-host-package.yml
@@ -49,6 +49,8 @@ on:
- "tests/standalone-telemetry-host-package/**"
- "tests/telemetry-host-release/**"
- "scripts/telemetry-host-release.py"
+ - "scripts/telemetry-host-published-org-read.py"
+ - "tests/telemetry-host-published-org-read/**"
- "scripts/telemetry-host-successor-*.py"
- "scripts/telemetry_host_successor_*.py"
- "scripts/release_successor_execution.py"
@@ -93,6 +95,8 @@ on:
- "tests/standalone-telemetry-host-package/**"
- "tests/telemetry-host-release/**"
- "scripts/telemetry-host-release.py"
+ - "scripts/telemetry-host-published-org-read.py"
+ - "tests/telemetry-host-published-org-read/**"
- "scripts/telemetry-host-successor-*.py"
- "scripts/telemetry_host_successor_*.py"
- "scripts/release_successor_execution.py"
@@ -167,6 +171,7 @@ jobs:
run: |
set -euo pipefail
python3 tests/telemetry-host-successor/run.py
+ python3 tests/telemetry-host-published-org-read/run.py
bash tests/telemetry-host-release/run.sh
- name: Test passive Host HTTP authority before expensive qualification
if: ${{ !(inputs.learn_native_fixture || inputs.learn_native_preflight) }}
@@ -175,7 +180,7 @@ jobs:
set -euo pipefail
dotnet restore tests/FS.GG.Telemetry.Tests/FS.GG.Telemetry.Tests.fsproj --locked-mode
dotnet test tests/FS.GG.Telemetry.Tests/FS.GG.Telemetry.Tests.fsproj -c Release --no-restore --filter "FullyQualifiedName~HostPassiveReceiptTests|FullyQualifiedName~HostSchemaProfileTests|FullyQualifiedName~RemoteTelemetryTests.protected native collector resolves durable dispatch and replays retained envelope" --blame --logger "console;verbosity=normal" --logger "trx;LogFileName=host-passive.trx" --results-directory "$RUNNER_TEMP/host-passive"
- python3 scripts/read-trx-count.py "$RUNNER_TEMP/host-passive/host-passive.trx" --minimum 4 --label "passive Host HTTP" --summary-file "$GITHUB_STEP_SUMMARY"
+ python3 scripts/read-trx-count.py "$RUNNER_TEMP/host-passive/host-passive.trx" --minimum 5 --label "passive Host HTTP" --summary-file "$GITHUB_STEP_SUMMARY"
- name: Retain passive Host HTTP diagnostics
if: ${{ always() && !(inputs.learn_native_fixture || inputs.learn_native_preflight) }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
diff --git a/docs/architecture.md b/docs/architecture.md
index 9f7d70404..9b355ea84 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -654,7 +654,7 @@ product's `FS.GG.UI.*` pin), which is a different axis from the template package
| `fs-gg-audio` | FS.GG.Audio | `0.6.0` | `0.6.0` |
| `fs-gg-net` | FS.GG.Net | `0.6.0` | `0.6.0` |
| `coord-engine` | FS-GG/.github | `0.113.0` | `0.112.0` |
-| `telemetry-host` | FS-GG/.github | `0.8.0` | `0.8.0` |
+| `telemetry-host` | FS-GG/.github | `0.9.0` | `0.8.0` |
| `new-sdd-workspace` | FS-GG/.github | `0.22.0` | `0.21.0` |
| `fs-gg-workspace-template` | FS.GG.Templates | `0.19.3` | `0.19.3` |
| `game-skills` | FS.GG.Game | `0.9.0` | `0.9.0` |
diff --git a/docs/coordination/telemetry-host-release.md b/docs/coordination/telemetry-host-release.md
index f1bb0a821..f5024aed8 100644
--- a/docs/coordination/telemetry-host-release.md
+++ b/docs/coordination/telemetry-host-release.md
@@ -1,5 +1,46 @@
# Telemetry host package and release boundary
+## Prospective Host 0.9 source — Outcome4640, 2026-10-23
+
+[PR4802](https://github.com/FS-GG/.github/pull/4802) taught the shared store to admit
+passive `provider-usage-observation/1` facts with provenance
+`claude-local-transcript/1`, each in its own native identity namespace
+(`claude-transcript/1`), beside `codex-local-rollout/1`. Every other provenance is still
+refused. Published Host0.8 was built from `74cbd6f0` and predates that change, so it
+answers a Claude transcript batch with `invalid-request`. The Host takes the parser
+through its Store and Coord.Core project references. It therefore needs a new
+independent package rather than any Host logic change. The packaged closure also carries
+other coherent113 source compiled through those references (the Claude transcript reader
+in `SkillTelemetryReaders.fs` and `Coord.Core/Telemetry.fs`); none of it is reachable from
+Host entry points. Root re-diffs the Host closure against `74cbd6f0` at the dispatch SHA.
+The bump also re-pins `telemetry-host-release.py` in the read-only org observer
+(`scripts/telemetry-host-published-org-read.py`), whose test now runs in the package
+workflow so a stale pin fails at PR time.
+
+This source prepares **0.9.0**, `telemetry-host/v0.9.0` and logical journal15 at
+`state/releases/utel-host-rel-15`. The predecessor is the accepted original Host0.8
+publication: [publisher39580298871](https://github.com/FS-GG/.github/actions/runs/39580298871)
+with independent readonly [39591087147](https://github.com/FS-GG/.github/actions/runs/39591087147),
+nonlatest release419394647 at `74cbd6f0b1b598d48556eb854c4ba6a3c3abda54`, release archive
+`44273495…5aba89` and common normalized payload `f036808546…1802ee`, with journal14.
+The read-first gate now requires the known0.8 predecessor and normalized0.9 absence.
+The other gate, artifact, publisher and journal mechanics are unchanged from Host0.8.
+These are prospective coordinates, not an availability claim. Root still verifies
+tag/release occupancy, Authority journal15 destination/protection and exact accepted
+source before any invocation. Publication needs its own fresh effect admission, and
+the publisher stays default-false.
+
+Store14 and the receipt ABI are unchanged. Since `74cbd6f0`, `TelemetryStore.fs`
+differs only in the provenance-to-namespace match. `TelemetryStoreApplication`
+(`currentSchemaVersion = 14`, DDL and passive query) is byte-identical, and the release
+manifest still selects only schema14. Claude observations use the existing
+`current_ingest_facts` rows and corrections path, so a Home Store14 workspace can move
+from0.8 to0.9 without migration. The focused passive Host HTTP preflight now requires
+five tests. The new test proves that the actual Host receipt endpoint applies a Claude
+transcript observation and refuses an unknown provenance or a Codex-namespaced identity.
+Home cutover, credential custody and installed acceptance remain separate.
+Published0.8/journal14, 0.7/journal13 and held0.6/journal12 remain unchanged.
+
## Prospective Host 0.8 source — Delivery4707, 2026-10-21
[Delivery4707](https://github.com/FS-GG/.github/issues/4707) owns the first installed
diff --git a/docs/registry/compatibility.md b/docs/registry/compatibility.md
index 02c4aa880..6b0d2bf40 100644
--- a/docs/registry/compatibility.md
+++ b/docs/registry/compatibility.md
@@ -180,7 +180,7 @@ authored `Version` cells of the Versioned contracts table (#748).*
| `fs-gg-net` | FS.GG.Net | `0.6.0` | `0.6.0` |
| `keyboard-input` | FS.GG.Rendering | `0.5.0` | — |
| `coord-engine` | FS-GG/.github | `0.113.0` | `0.112.0` |
-| `telemetry-host` | FS-GG/.github | `0.8.0` | `0.8.0` |
+| `telemetry-host` | FS-GG/.github | `0.9.0` | `0.8.0` |
| `new-sdd-workspace` | FS-GG/.github | `0.22.0` | `0.21.0` |
| `fs-gg-workspace-template` | FS.GG.Templates | `0.19.3` | `0.19.3` |
| `game-skills` | FS.GG.Game | `0.9.0` | `0.9.0` |
diff --git a/registry/CHANGELOG.md b/registry/CHANGELOG.md
index 6e9c6366c..10ca63ebc 100644
--- a/registry/CHANGELOG.md
+++ b/registry/CHANGELOG.md
@@ -143,6 +143,8 @@ no gate behaviour changes — this is purely how humans record the log.
## Entries
+- **2026-10-23** — HOST090 SOURCE (.github; #4640): advance only independent Telemetry Host source to0.9.0 so a Host built from current main admits passive provider-usage-observation/1 with provenance claude-local-transcript/1 (#4802); published Host0.8.0 at74cbd6f0 refuses it. New tag telemetry-host/v0.9.0 and journal15 (utel-host-rel-15) are prospective coordinates over predecessor published0.8/journal14. Store14 and the receipt ABI are unchanged; published package-version/tag stay0.8.0. No candidate, preflight, publication or Home adoption is asserted.
+
### 2026-10-23 — `wasm-shared-runtime`: 0.7 publication, separate last installed 0.6 (game; Game#728, Game#727)
Record original publisher [39894673177/attempt1](https://github.com/FS-GG/FS.GG.Game/actions/runs/39894673177) (release-wasm: preflight, admission, begin, org, public, assets and complete succeeded) at producer `15e287d7c8884f088b7db41140f108f3f0136043` (Game#728), release421418319 and lightweight tag `wasm/v0.7.0` → `15e287d7`. Release assets: SDK `090658bd…`, release-manifest `56e585db…`, promotion-binding `4372fe16…` (binding preparation39891458102/a1, artifact12476030732) and SHA256SUMS `dc62714d…`. Both nuget.org archives (Contracts `79060cef…`, Browser `bfc28ac9…`) were independently verified equal to the originals (`6a778633…`, `1d5e9df2…`) in every entry except `.signature.p7s`; the GitHub Packages side rests on the publisher's own org job. Published0.7 API classification against published0.6 producer `8a5e63ba` is compatible for both packages. 0.7.0 starts the policy deadline at Worker readiness with a separately bounded Worker startup (FS-GG/FS.GG.Game#727); the self-contained bundled Worker it retains has shipped since 0.5. Installed0.7 remains unqualified; the last accepted installed0.6 P8a5e/Q9a98 profile and every prior immutable receipt remain separate. Consumers stay empty; Workspace.Template 0.19.3 selecting [0.7.0] (FS.GG.Templates#748) is a source/package selection, not consumer acceptance.
diff --git a/registry/dependencies.yml b/registry/dependencies.yml
index 7616eaeca..17be544af 100644
--- a/registry/dependencies.yml
+++ b/registry/dependencies.yml
@@ -1020,7 +1020,7 @@ contracts:
surface: "the `fsgg-coord-engine` CLI verb surface (claim/take/batch/who/widen/set-paths/say/landable/done/release/flush/…) + its exit-code contract, emitted from src/FS.GG.Coord.Core/Protocol.fs; shipped as the FS.GG.Coord.Cli dotnet tool"
consumers: [sdd, rendering, governance, templates, game, audio, net] # All SEVEN coordination-kit receivers (registry/repos.yml `receives: coordination-kit`). `scripts/fsgg-coord` resolves and execs this package, so every receiver is a real TOOL consumer edge. `.github` itself is not listed because it resolves the engine from source.
- id: telemetry-host
- version: "0.8.0" # Accepted original Host0.8 publication: publisher39580298871 plus readonly39591087147; both feeds and all eight journal/promote assertions accepted. Full4707/Home adoption remains open. PRIOR source qualification: PROSPECTIVE SOURCE ONLY under4707: accepted unassigned-usage projection/browser source4685, independent new package/tag/journal14 planning. Org-feed occupancy remains Unknown after native REST/v3 HTTP403; fresh authoritative absence is required before candidate/publication. Published0.7/journal13 remains unchanged; retained0.6/journal12 tag intent remains Unknown. Store14/receipt ABI unchanged; source qualification, publication and Home adoption remain separate.
+ version: "0.9.0" # PROSPECTIVE SOURCE ONLY under4640: admit passive claude-local-transcript/1 usage (#4802) in an independent new package/tag/journal15 (utel-host-rel-15) over predecessor published Host0.8 (74cbd6f0, release419394647, journal14). No Host logic change; Store14/receipt ABI unchanged, so Home Store14 needs no migration. Fresh authoritative absence, candidate qualification, publication and Home adoption remain separate. PRIOR: Accepted original Host0.8 publication: publisher39580298871 plus readonly39591087147; both feeds and all eight journal/promote assertions accepted. Full4707/Home adoption remains open. PRIOR source qualification: PROSPECTIVE SOURCE ONLY under4707: accepted unassigned-usage projection/browser source4685, independent new package/tag/journal14 planning. Org-feed occupancy remains Unknown after native REST/v3 HTTP403; fresh authoritative absence is required before candidate/publication. Published0.7/journal13 remains unchanged; retained0.6/journal12 tag intent remains Unknown. Store14/receipt ABI unchanged; source qualification, publication and Home adoption remain separate.
package-version: "0.8.0" # Accepted original https://github.com/FS-GG/.github/actions/runs/39580298871 with independent readonly https://github.com/FS-GG/.github/actions/runs/39591087147; no republish. PRIOR immutable0.7.0: PUBLISHED: original39148437514/a1, journal13 generation17/all8 verified, release416586994 telemetry-host/v0.7.0, exact source2e60210be1e04d52fe0735a56333b3e3aef42284. Candidate/release archive d59ecac7ef4d1cdc75cebdcb08624b73960309821def1cd83d2909dbda5de253; signed NuGet archive c24af6af3e35f67dc0eca1f0f7c82657a8c82adf3c62ea11a986b1deb6bb4460; common normalized producer payload f8ec077387cfbcefbae89f74781ba7bbcfce1ebc2e97f7c7dd43d7bdcddeda1b. Old0.6 journal12/gen2 remains Unknown; no resend. Home installation/enrollment/restart/restore remain unqualified. PRIOR immutable0.5.0: CURRENT: successful publisher 37567512069; both feeds retain normalized producer payload sha256:4e2762e159229b433a3779f1694c2f8786618c4b51d22df3332086ae8071c68d. GitHub archive sha256:bdc8d0e92ff3c3a218cf78c349186049222e7487e364be434709cee6eba2cddc; signed nuget.org archive sha256:03a0bed87cafee1776970d854d30b458c5e87d35424700e62cdcac94d84278d6. PRIOR immutable0.4.0: first-success publisher 37220419362; both feeds serve normalized producer payload sha256:d7e747cebd6f425d775177bc844d0b3d187a911ae4032e269b008c875a872240. GitHub release archive sha256:150f3e697e088a10170db4f2404fd4e73be856ccefc8d776c53076f55adce45a; nuget.org signed archive sha256:293e2ccdbc7a4aed2c56d72cf82a7f806d7671b549439463cd93164dad574f77. PRIOR immutable 0.3.0: producer payload sha256:5572aa61f284abc5a37a12aa88f99b5169c4379be2aa9959e46c9934532f2836; GitHub archive sha256:c7cbaa474fdcd0ba577f8577ec92bb381f032db7842f86ddb1b3fb050d8a97ad; signed nuget.org archive sha256:7ad2c30894cb3eafbf498e5d247034bc3167ee30dd07c8b09c6b4915657c598a. PRIOR immutable 0.2.1: GitHub Packages archive sha256:4847c15ab207a33556462873840ad4109cf1c1e16fd7a15d5fffae5a8162f589 and signed nuget.org archive sha256:681faeca9941aaeaaad29368f1ab4f1f3c4b0c581655b8a2d8054b6448adc6f2; normalized payload sha256:207843031c78c2711e85a2db14b46a83ec0da9fbd3ee0f144a736320f7e8c2c9.
package-tag: "telemetry-host/v0.8.0" # Original Host0.8 publication accepted; source/publication/Home acceptance remain distinct. PRIOR immutable telemetry-host/v0.7.0: Accepted original39148437514/a1, exact three-asset nonlatest release416586994. Coherent latest remains0.106; accepted binding4 unchanged. Observed selector106 is not whole runtime/discovery qualification; prior selector and change timing Unknown. PRIOR immutable telemetry-host/v0.5.0: Promoted release 405350640; exact raw manifest sha256:38ef6cd52df01902545bc923662dc7b606b76ba72e066f109248b664cb2ef1c7 and publication-journal sha256:5363fe78311dbde1742088105a7f56dc5f48f51013cba2bf883b3a743b73c9ce. Canonical manifest sha256:94fed9c145cb437f7385ffb6d7cf3c83a57eeb7ffdc06904a9668057d83d6a7d matches protected journal utel-host-rel-11 head 57a3d1eac66d3e246d4e8de03988e0a35b7b3cb3, generation17/eight verified effects. PRIOR immutable0.4.0: tag telemetry-host/v0.4.0; Promoted three-asset release 403127331; exact raw manifest sha256:144e5aa3d382b8bfc6ddcac76ed583c23ebe8fbb6744f844a94fa5347819d3f0 and publication-journal asset sha256:c2409342cd3448325fb1dc3dff53b6c2e3783c7634b75101834049b86aa11731. Protected journal head 203ba60f7df146743d4a7373a52f55fc6c22858e retains 17 generations and eight verified effects. PRIOR immutable 0.3.0: release 401538149, manifest sha256:7d61b4888d08299b5578df2e3e283dee88b5acabafa53490b4ba8d00e6676704, journal sha256:ec63822c627cfad490f9eea731257ac531c9ac3bd681f4790b9489dcfc8470ce. PRIOR immutable 0.2.1: publisher 36739270730, manifest sha256:f16546855d4ce60060f762b6f7bed715cc115f9ef97cd7b55dad5294b2ea0816 and journal sha256:bce45f3f4c0701031863c6b1eb398a99982b152f585467cb2e7b06c00987c58e.
owner: github
diff --git a/registry/driver-skill-manifest.json b/registry/driver-skill-manifest.json
index 7aacbcd65..9d06ccc6a 100644
--- a/registry/driver-skill-manifest.json
+++ b/registry/driver-skill-manifest.json
@@ -2519,12 +2519,12 @@
{
"id": "publishing-and-deployment",
"scope": "operator",
- "sha256": "43461e46eacefe131d366f470b6d61be7fa4f077d529e10f1030024287a36b0a",
- "tree-sha256": "d30dba44220812f2a1f40bd867438d41a0ea12060cb408ba39c31a2e42c7aeb3",
+ "sha256": "2d42a8e9a4cda02846fd677265b33d73d5e2e971183ab28acef4f898dc75b93f",
+ "tree-sha256": "2201ac33635938634da52e598f8cc51ed6bb066bc70f373aa7675fd1cdd29dba",
"files": [
{
"path": "SKILL.md",
- "sha256": "43461e46eacefe131d366f470b6d61be7fa4f077d529e10f1030024287a36b0a",
+ "sha256": "2d42a8e9a4cda02846fd677265b33d73d5e2e971183ab28acef4f898dc75b93f",
"executable": false
},
{
diff --git a/scripts/telemetry-host-candidate-read-gate.py b/scripts/telemetry-host-candidate-read-gate.py
index 35a888468..37e5c2e7a 100644
--- a/scripts/telemetry-host-candidate-read-gate.py
+++ b/scripts/telemetry-host-candidate-read-gate.py
@@ -20,10 +20,10 @@
ROOT = pathlib.Path(__file__).resolve().parents[1]
PACKAGE = "FS.GG.Telemetry.Host"
-VERSION = "0.8.0"
-PREDECESSOR = "0.7.0"
-TAG = "telemetry-host/v0.8.0"
-JOURNAL = "refs/heads/fsgg/v2/journal/release/utel-host-rel-14"
+VERSION = "0.9.0"
+PREDECESSOR = "0.8.0"
+TAG = "telemetry-host/v0.9.0"
+JOURNAL = "refs/heads/fsgg/v2/journal/release/utel-host-rel-15"
API = "https://api.github.com/repos/FS-GG/.github"
FEEDS = {
"github": "https://nuget.pkg.github.com/FS-GG/download/fs.gg.telemetry.host",
diff --git a/scripts/telemetry-host-published-org-read.py b/scripts/telemetry-host-published-org-read.py
index 843fda99c..3957b2250 100644
--- a/scripts/telemetry-host-published-org-read.py
+++ b/scripts/telemetry-host-published-org-read.py
@@ -36,7 +36,7 @@
ORG_URL = "https://nuget.pkg.github.com/FS-GG/download/fs.gg.telemetry.host/0.8.0/fs.gg.telemetry.host.0.8.0.nupkg"
ASSETS = {"manifest.json": (662076940, 768, MANIFEST_SHA),
"publication-journal.json": (662078749, 831, JOURNAL_SHA)}
-PINS = {"telemetry-host-release.py": "b44d74c7f4fb1ab2bb6ce1e0ee00745351577879aede7743d24533b62a60a32c",
+PINS = {"telemetry-host-release.py": "b87002d3192c2cf70d9974a8d7a6b2c3cdca248378c171f04b81519d14c40f7e",
"release-saga.py": "24bdfa2a4c1491ecc1ddf24f797a072372fdce14881d413d9301b739dc77501c",
"fsgg_feed.py": "f7650429432417af72168c288cdee347b338328848870cb4f2f18d6b62d0733f"}
CHOICES = ("coherent-0112-absence", "host-080-org-artifact")
diff --git a/scripts/telemetry-host-release.py b/scripts/telemetry-host-release.py
index 356fa9c75..cb9bde252 100755
--- a/scripts/telemetry-host-release.py
+++ b/scripts/telemetry-host-release.py
@@ -77,7 +77,7 @@ def tree_digest(root: pathlib.Path) -> str:
def store_schema_profile(version: str) -> tuple[int, int]:
# The current release advertises only its selected operational schema. Legacy
# artifact inspection retains its original profile; this grants no migration.
- return (14, 14) if version in {"0.5.0", "0.6.0", "0.7.0", "0.8.0"} else (10, 12)
+ return (14, 14) if version in {"0.5.0", "0.6.0", "0.7.0", "0.8.0", "0.9.0"} else (10, 12)
def build_manifest(args: argparse.Namespace) -> dict:
diff --git a/scripts/telemetry-host-successor-artifact.py b/scripts/telemetry-host-successor-artifact.py
index 226c24bae..a73bf7a39 100644
--- a/scripts/telemetry-host-successor-artifact.py
+++ b/scripts/telemetry-host-successor-artifact.py
@@ -17,7 +17,7 @@
REPOSITORY_ID = 1269292704
WORKFLOW = ".github/workflows/release-telemetry-host-successor-candidate.yml"
-VERSION = "0.8.0"
+VERSION = "0.9.0"
PACKAGE = f"FS.GG.Telemetry.Host.{VERSION}.nupkg"
MEMBERS = {PACKAGE, "manifest.json", "package-evidence.json"}
diff --git a/scripts/telemetry-host-successor-publish.py b/scripts/telemetry-host-successor-publish.py
index 6e303fdb9..de6da092a 100644
--- a/scripts/telemetry-host-successor-publish.py
+++ b/scripts/telemetry-host-successor-publish.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-"""Publish the exact Host 0.8.0 candidate through fresh admitted effects."""
+"""Publish the exact Host 0.9.0 candidate through fresh admitted effects."""
from __future__ import annotations
@@ -19,7 +19,7 @@
from telemetry_host_successor_provider import HostProvider
REPOSITORY = "FS-GG/.github"
-REF = "refs/heads/fsgg/v2/journal/release/utel-host-rel-14"
+REF = "refs/heads/fsgg/v2/journal/release/utel-host-rel-15"
def require(ok: bool, detail: str) -> None:
@@ -47,14 +47,14 @@ def main() -> int:
parser.add_argument("--candidate-run-id", required=True, type=int)
parser.add_argument("--candidate-artifact-id", required=True, type=int)
parser.add_argument("--candidate-archive-sha256", required=True)
- parser.add_argument("--continue-failed-original", action="store_true", help="Retired Host 0.6 flag; current Host 0.8 refuses it")
+ parser.add_argument("--continue-failed-original", action="store_true", help="Retired Host 0.6 flag; current Host 0.9 refuses it")
parser.add_argument("--workdir", required=True, type=pathlib.Path)
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument("--preflight-only", action="store_true")
mode.add_argument("--publish", action="store_true")
args = parser.parse_args()
try:
- require(not args.continue_failed_original, "Host 0.6 failed-original continuation is retired; current Host 0.8 requires a new exact-source candidate")
+ require(not args.continue_failed_original, "Host 0.6 failed-original continuation is retired; current Host 0.9 requires a new exact-source candidate")
publisher_sha = os.environ["GITHUB_SHA"]
operator = os.environ["GITHUB_ACTOR"]
run_id = int(os.environ["GITHUB_RUN_ID"])
@@ -77,7 +77,7 @@ def main() -> int:
require(artifact.get("id") == args.candidate_artifact_id, "candidate artifact API identity differs")
candidate_source = run.get("head_sha")
require(isinstance(candidate_source, str) and len(candidate_source) == 40, "candidate source malformed")
- require(candidate_source == publisher_sha, "current Host 0.8 publication requires exact current main candidate")
+ require(candidate_source == publisher_sha, "current Host 0.9 publication requires exact current main candidate")
require(artifact.get("digest") == "sha256:" + args.candidate_archive_sha256, "artifact digest differs")
artifact_json = args.workdir / "artifact.json"
run_json = args.workdir / "run.json"
@@ -97,7 +97,7 @@ def main() -> int:
content_id, ordered = effects(manifest)
admission = HostAdmission(api, manifest, publisher_sha, run_id, operator, "refs/heads/main")
provider = HostProvider(api, manifest_path, github_token, nuget_key)
- intent = {"contentId": content_id, "sourceSha": candidate_source, "version": "0.8.0",
+ intent = {"contentId": content_id, "sourceSha": candidate_source, "version": "0.9.0",
"candidateArchiveSha256": args.candidate_archive_sha256, "operator": operator}
ledger_api = GitHubAPI(ledger_token)
route = ordinary_route()
diff --git a/scripts/telemetry_host_successor_admission.py b/scripts/telemetry_host_successor_admission.py
index f2e796b27..d75cb3646 100644
--- a/scripts/telemetry_host_successor_admission.py
+++ b/scripts/telemetry_host_successor_admission.py
@@ -1,4 +1,4 @@
-"""Fresh per-effect native Actions and main-ref admission for Host 0.8.0."""
+"""Fresh per-effect native Actions and main-ref admission for Host 0.9.0."""
from __future__ import annotations
diff --git a/scripts/telemetry_host_successor_execution.py b/scripts/telemetry_host_successor_execution.py
index 92cc6ffe7..f9f04903b 100644
--- a/scripts/telemetry_host_successor_execution.py
+++ b/scripts/telemetry_host_successor_execution.py
@@ -9,8 +9,8 @@
from release_successor_execution import Effect, Refused
PACKAGE = "FS.GG.Telemetry.Host"
-VERSION = "0.8.0"
-TAG = "telemetry-host/v0.8.0"
+VERSION = "0.9.0"
+TAG = "telemetry-host/v0.9.0"
def effects(manifest: dict) -> tuple[str, tuple[Effect, ...]]:
@@ -23,7 +23,7 @@ def effects(manifest: dict) -> tuple[str, tuple[Effect, ...]]:
or not re.fullmatch(r"[0-9a-f]{64}", manifest.get("archiveSha256", ""))
or not re.fullmatch(r"sha256:[0-9a-f]{64}", manifest.get("producerPayloadSha256", ""))
):
- raise Refused("not an exact Host 0.8.0 release manifest")
+ raise Refused("not an exact Host 0.9.0 release manifest")
canonical = json.dumps(manifest, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
manifest_digest = hashlib.sha256(canonical).hexdigest()
manifest_archive_digest = hashlib.sha256(canonical + b"\n").hexdigest()
diff --git a/src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj b/src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj
index cb3f535ec..440873782 100644
--- a/src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj
+++ b/src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj
@@ -12,7 +12,7 @@
- 0.8.0
+ 0.9.0
FS.GG.Telemetry.Host
FS.GG.Telemetry.Host
true
@@ -27,7 +27,7 @@
https://github.com/FS-GG/.github
git
https://github.com/FS-GG/.github/tree/main/src/FS.GG.Telemetry.Host
- 0.8.0 — Prospective independent Host release of the accepted bounded unassigned-usage dashboard projection. Package qualification checks corrected private observations in the actual installed browser before and after restore, with partial population and unknown attribution/cost. Store14 and the existing receipt ABI are unchanged. Host0.7 publication/journal13 and Host0.6 journal12/tag intent remain immutable. Source preparation does not qualify coordinate availability, publication or receiver adoption.
+ 0.9.0 — Prospective independent Host release that admits passive provider-usage-observation/1 facts with provenance claude-local-transcript/1 in their own native identity namespace, alongside codex-local-rollout/1. Other provenances remain refused. Store14 and the existing receipt ABI are unchanged, so an existing Store14 workspace needs no migration. Host0.8 publication/journal14, Host0.7 publication/journal13 and Host0.6 journal12/tag intent remain immutable. Source preparation does not qualify coordinate availability, publication or receiver adoption.
diff --git a/src/FS.GG.Telemetry.Host/README.md b/src/FS.GG.Telemetry.Host/README.md
index f9dbc5bbd..cba2dd67c 100644
--- a/src/FS.GG.Telemetry.Host/README.md
+++ b/src/FS.GG.Telemetry.Host/README.md
@@ -19,9 +19,11 @@ Browser access keys and producer credentials are separate principals. Secrets
belong in private files referenced by configuration; do not place them in
arguments, URLs, logs, package contents, or source control.
-Prospective version `0.8.0` bundles Store14 and the bounded private unassigned-usage
+Prospective version `0.9.0` bundles Store14 and the bounded private unassigned-usage
display. It accepts partial native response observations through the existing
-authenticated generic receipt API. Its manifest
+authenticated generic receipt API from local Codex rollouts
+(`codex-local-rollout/1`) and Claude Code transcripts (`claude-local-transcript/1`),
+each in its own native identity namespace; any other provenance is refused. Its manifest
selects Store14 only; no schema migration is implied. Item attribution, inventory,
terminal coverage and absent counters remain unknown. Passive observations stay
outside qualified item totals. Its fresh release requires a new candidate from the exact
@@ -29,7 +31,7 @@ current source. The retained `0.6.0` candidate and its uncertain tag intent rema
separate; this version does not retry or complete that publication.
Host is independently released under `telemetry-host/v`, separate from
-the Kit, Drivers and coordination CLI coherent set. Published Host `0.7.0` and older
+the Kit, Drivers and coordination CLI coherent set. Published Host `0.8.0` and older
artifacts remain unchanged. Source delivery does not establish a new publication, installed service,
TLS trust, client enrollment or receiver durability.
diff --git a/tests/FS.GG.Telemetry.Tests/HostPassiveReceiptTests.fs b/tests/FS.GG.Telemetry.Tests/HostPassiveReceiptTests.fs
index 736b09595..9126814e3 100644
--- a/tests/FS.GG.Telemetry.Tests/HostPassiveReceiptTests.fs
+++ b/tests/FS.GG.Telemetry.Tests/HostPassiveReceiptTests.fs
@@ -19,6 +19,114 @@ open FS.GG.Telemetry.Host
[]
module HostPassiveReceiptTests =
+ []
+ let ``passive HTTP receipt admits Claude transcript provenance and refuses unknown provenance`` () =
+ task {
+ let root = Path.Combine(Path.GetTempPath(), "fsgg-host-claude-" + Guid.NewGuid().ToString("N"))
+ Directory.CreateDirectory root |> ignore
+ let approved = TelemetryStore.ApprovedLocalDurable
+ let unwrap value = value |> Result.defaultWith (fun errors -> failwithf "%A" errors)
+ let store = Path.Combine(root, "store")
+ let owner: TelemetryReceipt.Scope = { Workspace = "host-claude"; Producer = "owner"; Stream = "observations" }
+ TelemetryStoreApplication.initialize store approved |> unwrap |> ignore
+ TelemetryStoreApplication.enrollReceiptPrincipal store approved
+ { Scope = owner; Role = TelemetryReceipt.Generic; GrantId = Some "owner-grant"; GrantGeneration = Some 1L } |> unwrap |> ignore
+ use listener = new TcpListener(IPAddress.Loopback, 0)
+ listener.Start()
+ let port = (listener.LocalEndpoint :?> IPEndPoint).Port
+ listener.Stop()
+ use rsa = RSA.Create(2048)
+ let request = CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
+ let san = SubjectAlternativeNameBuilder()
+ san.AddIpAddress(IPAddress.Loopback)
+ request.CertificateExtensions.Add(san.Build())
+ use certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddDays(1))
+ let privateFile (name: string) (value: string) =
+ let path = Path.Combine(root, name)
+ File.WriteAllText(path, value)
+ File.SetUnixFileMode(path, enum 0o600)
+ path
+ let secret = "owner" + String('s', 48)
+ let config: HostConfig =
+ { Schema = "fsgg.telemetry.host-config/2"; ListenUrl = $"https://127.0.0.1:{port}"
+ CertificatePath = privateFile "unused.pfx" "not loaded by endpoint fixture"
+ CertificatePasswordFile = privateFile "unused.pass" "not loaded by endpoint fixture"
+ ServiceLockPath = Path.Combine(root, "host.lock")
+ Stores = [| { WorkspaceId = owner.Workspace; Root = store } |]
+ Credentials =
+ [| { Reference = owner.Producer; SecretFile = privateFile "owner.key" secret
+ WorkspaceId = owner.Workspace; ProducerId = owner.Producer; StreamId = owner.Stream
+ Role = "generic"; GrantId = "owner-grant"; GrantGeneration = 1L; Revoked = false } |]
+ BrowserPrincipals = [||]; BrowserSession = RemoteTelemetryTests.browserSession }
+ let state = new Runtime.HostState(config, fun _ -> approved)
+ state.Ready <- true
+ state.StartDrain()
+ let app = (Hosting.createBuilder config.ListenUrl certificate).Build()
+ Endpoints.configure app state (Configuration.credentials config)
+ try
+ do! app.StartAsync()
+ use handler = new HttpClientHandler(AllowAutoRedirect = false)
+ // Exact self-signed leaf is trusted only inside this loopback fixture.
+ handler.ServerCertificateCustomValidationCallback <- fun _ cert _ errors ->
+ errors = Net.Security.SslPolicyErrors.RemoteCertificateChainErrors
+ && cert.GetCertHashString() = certificate.GetCertHashString()
+ use client = new HttpClient(handler)
+ let nativeThread = "44444444-4444-4444-4444-444444444444"
+ let nativeTurn = "55555555-5555-5555-5555-555555555555"
+ let identity (ns: string) =
+ "passive-" + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(ns + "\u001f" + nativeThread + "\u001f" + nativeTurn + "\u001fclaude-response"))).ToLowerInvariant()
+ let fact (ns: string) (provenance: string) =
+ JsonNode.Parse($"""{{"kind":"provider-usage-observation/1","identity":"{identity ns}","itemId":null,"revision":1,"threadId":"{nativeThread}","turnId":"{nativeTurn}","responseId":"claude-response","provider":null,"model":null,"effort":null,"input":40,"cachedInput":30,"cacheWriteInput":5,"output":7,"reasoning":null,"total":47,"sourceInstance":"{String('c',64)}","sourceGeneration":"claude-g1","sourceDigest":"{String('d',64)}","position":0,"capturedAt":"2026-10-22T08:00:00Z","occurredAt":null,"provenance":"{provenance}","populationCoverage":"partial","inventoryCoverage":"unknown","terminalCoverage":"unknown","attributionCoverage":"unknown"}}""")
+ let send (batch: string) (event: JsonNode) =
+ task {
+ let value = JsonNode.Parse($"""{{"schema":"fsgg.telemetry.envelope/1","workspaceId":"{owner.Workspace}","producerId":"{owner.Producer}","streamId":"{owner.Stream}","batchId":"{batch}","payload":{{"schema":"{TelemetryStore.BatchSchema}","ingestId":"{batch}","sourceIdentity":"{owner.Stream}","generation":"host-g1","cursor":"1","eventCount":1,"events":[]}}}}""")
+ value["payload"].["events"].AsArray().Add event
+ use message = new HttpRequestMessage(HttpMethod.Post, config.ListenUrl + "/v1/batches")
+ message.Headers.Authorization <- Headers.AuthenticationHeaderValue("Bearer", secret)
+ message.Content <- new ByteArrayContent(Encoding.UTF8.GetBytes(value.ToJsonString()))
+ use! response = client.SendAsync message
+ let! body = response.Content.ReadAsByteArrayAsync()
+ return response.StatusCode, body
+ }
+ // Unknown provenance and a Claude fact under the Codex namespace stay refused at the Host boundary.
+ for batch, ns, provenance in [ "unknown-provenance", "claude-transcript/1", "claude-statusline/1"
+ "foreign-namespace", "codex-rollout/1", "claude-local-transcript/1" ] do
+ let! status, body = send batch (fact ns provenance)
+ Assert.Equal(HttpStatusCode.BadRequest, status)
+ Assert.Equal(Some "invalid-request", RemoteContract.parseError body)
+ let! accepted, acceptedBody = send "claude-first" (fact "claude-transcript/1" "claude-local-transcript/1")
+ Assert.True(accepted = HttpStatusCode.Accepted || accepted = HttpStatusCode.OK, Encoding.UTF8.GetString acceptedBody)
+ let mutable applied = false
+ for _ in 1..100 do
+ if not applied then
+ use message = new HttpRequestMessage(HttpMethod.Get, config.ListenUrl + "/v1/receipts/claude-first")
+ message.Headers.Authorization <- Headers.AuthenticationHeaderValue("Bearer", secret)
+ use! response = client.SendAsync message
+ let! bytes = response.Content.ReadAsByteArrayAsync()
+ match RemoteContract.parseReceipt bytes with
+ | Ok value when value.Status = "applied" -> applied <- true
+ | _ -> do! Task.Delay 25
+ Assert.True(applied, "Claude transcript receipt never became applied")
+ let snapshot = JsonNode.Parse(TelemetryStoreApplication.passiveObservations store approved owner |> unwrap)
+ let current = Assert.Single(snapshot["observations"].AsArray())
+ Assert.Equal(47L, current["total"].GetValue())
+ Assert.Null(current["itemId"])
+ use connection = new SqliteConnection($"Data Source={Path.Combine(store, TelemetryStoreApplication.databaseFileName)};Mode=ReadOnly;Pooling=False")
+ connection.Open()
+ use command = connection.CreateCommand()
+ command.CommandText <- "SELECT json_extract(canonical,'$.provenance') || ' ' || identity FROM current_ingest_facts WHERE kind='provider-usage-observation/1';"
+ Assert.Equal("claude-local-transcript/1 " + identity "claude-transcript/1", string (command.ExecuteScalar()))
+ use items = connection.CreateCommand()
+ items.CommandText <- "SELECT count(*) FROM items;"
+ Assert.Equal(0L, Convert.ToInt64(items.ExecuteScalar()))
+ connection.Close()
+ finally
+ app.StopAsync().GetAwaiter().GetResult()
+ app.DisposeAsync().AsTask().GetAwaiter().GetResult()
+ (state :> IDisposable).Dispose()
+ if Directory.Exists root then Directory.Delete(root, true)
+ }
+
[]
let ``passive HTTP receipt binds generic authority corrections and unknown attribution`` () =
task {
diff --git a/tests/FS.GG.Telemetry.Tests/HostSchemaProfileTests.fs b/tests/FS.GG.Telemetry.Tests/HostSchemaProfileTests.fs
index 7ca5794b0..fe637afff 100644
--- a/tests/FS.GG.Telemetry.Tests/HostSchemaProfileTests.fs
+++ b/tests/FS.GG.Telemetry.Tests/HostSchemaProfileTests.fs
@@ -74,7 +74,7 @@ type HostSchemaProfileTests() =
let manifestPath = Path.Combine(backup, "backup-manifest.json")
let manifestBytes = File.ReadAllBytes manifestPath
use manifest = JsonDocument.Parse manifestBytes
- Assert.Equal("0.8.0", manifest.RootElement.GetProperty("hostVersion").GetString())
+ Assert.Equal("0.9.0", manifest.RootElement.GetProperty("hostVersion").GetString())
Assert.Equal(14, manifest.RootElement.GetProperty("supportedStoreSchemaMin").GetInt32())
Assert.Equal(14, manifest.RootElement.GetProperty("supportedStoreSchemaMax").GetInt32())
let innerPath = Path.Combine(backup, workspace, "manifest.json")
diff --git a/tests/telemetry-host-successor/run.py b/tests/telemetry-host-successor/run.py
index d49b3c7db..bcc8fd210 100644
--- a/tests/telemetry-host-successor/run.py
+++ b/tests/telemetry-host-successor/run.py
@@ -31,7 +31,7 @@
def manifest():
return {
"schema": "fsgg.telemetry.host-release/1", "packageId": "FS.GG.Telemetry.Host",
- "version": "0.8.0", "tag": "telemetry-host/v0.8.0", "sourceSha": "a" * 40,
+ "version": "0.9.0", "tag": "telemetry-host/v0.9.0", "sourceSha": "a" * 40,
"archiveSha256": "b" * 64, "producerPayloadSha256": "sha256:" + "c" * 64,
}
@@ -46,7 +46,7 @@ def test_prepared_current_package_manifest_uses_only_schema14(self):
package = root / "fixture.nupkg"
with zipfile.ZipFile(package, "w") as archive:
archive.writestr("FS.GG.Telemetry.Host.nuspec",
- "FS.GG.Telemetry.Host0.8.0")
+ "FS.GG.Telemetry.Host0.9.0")
archive.writestr("tools/net10.0/linux-x64/FS.GG.Telemetry.Host.dll", b"fixture")
lock = root / "lock.json"
lock.write_text("{}")
@@ -54,21 +54,22 @@ def test_prepared_current_package_manifest_uses_only_schema14(self):
assets.mkdir()
(assets / "app.js").write_text("fixture")
prepared = module.build_manifest(SimpleNamespace(package=package, lock=lock, assets=assets,
- version="0.8.0", tag="telemetry-host/v0.8.0", source_sha="a" * 40))
+ version="0.9.0", tag="telemetry-host/v0.9.0", source_sha="a" * 40))
self.assertEqual((prepared["supportedStoreSchemaMin"], prepared["supportedStoreSchemaMax"]), (14, 14))
- self.assertEqual(prepared["version"], "0.8.0")
+ self.assertEqual(prepared["version"], "0.9.0")
# Renaming a retained older package cannot become a new genuine candidate.
with zipfile.ZipFile(root / "renamed-old.nupkg", "w") as archive:
archive.writestr("FS.GG.Telemetry.Host.nuspec", "FS.GG.Telemetry.Host0.6.0")
archive.writestr("tools/net10.0/linux-x64/FS.GG.Telemetry.Host.dll", b"older fixture")
with self.assertRaisesRegex(ValueError, "package identity"):
module.build_manifest(SimpleNamespace(package=root / "renamed-old.nupkg", lock=lock, assets=assets,
- version="0.8.0", tag="telemetry-host/v0.8.0", source_sha="a" * 40))
+ version="0.9.0", tag="telemetry-host/v0.9.0", source_sha="a" * 40))
def test_current_profile_is_schema14_only_and_legacy_artifact_stays_unchanged(self):
spec = importlib.util.spec_from_file_location("host_release_profile", ROOT / "scripts/telemetry-host-release.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
+ self.assertEqual(module.store_schema_profile("0.9.0"), (14, 14))
self.assertEqual(module.store_schema_profile("0.8.0"), (14, 14))
self.assertEqual(module.store_schema_profile("0.7.0"), (14, 14))
self.assertEqual(module.store_schema_profile("0.6.0"), (14, 14))
@@ -93,23 +94,28 @@ def test_current_profile_is_schema14_only_and_legacy_artifact_stays_unchanged(se
"supportedStoreSchemaMin": 10, "supportedStoreSchemaMax": 12}
path.write_text(json.dumps(old))
self.assertEqual(module.load_manifest(path), old)
- with self.assertRaisesRegex(Refused, "exact Host 0.8.0"):
+ with self.assertRaisesRegex(Refused, "exact Host 0.9.0"):
effects(old)
previous = {**value, "version": "0.5.0", "tag": "telemetry-host/v0.5.0"}
path.write_text(json.dumps(previous))
self.assertEqual(module.load_manifest(path), previous)
- with self.assertRaisesRegex(Refused, "exact Host 0.8.0"):
+ with self.assertRaisesRegex(Refused, "exact Host 0.9.0"):
effects(previous)
held = {**value, "version": "0.6.0", "tag": "telemetry-host/v0.6.0"}
path.write_text(json.dumps(held))
self.assertEqual(module.load_manifest(path), held)
- with self.assertRaisesRegex(Refused, "exact Host 0.8.0"):
+ with self.assertRaisesRegex(Refused, "exact Host 0.9.0"):
effects(held)
published = {**value, "version": "0.7.0", "tag": "telemetry-host/v0.7.0"}
path.write_text(json.dumps(published))
self.assertEqual(module.load_manifest(path), published)
- with self.assertRaisesRegex(Refused, "exact Host 0.8.0"):
+ with self.assertRaisesRegex(Refused, "exact Host 0.9.0"):
effects(published)
+ predecessor = {**value, "version": "0.8.0", "tag": "telemetry-host/v0.8.0"}
+ path.write_text(json.dumps(predecessor))
+ self.assertEqual(module.load_manifest(path), predecessor)
+ with self.assertRaisesRegex(Refused, "exact Host 0.9.0"):
+ effects(predecessor)
class EarlyQualificationTests(unittest.TestCase):
@@ -118,7 +124,7 @@ def check(source, expensive, native=False):
pure = source.index("python3 tests/telemetry-host-successor/run.py")
test = source.index('--filter "FullyQualifiedName~HostPassiveReceiptTests|FullyQualifiedName~HostSchemaProfileTests|FullyQualifiedName~RemoteTelemetryTests.protected native collector resolves durable dispatch and replays retained envelope"')
assert pure < test < source.index(expensive)
- assert 'host-passive.trx" --minimum 4' in source
+ assert 'host-passive.trx" --minimum 5' in source
assert 'timeout-minutes: 5' in source
assert '--blame ' in source and '--blame-hang' not in source and '--blame-crash' not in source
assert 'Retain passive Host HTTP diagnostics' in source and 'always()' in source
@@ -135,7 +141,7 @@ def check(source, expensive, native=False):
check(source, expensive, native)
for mutant in [source.replace('FullyQualifiedName~HostPassiveReceiptTests', 'FullyQualifiedName~Unrelated'),
source.replace('FullyQualifiedName~RemoteTelemetryTests.protected native collector', 'FullyQualifiedName~UnrelatedNativeCollector'),
- source.replace('host-passive.trx" --minimum 4', 'host-passive.trx" --minimum 0'),
+ source.replace('host-passive.trx" --minimum 5', 'host-passive.trx" --minimum 0'),
source.replace('python3 tests/telemetry-host-successor/run.py', 'echo absent'),
source.replace('always()', 'success()'),
source.replace('npm run test:browser -- --reporter=line', 'echo browser-not-run'),
@@ -301,16 +307,16 @@ def advance(self):
def test_successor_identity_and_journal_are_coherent(self):
self.assertEqual(self.ordered[0].target_digest, "a" * 40)
import xml.etree.ElementTree as ET
- self.assertEqual(ET.parse(ROOT / "src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj").findtext(".//Version"), "0.8.0")
+ self.assertEqual(ET.parse(ROOT / "src/FS.GG.Telemetry.Host/FS.GG.Telemetry.Host.fsproj").findtext(".//Version"), "0.9.0")
publisher = (ROOT / "scripts" / "telemetry-host-successor-publish.py").read_text()
candidate = (ROOT / ".github" / "workflows" / "release-telemetry-host-successor-candidate.yml").read_text()
- self.assertIn('REF = "refs/heads/fsgg/v2/journal/release/utel-host-rel-14"', publisher)
+ self.assertIn('REF = "refs/heads/fsgg/v2/journal/release/utel-host-rel-15"', publisher)
self.assertNotIn("utel-host-rel-10", publisher)
- self.assertIn('"version": "0.8.0"', publisher)
- self.assertIn("--version 0.8.0 --tag telemetry-host/v0.8.0", candidate)
- self.assertIn("FS.GG.Telemetry.Host.0.8.0.nupkg", candidate)
- self.assertIn("telemetry-host-080-candidate", candidate)
- self.assertIn("telemetry-host-080-publisher", (ROOT / ".github/workflows/release-telemetry-host-successor-publish.yml").read_text())
+ self.assertIn('"version": "0.9.0"', publisher)
+ self.assertIn("--version 0.9.0 --tag telemetry-host/v0.9.0", candidate)
+ self.assertIn("FS.GG.Telemetry.Host.0.9.0.nupkg", candidate)
+ self.assertIn("telemetry-host-090-candidate", candidate)
+ self.assertIn("telemetry-host-090-publisher", (ROOT / ".github/workflows/release-telemetry-host-successor-publish.yml").read_text())
def test_every_effect_is_admitted_and_read_back_once(self):
self.assertEqual(len(self.ordered), 8)
@@ -347,13 +353,13 @@ def test_candidate_archive_identity_and_first_attempt_are_required(self):
archive = root / "candidate.zip"
package_digest = hashlib.sha256(b"fixture").hexdigest()
positive = {"schema": "fsgg.telemetry-host-package-evidence/v1", "packageId": "FS.GG.Telemetry.Host",
- "version": "0.8.0", "archiveSha256": package_digest, "passed": 1, "failed": 0,
+ "version": "0.9.0", "archiveSha256": package_digest, "passed": 1, "failed": 0,
"productionStorage": "eligible-linux-x64", "syntheticTls": True, "passiveReceiptQualified": True}
def write_archive(evidence):
with zipfile.ZipFile(archive, "w") as zipped:
zipped.writestr("manifest.json", json.dumps({**manifest(), "archiveSha256": package_digest}))
zipped.writestr("package-evidence.json", json.dumps(evidence))
- zipped.writestr("FS.GG.Telemetry.Host.0.8.0.nupkg", "fixture")
+ zipped.writestr("FS.GG.Telemetry.Host.0.9.0.nupkg", "fixture")
write_archive(positive)
digest = hashlib.sha256(archive.read_bytes()).hexdigest()
run = {"id": 123, "path": verifier.WORKFLOW, "head_sha": source,
@@ -365,7 +371,7 @@ def write_archive(evidence):
"repository_id": verifier.REPOSITORY_ID,
"head_repository_id": verifier.REPOSITORY_ID}}
with patch.object(verifier.subprocess, "run"):
- self.assertEqual(verifier.verify(artifact, run, archive, root / "good", source)["version"], "0.8.0")
+ self.assertEqual(verifier.verify(artifact, run, archive, root / "good", source)["version"], "0.9.0")
for field, value in [("productionStorage", "not-qualified:overlay"), ("passiveReceiptQualified", False),
("syntheticTls", False), ("failed", 1), ("failed", False), ("version", "0.5.0"),
("archiveSha256", "0" * 64)]:
@@ -389,6 +395,8 @@ def test_prior_release_identity_and_wrong_payload_are_refused(self):
("version", "0.2.1"), ("tag", "telemetry-host/v0.2.1"),
("version", "0.1.3"), ("tag", "telemetry-host/v0.1.3"),
("version", "0.6.0"), ("tag", "telemetry-host/v0.6.0"),
+ ("version", "0.7.0"), ("tag", "telemetry-host/v0.7.0"),
+ ("version", "0.8.0"), ("tag", "telemetry-host/v0.8.0"),
("producerPayloadSha256", "bad")):
row = manifest(); row[key] = value
with self.assertRaises(Refused):
@@ -403,9 +411,9 @@ def __init__(self):
self.assets = {}
self.writes = []
def get(self, path):
- if path.endswith("/git/ref/tags/telemetry-host/v0.8.0") and self.tag:
+ if path.endswith("/git/ref/tags/telemetry-host/v0.9.0") and self.tag:
return {"object": {"sha": self.tag}}
- if path.endswith("/releases/tags/telemetry-host/v0.8.0") and self.release and not self.release["draft"]:
+ if path.endswith("/releases/tags/telemetry-host/v0.9.0") and self.release and not self.release["draft"]:
return self.release
if path.endswith("/releases?per_page=100&page=1"):
return [self.release] if self.release else []
@@ -429,7 +437,7 @@ def download_asset(self, asset_id):
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
row = manifest()
- package = root / "FS.GG.Telemetry.Host.0.8.0.nupkg"
+ package = root / "FS.GG.Telemetry.Host.0.9.0.nupkg"
package.write_bytes(b"exact original Host archive")
row["archiveSha256"] = hashlib.sha256(package.read_bytes()).hexdigest()
manifest_path = root / "manifest.json"
@@ -457,10 +465,10 @@ class API:
def __init__(self, raw):
self.raw = raw
def get(self, path):
- if path.endswith("/releases/tags/telemetry-host/v0.8.0"):
+ if path.endswith("/releases/tags/telemetry-host/v0.9.0"):
raise NotFound(path)
if path.endswith("/releases?per_page=100&page=1"):
- return [{"id": 1, "tag_name": "telemetry-host/v0.8.0", "draft": True}]
+ return [{"id": 1, "tag_name": "telemetry-host/v0.9.0", "draft": True}]
if path.endswith("/releases/1/assets?per_page=100"):
return [{"id": 1, "name": "publication-journal.json"}]
raise AssertionError(path)
@@ -643,7 +651,7 @@ def authority_fixture(self):
import ast
route = ordinary_route()
namespace = dict(copy=copy, NotFound=NotFound, AUTHORITY="FS-GG/FS.GG.Coordination.Authority",
- REF="refs/heads/fsgg/v2/journal/release/utel-host-rel-14",
+ REF="refs/heads/fsgg/v2/journal/release/utel-host-rel-15",
reviewed_binding=route.authority_protection()[1])
source = ast.parse((ROOT / "tests/release-successor-live/run.py").read_text())
cls = next(node for node in source.body if isinstance(node, ast.ClassDef) and node.name == "AuthorityAPI")
@@ -693,7 +701,7 @@ def observe(self, effect): return Observation("absent")
continuation=case == "old-flag", old=case == "old-candidate", journal=Journal)
self.assertEqual(result, 0 if case == "current" else 1)
self.assertFalse(any(row[0] == "initialize" for row in writes))
- self.assertTrue(all(row[1:] == ("refs/heads/fsgg/v2/journal/release/utel-host-rel-14", True) for row in writes))
+ self.assertTrue(all(row[1:] == ("refs/heads/fsgg/v2/journal/release/utel-host-rel-15", True) for row in writes))
if case == "old-flag":
self.assertEqual(api.reads, [])
self.assertFalse((root / "candidate").exists())
@@ -710,7 +718,7 @@ def get(self, path):
path = pathlib.Path(temporary) / "manifest.json"; path.write_text(json.dumps(CURRENT_MANIFEST))
api = API(); provider = HostProvider(api, path, "fixture", "fixture")
self.assertEqual(provider.observe(effects(CURRENT_MANIFEST)[1][0]).state, "absent")
- self.assertEqual(api.paths, ["repos/FS-GG/.github/git/ref/tags/telemetry-host/v0.8.0"])
+ self.assertEqual(api.paths, ["repos/FS-GG/.github/git/ref/tags/telemetry-host/v0.9.0"])
def test_actual_new_journal_preserves_old12_and_unknown_never_resends(self):
import ast
@@ -787,7 +795,7 @@ def run(command, **kwargs):
self.assertEqual(state.effects, {"tag": "intent"})
self.assertEqual(output.getvalue().count("Host successor effect state: waiting"), 2)
self.assertNotIn("SECRET", output.getvalue())
- journal.validate_intent({"contentId": effects(CURRENT_MANIFEST)[0], "sourceSha": CURRENT_SOURCE, "version": "0.8.0",
+ journal.validate_intent({"contentId": effects(CURRENT_MANIFEST)[0], "sourceSha": CURRENT_SOURCE, "version": "0.9.0",
"candidateArchiveSha256": CURRENT_ARCHIVE, "operator": "EHotwagner"})
def test_actual_workflow_default_false_and_explicit_flag_shell(self):
@@ -845,7 +853,7 @@ def routes(self):
for base in gate.FEEDS.values():
index = base + "/index.json"
package = base + f"/{gate.VERSION}/fs.gg.telemetry.host.{gate.VERSION}.nupkg"
- routes[index] = [self.response(index, {"versions": ["0.6.0", "0.7.0"]})]
+ routes[index] = [self.response(index, {"versions": ["0.7.0", "0.8.0"]})]
routes[package] = [urllib.error.HTTPError(package, 404, "not found", {}, io.BytesIO())]
return routes
@@ -911,9 +919,9 @@ def test_unreadable_or_occupied_feed_never_reaches_candidate_work(self):
elif case == "bad-json":
replacement = self.response(index, None, raw=b"not json")
elif case == "duplicate-json":
- replacement = self.response(index, None, raw=b'{"versions":["0.8.0"],"versions":["0.7.0"]}')
+ replacement = self.response(index, None, raw=b'{"versions":["0.9.0"],"versions":["0.8.0"]}')
elif case == "nonfinite-json":
- replacement = self.response(index, None, raw=b'{"versions":["0.7.0"],"ignored":NaN}')
+ replacement = self.response(index, None, raw=b'{"versions":["0.8.0"],"ignored":NaN}')
elif case == "oversize":
replacement = self.response(index, None, raw=b" " * (gate.MAX_BYTES + 1))
elif case == "length":
@@ -927,9 +935,9 @@ def test_unreadable_or_occupied_feed_never_reaches_candidate_work(self):
"never-print-this-secret", {}, io.BytesIO())]
replacement = None
else:
- values = {"empty": [], "shape": "0.7.0", "missing-predecessor": ["0.6.0"],
- "occupied": ["0.7.0", "0.8.0"], "normalized-occupied": ["0.7", "0.8.0.0"],
- "newer": ["0.7.0", "0.9.0"], "malformed-version": ["0.7.0", "never-print-this-secret"]}
+ values = {"empty": [], "shape": "0.8.0", "missing-predecessor": ["0.7.0"],
+ "occupied": ["0.8.0", "0.9.0"], "normalized-occupied": ["0.8", "0.9.0.0"],
+ "newer": ["0.8.0", "0.10.0"], "malformed-version": ["0.8.0", "never-print-this-secret"]}
replacement = self.response(index, {"versions": values[case]})
if replacement is not None:
routes[index][0].close()
@@ -1007,9 +1015,9 @@ def test_source_journal_literal_is_checked_without_claiming_native_absence(self)
(root / path).parent.mkdir(parents=True, exist_ok=True)
(root / path).write_bytes((ROOT / path).read_bytes())
self.gate.source_bindings(root)
- for path, old, new in [(paths[0], "0.8.0", "0.7.0"),
- (paths[1], 'VERSION = "0.8.0"', 'VERSION = "0.6.0"'),
- (paths[2], "utel-host-rel-14", "utel-host-rel-12")]:
+ for path, old, new in [(paths[0], "0.9.0", "0.8.0"),
+ (paths[1], 'VERSION = "0.9.0"', 'VERSION = "0.8.0"'),
+ (paths[2], "utel-host-rel-15", "utel-host-rel-14")]:
original = (root / path).read_text()
(root / path).write_text(original.replace(old, new))
with self.assertRaises(self.gate.Refused):
@@ -1034,7 +1042,7 @@ def check(text):
assert "name: Retain passive Host HTTP diagnostics" in step
assert "always() && steps.read-first.outcome == 'success'" in step
assert "dotnet msbuild" in text and "Refuse changed main before retaining candidate" in text
- assert "--version 0.8.0 --tag telemetry-host/v0.8.0" in text
+ assert "--version 0.9.0 --tag telemetry-host/v0.9.0" in text
check(source)
gate = source.split(" - name: Refuse unreadable", 1)[1].split(" - uses: actions/setup-dotnet", 1)[0]