Establish Codex transport contract
This commit is contained in:
commit
0659cdacba
8 changed files with 1307 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/target/
|
||||
*.cc
|
||||
*.log
|
||||
|
||||
25
AGENTS.md
Normal file
25
AGENTS.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# CodexConnector Instructions
|
||||
|
||||
CodexConnector is the independent credential and transport boundary for
|
||||
Codex-compatible subscription inference. It is not an agent, scheduler, prompt
|
||||
owner, tool executor, decision store, or consumer runtime.
|
||||
|
||||
Keep one Cargo package and one production daemon entrypoint. Add another target
|
||||
only when an independent lifecycle or privilege invariant requires it.
|
||||
|
||||
The daemon may own credential loading and refresh, upstream client identity,
|
||||
authenticated caller admission, bounded concurrency/replay, provider transport,
|
||||
typed events, and transport receipts. Consumers own native requests, provider
|
||||
lowering, prompts, schemas, tool execution, retry between passes, interpretation,
|
||||
decisions, and state admission.
|
||||
|
||||
Use typed Rust contracts, CultCache state, CultNet transport, and CultMesh/Eve
|
||||
projection. JSON is permitted only at the upstream provider boundary or for
|
||||
published schemas. Never log credentials, prompts, model output, or decrypted
|
||||
request cargo.
|
||||
|
||||
Before broad compilation, name the exact package/target/profile, output root,
|
||||
current footprint, and retention decision. Focused library checks and tests are
|
||||
the default. Code and tests must buy consequential behavior; delete mirrors,
|
||||
wrappers, compatibility paths, and self-certifying status surfaces.
|
||||
|
||||
180
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
180
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
CodexConnector Contributor License Agreement
|
||||
===========================================
|
||||
|
||||
Version 1.0
|
||||
|
||||
This Contributor License Agreement ("Agreement") governs Contributions
|
||||
submitted to CodexConnector.
|
||||
|
||||
This Agreement is designed as a broad contributor license, not a copyright
|
||||
assignment. You keep ownership of Your Contributions, but You give the Project
|
||||
Steward enough rights to use, sublicense, and relicense them without returning
|
||||
to You for later permission.
|
||||
|
||||
This structure follows the same general family of contributor-license
|
||||
agreements used by projects and organizations such as the Apache Software
|
||||
Foundation, Project Harmony, and Canonical: contributors retain ownership, while
|
||||
the project receives a broad inbound license.
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
"You" or "Your" means the individual or legal entity making a Contribution.
|
||||
For legal entities, "You" includes any entity that controls, is controlled by,
|
||||
or is under common control with the contributing entity.
|
||||
|
||||
"Project" means CodexConnector and any successor project or distribution that
|
||||
continues, forks, renames, reorganizes, or commercializes substantially the
|
||||
same Project-Authored Material.
|
||||
|
||||
"Project Steward" means the copyright holder or holders who maintain
|
||||
CodexConnector, any legal entity later formed or designated by them to own,
|
||||
license, or commercialize Project-Authored Material, and any successor or
|
||||
assignee authorized by them in writing.
|
||||
|
||||
"Contribution" means any original work of authorship, invention, improvement,
|
||||
modification, patch, documentation, design, media, text, code, configuration,
|
||||
test, or other material intentionally submitted by You to the Project for
|
||||
inclusion in, or use with, the Project.
|
||||
|
||||
"Submitted" means communicated to the Project or its maintainers by any
|
||||
reasonable channel used for Project development, including GitHub pull
|
||||
requests, patches, commits, issues, discussions, email, chat, or other written
|
||||
communication. A submission is not a Contribution if You clearly mark it
|
||||
"Not a Contribution."
|
||||
|
||||
2. Copyright License
|
||||
--------------------
|
||||
|
||||
Subject to this Agreement, You grant the Project Steward a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable, transferable,
|
||||
and sublicensable copyright license to use, reproduce, modify, prepare
|
||||
derivative works of, publicly display, publicly perform, distribute,
|
||||
sublicense, relicense, make available, sell, offer for sale, import, and
|
||||
otherwise exploit Your Contributions, in source or object form, for any
|
||||
purpose.
|
||||
|
||||
This includes the right to license or relicense Your Contributions, and
|
||||
derivative works of them, under any license or terms the Project Steward
|
||||
chooses, including open-source, source-available, noncommercial, commercial,
|
||||
proprietary, dual-license, or future license terms.
|
||||
|
||||
3. Patent License
|
||||
-----------------
|
||||
|
||||
Subject to this Agreement, You grant the Project Steward and recipients of
|
||||
Project distributions a perpetual, worldwide, non-exclusive, no-charge,
|
||||
royalty-free, irrevocable patent license to make, have made, use, offer to
|
||||
sell, sell, import, and otherwise transfer the Project and Your Contributions.
|
||||
|
||||
This patent license covers patent claims that You can license and that would
|
||||
necessarily be infringed by Your Contributions alone or by combination of Your
|
||||
Contributions with the Project to which they were submitted.
|
||||
|
||||
If any entity brings a written patent infringement claim alleging that Your
|
||||
Contribution or the Project infringes a patent, any patent license granted to
|
||||
that entity under this Agreement terminates as of the date that claim is filed.
|
||||
|
||||
4. Ownership
|
||||
------------
|
||||
|
||||
You retain ownership of Your Contributions. This Agreement does not prevent You
|
||||
from using, licensing, selling, or contributing Your Contributions elsewhere,
|
||||
so long as doing so does not violate another agreement or law.
|
||||
|
||||
5. Representations
|
||||
------------------
|
||||
|
||||
You represent that:
|
||||
|
||||
- You are legally entitled to grant the rights in this Agreement.
|
||||
- Each Contribution is Your original creation, or You have sufficient rights to
|
||||
submit it under this Agreement.
|
||||
- If Your employer, client, school, sponsor, or another organization may own or
|
||||
control rights in Your Contribution, You have permission to submit it under
|
||||
this Agreement or that organization has provided a separate written
|
||||
authorization acceptable to the Project Steward.
|
||||
- Your Contribution does not knowingly violate any agreement, license, trade
|
||||
secret obligation, confidentiality obligation, or third-party right.
|
||||
- You will identify any third-party code, data, model output, assets, license
|
||||
restrictions, patent restrictions, trademark restrictions, or other legal
|
||||
constraints of which You are aware and that are associated with Your
|
||||
Contribution.
|
||||
|
||||
6. Third-Party Material
|
||||
-----------------------
|
||||
|
||||
If You submit material that is not entirely Your original creation, You must
|
||||
clearly identify its source and applicable license or restriction.
|
||||
|
||||
The Project Steward may reject, remove, rewrite, or separately track that
|
||||
material if it is incompatible with the Project's licensing or commercial
|
||||
plans.
|
||||
|
||||
7. Moral Rights
|
||||
---------------
|
||||
|
||||
To the maximum extent permitted by law, You waive and agree not to assert any
|
||||
moral rights, droit moral, rights of integrity, rights of attribution, or
|
||||
similar rights that would interfere with the Project Steward's exercise of the
|
||||
rights granted in this Agreement.
|
||||
|
||||
Where those rights cannot be waived, You grant the Project Steward a
|
||||
perpetual, worldwide, royalty-free license to exercise them and agree not to
|
||||
assert them against the Project Steward, its sublicensees, distributors,
|
||||
customers, or users.
|
||||
|
||||
8. No Obligation To Use
|
||||
-----------------------
|
||||
|
||||
The Project Steward is not required to accept, use, preserve, publish, or
|
||||
maintain any Contribution.
|
||||
|
||||
9. No Support Or Warranty
|
||||
-------------------------
|
||||
|
||||
You are not required to provide support for Your Contributions.
|
||||
|
||||
Unless required by law or separately agreed in writing, You provide Your
|
||||
Contributions on an "AS IS" basis, without warranties or conditions of any
|
||||
kind, including warranties or conditions of title, non-infringement,
|
||||
merchantability, or fitness for a particular purpose.
|
||||
|
||||
10. Acceptance
|
||||
--------------
|
||||
|
||||
You accept this Agreement by signing it, by submitting a Contribution after
|
||||
being presented with this Agreement, or by including the following statement in
|
||||
a pull request, issue, patch, email, or other written submission:
|
||||
|
||||
I agree that my Contributions to CodexConnector are submitted under the
|
||||
CodexConnector Contributor License Agreement version 1.0.
|
||||
|
||||
The Project Steward may require a separate signed copy or an organization-level
|
||||
agreement before accepting non-trivial, employer-owned, client-owned, or
|
||||
commercially significant Contributions.
|
||||
|
||||
11. Entire Agreement For Contributions
|
||||
--------------------------------------
|
||||
|
||||
This Agreement governs the rights You grant to the Project Steward for Your
|
||||
Contributions unless You and the Project Steward sign a different written
|
||||
agreement.
|
||||
|
||||
If any part of this Agreement is unenforceable, the remaining parts remain in
|
||||
effect to the maximum extent permitted by law.
|
||||
|
||||
12. Signature Record
|
||||
--------------------
|
||||
|
||||
Contributor name:
|
||||
|
||||
Contributor GitHub username or other account:
|
||||
|
||||
Contributor email:
|
||||
|
||||
If signing for an organization, organization name and authority to sign:
|
||||
|
||||
Signature:
|
||||
|
||||
Date:
|
||||
215
Cargo.lock
generated
Normal file
215
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "codex-connector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
|
||||
dependencies = [
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "codex-connector"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
autobins = false
|
||||
license-file = "LICENSE"
|
||||
description = "Credential-isolated typed transport for Codex-compatible subscription inference"
|
||||
repository = "https://github.com/GameCult/CodexConnector"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
rmp-serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sha2 = "0.10"
|
||||
thiserror = "2"
|
||||
261
LICENSE
Normal file
261
LICENSE
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
CodexConnector Repository License Notice
|
||||
=======================================
|
||||
|
||||
This repository is a multi-license distribution. Different parts of the
|
||||
repository are available under different terms.
|
||||
|
||||
Nothing in this file narrows, revokes, or relabels rights already granted by
|
||||
upstream licenses. In particular, nothing in this file should be read as
|
||||
withdrawing any permission granted by Apache License 2.0 for Apache-covered
|
||||
material.
|
||||
|
||||
1. Scope
|
||||
--------
|
||||
|
||||
For this file:
|
||||
|
||||
- `Project-Authored Material` means original software, documentation, and other
|
||||
repository content authored for CodexConnector, except where a file or
|
||||
directory states different terms.
|
||||
- `Upstream Material` means material copied or adapted from third parties and
|
||||
distributed under their own licenses.
|
||||
- `Mixed Apache Files` means files under `vendor/codex/**` that contain
|
||||
project-authored changes on top of upstream Apache-licensed material.
|
||||
|
||||
2. Upstream And Third-Party Material
|
||||
------------------------------------
|
||||
|
||||
- All material under `vendor/codex/**` remains subject to its upstream licenses
|
||||
and notices, including Apache License 2.0 at `vendor/codex/LICENSE` and any
|
||||
applicable `NOTICE` or file-level notices.
|
||||
- Other third-party material distributed with the repository keeps its own
|
||||
license terms where provided.
|
||||
- You must continue to comply with those upstream terms when using,
|
||||
redistributing, or modifying upstream material.
|
||||
|
||||
3. Public License For Project-Authored Material
|
||||
-----------------------------------------------
|
||||
|
||||
Except where a file or directory says otherwise, Project-Authored Material
|
||||
outside `vendor/codex/**` is licensed to the public under:
|
||||
|
||||
- PolyForm Noncommercial License 1.0.0
|
||||
|
||||
The full text of that license appears in Appendix A of this file.
|
||||
|
||||
Required Notice: Copyright 2026 CodexConnector authors and contributors.
|
||||
|
||||
4. Commercial License Option
|
||||
----------------------------
|
||||
|
||||
If you want to use Project-Authored Material for any purpose not permitted by
|
||||
PolyForm Noncommercial License 1.0.0, you must obtain a separate written
|
||||
commercial license from the applicable copyright holder or another party
|
||||
expressly authorized in writing to grant that license.
|
||||
|
||||
Commercial rights are not granted by:
|
||||
|
||||
- source availability
|
||||
- public access to this repository
|
||||
- silence
|
||||
- estoppel by implication
|
||||
- the fact that the licensors are not yet organized as a corporation or LLC
|
||||
|
||||
A natural person can own and license copyright. Until a separate business
|
||||
entity is formed and designated in writing, any commercial license for
|
||||
Project-Authored Material may be offered directly by the individual copyright
|
||||
holder or holders.
|
||||
|
||||
5. Mixed Apache Files Under `vendor/codex/**`
|
||||
---------------------------------------------
|
||||
|
||||
Some files under `vendor/codex/**` contain project-authored changes layered on
|
||||
top of upstream Apache-licensed code.
|
||||
|
||||
For those files:
|
||||
|
||||
- Apache License 2.0 and the applicable upstream notices still apply.
|
||||
- You must not read this repository notice as converting those files into a
|
||||
PolyForm-only codebase.
|
||||
- To the extent the project owns copyright in new additions to those files, the
|
||||
project may also offer separate commercial terms for its own contributions.
|
||||
- This repository notice does not claim to erase or retract Apache-2.0
|
||||
permissions already granted for Apache-covered material.
|
||||
|
||||
The strongest commercial boundary in this repository therefore applies to
|
||||
clearly separable Project-Authored Material outside `vendor/codex/**`,
|
||||
including `src/**` and other non-vendored project files.
|
||||
|
||||
6. Open Source Status
|
||||
---------------------
|
||||
|
||||
Because the public license path for Project-Authored Material is PolyForm
|
||||
Noncommercial License 1.0.0 rather than an OSI-approved license, the
|
||||
repository as a whole is source-available and dual-licensed. It is not an OSI
|
||||
Open Source distribution as a whole.
|
||||
|
||||
7. Reference Structures
|
||||
-----------------------
|
||||
|
||||
This licensing structure intentionally borrows the general dual-license shape
|
||||
used by established projects such as Qt and MySQL:
|
||||
|
||||
- one public-facing license path
|
||||
- one separate commercial license path
|
||||
|
||||
This repository does not claim that Qt or MySQL use the same exact public
|
||||
license terms used here. Those projects are referenced only as structural
|
||||
examples of dual licensing, not as the operative terms of this repository.
|
||||
|
||||
8. Contributions
|
||||
----------------
|
||||
|
||||
External contributions are accepted only under the CodexConnector Contributor
|
||||
License Agreement in `CONTRIBUTOR_LICENSE_AGREEMENT.md`, or under a separate
|
||||
written agreement accepted by the Project Steward.
|
||||
|
||||
The contributor agreement is intentionally broad. Contributors keep ownership
|
||||
of their contributions, but grant the Project Steward the right to sublicense
|
||||
and relicense those contributions under any license or terms the Project
|
||||
Steward chooses, including commercial or proprietary terms.
|
||||
|
||||
The project should not accept non-trivial outside contributions unless the
|
||||
contributor has agreed to that contributor agreement or an equivalent written
|
||||
agreement.
|
||||
|
||||
Appendix A: PolyForm Noncommercial License 1.0.0
|
||||
------------------------------------------------
|
||||
|
||||
The following text is included as the operative public license for
|
||||
Project-Authored Material described in Section 3.
|
||||
|
||||
# PolyForm Noncommercial License 1.0.0
|
||||
|
||||
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
||||
|
||||
## Acceptance
|
||||
|
||||
In order to get any license under these terms, you must agree
|
||||
to them as both strict obligations and conditions to all
|
||||
your licenses.
|
||||
|
||||
## Copyright License
|
||||
|
||||
The licensor grants you a copyright license for the
|
||||
software to do everything you might do with the software
|
||||
that would otherwise infringe the licensor's copyright
|
||||
in it for any permitted purpose. However, you may
|
||||
only distribute the software according to Distribution
|
||||
License and make changes or new works based on the software
|
||||
according to Changes and New Works License.
|
||||
|
||||
## Distribution License
|
||||
|
||||
The licensor grants you an additional copyright license
|
||||
to distribute copies of the software. Your license
|
||||
to distribute covers distributing the software with
|
||||
changes and new works permitted by Changes and New Works
|
||||
License.
|
||||
|
||||
## Notices
|
||||
|
||||
You must ensure that anyone who gets a copy of any part of
|
||||
the software from you also gets a copy of these terms or the
|
||||
URL for them above, as well as copies of any plain-text lines
|
||||
beginning with `Required Notice:` that the licensor provided
|
||||
with the software. For example:
|
||||
|
||||
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
||||
|
||||
## Changes and New Works License
|
||||
|
||||
The licensor grants you an additional copyright license to
|
||||
make changes and new works based on the software for any
|
||||
permitted purpose.
|
||||
|
||||
## Patent License
|
||||
|
||||
The licensor grants you a patent license for the software that
|
||||
covers patent claims the licensor can license, or becomes able
|
||||
to license, that you would infringe by using the software.
|
||||
|
||||
## Noncommercial Purposes
|
||||
|
||||
Any noncommercial purpose is a permitted purpose.
|
||||
|
||||
## Personal Uses
|
||||
|
||||
Personal use for research, experiment, and testing for
|
||||
the benefit of public knowledge, personal study, private
|
||||
entertainment, hobby projects, amateur pursuits, or religious
|
||||
observance, without any anticipated commercial application,
|
||||
is use for a permitted purpose.
|
||||
|
||||
## Noncommercial Organizations
|
||||
|
||||
Use by any charitable organization, educational institution,
|
||||
public research organization, public safety or health
|
||||
organization, environmental protection organization,
|
||||
or government institution is use for a permitted purpose
|
||||
regardless of the source of funding or obligations resulting
|
||||
from the funding.
|
||||
|
||||
## Fair Use
|
||||
|
||||
You may have "fair use" rights for the software under the
|
||||
law. These terms do not limit them.
|
||||
|
||||
## No Other Rights
|
||||
|
||||
These terms do not allow you to sublicense or transfer any of
|
||||
your licenses to anyone else, or prevent the licensor from
|
||||
granting licenses to anyone else. These terms do not imply
|
||||
any other licenses.
|
||||
|
||||
## Patent Defense
|
||||
|
||||
If you make any written claim that the software infringes or
|
||||
contributes to infringement of any patent, your patent license
|
||||
for the software granted under these terms ends immediately. If
|
||||
your company makes such a claim, your patent license ends
|
||||
immediately for work on behalf of your company.
|
||||
|
||||
## Violations
|
||||
|
||||
The first time you are notified in writing that you have
|
||||
violated any of these terms, or done anything with the software
|
||||
not covered by your licenses, your licenses can nonetheless
|
||||
continue if you come into full compliance with these terms,
|
||||
and take practical steps to correct past violations, within
|
||||
32 days of receiving notice. Otherwise, all your licenses
|
||||
end immediately.
|
||||
|
||||
## No Liability
|
||||
|
||||
As far as the law allows, the software comes as is, without
|
||||
any warranty or condition, and the licensor will not be liable
|
||||
to you for any damages arising out of these terms or the use
|
||||
or nature of the software, under any kind of legal claim.
|
||||
|
||||
## Definitions
|
||||
|
||||
The licensor is the individual or entity offering these
|
||||
terms, and the software is the software the licensor makes
|
||||
available under these terms.
|
||||
|
||||
You refers to the individual or entity agreeing to these
|
||||
terms.
|
||||
|
||||
Your company is any legal entity, sole proprietorship,
|
||||
or other kind of organization that you work for, plus all
|
||||
organizations that have control over, are under the control of,
|
||||
or are under common control with that organization. Control
|
||||
means ownership of substantially all the assets of an entity,
|
||||
or the power to direct its management and policies by vote,
|
||||
contract, or otherwise. Control can be direct or indirect.
|
||||
|
||||
Your licenses are all the licenses granted to you for the
|
||||
software under these terms.
|
||||
|
||||
Use means anything you do with the software requiring one
|
||||
of your licenses.
|
||||
26
README.md
Normal file
26
README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# CodexConnector
|
||||
|
||||
CodexConnector is a credential-isolated transport daemon for Codex-compatible
|
||||
subscription inference. Epiphany and Ghostlight remain separate minds. They
|
||||
derive their own exact provider requests, execute their own tools, interpret
|
||||
their own results, and admit their own state.
|
||||
|
||||
The connector owns one writable credential/refresh store, authenticated caller
|
||||
admission, bounded transport physiology, exact provider-request receipts, and
|
||||
redacted CultMesh health. It owns no prompt policy or decision authority.
|
||||
|
||||
The current source establishes the v2 typed contract before adding the daemon.
|
||||
This is deliberate: the existing Yggdrasil connector already proves the
|
||||
process seam, but its single-caller, fixed-model, stateless, no-tools wire law
|
||||
cannot become the shared contract by inertia.
|
||||
|
||||
## Contract invariant
|
||||
|
||||
Each consumer internally derives an exact typed Codex provider request. The
|
||||
connector verifies and transports those exact bytes and returns a terminal
|
||||
receipt binding their SHA-256. A consumer-native request digest is carried for
|
||||
audit correlation; the connector does not interpret or rederive consumer-native
|
||||
cognition.
|
||||
|
||||
Tool calls are typed output. The connector never executes them.
|
||||
|
||||
578
src/lib.rs
Normal file
578
src/lib.rs
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use thiserror::Error;
|
||||
|
||||
pub const PROVIDER_REQUEST_SCHEMA_ID: &str = "gamecult.codex.provider_request.v2";
|
||||
pub const INVOCATION_SCHEMA_ID: &str = "gamecult.codex.transport_invocation.v2";
|
||||
pub const RESULT_SCHEMA_ID: &str = "gamecult.codex.transport_result.v2";
|
||||
pub const RECEIPT_SCHEMA_ID: &str = "gamecult.codex.transport_receipt.v2";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodexProviderRequest {
|
||||
pub schema_id: String,
|
||||
pub request_id: String,
|
||||
pub conversation_id: String,
|
||||
pub model: String,
|
||||
pub instructions: String,
|
||||
pub input: Vec<CodexInputItem>,
|
||||
pub reasoning_effort: Option<String>,
|
||||
pub reasoning_summary: Option<String>,
|
||||
pub service_tier: Option<String>,
|
||||
pub output_contract_id: Option<String>,
|
||||
pub previous_response_id: Option<String>,
|
||||
pub tools: Vec<CodexToolDefinition>,
|
||||
pub output_schema_json: Option<String>,
|
||||
pub max_output_tokens: Option<u32>,
|
||||
pub prompt_cache_key: Option<String>,
|
||||
}
|
||||
|
||||
impl CodexProviderRequest {
|
||||
pub fn new(
|
||||
request_id: impl Into<String>,
|
||||
conversation_id: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
instructions: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
schema_id: PROVIDER_REQUEST_SCHEMA_ID.to_string(),
|
||||
request_id: request_id.into(),
|
||||
conversation_id: conversation_id.into(),
|
||||
model: model.into(),
|
||||
instructions: instructions.into(),
|
||||
input: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
reasoning_summary: None,
|
||||
service_tier: None,
|
||||
output_contract_id: None,
|
||||
previous_response_id: None,
|
||||
tools: Vec::new(),
|
||||
output_schema_json: None,
|
||||
max_output_tokens: None,
|
||||
prompt_cache_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), ContractError> {
|
||||
if self.schema_id != PROVIDER_REQUEST_SCHEMA_ID {
|
||||
return Err(ContractError::Schema);
|
||||
}
|
||||
require_id(&self.request_id, "request_id")?;
|
||||
require_id(&self.conversation_id, "conversation_id")?;
|
||||
require_id(&self.model, "model")?;
|
||||
require_content(&self.instructions, "instructions")?;
|
||||
if matches!(self.max_output_tokens, Some(0)) {
|
||||
return Err(ContractError::Invalid("max_output_tokens"));
|
||||
}
|
||||
if self
|
||||
.prompt_cache_key
|
||||
.as_deref()
|
||||
.is_some_and(|value| value.trim().is_empty())
|
||||
{
|
||||
return Err(ContractError::Invalid("prompt_cache_key"));
|
||||
}
|
||||
for (value, field) in [
|
||||
(self.reasoning_effort.as_deref(), "reasoning_effort"),
|
||||
(self.reasoning_summary.as_deref(), "reasoning_summary"),
|
||||
(self.service_tier.as_deref(), "service_tier"),
|
||||
(self.output_contract_id.as_deref(), "output_contract_id"),
|
||||
(self.previous_response_id.as_deref(), "previous_response_id"),
|
||||
] {
|
||||
if let Some(value) = value {
|
||||
require_id(value, field)?;
|
||||
}
|
||||
}
|
||||
if let Some(schema) = &self.output_schema_json {
|
||||
require_content(schema, "output_schema_json")?;
|
||||
}
|
||||
|
||||
let mut tool_names = HashSet::new();
|
||||
for tool in &self.tools {
|
||||
require_id(&tool.name, "tool.name")?;
|
||||
require_content(&tool.description, "tool.description")?;
|
||||
require_content(&tool.parameters_json, "tool.parameters_json")?;
|
||||
if !tool_names.insert(tool.name.as_str()) {
|
||||
return Err(ContractError::DuplicateTool(tool.name.clone()));
|
||||
}
|
||||
}
|
||||
for item in &self.input {
|
||||
match item {
|
||||
CodexInputItem::UserText { text } | CodexInputItem::AssistantText { text } => {
|
||||
require_content(text, "input.text")?;
|
||||
}
|
||||
CodexInputItem::ToolCall {
|
||||
call_id,
|
||||
name,
|
||||
arguments,
|
||||
} => {
|
||||
require_id(call_id, "input.call_id")?;
|
||||
require_id(name, "input.tool_name")?;
|
||||
require_content(arguments, "input.arguments")?;
|
||||
}
|
||||
CodexInputItem::ToolResult { call_id, output } => {
|
||||
require_id(call_id, "input.call_id")?;
|
||||
require_content(output, "input.output")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CodexInputItem {
|
||||
UserText {
|
||||
text: String,
|
||||
},
|
||||
AssistantText {
|
||||
text: String,
|
||||
},
|
||||
ToolCall {
|
||||
call_id: String,
|
||||
name: String,
|
||||
arguments: String,
|
||||
},
|
||||
ToolResult {
|
||||
call_id: String,
|
||||
output: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodexToolDefinition {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub parameters_json: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodexTransportInvocation {
|
||||
pub schema_id: String,
|
||||
pub caller_runtime_id: String,
|
||||
pub expires_at_unix_ms: u64,
|
||||
pub native_request_sha256: [u8; 32],
|
||||
pub provider_request_sha256: [u8; 32],
|
||||
pub request: CodexProviderRequest,
|
||||
}
|
||||
|
||||
impl CodexTransportInvocation {
|
||||
pub fn new(
|
||||
caller_runtime_id: impl Into<String>,
|
||||
expires_at_unix_ms: u64,
|
||||
native_request_sha256: [u8; 32],
|
||||
request: CodexProviderRequest,
|
||||
) -> Result<Self, ContractError> {
|
||||
let provider_request_sha256 = provider_request_sha256(&request)?;
|
||||
Ok(Self {
|
||||
schema_id: INVOCATION_SCHEMA_ID.to_string(),
|
||||
caller_runtime_id: caller_runtime_id.into(),
|
||||
expires_at_unix_ms,
|
||||
native_request_sha256,
|
||||
provider_request_sha256,
|
||||
request,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate(&self, now_unix_ms: u64, max_expiry_skew_ms: u64) -> Result<(), ContractError> {
|
||||
if self.schema_id != INVOCATION_SCHEMA_ID {
|
||||
return Err(ContractError::Schema);
|
||||
}
|
||||
require_id(&self.caller_runtime_id, "caller_runtime_id")?;
|
||||
if self.native_request_sha256 == [0; 32] {
|
||||
return Err(ContractError::Invalid("native_request_sha256"));
|
||||
}
|
||||
self.request.validate()?;
|
||||
if self.provider_request_sha256 != provider_request_sha256(&self.request)? {
|
||||
return Err(ContractError::ProviderDigest);
|
||||
}
|
||||
if self.expires_at_unix_ms < now_unix_ms
|
||||
|| self.expires_at_unix_ms > now_unix_ms.saturating_add(max_expiry_skew_ms)
|
||||
{
|
||||
return Err(ContractError::Expiry);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn request_id(&self) -> &str {
|
||||
&self.request.request_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CodexTransportResult {
|
||||
Refused {
|
||||
schema_id: String,
|
||||
request_id: String,
|
||||
caller_runtime_id: String,
|
||||
reason: CodexRefusal,
|
||||
},
|
||||
Transported {
|
||||
schema_id: String,
|
||||
request_id: String,
|
||||
caller_runtime_id: String,
|
||||
provider_request_sha256: [u8; 32],
|
||||
events: Vec<CodexTransportEvent>,
|
||||
receipt: CodexTransportReceipt,
|
||||
},
|
||||
}
|
||||
|
||||
impl CodexTransportResult {
|
||||
pub fn validate_against(
|
||||
&self,
|
||||
invocation: &CodexTransportInvocation,
|
||||
) -> Result<(), ContractError> {
|
||||
let (schema_id, request_id, caller_runtime_id) = match self {
|
||||
Self::Refused {
|
||||
schema_id,
|
||||
request_id,
|
||||
caller_runtime_id,
|
||||
..
|
||||
}
|
||||
| Self::Transported {
|
||||
schema_id,
|
||||
request_id,
|
||||
caller_runtime_id,
|
||||
..
|
||||
} => (schema_id, request_id, caller_runtime_id),
|
||||
};
|
||||
if schema_id != RESULT_SCHEMA_ID {
|
||||
return Err(ContractError::Schema);
|
||||
}
|
||||
if request_id != invocation.request_id()
|
||||
|| caller_runtime_id != &invocation.caller_runtime_id
|
||||
{
|
||||
return Err(ContractError::Identity);
|
||||
}
|
||||
|
||||
if let Self::Transported {
|
||||
provider_request_sha256,
|
||||
events,
|
||||
receipt,
|
||||
..
|
||||
} = self
|
||||
{
|
||||
if provider_request_sha256 != &invocation.provider_request_sha256 {
|
||||
return Err(ContractError::ProviderDigest);
|
||||
}
|
||||
for (expected, event) in events.iter().enumerate() {
|
||||
if event.sequence != expected as u64 {
|
||||
return Err(ContractError::EventSequence);
|
||||
}
|
||||
event.validate()?;
|
||||
}
|
||||
receipt.validate_against(invocation)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CodexTransportEventPayload {
|
||||
TextDelta {
|
||||
text: String,
|
||||
},
|
||||
ToolCall {
|
||||
call_id: String,
|
||||
name: String,
|
||||
arguments: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodexTransportEvent {
|
||||
pub sequence: u64,
|
||||
pub payload: CodexTransportEventPayload,
|
||||
}
|
||||
|
||||
impl CodexTransportEvent {
|
||||
fn validate(&self) -> Result<(), ContractError> {
|
||||
match &self.payload {
|
||||
CodexTransportEventPayload::TextDelta { text } => require_content(text, "event.text"),
|
||||
CodexTransportEventPayload::ToolCall {
|
||||
call_id,
|
||||
name,
|
||||
arguments,
|
||||
} => {
|
||||
require_id(call_id, "event.call_id")?;
|
||||
require_id(name, "event.tool_name")?;
|
||||
require_content(arguments, "event.arguments")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodexTransportReceipt {
|
||||
pub schema_id: String,
|
||||
pub request_id: String,
|
||||
pub caller_runtime_id: String,
|
||||
pub native_request_sha256: [u8; 32],
|
||||
pub provider_request_sha256: [u8; 32],
|
||||
pub model: String,
|
||||
pub transport: String,
|
||||
pub outcome: CodexTransportOutcome,
|
||||
}
|
||||
|
||||
impl CodexTransportReceipt {
|
||||
pub fn validate_against(
|
||||
&self,
|
||||
invocation: &CodexTransportInvocation,
|
||||
) -> Result<(), ContractError> {
|
||||
if self.schema_id != RECEIPT_SCHEMA_ID {
|
||||
return Err(ContractError::Schema);
|
||||
}
|
||||
if self.request_id != invocation.request_id()
|
||||
|| self.caller_runtime_id != invocation.caller_runtime_id
|
||||
|| self.model != invocation.request.model
|
||||
{
|
||||
return Err(ContractError::Identity);
|
||||
}
|
||||
if self.provider_request_sha256 != invocation.provider_request_sha256 {
|
||||
return Err(ContractError::ProviderDigest);
|
||||
}
|
||||
if self.native_request_sha256 != invocation.native_request_sha256 {
|
||||
return Err(ContractError::NativeDigest);
|
||||
}
|
||||
require_id(&self.transport, "receipt.transport")?;
|
||||
match &self.outcome {
|
||||
CodexTransportOutcome::Completed {
|
||||
provider_response_id: Some(value),
|
||||
..
|
||||
} => require_id(value, "receipt.provider_response_id")?,
|
||||
CodexTransportOutcome::Failed {
|
||||
failure_kind,
|
||||
message,
|
||||
} => {
|
||||
require_id(failure_kind, "receipt.failure_kind")?;
|
||||
require_content(message, "receipt.failure_message")?;
|
||||
}
|
||||
CodexTransportOutcome::Completed {
|
||||
provider_response_id: None,
|
||||
..
|
||||
} => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CodexTransportOutcome {
|
||||
Completed {
|
||||
provider_response_id: Option<String>,
|
||||
input_tokens: Option<u64>,
|
||||
output_tokens: Option<u64>,
|
||||
reasoning_output_tokens: Option<u64>,
|
||||
cached_input_tokens: Option<u64>,
|
||||
},
|
||||
Failed {
|
||||
failure_kind: String,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CodexRefusal {
|
||||
CallerNotAdmitted,
|
||||
Expired,
|
||||
IdentitySubstitution,
|
||||
ProviderDigestSubstitution,
|
||||
Policy,
|
||||
ReplayConflict,
|
||||
Malformed,
|
||||
}
|
||||
|
||||
pub fn canonical_provider_request_bytes(
|
||||
request: &CodexProviderRequest,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
request.validate()?;
|
||||
rmp_serde::to_vec(request).map_err(|_| ContractError::Encoding)
|
||||
}
|
||||
|
||||
pub fn provider_request_sha256(request: &CodexProviderRequest) -> Result<[u8; 32], ContractError> {
|
||||
Ok(Sha256::digest(canonical_provider_request_bytes(request)?).into())
|
||||
}
|
||||
|
||||
fn require_id(value: &str, field: &'static str) -> Result<(), ContractError> {
|
||||
if value.trim().is_empty() || value.trim() != value {
|
||||
return Err(ContractError::Invalid(field));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_content(value: &str, field: &'static str) -> Result<(), ContractError> {
|
||||
if value.is_empty() {
|
||||
return Err(ContractError::Invalid(field));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum ContractError {
|
||||
#[error("unexpected schema")]
|
||||
Schema,
|
||||
#[error("invalid {0}")]
|
||||
Invalid(&'static str),
|
||||
#[error("duplicate tool {0:?}")]
|
||||
DuplicateTool(String),
|
||||
#[error("request identity substitution")]
|
||||
Identity,
|
||||
#[error("provider request digest substitution")]
|
||||
ProviderDigest,
|
||||
#[error("native request digest substitution")]
|
||||
NativeDigest,
|
||||
#[error("invocation expiry is outside the admitted horizon")]
|
||||
Expiry,
|
||||
#[error("transport event sequence is not contiguous")]
|
||||
EventSequence,
|
||||
#[error("canonical MessagePack encoding failed")]
|
||||
Encoding,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn request() -> CodexProviderRequest {
|
||||
let mut request = CodexProviderRequest::new(
|
||||
"request-1",
|
||||
"conversation-1",
|
||||
"gpt-5.4",
|
||||
"Return the typed result.",
|
||||
);
|
||||
request.input.push(CodexInputItem::UserText {
|
||||
text: "Projected state".to_string(),
|
||||
});
|
||||
request.tools.push(CodexToolDefinition {
|
||||
name: "read_source".to_string(),
|
||||
description: "Read one admitted source.".to_string(),
|
||||
parameters_json: r#"{"type":"object"}"#.to_string(),
|
||||
});
|
||||
request
|
||||
}
|
||||
|
||||
fn invocation() -> CodexTransportInvocation {
|
||||
CodexTransportInvocation::new("epiphany-yggdrasil", 2_000, [7; 32], request()).unwrap()
|
||||
}
|
||||
|
||||
fn receipt(invocation: &CodexTransportInvocation) -> CodexTransportReceipt {
|
||||
CodexTransportReceipt {
|
||||
schema_id: RECEIPT_SCHEMA_ID.to_string(),
|
||||
request_id: invocation.request_id().to_string(),
|
||||
caller_runtime_id: invocation.caller_runtime_id.clone(),
|
||||
native_request_sha256: invocation.native_request_sha256,
|
||||
provider_request_sha256: invocation.provider_request_sha256,
|
||||
model: invocation.request.model.clone(),
|
||||
transport: "codex-responses-sse".to_string(),
|
||||
outcome: CodexTransportOutcome::Completed {
|
||||
provider_response_id: Some("response-1".to_string()),
|
||||
input_tokens: Some(10),
|
||||
output_tokens: Some(3),
|
||||
reasoning_output_tokens: Some(1),
|
||||
cached_input_tokens: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_digest_is_stable_and_content_addressed() {
|
||||
let original = request();
|
||||
let same = original.clone();
|
||||
assert_eq!(
|
||||
provider_request_sha256(&original).unwrap(),
|
||||
provider_request_sha256(&same).unwrap()
|
||||
);
|
||||
let mut changed = original.clone();
|
||||
changed.instructions.push_str(" Changed.");
|
||||
assert_ne!(
|
||||
provider_request_sha256(&original).unwrap(),
|
||||
provider_request_sha256(&changed).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invocation_refuses_provider_request_substitution() {
|
||||
let mut invocation = invocation();
|
||||
invocation.request.model = "substituted".to_string();
|
||||
assert_eq!(
|
||||
invocation.validate(1_000, 2_000),
|
||||
Err(ContractError::ProviderDigest)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invocation_refuses_expired_or_blank_native_provenance() {
|
||||
let mut invocation = invocation();
|
||||
assert_eq!(
|
||||
invocation.validate(2_001, 2_000),
|
||||
Err(ContractError::Expiry)
|
||||
);
|
||||
invocation.expires_at_unix_ms = 2_500;
|
||||
invocation.native_request_sha256 = [0; 32];
|
||||
assert_eq!(
|
||||
invocation.validate(2_001, 2_000),
|
||||
Err(ContractError::Invalid("native_request_sha256"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_tool_round_survives_messagepack() {
|
||||
let invocation = invocation();
|
||||
let bytes = rmp_serde::to_vec(&invocation).unwrap();
|
||||
let decoded: CodexTransportInvocation = rmp_serde::from_slice(&bytes).unwrap();
|
||||
assert_eq!(decoded, invocation);
|
||||
assert!(matches!(
|
||||
decoded.request.tools.as_slice(),
|
||||
[CodexToolDefinition { name, .. }] if name == "read_source"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transported_result_binds_identity_digest_and_event_order() {
|
||||
let invocation = invocation();
|
||||
let result = CodexTransportResult::Transported {
|
||||
schema_id: RESULT_SCHEMA_ID.to_string(),
|
||||
request_id: invocation.request_id().to_string(),
|
||||
caller_runtime_id: invocation.caller_runtime_id.clone(),
|
||||
provider_request_sha256: invocation.provider_request_sha256,
|
||||
events: vec![CodexTransportEvent {
|
||||
sequence: 0,
|
||||
payload: CodexTransportEventPayload::ToolCall {
|
||||
call_id: "call-1".to_string(),
|
||||
name: "read_source".to_string(),
|
||||
arguments: "{}".to_string(),
|
||||
},
|
||||
}],
|
||||
receipt: receipt(&invocation),
|
||||
};
|
||||
assert_eq!(result.validate_against(&invocation), Ok(()));
|
||||
|
||||
let mut reordered = result;
|
||||
let CodexTransportResult::Transported { events, .. } = &mut reordered else {
|
||||
unreachable!()
|
||||
};
|
||||
events[0].sequence = 1;
|
||||
assert_eq!(
|
||||
reordered.validate_against(&invocation),
|
||||
Err(ContractError::EventSequence)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receipt_refuses_caller_substitution() {
|
||||
let invocation = invocation();
|
||||
let mut substituted_caller = receipt(&invocation);
|
||||
substituted_caller.caller_runtime_id = "ghostlight-dungeon-yggdrasil".to_string();
|
||||
assert_eq!(
|
||||
substituted_caller.validate_against(&invocation),
|
||||
Err(ContractError::Identity)
|
||||
);
|
||||
|
||||
let mut substituted_native_basis = receipt(&invocation);
|
||||
substituted_native_basis.native_request_sha256 = [8; 32];
|
||||
assert_eq!(
|
||||
substituted_native_basis.validate_against(&invocation),
|
||||
Err(ContractError::NativeDigest)
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue