Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,53 @@ jobs:
working-directory: rust
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: MSRV metadata
run: python3 ../scripts/check-rust-msrv.py .
- name: Build
run: cargo build --release
run: cargo build --release --locked
- name: Clippy
run: cargo clippy --release --all-targets -- -D warnings
- name: Tests
run: cargo test
run: cargo clippy --release --all-targets --all-features --locked -- -D warnings
- name: Default tests
run: cargo test --locked
- name: Repository fixture tests
run: cargo run --locked --example family-connection-context-v1-vectors --features repository-fixtures
- name: Packaged all-features tests
run: |
cargo package --locked
mkdir -p target/package-test
tar -xzf target/package/determa-*.crate -C target/package-test
cargo test --locked --all-features --manifest-path target/package-test/determa-*/Cargo.toml

rust-msrv:
name: rust launcher MSRV
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: 1.81.0
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust
key: msrv-1.81
- name: MSRV metadata
run: python3 ../scripts/check-rust-msrv.py .
- name: Build
run: cargo build --release --locked
- name: Default tests
run: cargo test --locked
- name: Repository fixture tests
run: cargo run --locked --example family-connection-context-v1-vectors --features repository-fixtures

node:
name: node launcher
Expand All @@ -66,5 +101,6 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- run: npm ci
- name: Tests
run: node test/dispatch.test.js
run: npm test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dist/
# Rust
/rust/target/
Cargo.lock
!/rust/Cargo.lock

# Node
node_modules/
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ python3 scripts/validate-family-connection-v1-vectors.py
# python
cd python && pip install -e '.[dev]' && ruff check . && pytest -q
# rust
cd rust && cargo build --release && cargo clippy --release --all-targets -- -D warnings && cargo test
python3 scripts/check-rust-msrv.py rust
cd rust && cargo build --release --locked && cargo clippy --release --all-targets --all-features --locked -- -D warnings && cargo test --locked && cargo run --locked --example family-connection-context-v1-vectors --features repository-fixtures
# rust MSRV
cd rust && cargo +1.81.0 build --release --locked && cargo +1.81.0 test --locked && cargo +1.81.0 run --locked --example family-connection-context-v1-vectors --features repository-fixtures
# node
cd node && node test/dispatch.test.js
cd node && npm ci && npm test
```
Note: unlike `determa-state-rust`, the rust launcher **does** enforce `clippy -D warnings` in CI — keep it clean.

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ change needed, they just have to be on `PATH` as `determa-<product>`.
## Family configuration

[Family Connection and Context Configuration v1](docs/family-connection-context-v1.md)
reserves the language-neutral connection, context, endpoint-routing, and future
family command contract. It is design documentation only: the current launchers
remain local dispatchers and do not yet implement remote connections or clients.
defines the language-neutral connection, context, endpoint-routing, and future
family command contract. The current packages include local resolver APIs and
reserve `determa config`, `determa context`, and `determa auth` before product
dispatch, but they do not yet implement remote connections, clients, credential
storage, or configuration-file discovery.

The machine-readable [v1 conformance vectors](conformance/family-connection-v1/)
fix the expected configuration, endpoint, environment-name, and routing results
Expand Down
4 changes: 2 additions & 2 deletions conformance/family-connection-v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ exactly one of:
| code | meaning |
|---|---|
| `duplicate_key` | a source object repeats a key before model construction |
| `invalid_source` | source JSON is malformed, unavailable as a string, or contains a non-JSON numeric constant |
| `invalid_source` | source JSON is malformed, unavailable as a string, contains a non-JSON numeric constant, or decodes a lone surrogate |
| `missing_field` | a required closed-model field is absent |
| `unknown_field` | a closed-model object contains an undeclared field |
| `invalid_type` | a value has a type not accepted at that location |
Expand Down Expand Up @@ -67,7 +67,7 @@ python3 -m pip install --require-hashes \
python3 scripts/validate-family-connection-v1-vectors.py
```

