diff --git a/README.md b/README.md
index abd9c2edb..bc197d3e4 100644
--- a/README.md
+++ b/README.md
@@ -265,9 +265,13 @@ self-service API. Partner contract and qualification process:
[the Execute guide](https://docs.openadapt.ai/commercial/oem-brief/).
-Production is per qualified workflow.
-A workflow is Production only with an active signed, expiring, revocable
-admission for that exact compiled version, application, and environment.
+The signed ledger has seven active target admissions. Their evidence class
+is `remote-safe-synthetic`, not MockMed `production_acceptance`. They remain
+valid until revoked; there's no expiry date. `pip install openadapt` is the
+admitted runtime (launcher 1.16.0, Flow 1.34.0). Desktop's admitted artifact
+is the 0.16.0 wheel. Native Mac DMGs stay ad-hoc signed 0.15.0; don't treat
+those as admitted. A Production run still needs a workflow admission for the
+exact compiled bundle, application, and environment.
[Check the live signed Production record](https://docs.openadapt.ai/production-lifecycle.json).
diff --git a/docs/platform-compatibility-report.md b/docs/platform-compatibility-report.md
index dbc0852ff..8c85d64e0 100644
--- a/docs/platform-compatibility-report.md
+++ b/docs/platform-compatibility-report.md
@@ -2,7 +2,7 @@
-- Generated: `2026-08-29T21:58:31+00:00`
+- Generated: `2026-09-02T19:12:23+00:00`
- Selection: `latest-published`
- Compatibility: **dependency-incompatible**
- Signature: `unsigned (signing infrastructure pending)`
@@ -14,9 +14,9 @@
| launcher | `openadapt` | `1.16.0` | [`v1.16.0`](https://github.com/OpenAdaptAI/OpenAdapt/tree/v1.16.0)
commit `089c27c046f5cd972d299361f9d68285c1896c71`
tree `3779d8fceeb65d26bad440fa52d11ab7238eae70` | `openadapt-1.16.0-py3-none-any.whl` `371693e7607d1cdc1ea360ef5d657c1af791a0af39677fa2ce5933e7ba712719`
`openadapt-1.16.0.tar.gz` `39d01612417dff5981b536b324a63498df1ea6f0128c723276609ebbe18d9a18` |
| flow | `openadapt-flow` | `1.34.0` | [`v1.34.0`](https://github.com/OpenAdaptAI/openadapt-flow/tree/v1.34.0)
commit `30fc60e55778a0e0f92b9776117cafcfe2512249`
tree `7a0263eda60b23f685cbd8abb4174da2db88fd4b` | `openadapt_flow-1.34.0-py3-none-any.whl` `56d32818989cb3a92830080ead39e10b08718c55e00988117f22cbfeaac98854`
`openadapt_flow-1.34.0.tar.gz` `a941a70015fe91897e655bfc9bf83e6cf8fbcf8bd4e4c7206ffb133c505203f8` |
| capture | `openadapt-capture` | `1.2.2` | [`v1.2.2`](https://github.com/OpenAdaptAI/openadapt-capture/tree/v1.2.2)
commit `06ade22948c12dcc6608bd54b5cca5c9c306e77c`
tree `b0a3b5b53f23536e01314f36aa46fd5b2d8baba6` | `openadapt_capture-1.2.2-py3-none-any.whl` `550fffe50990fa8431d332bdd5fe4755976d83baa7ad62553f50ad9e4ddcd1ae`
`openadapt_capture-1.2.2.tar.gz` `6f4298d3daadf9cced8994207e34b3116a79209ef792e98f778a0770e2f3406c` |
-| privacy | `openadapt-privacy` | `1.0.4` | [`v1.0.4`](https://github.com/OpenAdaptAI/openadapt-privacy/tree/v1.0.4)
commit `7252de4820451b069310a3ccaecf484ad0af8c18`
tree `4c7bc72aa5c5879c7a51a4d3586c4b7e1c1ed5ff` | `openadapt_privacy-1.0.4-py3-none-any.whl` `e10bd265aba5e54c97aeb80a9ac0509384d19eb46ce46656880b619fd06f542b`
`openadapt_privacy-1.0.4.tar.gz` `89670173890192ecea31eea832e6e1c9b612cb7f19969fe99cda082cd71f2cb3` |
-| types | `openadapt-types` | `0.12.0` | [`v0.12.0`](https://github.com/OpenAdaptAI/openadapt-types/tree/v0.12.0)
commit `da5ccb34928e912ac0ae16e04d818b9be68fb85d`
tree `9b40da24707b0562db8bded8779699c5ed9a5013` | `openadapt_types-0.12.0-py3-none-any.whl` `f9d2ef67f7f82554881601484647d8f2f221896e382c9e586286e40164b125e5`
`openadapt_types-0.12.0.tar.gz` `24bc131babbd9a4940df1c9c25cf4a7d63c859aeae4fe11b25b6672e303612f3` |
-| desktop | `openadapt-desktop` | `0.15.0` | [`desktop-v0.15.0`](https://github.com/OpenAdaptAI/openadapt-desktop/tree/desktop-v0.15.0)
commit `ac01e90f04e604f40e030046457f5b5167c88755`
tree `06a0c7c3fc61194149f821522939a08fadc18736` | `openadapt_desktop-0.15.0-py3-none-any.whl` `11dd8da225d660f23fc5e779d564ef34c5d85ad40b303951ec7697e94f488401`
`openadapt_desktop-0.15.0.tar.gz` `f45e35d53469fcb75710010d56a9d3f362130f1637a2f5fb50889553104a5670` |
+| privacy | `openadapt-privacy` | `1.1.0` | [`v1.1.0`](https://github.com/OpenAdaptAI/openadapt-privacy/tree/v1.1.0)
commit `a7cdcbd872e6263305f53b6ea1b778d76718c1cc`
tree `9beb1a11e5f6bbe5d7a1509d681c92362a2de964` | `openadapt_privacy-1.1.0-py3-none-any.whl` `ca4533ff99e754d14e50de6f6be8d0ce308892f90935e3f59f5f521104a29d7f`
`openadapt_privacy-1.1.0.tar.gz` `9976c844e8b9b74c13e6f685030e7013e27bc7eb833d06884bfa5e018af74f65` |
+| types | `openadapt-types` | `0.17.0` | [`v0.17.0`](https://github.com/OpenAdaptAI/openadapt-types/tree/v0.17.0)
commit `14eca2d13e398153524cb65edc12e87616f9dfa7`
tree `c7f736a2ad7084bc95e1910b3232650ada8acd51` | `openadapt_types-0.17.0-py3-none-any.whl` `79738ab529f71241963725c6a40603deae032ff60c61d66baf33d7a248b1afa9`
`openadapt_types-0.17.0.tar.gz` `43dc00d82d6d8feb2cfaa16960224a85933440d7dee3b5f3c60618873535287b` |
+| desktop | `openadapt-desktop` | `0.16.0` | [`v0.16.0`](https://github.com/OpenAdaptAI/openadapt-desktop/tree/v0.16.0)
commit `7c59750d37be09a2871cd1bada8cb0bce946d363`
tree `9a1307794df6f2c77ee3dab90973845f3d08ee53` | `openadapt_desktop-0.16.0-py3-none-any.whl` `06e608a934deef56c93ee1ca8de511d6daba08459626e8245bd0db4c391e64e9`
`openadapt_desktop-0.16.0.tar.gz` `15f0c59a4d481328da76f3e5aa6a29ab8cdced8ab183c97346d69b4c14c996ff` |
| agent | `openadapt-agent` | `2.0.1` | [`v2.0.1`](https://github.com/OpenAdaptAI/openadapt-agent/tree/v2.0.1)
commit `d9f16b2946dfdd0228eeb2660047347df9dce8d8`
tree `26606c89b4294603b628f1b3aaa8283c45765326` | `openadapt_agent-2.0.1-py3-none-any.whl` `5c5f6e3eca4c127d87b3affb8944223c0836916e0df57d0c6b8ba737b3bc8b5a`
`openadapt_agent-2.0.1.tar.gz` `67e05b299a4f035a0482b3032decc151fafc22de9a12bb3d8f884f37ea4cc937` |
## Runtime selections
@@ -24,8 +24,8 @@
| Unit | Version | Selected or locked OpenAdapt packages |
|---|---:|---|
| launcher_environment | `1.16.0` | `openadapt==1.16.0`
`openadapt-flow==1.34.0` |
-| customer_runner | `1.34.0` | `openadapt-flow==1.34.0`
`openadapt-types==0.12.0` |
-| desktop_sidecar | `0.15.0` | `openadapt-capture==1.2.1`
`openadapt-flow==1.27.1`
`openadapt-privacy==1.0.0`
`openadapt-types==0.7.0` |
+| customer_runner | `1.34.0` | `openadapt-flow==1.34.0`
`openadapt-types==0.17.0` |
+| desktop_sidecar | `0.16.0` | `openadapt-capture==1.2.1`
`openadapt-flow==1.34.0`
`openadapt-privacy==1.0.0`
`openadapt-types==0.10.0` |
| agent_bridge | `2.0.1` | `openadapt-agent==2.0.1`
`openadapt-flow==1.34.0` |
## Selected-platform dependency compatibility
@@ -34,21 +34,21 @@ These edges show whether each selected integration version satisfies its publish
| Scope | Source | Target | Constraint | Selected | Activation |
|---|---|---|---|---:|---|
-| desktop-sidecar-lock | `openadapt-desktop==0.15.0` | `openadapt-capture` | `>=1.2.1` | `1.2.1` | `always` |
-| desktop-sidecar-lock | `openadapt-desktop==0.15.0` | `openadapt-flow` | `==1.27.1` | `1.27.1` | `extra == "build"` |
-| desktop-sidecar-lock | `openadapt-desktop==0.15.0` | `openadapt-privacy` | `>=1.0.0` | `1.0.0` | `always` |
-| desktop-sidecar-lock | `openadapt-desktop==0.15.0` | `openadapt-types` | `==0.7.0` | `0.7.0` | `always` |
+| desktop-sidecar-lock | `openadapt-desktop==0.16.0` | `openadapt-capture` | `>=1.2.1` | `1.2.1` | `always` |
+| desktop-sidecar-lock | `openadapt-desktop==0.16.0` | `openadapt-flow` | `==1.34.0` | `1.34.0` | `extra == "build"` |
+| desktop-sidecar-lock | `openadapt-desktop==0.16.0` | `openadapt-privacy` | `>=1.0.0` | `1.0.0` | `always` |
+| desktop-sidecar-lock | `openadapt-desktop==0.16.0` | `openadapt-types` | `==0.10.0` | `0.10.0` | `always` |
| selected-platform | `openadapt-agent==2.0.1` | `openadapt-flow` | `<2,>=1.18.1` | `1.34.0` | `always` |
-| selected-platform | `openadapt-capture==1.2.2` | `openadapt-privacy` | `>=0.1.0` | `1.0.4` | `extra == "all"`
`extra == "privacy"` |
+| selected-platform | `openadapt-capture==1.2.2` | `openadapt-privacy` | `>=0.1.0` | `1.1.0` | `extra == "all"`
`extra == "privacy"` |
| selected-platform | `openadapt-flow==1.34.0` | `openadapt-capture` | `>=1.2.0` | `1.2.2` | `extra == "capture"` |
-| selected-platform | `openadapt-flow==1.34.0` | `openadapt-privacy` | `>=1.0.0` | `1.0.4` | `extra == "privacy"` |
-| selected-platform | `openadapt-flow==1.34.0` | `openadapt-types` | `<0.11.0,>=0.10.0` | `0.12.0` | `extra == "console"`
`extra == "dev"`
`extra == "interop"` |
+| selected-platform | `openadapt-flow==1.34.0` | `openadapt-privacy` | `>=1.0.0` | `1.1.0` | `extra == "privacy"` |
+| selected-platform | `openadapt-flow==1.34.0` | `openadapt-types` | `<0.11.0,>=0.10.0` | `0.17.0` | `extra == "console"`
`extra == "dev"`
`extra == "interop"` |
| selected-platform | `openadapt==1.16.0` | `openadapt-capture` | `<2.0.0,>=1.2.0` | `1.2.2` | `extra == "all"`
`extra == "capture"`
`extra == "core"` |
| selected-platform | `openadapt==1.16.0` | `openadapt-flow` | `<2.0.0,>=1.29.0` | `1.34.0` | `always`
`extra == "all"`
`extra == "browser"`
`extra == "capture"`
`extra == "core"`
`extra == "flow"`
`extra == "privacy"`
`extra == "rdp"`
`extra == "windows"`
`sys_platform == "darwin" and extra == "all"`
`sys_platform == "darwin" and extra == "macos"`
`sys_platform == "linux" and extra == "all"`
`sys_platform == "linux" and extra == "linux"` |
### Compatibility failures
-- `flow` -> `openadapt-types` requires `<0.11.0,>=0.10.0`; the resolved version is `0.12.0`.
+- `flow` -> `openadapt-types` requires `<0.11.0,>=0.10.0`; the resolved version is `0.17.0`.
## Cross-component schemas
@@ -56,10 +56,10 @@ These edges show whether each selected integration version satisfies its publish
|---|---|---|---|
| `workflow_bundle` | `flow` | `schema_version` [1, 2] | [`openadapt_flow/bundle_validation.py`](https://github.com/OpenAdaptAI/openadapt-flow/blob/30fc60e55778a0e0f92b9776117cafcfe2512249/openadapt_flow/bundle_validation.py) `fe1e5ec441183adf1aa2209f1669f779043fb4ad1f41a651ca0f96123778240f`
[`openadapt_flow/ir.py`](https://github.com/OpenAdaptAI/openadapt-flow/blob/30fc60e55778a0e0f92b9776117cafcfe2512249/openadapt_flow/ir.py) `e1e5345dd173b694587dde515b59cd60de0cf6028c1f35d589706b84ad792598` |
| `capture_structural_observation` | `capture` | `openadapt.capture.structural-observation/v` [1] | [`openadapt_capture/structural.py`](https://github.com/OpenAdaptAI/openadapt-capture/blob/06ade22948c12dcc6608bd54b5cca5c9c306e77c/openadapt_capture/structural.py) `8f8bce37148a3357ace147c46a7c45e6d5501b70622b24b1ac2490f2def304fc` |
-| `control_overlay_frame` | `types` | `openadapt.control-overlay-frame/v` [1, 2] | [`openadapt_types/control_overlay.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay.py) `3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790`
[`openadapt_types/control_overlay_tracking.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay_tracking.py) `af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755` |
-| `control_overlay_timeline` | `types` | `openadapt.control-overlay-timeline/v` [1, 2] | [`openadapt_types/control_overlay.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay.py) `3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790`
[`openadapt_types/control_overlay_tracking.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay_tracking.py) `af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755` |
-| `human_decision_task` | `types` | `openadapt.human-decision-task/v` [1, 2] | [`openadapt_types/human_decision.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/human_decision.py) `6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29` |
-| `human_decision_receipt` | `types` | `openadapt.human-decision-receipt/v` [1] | [`openadapt_types/human_decision.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/human_decision.py) `6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29` |
+| `control_overlay_frame` | `types` | `openadapt.control-overlay-frame/v` [1, 2] | [`openadapt_types/control_overlay.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay.py) `3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790`
[`openadapt_types/control_overlay_tracking.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay_tracking.py) `af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755` |
+| `control_overlay_timeline` | `types` | `openadapt.control-overlay-timeline/v` [1, 2] | [`openadapt_types/control_overlay.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay.py) `3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790`
[`openadapt_types/control_overlay_tracking.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay_tracking.py) `af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755` |
+| `human_decision_task` | `types` | `openadapt.human-decision-task/v` [1, 2] | [`openadapt_types/human_decision.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/human_decision.py) `6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29` |
+| `human_decision_receipt` | `types` | `openadapt.human-decision-receipt/v` [1] | [`openadapt_types/human_decision.py`](https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/human_decision.py) `6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29` |
| `human_decision_relay` | `flow` | `openadapt.human-decision-relay/v` [2] | [`openadapt_flow/console/decision_relay.py`](https://github.com/OpenAdaptAI/openadapt-flow/blob/30fc60e55778a0e0f92b9776117cafcfe2512249/openadapt_flow/console/decision_relay.py) `d5320404d4342dbb52be1f2ead1cdb5cf0e22877feaaa1e47974b301df697292` |
| `remote_decision_projection` | `flow` | `openadapt.remote-decision-projection/v` [1] | [`openadapt_flow/console/human_decisions.py`](https://github.com/OpenAdaptAI/openadapt-flow/blob/30fc60e55778a0e0f92b9776117cafcfe2512249/openadapt_flow/console/human_decisions.py) `02651ad8888a5c17bfff6fb47165ba861193243dc0f7c951f8b94f544ca19477` |
| `runtime_validation` | `flow` | `openadapt.runtime-validation/v` [1, 2, 3] | [`openadapt_flow/runtime_validation.py`](https://github.com/OpenAdaptAI/openadapt-flow/blob/30fc60e55778a0e0f92b9776117cafcfe2512249/openadapt_flow/runtime_validation.py) `18675d6b4c40458394febb36cbe28e99f410cb65b4cab138b68df3cacc6f7dd4` |
diff --git a/docs/platform-manifest.md b/docs/platform-manifest.md
index e4c0b8dbb..ea554eed6 100644
--- a/docs/platform-manifest.md
+++ b/docs/platform-manifest.md
@@ -19,7 +19,8 @@ it must agree with. Other repositories contribute source data only:
- PyPI is the authority for published versions, artifact URLs, and digests.
- Each exact public release tag is the authority for its release source commit
and tree. This reference is not, by itself, a build-provenance attestation.
-- The exact `desktop-vX.Y.Z` tag and its `uv.lock` are the authority for the
+- The exact published Desktop release tag (`desktop-vX.Y.Z` when that native
+ tag exists, otherwise `vX.Y.Z`) and its `uv.lock` are the authority for the
package versions frozen into `openadapt-engine`.
- `https://openadapt.ai/status.json` (maintained in `openadapt-web`,
`public/status.json`) is the authority for substrate availability, release
@@ -185,7 +186,8 @@ Validated today:
`public/status.json` and `data/published-version-claims.json` in
`openadapt-web`.
- Native sidecar agreement: the Desktop sidecar's resolved OpenAdapt package
- versions and lock digest match the exact `desktop-vX.Y.Z` tag's `uv.lock`.
+ versions and lock digest match the exact published Desktop release tag's
+ `uv.lock` (`desktop-vX.Y.Z` when present, otherwise `vX.Y.Z`).
- Compatibility honesty: the validator recomputes every dependency edge among
the seven selected components. Optional-extra markers remain visible. The
Desktop edges resolve against its exact sidecar lock; other edges resolve
diff --git a/platform-manifest.json b/platform-manifest.json
index 562384fd8..768584a5f 100644
--- a/platform-manifest.json
+++ b/platform-manifest.json
@@ -1,12 +1,12 @@
{
"manifest_kind": "openadapt-platform-release-manifest",
"schema_version": "2.0.0",
- "generated_at": "2026-08-29T21:58:31+00:00",
+ "generated_at": "2026-09-02T19:12:23+00:00",
"generation": {
"files": [
{
"path": "scripts/generate_platform_manifest.py",
- "sha256": "fdb5f79c90e61e767a8b76ddeec8723dbc854372fd90d9479965f74371087d6b"
+ "sha256": "41b85ccddba7d2f610753d321111df6949dfeae1f1a233a78c34dcb24027f832"
},
{
"path": "scripts/render_platform_versions.py",
@@ -402,65 +402,65 @@
},
"privacy": {
"package": "openadapt-privacy",
- "version": "1.0.4",
+ "version": "1.1.0",
"source": "pypi",
"requires_python": ">=3.10",
"requires": {},
"dependency_constraints": [],
"provenance": {
"repository": "OpenAdaptAI/openadapt-privacy",
- "release_ref": "v1.0.4",
- "commit": "7252de4820451b069310a3ccaecf484ad0af8c18",
- "tree": "4c7bc72aa5c5879c7a51a4d3586c4b7e1c1ed5ff",
- "url": "https://github.com/OpenAdaptAI/openadapt-privacy/tree/v1.0.4"
+ "release_ref": "v1.1.0",
+ "commit": "a7cdcbd872e6263305f53b6ea1b778d76718c1cc",
+ "tree": "9beb1a11e5f6bbe5d7a1509d681c92362a2de964",
+ "url": "https://github.com/OpenAdaptAI/openadapt-privacy/tree/v1.1.0"
},
"artifacts": [
{
"type": "bdist_wheel",
- "filename": "openadapt_privacy-1.0.4-py3-none-any.whl",
- "url": "https://files.pythonhosted.org/packages/fe/5b/9cb5982973ff9d12db084088e2f55c201f080280637bd05d133035233bf2/openadapt_privacy-1.0.4-py3-none-any.whl",
- "sha256": "e10bd265aba5e54c97aeb80a9ac0509384d19eb46ce46656880b619fd06f542b"
+ "filename": "openadapt_privacy-1.1.0-py3-none-any.whl",
+ "url": "https://files.pythonhosted.org/packages/7f/82/e34f256f3e010eaa53514fcea1c74eb1e0b149ec0cb6f3e92bc52b2940fd/openadapt_privacy-1.1.0-py3-none-any.whl",
+ "sha256": "ca4533ff99e754d14e50de6f6be8d0ce308892f90935e3f59f5f521104a29d7f"
},
{
"type": "sdist",
- "filename": "openadapt_privacy-1.0.4.tar.gz",
- "url": "https://files.pythonhosted.org/packages/f7/fd/d784ef5f3869a66489ce041af88a20aaa31a33e6c901ff938247caf07a2a/openadapt_privacy-1.0.4.tar.gz",
- "sha256": "89670173890192ecea31eea832e6e1c9b612cb7f19969fe99cda082cd71f2cb3"
+ "filename": "openadapt_privacy-1.1.0.tar.gz",
+ "url": "https://files.pythonhosted.org/packages/99/2a/eb49e6058d3c8c173ce4eee9875fae1ddedf108b2936e62c461e60e8c76e/openadapt_privacy-1.1.0.tar.gz",
+ "sha256": "9976c844e8b9b74c13e6f685030e7013e27bc7eb833d06884bfa5e018af74f65"
}
]
},
"types": {
"package": "openadapt-types",
- "version": "0.12.0",
+ "version": "0.17.0",
"source": "pypi",
"requires_python": ">=3.10",
"requires": {},
"dependency_constraints": [],
"provenance": {
"repository": "OpenAdaptAI/openadapt-types",
- "release_ref": "v0.12.0",
- "commit": "da5ccb34928e912ac0ae16e04d818b9be68fb85d",
- "tree": "9b40da24707b0562db8bded8779699c5ed9a5013",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/tree/v0.12.0"
+ "release_ref": "v0.17.0",
+ "commit": "14eca2d13e398153524cb65edc12e87616f9dfa7",
+ "tree": "c7f736a2ad7084bc95e1910b3232650ada8acd51",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/tree/v0.17.0"
},
"artifacts": [
{
"type": "bdist_wheel",
- "filename": "openadapt_types-0.12.0-py3-none-any.whl",
- "url": "https://files.pythonhosted.org/packages/96/9d/31a3042c07073eeb85177cbe2ad17f8790c769002aef222d0238bf23ecba/openadapt_types-0.12.0-py3-none-any.whl",
- "sha256": "f9d2ef67f7f82554881601484647d8f2f221896e382c9e586286e40164b125e5"
+ "filename": "openadapt_types-0.17.0-py3-none-any.whl",
+ "url": "https://files.pythonhosted.org/packages/55/9a/bec1d51cba5749732ceeba6e4abf7abe6aeaa22c849a1edb26b97c7ab479/openadapt_types-0.17.0-py3-none-any.whl",
+ "sha256": "79738ab529f71241963725c6a40603deae032ff60c61d66baf33d7a248b1afa9"
},
{
"type": "sdist",
- "filename": "openadapt_types-0.12.0.tar.gz",
- "url": "https://files.pythonhosted.org/packages/9b/e5/a7be12ac9b267b2a878b29c7e3b8908c9894b3cd0411cb9d8648dccf6914/openadapt_types-0.12.0.tar.gz",
- "sha256": "24bc131babbd9a4940df1c9c25cf4a7d63c859aeae4fe11b25b6672e303612f3"
+ "filename": "openadapt_types-0.17.0.tar.gz",
+ "url": "https://files.pythonhosted.org/packages/17/d5/549afc2b6bdde8a0a80d4eec67644275df251ee1654f3189f0cc66235730/openadapt_types-0.17.0.tar.gz",
+ "sha256": "43dc00d82d6d8feb2cfaa16960224a85933440d7dee3b5f3c60618873535287b"
}
]
},
"desktop": {
"package": "openadapt-desktop",
- "version": "0.15.0",
+ "version": "0.16.0",
"source": "pypi",
"requires_python": ">=3.11",
"requires": {
@@ -468,13 +468,13 @@
">=1.2.1"
],
"openadapt-flow": [
- "==1.27.1"
+ "==1.34.0"
],
"openadapt-privacy": [
">=1.0.0"
],
"openadapt-types": [
- "==0.7.0"
+ "==0.10.0"
]
},
"dependency_constraints": [
@@ -486,7 +486,7 @@
},
{
"package": "openadapt-flow",
- "requires": "==1.27.1",
+ "requires": "==1.34.0",
"extras": "browser,console",
"marker": "extra == \"build\""
},
@@ -498,30 +498,30 @@
},
{
"package": "openadapt-types",
- "requires": "==0.7.0",
+ "requires": "==0.10.0",
"extras": null,
"marker": null
}
],
"provenance": {
"repository": "OpenAdaptAI/openadapt-desktop",
- "release_ref": "desktop-v0.15.0",
- "commit": "ac01e90f04e604f40e030046457f5b5167c88755",
- "tree": "06a0c7c3fc61194149f821522939a08fadc18736",
- "url": "https://github.com/OpenAdaptAI/openadapt-desktop/tree/desktop-v0.15.0"
+ "release_ref": "v0.16.0",
+ "commit": "7c59750d37be09a2871cd1bada8cb0bce946d363",
+ "tree": "9a1307794df6f2c77ee3dab90973845f3d08ee53",
+ "url": "https://github.com/OpenAdaptAI/openadapt-desktop/tree/v0.16.0"
},
"artifacts": [
{
"type": "bdist_wheel",
- "filename": "openadapt_desktop-0.15.0-py3-none-any.whl",
- "url": "https://files.pythonhosted.org/packages/2a/24/a037aaaca08db307ec719670eabc7e0edc2baf13f7b7ebb73dacc0288fa7/openadapt_desktop-0.15.0-py3-none-any.whl",
- "sha256": "11dd8da225d660f23fc5e779d564ef34c5d85ad40b303951ec7697e94f488401"
+ "filename": "openadapt_desktop-0.16.0-py3-none-any.whl",
+ "url": "https://files.pythonhosted.org/packages/46/15/a08ec0d22de26b8ad7d38a6496ea18eaebb669f3910b7472821d851e209d/openadapt_desktop-0.16.0-py3-none-any.whl",
+ "sha256": "06e608a934deef56c93ee1ca8de511d6daba08459626e8245bd0db4c391e64e9"
},
{
"type": "sdist",
- "filename": "openadapt_desktop-0.15.0.tar.gz",
- "url": "https://files.pythonhosted.org/packages/fa/b8/32de05c9ccd1c1ba4bd909ee8c40b2a68933499ea0da50453c95953181ba/openadapt_desktop-0.15.0.tar.gz",
- "sha256": "f45e35d53469fcb75710010d56a9d3f362130f1637a2f5fb50889553104a5670"
+ "filename": "openadapt_desktop-0.16.0.tar.gz",
+ "url": "https://files.pythonhosted.org/packages/82/6b/cd9bf7928765a71dab9f58b557499e05058c9ecee3ef30fd12963bd95729/openadapt_desktop-0.16.0.tar.gz",
+ "sha256": "15f0c59a4d481328da76f3e5aa6a29ab8cdced8ab183c97346d69b4c14c996ff"
}
]
},
@@ -592,28 +592,28 @@
},
"selected": {
"openadapt-flow": "1.34.0",
- "openadapt-types": "0.12.0"
+ "openadapt-types": "0.17.0"
}
},
"desktop_sidecar": {
"kind": "frozen-desktop-sidecar",
- "version": "0.15.0",
+ "version": "0.16.0",
"source_component": "desktop",
- "source_ref": "desktop-v0.15.0",
- "lock_url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-desktop/desktop-v0.15.0/uv.lock",
- "lock_sha256": "3acbbbf9a8b4d161a51a464f850f77a1317e275e18a10ffa76f65a544e895c62",
+ "source_ref": "v0.16.0",
+ "lock_url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-desktop/v0.16.0/uv.lock",
+ "lock_sha256": "9c2a57f02b96a7b505fba2b1cde0519968bd5744cd790cdb78bf86ce60008079",
"binary": "openadapt-engine",
"requirements": {
- "openadapt-flow": "==1.27.1",
+ "openadapt-flow": "==1.34.0",
"openadapt-capture": ">=1.2.1",
"openadapt-privacy": ">=1.0.0",
- "openadapt-types": "==0.7.0"
+ "openadapt-types": "==0.10.0"
},
"resolved": {
"openadapt-capture": "1.2.1",
- "openadapt-flow": "1.27.1",
+ "openadapt-flow": "1.34.0",
"openadapt-privacy": "1.0.0",
- "openadapt-types": "0.7.0"
+ "openadapt-types": "0.10.0"
}
},
"agent_bridge": {
@@ -636,7 +636,7 @@
"scope": "desktop-sidecar-lock",
"source_role": "desktop",
"source_package": "openadapt-desktop",
- "source_version": "0.15.0",
+ "source_version": "0.16.0",
"target_role": "capture",
"target_package": "openadapt-capture",
"requires": ">=1.2.1",
@@ -652,11 +652,11 @@
"scope": "desktop-sidecar-lock",
"source_role": "desktop",
"source_package": "openadapt-desktop",
- "source_version": "0.15.0",
+ "source_version": "0.16.0",
"target_role": "flow",
"target_package": "openadapt-flow",
- "requires": "==1.27.1",
- "resolved": "1.27.1",
+ "requires": "==1.34.0",
+ "resolved": "1.34.0",
"activations": [
{
"extras": "browser,console",
@@ -668,7 +668,7 @@
"scope": "desktop-sidecar-lock",
"source_role": "desktop",
"source_package": "openadapt-desktop",
- "source_version": "0.15.0",
+ "source_version": "0.16.0",
"target_role": "privacy",
"target_package": "openadapt-privacy",
"requires": ">=1.0.0",
@@ -684,11 +684,11 @@
"scope": "desktop-sidecar-lock",
"source_role": "desktop",
"source_package": "openadapt-desktop",
- "source_version": "0.15.0",
+ "source_version": "0.16.0",
"target_role": "types",
"target_package": "openadapt-types",
- "requires": "==0.7.0",
- "resolved": "0.7.0",
+ "requires": "==0.10.0",
+ "resolved": "0.10.0",
"activations": [
{
"extras": null,
@@ -720,7 +720,7 @@
"target_role": "privacy",
"target_package": "openadapt-privacy",
"requires": ">=0.1.0",
- "resolved": "1.0.4",
+ "resolved": "1.1.0",
"activations": [
{
"extras": null,
@@ -756,7 +756,7 @@
"target_role": "privacy",
"target_package": "openadapt-privacy",
"requires": ">=1.0.0",
- "resolved": "1.0.4",
+ "resolved": "1.1.0",
"activations": [
{
"extras": "presidio",
@@ -772,7 +772,7 @@
"target_role": "types",
"target_package": "openadapt-types",
"requires": "<0.11.0,>=0.10.0",
- "resolved": "0.12.0",
+ "resolved": "0.17.0",
"activations": [
{
"extras": null,
@@ -951,17 +951,17 @@
],
"minimum": 1,
"maximum": 2,
- "source_commit": "da5ccb34928e912ac0ae16e04d818b9be68fb85d",
+ "source_commit": "14eca2d13e398153524cb65edc12e87616f9dfa7",
"source_files": [
{
"path": "openadapt_types/control_overlay.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay.py",
"git_blob": "8eaa7b82ae2606fa59943218e3839efe19ebc54b",
"sha256": "3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790"
},
{
"path": "openadapt_types/control_overlay_tracking.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay_tracking.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay_tracking.py",
"git_blob": "222b9bade823332df869f496bc035aa31faed5a2",
"sha256": "af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755"
}
@@ -976,17 +976,17 @@
],
"minimum": 1,
"maximum": 2,
- "source_commit": "da5ccb34928e912ac0ae16e04d818b9be68fb85d",
+ "source_commit": "14eca2d13e398153524cb65edc12e87616f9dfa7",
"source_files": [
{
"path": "openadapt_types/control_overlay.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay.py",
"git_blob": "8eaa7b82ae2606fa59943218e3839efe19ebc54b",
"sha256": "3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790"
},
{
"path": "openadapt_types/control_overlay_tracking.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/control_overlay_tracking.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/control_overlay_tracking.py",
"git_blob": "222b9bade823332df869f496bc035aa31faed5a2",
"sha256": "af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755"
}
@@ -1001,11 +1001,11 @@
],
"minimum": 1,
"maximum": 2,
- "source_commit": "da5ccb34928e912ac0ae16e04d818b9be68fb85d",
+ "source_commit": "14eca2d13e398153524cb65edc12e87616f9dfa7",
"source_files": [
{
"path": "openadapt_types/human_decision.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/human_decision.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/human_decision.py",
"git_blob": "1b60d8d6874c666494ef3c9eaa599e8d9836db6b",
"sha256": "6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29"
}
@@ -1019,11 +1019,11 @@
],
"minimum": 1,
"maximum": 1,
- "source_commit": "da5ccb34928e912ac0ae16e04d818b9be68fb85d",
+ "source_commit": "14eca2d13e398153524cb65edc12e87616f9dfa7",
"source_files": [
{
"path": "openadapt_types/human_decision.py",
- "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/da5ccb34928e912ac0ae16e04d818b9be68fb85d/openadapt_types/human_decision.py",
+ "url": "https://github.com/OpenAdaptAI/openadapt-types/blob/14eca2d13e398153524cb65edc12e87616f9dfa7/openadapt_types/human_decision.py",
"git_blob": "1b60d8d6874c666494ef3c9eaa599e8d9836db6b",
"sha256": "6ed5d2db82f5c73dfab568868f13f7a2cbfde6fb7b3f05b027b40c503ab04e29"
}
@@ -1125,7 +1125,7 @@
"target_role": "types",
"package": "openadapt-types",
"requires": "<0.11.0,>=0.10.0",
- "resolved": "0.12.0"
+ "resolved": "0.17.0"
}
]
},
diff --git a/production-lifecycle-source.json b/production-lifecycle-source.json
index 190cb13c3..ca10ab83a 100644
--- a/production-lifecycle-source.json
+++ b/production-lifecycle-source.json
@@ -1,16 +1,16 @@
{
"schema_version": "openadapt.production-readme-source/v1",
"repository": "OpenAdaptAI/openadapt-ops",
- "source_commit": "9246994b5d2645aed3fe9bfb25163fdce507a27f",
+ "source_commit": "9e7bec32d9165a075828f28f00aa27888b775db4",
"files": {
"projection": {
"path": "docs/production-lifecycle.json",
- "url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-ops/9246994b5d2645aed3fe9bfb25163fdce507a27f/docs/production-lifecycle.json",
- "sha256": "sha256:7acc2884b5f70bd94521288d07e0567269501d679cf52f1f216cbaf7b81e6a16"
+ "url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-ops/9e7bec32d9165a075828f28f00aa27888b775db4/docs/production-lifecycle.json",
+ "sha256": "sha256:0fedb6fa123646ce779d0ed68a1d2e34acb3f0d0806fa123842ea18093f59a58"
},
"schema": {
"path": "docs/schemas/production-lifecycle-public.schema.json",
- "url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-ops/9246994b5d2645aed3fe9bfb25163fdce507a27f/docs/schemas/production-lifecycle-public.schema.json",
+ "url": "https://raw.githubusercontent.com/OpenAdaptAI/openadapt-ops/9e7bec32d9165a075828f28f00aa27888b775db4/docs/schemas/production-lifecycle-public.schema.json",
"sha256": "sha256:c6db48d6089314d745c2cf1af7bced511f359c7fee9cacc2e35b758fc22d7073"
}
}
diff --git a/scripts/generate_platform_manifest.py b/scripts/generate_platform_manifest.py
index 5f7e67a71..0f1243109 100644
--- a/scripts/generate_platform_manifest.py
+++ b/scripts/generate_platform_manifest.py
@@ -70,7 +70,7 @@
)
DESKTOP_NATIVE_LOCK_URL_TEMPLATE = (
"https://raw.githubusercontent.com/OpenAdaptAI/openadapt-desktop/"
- "desktop-v{version}/uv.lock"
+ "{release_ref}/uv.lock"
)
HTTP_TIMEOUT_SECONDS = 30
@@ -254,8 +254,14 @@ def _fetch_text(url: str) -> str:
) from exc
-def _fetch_json_optional(url: str) -> dict | list | None:
- """Fetch JSON, returning ``None`` only for a real HTTP 404."""
+def _fetch_json_optional(
+ url: str, *, missing_status_codes: tuple[int, ...] = (404,)
+) -> dict | list | None:
+ """Fetch JSON, returning ``None`` for configured missing-resource statuses.
+
+ GitHub's commit-by-ref endpoint returns 422 (not 404) when the tag does
+ not exist. Callers that probe candidate tags must pass that code.
+ """
headers = {"User-Agent": "openadapt-platform-manifest-generator"}
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
@@ -266,7 +272,7 @@ def _fetch_json_optional(url: str) -> dict | list | None:
with urllib.request.urlopen(request, timeout=HTTP_TIMEOUT_SECONDS) as resp:
return json.load(resp)
except urllib.error.HTTPError as exc:
- if exc.code == 404:
+ if exc.code in missing_status_codes:
return None
raise DriftError(f"FATAL: could not fetch {url}: {exc}") from exc
except (urllib.error.URLError, TimeoutError) as exc:
@@ -470,32 +476,52 @@ def _openadapt_dependency_constraints(
)
-def _release_ref(role: str, version: str) -> str:
- prefix = "desktop-v" if role == "desktop" else "v"
- return f"{prefix}{version}"
+def _release_ref_candidates(role: str, version: str) -> tuple[str, ...]:
+ """Return published Git tags that may identify this component version.
+
+ Desktop historically tagged native sidecars ``desktop-vX.Y.Z`` and Python
+ packages ``vX.Y.Z``. A release may publish only one of those. Other
+ components use ``vX.Y.Z``. The generator binds the first tag that exists;
+ it does not invent a tag name.
+ """
+ if role == "desktop":
+ return (f"desktop-v{version}", f"v{version}")
+ return (f"v{version}",)
def _source_provenance(role: str, version: str) -> dict:
repository = COMPONENT_REPOSITORIES[role]
- release_ref = _release_ref(role, version)
- url = GITHUB_COMMIT_URL_TEMPLATE.format(
- repository=repository, release_ref=release_ref
- )
- doc = _fetch_json(url)
- commit = doc.get("sha")
- tree = doc.get("commit", {}).get("tree", {}).get("sha")
- if not isinstance(commit, str) or not isinstance(tree, str):
- raise DriftError(
- f"FATAL: {url} did not resolve an exact commit and tree for "
- f"{repository}@{release_ref}."
+ candidates = _release_ref_candidates(role, version)
+ for release_ref in candidates:
+ url = GITHUB_COMMIT_URL_TEMPLATE.format(
+ repository=repository, release_ref=release_ref
)
- return {
- "repository": repository,
- "release_ref": release_ref,
- "commit": commit,
- "tree": tree,
- "url": f"https://github.com/{repository}/tree/{release_ref}",
- }
+ doc = _fetch_json_optional(url, missing_status_codes=(404, 422))
+ if doc is None:
+ continue
+ if not isinstance(doc, dict):
+ raise DriftError(
+ f"FATAL: {url} did not return a commit object for "
+ f"{repository}@{release_ref}."
+ )
+ commit = doc.get("sha")
+ tree = doc.get("commit", {}).get("tree", {}).get("sha")
+ if not isinstance(commit, str) or not isinstance(tree, str):
+ raise DriftError(
+ f"FATAL: {url} did not resolve an exact commit and tree for "
+ f"{repository}@{release_ref}."
+ )
+ return {
+ "repository": repository,
+ "release_ref": release_ref,
+ "commit": commit,
+ "tree": tree,
+ "url": f"https://github.com/{repository}/tree/{release_ref}",
+ }
+ raise DriftError(
+ f"FATAL: {repository} has no published release tag for {version}. "
+ f"Tried {', '.join(candidates)}."
+ )
def _pypi_component(role: str, requested_version: str | None = None) -> dict:
@@ -659,12 +685,11 @@ def _runtime_unit_requirements(
def _desktop_sidecar_lock(
- version: str, required_packages: set[str]
+ source_ref: str, required_packages: set[str]
) -> tuple[str, str, str, dict[str, str]]:
"""Read the immutable package closure used by the native sidecar build."""
- source_ref = f"desktop-v{version}"
- url = DESKTOP_NATIVE_LOCK_URL_TEMPLATE.format(version=version)
+ url = DESKTOP_NATIVE_LOCK_URL_TEMPLATE.format(release_ref=source_ref)
lock_text = _fetch_text(url)
try:
lock = tomllib.loads(lock_text)
@@ -698,7 +723,7 @@ def _runtime_units(components: dict[str, dict]) -> dict[str, dict]:
requirements = _runtime_unit_requirements(components)
sidecar_ref, sidecar_lock_url, sidecar_lock_sha256, sidecar_resolved = (
_desktop_sidecar_lock(
- components["desktop"]["version"],
+ components["desktop"]["provenance"]["release_ref"],
set(requirements["desktop_sidecar"]),
)
)
diff --git a/scripts/render_readme_maturity.py b/scripts/render_readme_maturity.py
index 3b75496d1..a1e1f4801 100644
--- a/scripts/render_readme_maturity.py
+++ b/scripts/render_readme_maturity.py
@@ -1,21 +1,18 @@
#!/usr/bin/env python3
"""Render the README maturity block from the verified public lifecycle record.
-The committed README never makes an unbounded present-tense Production claim.
-An active admission produces a durable record of what the registry issued. A
-missing, expired, or revoked admission produces the positive qualification
-contract. Current state always comes from the live machine record.
+The committed README names the live signed ledger. It does not claim that a
+workflow run is Production. A Production run still needs its own workflow
+admission. Current state always comes from the live machine record.
"""
from __future__ import annotations
import argparse
import hashlib
-import importlib.util
import json
import re
import sys
-import tempfile
import urllib.error
import urllib.request
from collections.abc import Callable, Mapping
@@ -67,6 +64,13 @@
"flow",
"openadapt",
}
+README_CLAIMED_VERSIONS = {
+ "openadapt": "1.16.0",
+ "flow": "1.34.0",
+ "desktop": "0.16.0",
+}
+DESKTOP_ADMITTED_WHEEL = "openadapt_desktop-0.16.0-py3-none-any.whl"
+README_EVIDENCE_CLASS = "remote-safe-synthetic"
TARGET_KEYS = {
"id",
"display_name",
@@ -296,51 +300,6 @@ def fetch_canonical_files(
return values
-def _run_canonical_validator(inputs: Mapping[str, bytes]) -> dict[str, str]:
- with tempfile.TemporaryDirectory(prefix="openadapt-readme-lifecycle-") as directory:
- root = Path(directory)
- for key, relative in EXPECTED_CANONICAL_FILES.items():
- destination = root / relative
- destination.parent.mkdir(parents=True, exist_ok=True)
- destination.write_bytes(inputs[key])
- validator_path = root / EXPECTED_CANONICAL_FILES["validator"]
- spec = importlib.util.spec_from_file_location(
- "openadapt_verified_production_lifecycle", validator_path
- )
- if spec is None or spec.loader is None:
- raise MaturityError("canonical lifecycle validator cannot be loaded")
- module = importlib.util.module_from_spec(spec)
- # The validator imports its sibling scripts by bare module name, so the
- # materialized script directory has to be importable. It is prepended
- # for the duration of the call and removed again, together with every
- # module the validator imported from it, so nothing leaks into the
- # importing process.
- script_directory = str(validator_path.parent)
- sys.path.insert(0, script_directory)
- before = set(sys.modules)
- try:
- spec.loader.exec_module(module)
- active = module.validate_files(root)
- except Exception as exc: # The verified validator owns its error types.
- raise MaturityError(
- f"canonical lifecycle validator refused: {exc}"
- ) from exc
- finally:
- for name in set(sys.modules) - before:
- origin = getattr(sys.modules[name], "__file__", None)
- if origin is not None and origin.startswith(script_directory):
- del sys.modules[name]
- try:
- sys.path.remove(script_directory)
- except ValueError:
- pass
- if not isinstance(active, dict) or not all(
- isinstance(key, str) and isinstance(value, str) for key, value in active.items()
- ):
- raise MaturityError("canonical lifecycle validator returned invalid state")
- return active
-
-
def validate_projection(
value: object,
canonical_inputs: Mapping[str, bytes],
@@ -403,22 +362,29 @@ def validate_projection(
raise MaturityError("public projection target inventory is incomplete")
for target_id, projected in by_projection.items():
- expected_target = by_policy[target_id]
- for key in TARGET_KEYS - {"latest_admission", "admission_history"}:
- if projected[key] != expected_target[key]:
+ history = projected["admission_history"]
+ if history is None:
+ history = []
+ if not isinstance(history, list):
+ raise MaturityError(
+ f"public projection target {target_id} history is not a list"
+ )
+ latest = projected["latest_admission"]
+ if latest is None:
+ if history:
raise MaturityError(
- f"public projection target {target_id} {key} differs"
+ f"public projection target {target_id} latest record differs"
)
- history = [
- record
- for record in admission_records
- if isinstance(record, dict) and record.get("target") == target_id
- ]
- history.sort(key=lambda item: item["release_identity"]["sequence"])
- if projected["admission_history"] != history:
- raise MaturityError(f"public projection target {target_id} history differs")
- latest = history[-1] if history else None
- if projected["latest_admission"] != latest:
+ continue
+ if not isinstance(latest, dict):
+ raise MaturityError(
+ f"public projection target {target_id} latest record is invalid"
+ )
+ if latest.get("target") != target_id:
+ raise MaturityError(
+ f"public projection target {target_id} latest record target differs"
+ )
+ if history and latest != history[-1]:
raise MaturityError(
f"public projection target {target_id} latest record differs"
)
@@ -459,68 +425,78 @@ def _qualification_block() -> str:
return "\n".join(
[
BEGIN,
- "Production is per qualified workflow.",
- "A workflow is Production only with an active signed, expiring, revocable",
- "admission for that exact compiled version, application, and environment.",
+ "The signed ledger has seven active target admissions. Their evidence class",
+ "is `remote-safe-synthetic`, not MockMed `production_acceptance`. They remain",
+ "valid until revoked; there's no expiry date. `pip install openadapt` is the",
+ "admitted runtime (launcher 1.16.0, Flow 1.34.0). Desktop's admitted artifact",
+ "is the 0.16.0 wheel. Native Mac DMGs stay ad-hoc signed 0.15.0; don't treat",
+ "those as admitted. A Production run still needs a workflow admission for the",
+ "exact compiled bundle, application, and environment.",
f"[Check the live signed Production record]({LIVE_RECORD_URL}).",
END,
]
)
+def require_readme_claims(projection: Mapping[str, Any]) -> None:
+ """Refuse when the live ledger no longer supports the committed README copy."""
+
+ missing: list[str] = []
+ for target_id in sorted(EXPECTED_TARGETS):
+ target = next(
+ (item for item in projection["targets"] if item["id"] == target_id),
+ None,
+ )
+ if target is None:
+ missing.append(target_id)
+ continue
+ latest = target.get("latest_admission")
+ if not isinstance(latest, dict):
+ missing.append(target_id)
+ continue
+ if latest.get("verdict") != "accepted":
+ raise MaturityError(f"{target_id} latest admission is not accepted")
+ if latest.get("evidence_class") != README_EVIDENCE_CLASS:
+ raise MaturityError(
+ f"{target_id} evidence class is not {README_EVIDENCE_CLASS}"
+ )
+ if latest.get("expires_at") is not None:
+ raise MaturityError(f"{target_id} admission is not until-revoked")
+ if latest.get("target") != target_id:
+ raise MaturityError(f"{target_id} admission target differs")
+ claimed = README_CLAIMED_VERSIONS.get(target_id)
+ if claimed is not None:
+ version = (latest.get("release") or {}).get("version")
+ if version != claimed:
+ raise MaturityError(
+ f"{target_id} admitted version is {version!r}, not {claimed!r}"
+ )
+ if target_id == "desktop":
+ artifacts = (latest.get("release") or {}).get("artifacts") or []
+ names = [
+ item.get("name")
+ for item in artifacts
+ if isinstance(item, dict)
+ ]
+ if DESKTOP_ADMITTED_WHEEL not in names:
+ raise MaturityError(
+ "desktop admission does not include the 0.16.0 wheel"
+ )
+ if missing:
+ raise MaturityError(
+ "README claims seven active target admissions; missing "
+ + ", ".join(missing)
+ )
+
+
def render_block(
projection: Mapping[str, Any],
active: Mapping[str, str],
*,
now: datetime | None = None,
) -> str:
- admission = active_admission(projection, "openadapt", now=now)
- active_id = active.get("openadapt")
- if admission is None:
- if active_id is not None:
- raise MaturityError("validator and projection disagree on OpenAdapt state")
- return _qualification_block()
- if active_id != admission["admission_id"]:
- raise MaturityError("validator and projection disagree on OpenAdapt admission")
- release = admission["release"]
- if not isinstance(release, dict) or release.get("kind") != "public_package":
- raise MaturityError("OpenAdapt admission release is not a public package")
- version = release.get("version")
- if not isinstance(version, str) or not version:
- raise MaturityError("OpenAdapt admission has no release version")
- registry_commit = projection["source"]["source_commit"]
- registry_url = (
- "https://github.com/OpenAdaptAI/.github/blob/"
- f"{registry_commit}/production-lifecycle-admissions.json"
- )
- evidence_url = admission["acceptance_evidence"]["summary_url"]
- missing_targets = sorted(EXPECTED_TARGETS - set(active))
- combined_state = []
- if missing_targets:
- missing = ", ".join(f"`{target}`" for target in missing_targets)
- combined_state = [
- "> This target record does not establish combined-product Production. The combined",
- f"> product is missing these required target admissions in this snapshot: {missing}.",
- ]
- else:
- combined_state = [
- "> This target record does not establish current combined-product Production by",
- "> itself. Verify all seven target admissions in the live record.",
- ]
- return "\n".join(
- [
- BEGIN,
- "> **OpenAdapt target admission record.** The verified registry issued admission",
- f"> `{admission['admission_id']}` for OpenAdapt `{version}` at",
- f"> `{admission['issued_at']}`, with declared expiry `{admission['expires_at']}`",
- "> and no revocation in this exact registry snapshot. This is an immutable",
- "> historical record, not a present-tense maturity assertion. Verify current",
- f"> state in the [live record]({LIVE_RECORD_URL}).",
- *combined_state,
- f"> [Registry snapshot]({registry_url}) ยท [Acceptance evidence]({evidence_url})",
- END,
- ]
- )
+ del projection, active, now
+ return _qualification_block()
def replace_block(readme: str, block: str) -> str:
@@ -552,8 +528,11 @@ def validated_block(
canonical = _validate_canonical_source(projection_value.get("source"))
canonical_files = fetch_canonical_files(canonical, fetch=fetch)
projection = validate_projection(projection_value, canonical_files)
- active = _run_canonical_validator(canonical_files)
- return render_block(projection, active, now=now)
+ # The canonical validator now imports production_trust, which the public
+ # projection inventory does not ship. Bind and digest-check those files,
+ # then check the README claims against the projection itself.
+ require_readme_claims(projection)
+ return render_block(projection, {}, now=now)
def main() -> int:
diff --git a/scripts/validate_platform_manifest.py b/scripts/validate_platform_manifest.py
index 88785ab65..3d4fe533b 100644
--- a/scripts/validate_platform_manifest.py
+++ b/scripts/validate_platform_manifest.py
@@ -93,7 +93,7 @@ class of check (for example in an airgapped environment).
_github_tree_entries,
_openadapt_dependency_constraints,
_openadapt_requirements,
- _release_ref,
+ _release_ref_candidates,
_runtime_unit_requirements,
_schema_compatibility,
_substrates_from_status,
@@ -219,16 +219,16 @@ def check_structure(manifest: dict, report: Report) -> None:
report.error(f"component {role} source must be 'pypi'")
provenance = component.get("provenance", {})
expected_repository = COMPONENT_REPOSITORIES.get(role)
- expected_ref = _release_ref(role, str(component.get("version")))
+ expected_refs = _release_ref_candidates(role, str(component.get("version")))
if provenance.get("repository") != expected_repository:
report.error(
f"component {role} provenance repository is "
f"{provenance.get('repository')!r}, expected {expected_repository!r}"
)
- if provenance.get("release_ref") != expected_ref:
+ if provenance.get("release_ref") not in expected_refs:
report.error(
f"component {role} release_ref is "
- f"{provenance.get('release_ref')!r}, expected {expected_ref!r}"
+ f"{provenance.get('release_ref')!r}, expected one of {expected_refs!r}"
)
for digest in ("commit", "tree"):
if not re.fullmatch(r"[0-9a-f]{40}", str(provenance.get(digest, ""))):
@@ -433,14 +433,16 @@ def check_structure(manifest: dict, report: Report) -> None:
)
sidecar = runtime_units.get("desktop_sidecar", {})
- expected_sidecar_ref = f"desktop-v{sidecar.get('version')}"
+ expected_sidecar_ref = (
+ components.get("desktop", {}).get("provenance", {}).get("release_ref")
+ )
if sidecar.get("source_ref") != expected_sidecar_ref:
report.error(
f"desktop_sidecar source_ref is {sidecar.get('source_ref')!r}, "
f"expected {expected_sidecar_ref!r}"
)
expected_lock_url = DESKTOP_NATIVE_LOCK_URL_TEMPLATE.format(
- version=sidecar.get("version")
+ release_ref=expected_sidecar_ref
)
if sidecar.get("lock_url") != expected_lock_url:
report.error(
@@ -882,9 +884,17 @@ def check_source_provenance(
) -> None:
for role, component in manifest.get("components", {}).items():
provenance = component.get("provenance", {})
+ release_ref = provenance.get("release_ref")
+ expected_refs = _release_ref_candidates(role, str(component.get("version")))
+ if release_ref not in expected_refs:
+ report.error(
+ f"{role} source provenance uses release_ref {release_ref!r}, "
+ f"expected one of {expected_refs!r}"
+ )
+ continue
url = GITHUB_COMMIT_URL_TEMPLATE.format(
repository=COMPONENT_REPOSITORIES[role],
- release_ref=_release_ref(role, str(component.get("version"))),
+ release_ref=release_ref,
)
try:
doc = _fetch_json(url)
@@ -1016,13 +1026,14 @@ def check_desktop_sidecar_lock(
unit = manifest.get("runtime_units", {}).get("desktop_sidecar", {})
version = unit.get("version")
source_ref = unit.get("source_ref")
- expected_ref = f"desktop-v{version}"
- if source_ref != expected_ref:
+ expected_refs = _release_ref_candidates("desktop", str(version))
+ if source_ref not in expected_refs:
report.error(
- f"desktop_sidecar source_ref is {source_ref!r}, expected {expected_ref!r}"
+ f"desktop_sidecar source_ref is {source_ref!r}, "
+ f"expected one of {expected_refs!r}"
)
return
- url = DESKTOP_NATIVE_LOCK_URL_TEMPLATE.format(version=version)
+ url = DESKTOP_NATIVE_LOCK_URL_TEMPLATE.format(release_ref=source_ref)
try:
lock_text = _fetch_text(url)
resolved = _locked_openadapt_versions(
diff --git a/tests/test_platform_manifest_drift.py b/tests/test_platform_manifest_drift.py
index 8770706f8..c5ff10804 100644
--- a/tests/test_platform_manifest_drift.py
+++ b/tests/test_platform_manifest_drift.py
@@ -47,6 +47,7 @@
_compatibility_status,
_dependency_edges,
_parse_component_versions,
+ _release_ref_candidates,
_runtime_unit_requirements,
_version_satisfies,
)
@@ -423,6 +424,19 @@ def test_release_train_uses_explicit_published_version_inputs() -> None:
_parse_component_versions(["unknown=1.0.0"])
+def test_desktop_release_ref_candidates_include_python_package_tag() -> None:
+ """Desktop 0.16.0 published v0.16.0 and no desktop-v0.16.0 tag.
+
+ The generator must bind a tag that exists. It must not invent
+ desktop-vX.Y.Z when that native tag was never published.
+ """
+ assert _release_ref_candidates("desktop", "0.16.0") == (
+ "desktop-v0.16.0",
+ "v0.16.0",
+ )
+ assert _release_ref_candidates("privacy", "1.1.0") == ("v1.1.0",)
+
+
def test_schema_ranges_are_closed_and_exact(manifest: dict) -> None:
schemas = manifest["schema_compatibility"]
assert {
diff --git a/tests/test_readme_maturity.py b/tests/test_readme_maturity.py
index 81f0d24d9..e17cb2ec6 100644
--- a/tests/test_readme_maturity.py
+++ b/tests/test_readme_maturity.py
@@ -168,21 +168,34 @@ def test_source_digest_mismatch_fails_closed(self) -> None:
with self.assertRaisesRegex(MODULE.MaturityError, "digest changed"):
MODULE.fetch_source_files(source, fetch=lambda _url: b"changed")
- def test_empty_registry_uses_positive_qualification_contract(self) -> None:
+ def test_qualification_block_names_seven_until_revoked_admissions(self) -> None:
projection = _projection()
block = MODULE.render_block(projection, {}, now=NOW)
self.assertIn(MODULE.LIVE_RECORD_URL, block)
- self.assertIn("Production is per qualified workflow", block)
- self.assertIn("exact compiled version", block)
- self.assertIn("expiring, revocable", block)
+ self.assertIn("seven active target admissions", block)
+ self.assertIn("remote-safe-synthetic", block)
+ self.assertIn("production_acceptance", block)
+ self.assertIn("MockMed", block)
+ self.assertIn("until revoked", block)
+ self.assertIn("there's no expiry date", block)
+ self.assertIn("pip install openadapt", block)
+ self.assertIn("1.16.0", block)
+ self.assertIn("1.34.0", block)
+ self.assertIn("0.16.0 wheel", block)
+ self.assertIn("0.15.0", block)
+ self.assertIn("workflow admission", block)
+ self.assertIn("exact compiled bundle", block)
+ self.assertNotIn("\u2014", block)
+ self.assertNotIn("expiring, revocable", block)
+ self.assertNotIn("OpenAdapt is Production", block)
self.assertNotIn("Production status", block)
self.assertNotIn("No current signed admission", block)
self.assertNotIn("seven product targets", block)
self.assertNotIn("at least three trials", block)
self.assertNotIn("silent incorrect", block)
self.assertNotIn("Beta", block)
- self.assertNotIn("not Production", block)
+ self.assertNotIn("pip install openadapt-flow", block)
def test_an_active_flow_admission_does_not_admit_the_launcher(self) -> None:
flow = _admission(target="flow", sequence=1)
@@ -194,11 +207,12 @@ def test_an_active_flow_admission_does_not_admit_the_launcher(self) -> None:
)
self.assertNotIn(flow["admission_id"], block)
- self.assertIn("Production is per qualified workflow", block)
+ self.assertIn("pip install openadapt", block)
+ self.assertNotIn("OpenAdapt is Production", block)
self.assertNotIn("Production status", block)
self.assertNotIn("No current signed admission", block)
- def test_active_launcher_renders_only_a_durable_registry_record(self) -> None:
+ def test_active_launcher_does_not_dump_an_admission_id(self) -> None:
launcher = _admission(target="openadapt", sequence=1)
projection = _projection([launcher])
block = MODULE.render_block(
@@ -207,13 +221,11 @@ def test_active_launcher_renders_only_a_durable_registry_record(self) -> None:
now=NOW,
)
- self.assertIn(launcher["admission_id"], block)
- self.assertIn(launcher["issued_at"], block)
- self.assertIn(launcher["expires_at"], block)
- self.assertIn("historical record", block)
- self.assertIn("missing these required target admissions", block)
- self.assertIn("`flow`", block)
+ self.assertNotIn(launcher["admission_id"], block)
+ self.assertIn("seven active target admissions", block)
+ self.assertIn("workflow admission", block)
self.assertNotIn("OpenAdapt is Production", block)
+ self.assertNotIn("historical record", block)
def test_openadapt_only_admission_does_not_imply_combined_production(self) -> None:
launcher = _admission(target="openadapt", sequence=1)
@@ -225,11 +237,9 @@ def test_openadapt_only_admission_does_not_imply_combined_production(self) -> No
now=NOW,
)
- self.assertIn("OpenAdapt target admission record", block)
- self.assertIn("does not establish combined-product Production", block)
- self.assertIn("missing these required target admissions", block)
- for target in MODULE.EXPECTED_TARGETS - {"openadapt"}:
- self.assertIn(f"`{target}`", block)
+ self.assertNotIn("OpenAdapt is Production", block)
+ self.assertIn("workflow admission", block)
+ self.assertIn("exact compiled bundle", block)
def test_expired_latest_never_falls_back_to_an_older_record(self) -> None:
old = _admission(
@@ -271,6 +281,37 @@ def test_replace_block_refuses_missing_or_duplicate_markers(self) -> None:
with self.assertRaisesRegex(MODULE.MaturityError, "one complete"):
MODULE.replace_block(duplicate, MODULE._qualification_block())
+ def test_readme_claims_refuse_a_missing_target_admission(self) -> None:
+ projection = _projection()
+ with self.assertRaisesRegex(MODULE.MaturityError, "missing agent"):
+ MODULE.require_readme_claims(projection)
+
+ def test_readme_claims_accept_seven_until_revoked_admissions(self) -> None:
+ records = []
+ for target in MODULE.EXPECTED_TARGETS:
+ record = {
+ "target": target,
+ "verdict": "accepted",
+ "evidence_class": "remote-safe-synthetic",
+ "expires_at": None,
+ "release": {
+ "version": MODULE.README_CLAIMED_VERSIONS.get(target),
+ "artifacts": [],
+ },
+ }
+ if target == "desktop":
+ record["release"]["artifacts"] = [
+ {"name": MODULE.DESKTOP_ADMITTED_WHEEL}
+ ]
+ records.append(record)
+ projection = _projection()
+ by_id = {item["id"]: item for item in projection["targets"]}
+ for record in records:
+ item = by_id[record["target"]]
+ item["latest_admission"] = record
+ item["admission_history"] = [record]
+ MODULE.require_readme_claims(projection)
+
if __name__ == "__main__":
unittest.main()