responsibility-pathway-runtime 0.1.0a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- responsibility_pathway_runtime-0.1.0a2.dist-info/METADATA +113 -0
- responsibility_pathway_runtime-0.1.0a2.dist-info/RECORD +65 -0
- responsibility_pathway_runtime-0.1.0a2.dist-info/WHEEL +5 -0
- responsibility_pathway_runtime-0.1.0a2.dist-info/entry_points.txt +2 -0
- responsibility_pathway_runtime-0.1.0a2.dist-info/licenses/LICENSE +21 -0
- responsibility_pathway_runtime-0.1.0a2.dist-info/top_level.txt +1 -0
- rpr/__init__.py +36 -0
- rpr/_generated_transitions.py +25 -0
- rpr/agent_adapters.py +88 -0
- rpr/assurance_manifest.py +195 -0
- rpr/attempts.py +402 -0
- rpr/authority.py +91 -0
- rpr/backup.py +126 -0
- rpr/candidate_readiness.py +98 -0
- rpr/canonical_transition_spec.py +75 -0
- rpr/claim_traceability.py +138 -0
- rpr/clean_export.py +124 -0
- rpr/cli.py +26 -0
- rpr/compensation.py +38 -0
- rpr/customer_handover.py +189 -0
- rpr/delivery_acceptance.py +140 -0
- rpr/delivery_acceptance_baseline.py +79 -0
- rpr/dependency_inventory.py +77 -0
- rpr/diagnostics.py +103 -0
- rpr/epistemic_claims.py +181 -0
- rpr/evidence.py +53 -0
- rpr/executor.py +107 -0
- rpr/formal_consistency.py +127 -0
- rpr/hash_bundle.py +48 -0
- rpr/http_executor.py +144 -0
- rpr/identity.py +103 -0
- rpr/inspection.py +67 -0
- rpr/integration_acceptance_inventory.py +86 -0
- rpr/launch_claims.py +49 -0
- rpr/lifecycle_acceptance.py +320 -0
- rpr/mcp_admission.py +277 -0
- rpr/mcp_compatibility.py +95 -0
- rpr/mcp_jsonrpc_session.py +136 -0
- rpr/mcp_local_subprocess_stdio.py +214 -0
- rpr/mcp_retained_evidence.py +194 -0
- rpr/mcp_stable_snapshot.py +135 -0
- rpr/mcp_stable_transport.py +134 -0
- rpr/mcp_stdio_channel.py +144 -0
- rpr/mcp_stdio_framing.py +169 -0
- rpr/mcp_subprocess_lifecycle.py +147 -0
- rpr/mcp_tool_executor.py +188 -0
- rpr/message_executor.py +154 -0
- rpr/models.py +116 -0
- rpr/principal.py +51 -0
- rpr/py.typed +0 -0
- rpr/rc_execution.py +189 -0
- rpr/read_only_connection.py +117 -0
- rpr/reconciliation.py +63 -0
- rpr/redaction.py +51 -0
- rpr/release_audit.py +111 -0
- rpr/release_gate.py +63 -0
- rpr/repair.py +170 -0
- rpr/rpe.py +128 -0
- rpr/runtime.py +363 -0
- rpr/sbom.py +71 -0
- rpr/security_review.py +69 -0
- rpr/source_context.py +81 -0
- rpr/state_machine.py +17 -0
- rpr/storage.py +210 -0
- rpr/tenant.py +141 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: responsibility-pathway-runtime
|
|
3
|
+
Version: 0.1.0a2
|
|
4
|
+
Summary: A responsibility-pathway runtime for AI agent actions
|
|
5
|
+
Author: Akihisa Ono
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://yutorikomeiji.github.io/responsibility-pathway-runtime/
|
|
8
|
+
Project-URL: Documentation, https://yutorikomeiji.github.io/responsibility-pathway-runtime/
|
|
9
|
+
Project-URL: Repository, https://github.com/YutoriKomeiji/responsibility-pathway-runtime
|
|
10
|
+
Project-URL: Issues, https://github.com/YutoriKomeiji/responsibility-pathway-runtime/issues
|
|
11
|
+
Project-URL: Demo, https://yutorikomeiji.github.io/responsibility-pathway-runtime/demo.html
|
|
12
|
+
Keywords: ai-agents,governance,responsibility,human-in-the-loop,runtime
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Responsibility Pathway Runtime
|
|
27
|
+
|
|
28
|
+
**Govern external actions without losing evidence, recovery, or the human decision point.**
|
|
29
|
+
|
|
30
|
+
Responsibility Pathway Runtime (RPR) is an MIT-licensed Python runtime for applications that need to place an explicit responsibility pathway in front of consequential external actions. It helps a host application preserve actor and authority declarations, execution-attempt continuity, readback evidence, fail-closed ambiguity handling, repair routes, and Human Gate decisions.
|
|
31
|
+
|
|
32
|
+
> **Public alpha source and live demo — 0.1.0a2**
|
|
33
|
+
> Freeze ID: `RPR-CF-2026-08-02-01`
|
|
34
|
+
> Tested final rehearsal profile: Linux, Python 3.11
|
|
35
|
+
> The repository, product site, and browser demo are public. A final tag, GitHub Release, and package-registry distribution have not yet been published.
|
|
36
|
+
> RPR is not a legal-responsibility engine, identity provider, secret manager, production gateway, or guarantee of exactly-once effects across arbitrary remote systems.
|
|
37
|
+
|
|
38
|
+
[Product site](https://yutorikomeiji.github.io/responsibility-pathway-runtime/) · [Live browser demo](https://yutorikomeiji.github.io/responsibility-pathway-runtime/demo.html) · [日本語の入口](docs/ja/README.md) · [Quick Start](docs/en/quick-start.md) · [Product documentation](docs/en/README.md) · [Report an issue](https://github.com/YutoriKomeiji/responsibility-pathway-runtime/issues)
|
|
39
|
+
|
|
40
|
+
## Why RPR
|
|
41
|
+
|
|
42
|
+
AI agents and automation can execute faster than people can reconstruct what happened. RPR provides a bounded runtime layer where an integrating application can:
|
|
43
|
+
|
|
44
|
+
- declare actors, authority, and the proposed action;
|
|
45
|
+
- evaluate and retain an explicit pathway state;
|
|
46
|
+
- bind operations, attempts, and idempotency identity;
|
|
47
|
+
- require independent readback before completion;
|
|
48
|
+
- stop ambiguous writes as `write_status_unknown` instead of false success;
|
|
49
|
+
- retain Human Gate, repair, resume, reconciliation, and evidence continuity;
|
|
50
|
+
- survive restart without silently repeating unresolved effects.
|
|
51
|
+
|
|
52
|
+
## Verified in the frozen alpha candidate
|
|
53
|
+
|
|
54
|
+
- pathway registration and authorized state transitions;
|
|
55
|
+
- persistent pathway and execution-attempt stores;
|
|
56
|
+
- Human Gate, repair, resume, and reconciliation boundaries;
|
|
57
|
+
- local-file, allow-listed HTTP, durable outbound-message, and real MCP subprocess paths;
|
|
58
|
+
- HTTP and MCP fault injection;
|
|
59
|
+
- crash/restart continuity and duplicate-dispatch prevention;
|
|
60
|
+
- backup, restore, diagnostics, uninstall, package/CLI residue checks, and customer-data retention;
|
|
61
|
+
- clean wheel and source-distribution installation;
|
|
62
|
+
- two independent byte-for-byte reproducible builds;
|
|
63
|
+
- English-primary and Japanese-parallel product documentation;
|
|
64
|
+
- Lean 4 verification of selected published state-machine invariants;
|
|
65
|
+
- Chromium and Pyodide execution of the CI-built RPR wheel in the public browser demo.
|
|
66
|
+
|
|
67
|
+
## We need field-test reports for
|
|
68
|
+
|
|
69
|
+
RPR is public so real users can report reproducible environment and integration findings. Please open an Issue for:
|
|
70
|
+
|
|
71
|
+
- Windows, macOS, other Linux distributions, containers, and Python environments beyond the final Linux/Python 3.11 rehearsal;
|
|
72
|
+
- proxy, TLS, enterprise identity, credential handling, remote MCP, and service-specific connectivity;
|
|
73
|
+
- framework, agent, CI/CD, RPA, batch, and application integrations;
|
|
74
|
+
- installation, upgrade, backup/restore, removal, and operational usability;
|
|
75
|
+
- confusing states, missing examples, documentation gaps, and unsupported assumptions.
|
|
76
|
+
|
|
77
|
+
A user report is field evidence for that environment. It does not imply universal production readiness.
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
Until a package-registry distribution or GitHub Release is published, install from the public repository in a disposable environment:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/YutoriKomeiji/responsibility-pathway-runtime.git
|
|
85
|
+
cd responsibility-pathway-runtime
|
|
86
|
+
python3.11 -m venv .venv
|
|
87
|
+
. .venv/bin/activate
|
|
88
|
+
python -m pip install --upgrade pip
|
|
89
|
+
python -m pip install -e .
|
|
90
|
+
rpr --help
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To inspect the runtime without installing it locally, use the [live browser demo](https://yutorikomeiji.github.io/responsibility-pathway-runtime/demo.html). The demo runs the CI-built RPR wheel in Pyodide with SQLite; only the external payment provider is simulated.
|
|
94
|
+
|
|
95
|
+
The verified candidate artifact hashes are recorded in [`release-evidence/replacement-freeze-2026-08-02.json`](release-evidence/replacement-freeze-2026-08-02.json). Those candidate artifacts are not yet a general package distribution.
|
|
96
|
+
|
|
97
|
+
## Integration boundary
|
|
98
|
+
|
|
99
|
+
The host application remains responsible for authentication, credential isolation, network controls, bypass prevention, domain-specific authorization, and the independent readback source. RPE integration is optional; RPE absence, malformed output, or unsupported results must not become implicit permission.
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
- [Product, scope, and architecture](docs/en/product-scope-architecture.md)
|
|
104
|
+
- [Installation, operations, and recovery](docs/en/install-operations-recovery.md)
|
|
105
|
+
- [Security, limitations, integration, and API](docs/en/security-integration-api.md)
|
|
106
|
+
- [Verification, known issues, release notes, and UAT](docs/en/verification-release-uat.md)
|
|
107
|
+
- [Support and field testing](SUPPORT.md)
|
|
108
|
+
- [Security reporting](SECURITY.md)
|
|
109
|
+
- [Contributing](CONTRIBUTING.md)
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
RPR is released under the [MIT License](LICENSE). Copyright © 2026 Akihisa Ono.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/licenses/LICENSE,sha256=LOorZGMvZRrYKJ7_pFyNmfvOz0GxAVHDX_S_l3wxuPM,1068
|
|
2
|
+
rpr/__init__.py,sha256=RwGPcjeqfWFUj55KhaxOCRuEu8Im9INhstTGOYyQqS0,2981
|
|
3
|
+
rpr/_generated_transitions.py,sha256=76Ndlgntg1rzibkBOLWFQy6w5k0YBFsJPqgIao0jR_4,1697
|
|
4
|
+
rpr/agent_adapters.py,sha256=bo-M_uCHEa0go0nh2jzw49wJrv3JMyJVsKiHzlUsWVU,3500
|
|
5
|
+
rpr/assurance_manifest.py,sha256=GB3FCNws4lqRgPje5kDb0IwRR52oyODqPBkRjWw7PeE,8700
|
|
6
|
+
rpr/attempts.py,sha256=KVIau_IerQr5Z1KexeouV3FDV0Fa9eGHgfP0lGmklIA,16080
|
|
7
|
+
rpr/authority.py,sha256=g2fCHOO0lN-3lNlL4iOj5JZCQ_o12JMIUItvYOQr590,3932
|
|
8
|
+
rpr/backup.py,sha256=2Dr6ZWaxlczxHN8MnA78VenM3RlPC3eEwOcyQiLCwFw,4359
|
|
9
|
+
rpr/candidate_readiness.py,sha256=x2Ze789xJ-rgTIXCb4fZjg8KUJxhiAxJ1bl-LlSMltY,3657
|
|
10
|
+
rpr/canonical_transition_spec.py,sha256=hypwigmluciw4OxQrDVwC6zbintodr3scS1bDpNYZ5k,2984
|
|
11
|
+
rpr/claim_traceability.py,sha256=PySi0oF5GbGfHPaIiEj0MsJ_IiS-pG3cgcdIWYGR8tg,6761
|
|
12
|
+
rpr/clean_export.py,sha256=gJXqN_Aem2qBkXMJEbTMtczPhMLD7wN0Jngppq4gyRU,4683
|
|
13
|
+
rpr/cli.py,sha256=N3H0Mh8tdaYToo6x57w3m4nuONVpMiUjrMqtNXKaiAA,863
|
|
14
|
+
rpr/compensation.py,sha256=zer6PUVXOv5jIDhUIZIHft3aR5hXRGkamn4GGSUNRl4,1243
|
|
15
|
+
rpr/customer_handover.py,sha256=6Ob7zBbTZKYIzx2hMsWfgTbTBnvKEaa_9Fp0YgwkEp0,9072
|
|
16
|
+
rpr/delivery_acceptance.py,sha256=xcjXNQlbfg6dICMF6dkbN5pPMl_6k5e4r6kCh9gcBbE,5166
|
|
17
|
+
rpr/delivery_acceptance_baseline.py,sha256=-UV3Kqu8eeF-jS_bF0_GqCW3AQUZPbcxaXHfCL7zjIk,2730
|
|
18
|
+
rpr/dependency_inventory.py,sha256=gFJ58jgjoXUNV-WyPiosx4JBXGXZl0kjTGw5Z-Givvw,2676
|
|
19
|
+
rpr/diagnostics.py,sha256=eh4PhXoiW-bXO6x_7Y56chErxKdqMUWXcpMfV3__7HI,4027
|
|
20
|
+
rpr/epistemic_claims.py,sha256=a_bQO8QchO9XGsB6fqTsCKiC_I__OW6HWLsUTSplB-U,7840
|
|
21
|
+
rpr/evidence.py,sha256=3JD6cwuzquBS1khY1dbZTJD8AWbMJFrNU7SfoIfvT3E,2423
|
|
22
|
+
rpr/executor.py,sha256=KnpJfJ72l8DAwFestEcv5YtckHSujRTYQAt2S4nGBAk,4468
|
|
23
|
+
rpr/formal_consistency.py,sha256=LeXzJ85_ccoZXpbTYXLKYkOkRTUu0QTqa1T575GNOcc,4810
|
|
24
|
+
rpr/hash_bundle.py,sha256=T6G8G9ZVuofEL4AX8ac9OibUyMuIzz-t50BcHW9nu8c,1721
|
|
25
|
+
rpr/http_executor.py,sha256=LUpCM81pLorr85l1XJfqHHXn-mUP4vnrv8Oa4OQZciM,7237
|
|
26
|
+
rpr/identity.py,sha256=3t069R-MLc_qPf3pQbbQJ_WzKOIcWozeOfQz4wZPBeQ,4469
|
|
27
|
+
rpr/inspection.py,sha256=liHt9YvdMVCyP4w5Ba659F8Wsk115qIjB-nLrDZlv7c,3390
|
|
28
|
+
rpr/integration_acceptance_inventory.py,sha256=A3UOhB8WFNyBC3IKzkbeaNZOKYfArAJy4Cs-Q_mvifY,3824
|
|
29
|
+
rpr/launch_claims.py,sha256=-uwm9TJR1U4NlchM-L3gw0xCEJ_wT-LZ-mM0jjjLRTk,1875
|
|
30
|
+
rpr/lifecycle_acceptance.py,sha256=O565Bd0NXNsPiusT8f_NRYYs1Ne_kGB2alMd7Y-Sz4o,12575
|
|
31
|
+
rpr/mcp_admission.py,sha256=lDK-xViedg8vh79ugl8ctVJ1kevPfR80qWiL8_HqNqM,10551
|
|
32
|
+
rpr/mcp_compatibility.py,sha256=m2c0MQJ67K3PkRUq-m62tS3gT1cXdgD_3O46IvVsQn8,5174
|
|
33
|
+
rpr/mcp_jsonrpc_session.py,sha256=IoE-56uYeTt62KBiWcG6AW-IafGn0m4U8FatvueUQ8o,5627
|
|
34
|
+
rpr/mcp_local_subprocess_stdio.py,sha256=VGjzkMz3UHIsH7kJRlKdqhZpDFe0hSa6ULYYU-IxZHo,8373
|
|
35
|
+
rpr/mcp_retained_evidence.py,sha256=Orvkexedbt3ifOAojbv91h8ioSr89-p8FS_iwCzxf04,6989
|
|
36
|
+
rpr/mcp_stable_snapshot.py,sha256=6Mk-5CYAUFGAv7yd5M9kUCZsfeo2Yl0Ey87noxhHBAA,5502
|
|
37
|
+
rpr/mcp_stable_transport.py,sha256=HOGQeVP2ckysRxlKnu6qLPka6T2WMK8UuygKwc8H-8Y,4872
|
|
38
|
+
rpr/mcp_stdio_channel.py,sha256=wpqSmIbAkwRYiiMjMjXCsuYp3Ee69wqfCuAsvdf4lhY,5376
|
|
39
|
+
rpr/mcp_stdio_framing.py,sha256=aKScCDcvArJAhA5NLC16_uzo39TsUxfiYIf3_gcizCM,6400
|
|
40
|
+
rpr/mcp_subprocess_lifecycle.py,sha256=flm3TrNeLz4x-HV52-ZHJKTozEHOh81zo9K4ktp7teM,5238
|
|
41
|
+
rpr/mcp_tool_executor.py,sha256=XbjU3A71HqF0WQojPvIKIVur5I4QrLo7uJgNLL9xgJI,7315
|
|
42
|
+
rpr/message_executor.py,sha256=7YLfSXkqmvPcq3WQ8oq0SLFBVwCt_lG5S1wC5OcwObs,7580
|
|
43
|
+
rpr/models.py,sha256=G8kTSyla5t1HWmKjBMWxg_AJ_7rtDYsi2wjSSb4WblA,3556
|
|
44
|
+
rpr/principal.py,sha256=1lOT8Ym2FcW7INRJqHfbKTwWSxHXX86HMiVaqhYEphw,1607
|
|
45
|
+
rpr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
rpr/rc_execution.py,sha256=YO3Mj_wO79QW5W_HtbBhuPQr4ahW29ndqHSeHRP3lE4,8634
|
|
47
|
+
rpr/read_only_connection.py,sha256=XgBKOvCibFQtNJ97M8A6dJOw0Rcs4IiPg4AovoNEqpE,4401
|
|
48
|
+
rpr/reconciliation.py,sha256=deou6YeGqaXVKiQisvlb6bL5sFzcFwRSemvM6M0qwGY,2292
|
|
49
|
+
rpr/redaction.py,sha256=Pdq48eprlyxb9BvHVnwmXn9jeJ5BXdUEAPo9xO_Udvw,1916
|
|
50
|
+
rpr/release_audit.py,sha256=enbOK5pfcNfUa2YbhL-38J6TIDGM4J3-SX06pJCzAs8,4344
|
|
51
|
+
rpr/release_gate.py,sha256=Gd219MNz2BrAIAybTyoCpYK-PsES46MByUlmcC1lfl0,2335
|
|
52
|
+
rpr/repair.py,sha256=Z1vW1FOZ-RUBa7hUMC7CrjZcp_qiqMTn4Ua7Ai2TliM,6749
|
|
53
|
+
rpr/rpe.py,sha256=qOY8uql_ZWrJXwvYqKZGKRsRhE_exZY6i7C_GL_EHZ0,5145
|
|
54
|
+
rpr/runtime.py,sha256=zk5Dh_D9hdka_Sc87G4zTiEOOPUdEYz6ODOOJE79hEo,21880
|
|
55
|
+
rpr/sbom.py,sha256=Dj-zjO7be1v2CNJ1_KBhoxFyNlinkr3oUxHgEXjAl8w,2764
|
|
56
|
+
rpr/security_review.py,sha256=JXGSoUauhz-Z-NDDd-RJrjwroCwcBQM_PNO440rPcnI,2616
|
|
57
|
+
rpr/source_context.py,sha256=0leXTlorPZXVFGKjyDEpgnTiynMYU7tlEyr8h1AsDoU,3112
|
|
58
|
+
rpr/state_machine.py,sha256=ulnlef4AC3dpxsSEq8ueXWT_VusyVkwLl6v8TXn3VCA,483
|
|
59
|
+
rpr/storage.py,sha256=nw_1ON27XUNqGUKlSXHZ3PByudjBr5uyjkOv5W4F1Jw,9693
|
|
60
|
+
rpr/tenant.py,sha256=LevzR8eipmRe0NNDQi1feD0Tk2zbRBzxX5lQ51C15VM,6242
|
|
61
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/METADATA,sha256=c00NvzEtcV00UJkCkLmWfuKB3ufXpZWKZkFb_yOmFsY,6538
|
|
62
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
63
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/entry_points.txt,sha256=Rtm2BOb2RUFT7LRVK3ADREGxPELseAz_dRxMPplULhU,37
|
|
64
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/top_level.txt,sha256=-MpVZCITUsm3s5OxjrfOYmnLAMxTal0N1KewaSYPJrU,4
|
|
65
|
+
responsibility_pathway_runtime-0.1.0a2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akihisa Ono
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rpr
|
rpr/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright (c) 2026 Akihisa Ono
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Responsibility Pathway Runtime public API."""
|
|
4
|
+
|
|
5
|
+
from .agent_adapters import AgentToolCall, AgentToolOutcome, RprToolBoundary, langgraph_tool_node, openai_function_tool_handler
|
|
6
|
+
from .attempts import AttemptConflictError, ExecutionAttemptRecord, SQLiteExecutionAttemptLedger
|
|
7
|
+
from .authority import AuthorityError
|
|
8
|
+
from .compensation import CompensationPlan, CompensationStatus, NoAutomaticCompensation
|
|
9
|
+
from .executor import ExecutionRequest, ExecutionResult, ExecutionStatus, LocalFileExecutor, ReadbackEvidence
|
|
10
|
+
from .http_executor import HttpMutationExecutor, JsonFieldReadback, ReadbackStrategy
|
|
11
|
+
from .identity import ExternalTokenVerifierResolver, VerifiedClaimsPrincipalResolver, VerifiedTokenClaims
|
|
12
|
+
from .message_executor import DeliveryReceipt, MessageTransport, OutboundMessageExecutor, SQLiteOutbox
|
|
13
|
+
from .models import ActionClass, EnvironmentTrust, PathwayDefinition, PathwayState, RuntimeDecision
|
|
14
|
+
from .principal import Principal, PrincipalError, StaticActorBinding, TrustedPrincipalResolver
|
|
15
|
+
from .reconciliation import ReconciliationResult, ReconciliationStatus, ReconciliationStrategy, reconcile_started_attempt
|
|
16
|
+
from .redaction import EvidenceLimitError, RedactionPolicy
|
|
17
|
+
from .rpe import PythonRpeEvaluator, RestRpeEvaluator, RpeContractError
|
|
18
|
+
from .runtime import EvidenceVerificationResult, RegistrationResult, ResponsibilityPathwayRuntime
|
|
19
|
+
from .source_context import SourceAuthority, SourceContext, SourceContextError
|
|
20
|
+
from .storage import IdempotencyConflictError, SQLiteStore
|
|
21
|
+
from .tenant import SQLiteTenantRegistry, TenantBoundaryError, TenantContext, TenantScopedRuntime
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ActionClass", "AgentToolCall", "AgentToolOutcome", "AttemptConflictError", "AuthorityError",
|
|
25
|
+
"CompensationPlan", "CompensationStatus", "DeliveryReceipt", "EnvironmentTrust", "EvidenceLimitError",
|
|
26
|
+
"EvidenceVerificationResult", "ExecutionAttemptRecord", "ExecutionRequest", "ExecutionResult", "ExecutionStatus",
|
|
27
|
+
"ExternalTokenVerifierResolver", "HttpMutationExecutor", "IdempotencyConflictError", "JsonFieldReadback",
|
|
28
|
+
"LocalFileExecutor", "MessageTransport", "NoAutomaticCompensation", "OutboundMessageExecutor",
|
|
29
|
+
"PathwayDefinition", "PathwayState", "Principal", "PrincipalError", "PythonRpeEvaluator", "ReadbackEvidence",
|
|
30
|
+
"ReadbackStrategy", "ReconciliationResult", "ReconciliationStatus", "ReconciliationStrategy", "RedactionPolicy",
|
|
31
|
+
"RegistrationResult", "ResponsibilityPathwayRuntime", "RestRpeEvaluator", "RpeContractError", "RprToolBoundary",
|
|
32
|
+
"RuntimeDecision", "SQLiteExecutionAttemptLedger", "SQLiteOutbox", "SQLiteStore", "SQLiteTenantRegistry",
|
|
33
|
+
"SourceAuthority", "SourceContext", "SourceContextError", "StaticActorBinding", "TenantBoundaryError",
|
|
34
|
+
"TenantContext", "TenantScopedRuntime", "TrustedPrincipalResolver", "VerifiedClaimsPrincipalResolver",
|
|
35
|
+
"VerifiedTokenClaims", "langgraph_tool_node", "openai_function_tool_handler", "reconcile_started_attempt",
|
|
36
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2026 Akihisa Ono
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# Generated from specs/pathway-state-machine.json. Do not edit manually.
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from .models import PathwayState
|
|
7
|
+
|
|
8
|
+
ALLOWED_TRANSITIONS: dict[PathwayState, frozenset[PathwayState]] = {
|
|
9
|
+
PathwayState.PROPOSED: frozenset({PathwayState.AWAITING_APPROVAL, PathwayState.APPROVED, PathwayState.DENIED}),
|
|
10
|
+
PathwayState.AWAITING_APPROVAL: frozenset({PathwayState.APPROVED, PathwayState.DENIED, PathwayState.HUMAN_GATE}),
|
|
11
|
+
PathwayState.APPROVED: frozenset({PathwayState.RUNNING, PathwayState.HELD, PathwayState.ABORTED}),
|
|
12
|
+
PathwayState.RUNNING: frozenset({PathwayState.COMPLETED, PathwayState.STOPPED, PathwayState.PARTIALLY_COMPLETED, PathwayState.WRITE_STATUS_UNKNOWN, PathwayState.REPAIR_REQUIRED}),
|
|
13
|
+
PathwayState.HELD: frozenset({PathwayState.HUMAN_GATE, PathwayState.APPROVED, PathwayState.ABORTED}),
|
|
14
|
+
PathwayState.HUMAN_GATE: frozenset({PathwayState.APPROVED, PathwayState.DENIED, PathwayState.ABORTED}),
|
|
15
|
+
PathwayState.STOPPED: frozenset({PathwayState.REPAIR_REQUIRED, PathwayState.ABORTED}),
|
|
16
|
+
PathwayState.PARTIALLY_COMPLETED: frozenset({PathwayState.REPAIR_REQUIRED}),
|
|
17
|
+
PathwayState.WRITE_STATUS_UNKNOWN: frozenset({PathwayState.COMPLETED, PathwayState.REPAIR_REQUIRED}),
|
|
18
|
+
PathwayState.REPAIR_REQUIRED: frozenset({PathwayState.READY_TO_RESUME, PathwayState.ABORTED}),
|
|
19
|
+
PathwayState.READY_TO_RESUME: frozenset({PathwayState.RUNNING, PathwayState.ABORTED}),
|
|
20
|
+
PathwayState.COMPLETED: frozenset(),
|
|
21
|
+
PathwayState.DENIED: frozenset(),
|
|
22
|
+
PathwayState.ABORTED: frozenset(),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TERMINAL_STATES = frozenset({PathwayState.COMPLETED, PathwayState.DENIED, PathwayState.ABORTED})
|
rpr/agent_adapters.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright (c) 2026 Akihisa Ono
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Callable, Mapping
|
|
8
|
+
|
|
9
|
+
from .executor import ExecutionRequest, Executor
|
|
10
|
+
from .runtime import ResponsibilityPathwayRuntime
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class AgentToolCall:
|
|
15
|
+
tool_name: str
|
|
16
|
+
arguments: Mapping[str, Any]
|
|
17
|
+
call_id: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class AgentToolOutcome:
|
|
22
|
+
allowed: bool
|
|
23
|
+
pathway_id: str
|
|
24
|
+
state: str
|
|
25
|
+
output: Mapping[str, Any]
|
|
26
|
+
reason: str | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RprToolBoundary:
|
|
30
|
+
"""Framework-neutral boundary used by concrete agent-framework adapters."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, runtime: ResponsibilityPathwayRuntime, executor_for: Callable[[str], Executor]) -> None:
|
|
33
|
+
self.runtime = runtime
|
|
34
|
+
self.executor_for = executor_for
|
|
35
|
+
|
|
36
|
+
def invoke(self, *, pathway_id: str, actor: str, call: AgentToolCall) -> AgentToolOutcome:
|
|
37
|
+
request = ExecutionRequest(
|
|
38
|
+
operation_id=call.call_id,
|
|
39
|
+
attempt_id=call.call_id,
|
|
40
|
+
idempotency_key=call.call_id,
|
|
41
|
+
action=call.tool_name,
|
|
42
|
+
parameters=dict(call.arguments),
|
|
43
|
+
)
|
|
44
|
+
result = self.runtime.execute(pathway_id, request, actor=actor, executor=self.executor_for(call.tool_name))
|
|
45
|
+
state = self.runtime.store.get_state(pathway_id).value
|
|
46
|
+
return AgentToolOutcome(
|
|
47
|
+
allowed=result.status.value == "succeeded",
|
|
48
|
+
pathway_id=pathway_id,
|
|
49
|
+
state=state,
|
|
50
|
+
output={
|
|
51
|
+
"status": result.status.value,
|
|
52
|
+
"evidence": dict(result.evidence),
|
|
53
|
+
"readback": None if result.readback is None else {
|
|
54
|
+
"verified": result.readback.verified,
|
|
55
|
+
"observed": dict(result.readback.observed),
|
|
56
|
+
"reason": result.readback.reason,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
reason=result.reason,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def openai_function_tool_handler(boundary: RprToolBoundary, *, pathway_id: str, actor: str, tool_name: str) -> Callable[[str], str]:
|
|
64
|
+
"""Return a dependency-free handler suitable for wrapping with Agents SDK `function_tool`.
|
|
65
|
+
|
|
66
|
+
RPR does not import the SDK. The application wraps this callable and may attach SDK tool
|
|
67
|
+
guardrails. Hosted and built-in tools require a different integration boundary.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def handler(arguments_json: str) -> str:
|
|
71
|
+
arguments = json.loads(arguments_json)
|
|
72
|
+
call_id = str(arguments.pop("_rpr_call_id"))
|
|
73
|
+
outcome = boundary.invoke(pathway_id=pathway_id, actor=actor, call=AgentToolCall(tool_name, arguments, call_id))
|
|
74
|
+
return json.dumps(outcome.output | {"pathway_id": outcome.pathway_id, "state": outcome.state, "reason": outcome.reason}, ensure_ascii=False)
|
|
75
|
+
|
|
76
|
+
return handler
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def langgraph_tool_node(boundary: RprToolBoundary, *, pathway_id: str, actor: str, tool_name: str) -> Callable[[Mapping[str, Any]], Mapping[str, Any]]:
|
|
80
|
+
"""Return a node callable usable from a LangGraph `StateGraph` or tool wrapper."""
|
|
81
|
+
|
|
82
|
+
def node(state: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
83
|
+
call_id = str(state["call_id"])
|
|
84
|
+
arguments = dict(state.get("arguments", {}))
|
|
85
|
+
outcome = boundary.invoke(pathway_id=pathway_id, actor=actor, call=AgentToolCall(tool_name, arguments, call_id))
|
|
86
|
+
return dict(state) | {"rpr": {"allowed": outcome.allowed, "state": outcome.state, "output": dict(outcome.output), "reason": outcome.reason}}
|
|
87
|
+
|
|
88
|
+
return node
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Copyright (c) 2026 Akihisa Ono
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AssuranceManifestError(ValueError):
|
|
11
|
+
"""The assurance manifest is malformed or exceeds its declared boundary."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_ALLOWED_STATUSES = {
|
|
15
|
+
"not_implemented",
|
|
16
|
+
"implemented_unverified",
|
|
17
|
+
"passing",
|
|
18
|
+
"failing",
|
|
19
|
+
"deferred",
|
|
20
|
+
"out_of_scope",
|
|
21
|
+
}
|
|
22
|
+
_EVIDENCE_ORDER = {f"E{level}": level for level in range(6)}
|
|
23
|
+
_REQUIRED_HUMAN_GATES = {
|
|
24
|
+
"repository_publicization",
|
|
25
|
+
"pages_publication",
|
|
26
|
+
"package_release",
|
|
27
|
+
"tag_or_github_release",
|
|
28
|
+
"external_or_live_mcp_connection",
|
|
29
|
+
"credential_use",
|
|
30
|
+
"license_change",
|
|
31
|
+
"claim_escalation_to_e4_or_e5",
|
|
32
|
+
}
|
|
33
|
+
_REQUIRED_NOT_PERMITTED = {
|
|
34
|
+
"full formal verification",
|
|
35
|
+
"legal or regulatory compliance",
|
|
36
|
+
"arbitrary third-party MCP compatibility",
|
|
37
|
+
"live external MCP interoperability",
|
|
38
|
+
"distributed correctness",
|
|
39
|
+
"production readiness",
|
|
40
|
+
"signed non-repudiation",
|
|
41
|
+
"release-complete declaration without authorization",
|
|
42
|
+
}
|
|
43
|
+
_REQUIRED_CLAIM_FIELDS = {
|
|
44
|
+
"claim_id",
|
|
45
|
+
"status",
|
|
46
|
+
"evidence_level",
|
|
47
|
+
"statement",
|
|
48
|
+
"implementation_anchors",
|
|
49
|
+
"test_anchors",
|
|
50
|
+
"retained_evidence",
|
|
51
|
+
"residual_owner",
|
|
52
|
+
"blocked_by",
|
|
53
|
+
"residual_risks",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_assurance_manifest(path: str | Path, *, root: str | Path | None = None) -> dict[str, Any]:
|
|
58
|
+
manifest_path = Path(path)
|
|
59
|
+
try:
|
|
60
|
+
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
61
|
+
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
|
62
|
+
raise AssuranceManifestError(f"manifest_load_failed: {type(exc).__name__}: {exc}") from exc
|
|
63
|
+
validate_assurance_manifest(payload, root=manifest_path.parent.parent if root is None else root)
|
|
64
|
+
return payload
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def validate_assurance_manifest(payload: Any, *, root: str | Path | None = None) -> None:
|
|
68
|
+
if not isinstance(payload, Mapping):
|
|
69
|
+
raise AssuranceManifestError("manifest_must_be_object")
|
|
70
|
+
if payload.get("schema_version") != 1:
|
|
71
|
+
raise AssuranceManifestError("unsupported_schema_version")
|
|
72
|
+
if payload.get("product") != "Responsibility Pathway Runtime":
|
|
73
|
+
raise AssuranceManifestError("unexpected_product")
|
|
74
|
+
if payload.get("canonical_source") != "YutoriKomeiji/responsibility-pathway-runtime":
|
|
75
|
+
raise AssuranceManifestError("unexpected_canonical_source")
|
|
76
|
+
if payload.get("release_boundary") != "public_alpha_candidate":
|
|
77
|
+
raise AssuranceManifestError("release_boundary_must_be_public_alpha_candidate")
|
|
78
|
+
|
|
79
|
+
human_gates = _string_set(payload.get("human_gate_required_for"), "human_gate_required_for")
|
|
80
|
+
missing_gates = _REQUIRED_HUMAN_GATES - human_gates
|
|
81
|
+
if missing_gates:
|
|
82
|
+
raise AssuranceManifestError(f"missing_human_gates: {sorted(missing_gates)!r}")
|
|
83
|
+
|
|
84
|
+
maximum = payload.get("maximum_current_evidence_level")
|
|
85
|
+
if maximum not in _EVIDENCE_ORDER:
|
|
86
|
+
raise AssuranceManifestError("invalid_maximum_current_evidence_level")
|
|
87
|
+
if _EVIDENCE_ORDER[maximum] > _EVIDENCE_ORDER["E3"]:
|
|
88
|
+
raise AssuranceManifestError("evidence_level_escalation_requires_human_gate")
|
|
89
|
+
|
|
90
|
+
boundary = payload.get("global_claim_boundary")
|
|
91
|
+
if not isinstance(boundary, Mapping):
|
|
92
|
+
raise AssuranceManifestError("global_claim_boundary_must_be_object")
|
|
93
|
+
permitted = boundary.get("permitted")
|
|
94
|
+
if not isinstance(permitted, str) or not permitted.strip():
|
|
95
|
+
raise AssuranceManifestError("global_permitted_wording_required")
|
|
96
|
+
not_permitted = _string_set(payload.get("global_claim_boundary", {}).get("not_permitted"), "global_claim_boundary.not_permitted")
|
|
97
|
+
missing_prohibitions = _REQUIRED_NOT_PERMITTED - not_permitted
|
|
98
|
+
if missing_prohibitions:
|
|
99
|
+
raise AssuranceManifestError(f"missing_claim_prohibitions: {sorted(missing_prohibitions)!r}")
|
|
100
|
+
|
|
101
|
+
source_documents = _string_list(payload.get("source_documents"), "source_documents", allow_empty=False)
|
|
102
|
+
claims = payload.get("claims")
|
|
103
|
+
if not isinstance(claims, list) or not claims:
|
|
104
|
+
raise AssuranceManifestError("claims_must_be_non_empty_list")
|
|
105
|
+
|
|
106
|
+
claim_ids: set[str] = set()
|
|
107
|
+
for index, claim in enumerate(claims):
|
|
108
|
+
_validate_claim(claim, index=index, maximum=maximum, claim_ids=claim_ids)
|
|
109
|
+
|
|
110
|
+
if root is not None:
|
|
111
|
+
root_path = Path(root)
|
|
112
|
+
for relative in source_documents:
|
|
113
|
+
_require_relative_file(root_path, relative, "source_document")
|
|
114
|
+
for claim in claims:
|
|
115
|
+
for relative in claim["implementation_anchors"]:
|
|
116
|
+
_require_relative_file(root_path, relative, f"implementation_anchor:{claim['claim_id']}")
|
|
117
|
+
for relative in claim["test_anchors"]:
|
|
118
|
+
_require_relative_file(root_path, relative, f"test_anchor:{claim['claim_id']}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _validate_claim(claim: Any, *, index: int, maximum: str, claim_ids: set[str]) -> None:
|
|
122
|
+
if not isinstance(claim, Mapping):
|
|
123
|
+
raise AssuranceManifestError(f"claim_{index}_must_be_object")
|
|
124
|
+
missing = _REQUIRED_CLAIM_FIELDS - set(claim)
|
|
125
|
+
if missing:
|
|
126
|
+
raise AssuranceManifestError(f"claim_{index}_missing_fields: {sorted(missing)!r}")
|
|
127
|
+
|
|
128
|
+
claim_id = claim.get("claim_id")
|
|
129
|
+
if not isinstance(claim_id, str) or not claim_id.strip():
|
|
130
|
+
raise AssuranceManifestError(f"claim_{index}_invalid_id")
|
|
131
|
+
if claim_id in claim_ids:
|
|
132
|
+
raise AssuranceManifestError(f"duplicate_claim_id: {claim_id}")
|
|
133
|
+
claim_ids.add(claim_id)
|
|
134
|
+
|
|
135
|
+
status = claim.get("status")
|
|
136
|
+
if status not in _ALLOWED_STATUSES:
|
|
137
|
+
raise AssuranceManifestError(f"claim_{claim_id}_invalid_status")
|
|
138
|
+
evidence_level = claim.get("evidence_level")
|
|
139
|
+
if evidence_level not in _EVIDENCE_ORDER:
|
|
140
|
+
raise AssuranceManifestError(f"claim_{claim_id}_invalid_evidence_level")
|
|
141
|
+
if _EVIDENCE_ORDER[evidence_level] > _EVIDENCE_ORDER[maximum]:
|
|
142
|
+
raise AssuranceManifestError(f"claim_{claim_id}_exceeds_manifest_maximum")
|
|
143
|
+
if status == "passing" and _EVIDENCE_ORDER[evidence_level] < _EVIDENCE_ORDER["E2"]:
|
|
144
|
+
raise AssuranceManifestError(f"claim_{claim_id}_passing_without_sufficient_evidence")
|
|
145
|
+
|
|
146
|
+
statement = claim.get("statement")
|
|
147
|
+
if not isinstance(statement, str) or not statement.strip():
|
|
148
|
+
raise AssuranceManifestError(f"claim_{claim_id}_statement_required")
|
|
149
|
+
owner = claim.get("residual_owner")
|
|
150
|
+
if not isinstance(owner, str) or not owner.strip():
|
|
151
|
+
raise AssuranceManifestError(f"claim_{claim_id}_residual_owner_required")
|
|
152
|
+
|
|
153
|
+
implementation = _string_list(claim.get("implementation_anchors"), f"claim_{claim_id}.implementation_anchors", allow_empty=False)
|
|
154
|
+
tests = _string_list(claim.get("test_anchors"), f"claim_{claim_id}.test_anchors", allow_empty=True)
|
|
155
|
+
_string_list(claim.get("retained_evidence"), f"claim_{claim_id}.retained_evidence", allow_empty=True)
|
|
156
|
+
blocked_by = _string_list(claim.get("blocked_by"), f"claim_{claim_id}.blocked_by", allow_empty=True)
|
|
157
|
+
risks = _string_list(claim.get("residual_risks"), f"claim_{claim_id}.residual_risks", allow_empty=False)
|
|
158
|
+
|
|
159
|
+
if status == "passing" and not tests:
|
|
160
|
+
raise AssuranceManifestError(f"claim_{claim_id}_passing_without_test_anchor")
|
|
161
|
+
if status == "implemented_unverified" and not blocked_by:
|
|
162
|
+
raise AssuranceManifestError(f"claim_{claim_id}_unverified_without_blocker")
|
|
163
|
+
if not implementation or not risks:
|
|
164
|
+
raise AssuranceManifestError(f"claim_{claim_id}_missing_assurance_boundary")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _string_list(value: Any, field: str, *, allow_empty: bool) -> list[str]:
|
|
168
|
+
if not isinstance(value, list):
|
|
169
|
+
raise AssuranceManifestError(f"{field}_must_be_list")
|
|
170
|
+
if not allow_empty and not value:
|
|
171
|
+
raise AssuranceManifestError(f"{field}_must_not_be_empty")
|
|
172
|
+
result: list[str] = []
|
|
173
|
+
for item in value:
|
|
174
|
+
if not isinstance(item, str) or not item.strip():
|
|
175
|
+
raise AssuranceManifestError(f"{field}_contains_invalid_string")
|
|
176
|
+
result.append(item)
|
|
177
|
+
if len(result) != len(set(result)):
|
|
178
|
+
raise AssuranceManifestError(f"{field}_contains_duplicates")
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _string_set(value: Any, field: str) -> set[str]:
|
|
183
|
+
return set(_string_list(value, field, allow_empty=False))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _require_relative_file(root: Path, relative: str, field: str) -> None:
|
|
187
|
+
candidate = Path(relative)
|
|
188
|
+
if candidate.is_absolute() or ".." in candidate.parts:
|
|
189
|
+
raise AssuranceManifestError(f"{field}_must_be_relative")
|
|
190
|
+
resolved = (root / candidate).resolve()
|
|
191
|
+
root_resolved = root.resolve()
|
|
192
|
+
if root_resolved != resolved and root_resolved not in resolved.parents:
|
|
193
|
+
raise AssuranceManifestError(f"{field}_escapes_root")
|
|
194
|
+
if not resolved.is_file():
|
|
195
|
+
raise AssuranceManifestError(f"{field}_missing: {relative}")
|