The test-only IDNA path uses the hash-pinned `idna==3.7` package as a source for
The test-only IDNA path uses the hash-pinned `idna==3.10` package as a source for
tables generated from the official [Unicode 15.1.0 IDNA mapping table](https://www.unicode.org/Public/idna/15.1.0/IdnaMappingTable.txt)
and for the ContextJ and RFC 5893 bidi helpers referenced by UTS #46. The
harness verifies at startup that the package's IDNA and UTS #46 data report
Expand Down
25 changes: 25 additions & 0 deletions conformance/family-connection-v1/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,31 @@
"id": "config-negative-infinity-source",
"source": "{\"version\":-Infinity,\"connections\":{},\"contexts\":{}}",
"expect": {"error": "invalid_source"}
},
{
"id": "config-version-overflowing-exponent",
"source": "{\"version\":1e400,\"connections\":{},\"contexts\":{}}",
"expect": {"error": "invalid_version"}
},
{
"id": "config-version-400-digit-integer",
"source": "{\"version\":9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,\"connections\":{},\"contexts\":{}}",
"expect": {"error": "invalid_version"}
},
{
"id": "config-lone-surrogate-source",
"source": "{\"version\":1,\"connections\":{},\"contexts\":{},\"default_context\":\"\\ud800\"}",
"expect": {"error": "invalid_source"}
},
{
"id": "config-prototype-key-at-root",
"source": "{\"version\":1,\"connections\":{},\"contexts\":{},\"__proto__\":{}}",
"expect": {"error": "unknown_field"}
},
{
"id": "config-prototype-key-in-connection",
"source": "{\"version\":1,\"connections\":{\"cloud\":{\"endpoint\":\"https://example.com\",\"__proto__\":{}}},\"contexts\":{}}",
"expect": {"error": "unknown_field"}
}
]
}
15 changes: 15 additions & 0 deletions conformance/family-connection-v1/endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@
"input": "ftp://example.com/",
"expect": {"error": "unsupported_endpoint_scheme"}
},
{
"id": "endpoint-query-precedes-unsupported-scheme",
"input": "ftp://example.com/?x=1",
"expect": {"error": "invalid_endpoint_syntax"}
},
{
"id": "endpoint-empty-host",
"input": "https:///path",
Expand Down Expand Up @@ -262,6 +267,16 @@
"input": "https://2001:db8::1/",
"expect": {"error": "invalid_endpoint_authority"}
},
{
"id": "endpoint-ipv4-suffix-not-final-32-bits",
"input": "https://[192.0.2.1::1]/",
"expect": {"error": "invalid_endpoint_host"}
},
{
"id": "endpoint-multiple-ipv4-productions-in-ipv6",
"input": "https://[192.0.2.1::5.6.7.8]/",
"expect": {"error": "invalid_endpoint_host"}
},
{
"id": "endpoint-ipv6-zone",
"input": "http://[fe80::1%25en0]/",
Expand Down
18 changes: 18 additions & 0 deletions conformance/family-connection-v1/routing.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,24 @@
"configuration": "no-fallback",
"request": {"resource": "state/foo_bar"},
"expect": {"error": "invalid_name"}
},
{
"id": "routing-request-not-object",
"configuration": "no-fallback",
"request": [],
"expect": {"error": "invalid_type"}
},
{
"id": "routing-request-missing-resource",
"configuration": "no-fallback",
"request": {},
"expect": {"error": "missing_field"}
},
{
"id": "routing-request-unknown-field",
"configuration": "no-fallback",
"request": {"resource": "state", "extra": true},
"expect": {"error": "unknown_field"}
}
]
}
79 changes: 61 additions & 18 deletions docs/family-connection-context-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ implemented.
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**,
and **MAY** are to be interpreted as described in RFC 2119 and RFC 8174.

This is a design contract only. Current Python, Rust, and Node `determa`
launchers do not read this configuration and retain their existing behavior.
It does not define a configuration-file location, credential store, network
protocol, server, client, or State machine/checkpoint format.
Current Python, Rust, and Node `determa` packages implement this contract as
local resolver APIs and reserve the family command names below before product
dispatch. They do not discover or read a configuration file and do not perform
remote transport. This document does not define a configuration-file location,
credential store, network protocol, server, client, or State machine/checkpoint
format. The Rust implementation uses exact behavior-relevant ICU data
dependencies and a checked lockfile because the v1 endpoint profile depends on
the exact Unicode 15.1 UTS #46 boundary, and declares an MSRV matching the
maximum Rust version required by the resolved normal dependency graph. The
Python implementation likewise pins `idna` tables plus `unicodedata2` 15.1 data
for normalization, category, combining-class, and bidi decisions rather than
relying on the interpreter's bundled Unicode version. The supported runtime
floors are Python 3.11, Node 22, and Rust 1.81; CI exercises each floor.

## Principles

Expand All @@ -34,6 +43,11 @@ logical model. Its file syntax, location, and discovery rules are intentionally
unspecified in v1. Concrete syntaxes MUST preserve the value types below and
MUST reject duplicate map keys before constructing this model.

A configuration source MUST be valid JSON whose object keys and string values
decode to Unicode scalar-value sequences. A source containing an escaped or
unescaped lone UTF-16 surrogate is `invalid_source`, even on a host language
whose string type can represent lone surrogates.

```yaml
version: 1
default_context: personal
Expand Down Expand Up @@ -135,11 +149,23 @@ A source document containing two `cloud` keys in the same `connections` map is
invalid before model construction. A parser MUST NOT keep the first value, keep
the last value, or silently merge the two connection objects.

The public parser and decoded-value validator MUST return an opaque,
implementation-defined `ValidatedConfiguration`, not expose the normalized
model as mutable configuration state. The validated value MUST be immutable or
defensively isolated from every caller-owned input and exported copy. A public
conversion back to the JSON-shaped logical model MAY return a fresh copy.
Resolution MUST accept only a `ValidatedConfiguration`; any other value and any
malformed request MUST fail with the error codes in this contract rather than a
language-native exception or panic. A decoded-value validator accepts ordinary
JSON values, including numeric integer `1` for `version`; source-only numeric
token distinctions are enforced by the source parser before model construction.

## Canonical endpoints

Each connection has exactly one `endpoint`. A configuration reader MUST apply
the ordered algorithm below before comparison, routing, or persistence. The
result is an ASCII absolute URI under RFC 3986. Implementations MUST implement
first failing numbered step determines the error; later failures MUST NOT
replace it. The result is an ASCII absolute URI under RFC 3986. Implementations MUST implement
this profile directly or prove their URL library produces the same result; a
library's platform-dependent URL normalization is not normative.

Expand Down Expand Up @@ -172,10 +198,12 @@ library's platform-dependent URL normalization is not normative.
accepted by some URL libraries.
- A bracketed IPv6 literal MUST parse under RFC 4291 section 2.2 and MUST be
emitted in brackets using RFC 5952 sections 4.1 through 4.3. Always emit
all 128 bits in hexadecimal form; an accepted IPv4-embedded input such as
all 128 bits in hexadecimal form. At most one dotted-decimal IPv4
production is accepted, and only when it supplies the single final 32-bit
component of the IPv6 address; for example,
`::ffff:192.0.2.1` is emitted as `::ffff:c000:201`, never with dotted
decimal. IPvFuture literals are invalid. Zone identifiers, including
RFC 6874 `%25zone` syntax, are invalid.
decimal, while `192.0.2.1::1` is invalid. IPvFuture literals are invalid.
Zone identifiers, including RFC 6874 `%25zone` syntax, are invalid.
5. A port, when present, MUST contain only ASCII decimal digits, begin with
`1` through `9`, and have value 1 through 65535. Remove port 443 for `https`
and port 80 for `http`; emit every other port as its shortest decimal form.
Expand Down Expand Up @@ -256,6 +284,14 @@ from this v1 document.

## Resolution precedence

A resolver request is a closed JSON-shaped object. It requires string field
`resource` and permits only optional `explicit_connection`, `environment`, and
`selected_context` fields. `explicit_connection` and `selected_context` MUST be
strings when present. `environment` MUST be a map of string keys to string
values. A non-object request is `invalid_type`, a missing `resource` is
`missing_field`, and any other field is `unknown_field`; these structural checks
precede the resolution steps below.

For a request for product/resource `R`, connection resolution is exactly:

1. An explicit per-request connection override.
Expand Down Expand Up @@ -314,15 +350,22 @@ variable for selecting a context.
## Command namespace reservation

The family-level command names `config`, `context`, and `auth` are reserved.
No present or future product may claim those names, and each future Python,
Rust, and Node launcher implementation MUST recognize them before product
dispatch. Their commands, flags, output, and persistence behavior are not
implemented or specified here.
No present or future product may claim those names, and each Python, Rust, and
Node launcher implementation MUST recognize them before product dispatch. Their
subcommands, flags, output, and persistence behavior are not implemented or
specified here.

Product discovery, `determa list`, and launcher help MUST omit executables
whose product stem is one of these reserved names, including
implementation-suffixed forms such as `determa-config-rust`.

This reservation does not change current launcher behavior. In particular,
this document does not add a parser, help entry, executable command, or
compatibility promise for `determa config`, `determa context`, or `determa auth`
until a later implementation release changes all three launchers together.
Until command syntax is specified, invoking `determa config`, `determa context`,
or `determa auth` MUST fail locally before product dispatch with exit status
`2`, empty stdout, and stderr exactly:

```text
determa: family command '<command>' is reserved but not implemented yet.
```

## Local implementation selection and State storage

Expand Down Expand Up @@ -376,12 +419,12 @@ The following work is intentionally excluded from v1 and requires separate
issues and review before implementation:

- Configuration-file discovery, editing, and credential-provider behavior.
- Launcher parsing and behavior for the reserved family commands.
- Subcommand syntax and behavior beneath the reserved family commands.
- A versioned managed/self-hosted protocol, capability discovery, closed error
envelopes, resource identity, authentication rules, idempotency, concurrency,
pagination, TLS, and redirect rules.
- Language-specific `DetermaClient` packages and transport implementations.
- Shared routing conformance vectors and protocol conformance.
- Protocol conformance beyond the shared local resolver vectors.
- Durable host-owned route-binding/outbox schemas and SaaS control-plane work.
- State specification, engine, checkpoint, store, socket, MCP, or example
changes.
3 changes: 3 additions & 0 deletions node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ $ determa --version
It is language-agnostic: it dispatches to whichever `determa-state` is on `PATH`, be it
the Node, Python, or Rust build.

The package requires Node 22 or newer. It also exposes the Family
Connection/Context v1 resolver APIs through the pinned UTS #46 dependency.

## License

MIT
9 changes: 8 additions & 1 deletion node/bin/determa.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
const { spawnSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const { RESERVED_FAMILY_COMMANDS } = require("../lib/family-connection-context-v1");

const PREFIX = "determa-";
const VERSION = require("../package.json").version;
Expand Down Expand Up @@ -60,7 +61,9 @@ function splitVariant(stem, all) {

// Products -> sorted impl variants (canonical products map to []). Array of [product, impls].
function discover() {
const all = stems();
const all = new Set(
[...stems()].filter(stem => !RESERVED_FAMILY_COMMANDS.has(stem.split("-", 1)[0]))
);
const products = new Map();
for (const stem of all) {
const [product, impl] = splitVariant(stem, all);
Expand Down Expand Up @@ -150,6 +153,10 @@ function main(argv) {
discover().forEach(([p, i]) => console.log(formatProduct(p, i)));
return 0;
}
if (RESERVED_FAMILY_COMMANDS.has(cmd)) {
process.stderr.write(`determa: family command '${cmd}' is reserved but not implemented yet.\n`);
return 2;
}
const exe = exeFor(cmd);
if (!exe) {
process.stderr.write(
Expand Down
Loading