Skip to content

M2/E4/#11 — Contract Schemas - #14

Merged
Rexy-5097 merged 1 commit into
mainfrom
feat/m2-e4-issue-11-contracts
Jul 30, 2026
Merged

Rexy-5097 merged 1 commit into
mainfrom
feat/m2-e4-issue-11-contracts

Conversation

@Rexy-5097

Copy link
Copy Markdown
Owner

Canonical identifier: Milestone M2 · Epic E4 · Issue #11 — Contract Schemas
Dependencies: #10 (complete). Eligibility recomputed mechanically from the committed plan before starting: #11 was the only eligible issue. No abort condition triggered.

What this adds

Ten object contracts plus a shared definitions file, under contracts/:

observation · dataset-release · label-release · protocol · environment-release · method-release · evaluation · evidence-binding · supersession · provenance-record — and common, which nothing validates against directly.

No producers. No consumers. Schemas and their gate only, per the issue's explicit scope.

Decisions worth reviewing

$id is a URN, not a URL. urn:adityanet:contract:<name>:<major>. An https:// $id implies a resolvable endpoint; this project serves none, and ADR-0001 forbids implying a capability the platform lacks.

Every object is additionalProperties: false. An unrecognised field is exactly how a producer and a consumer disagree about a payload while both believe they conform. One exception, environment-release.thread_counts, whose keys are set by the libraries installed rather than by the contract — and which says so in its own description.

null and absent are different, and the difference is load-bearing. ingest_time is nullable with exactly one meaning (ADR-0022, L-11) and is a required field, so absence cannot be mistaken for unknown. value is nullable meaning observed-to-be-absent, never imputed (ADR-0017, L-07).

Defects found, all mine, all found by tests

Contract defect — format is annotation-only. JSON Schema does not require any validator to enforce format: date-time unless explicitly configured with a format checker. A timestamp constrained only by format is therefore validated by some consumers and silently not by others — worse than no constraint, because it looks like one. The timestamp definition now carries a pattern as well, which every validator enforces.

IMPL-010 — two bugs in the additive-diff gate, both silently disabling a check. walk() recursed into scalars inside lists rather than capturing them, so enum members, required names and union type arrays vanished from the flattened form entirely. And the added-required-field check derived its container path by string-splitting a pointer, producing //required/ at the document root, matching nothing. Neither could have been found against the real corpus — every contract here is new, so the gate compared nothing. They were found only because the diff logic is tested against synthetic baselines.

IMPL-009 — test-side only. A Registry bulk constructor that does not exist, and a no-domain-terms scan that flagged $id, which legitimately contains the project name.

The gate no longer overstates. With no baseline it now reports that the additive-only comparison was not exercised, rather than printing "every change additive". A gate asserting a property it did not check is the failure mode this project exists to refuse. A test pins that wording.

Verification

tests/architecture   352 passed   (141 for contracts)
tests/integration     24 passed
kernel                78 passed
research/tests/v2    188 passed
links / imports / contracts gates            clean
clean export (git archive HEAD, STD-12)      352 passed, all gates clean

Contract tests: per contract, a valid example, an unknown-field rejection, a closed-object assertion, and every required field individually dropped to prove it is enforced. Plus 19 invalid values, 5 boundary values, nullability semantics, the five pinned inputs of ADR-0021 individually, the three reproduction classes, the three supersession severities. Diff logic: 12 breaking cases, 7 additive cases, all synthetic.

CI impact

jsonschema added to the architecture job's install. No new job, no new required context — still eight.

🤖 Generated with Claude Code

Canonical: Milestone M2 · Epic E4 · Issue #11. Deps: #10 (complete).
Eligibility recomputed mechanically from the committed plan: exactly one eligible
issue, #11. No abort condition.

Ten object contracts plus shared definitions, in contracts/:

  observation  dataset-release  label-release  protocol  environment-release
  method-release  evaluation  evidence-binding  supersession  provenance-record
  common (definitions; nothing validates against it directly)

CONVENTIONS

$id is urn:adityanet:contract:<name>:<major>. A URN rather than an https:// URL
deliberately — an https:// $id implies a resolvable endpoint, this project serves no
such endpoint, and ADR-0001 forbids implying capability the platform lacks.

Every object sets additionalProperties: false. An unrecognised field is how a producer
and a consumer come to disagree about a payload while both believe they conform. The
single exception is environment-release.thread_counts, whose keys are set by the
libraries installed rather than by the contract, and which says so.

null and absent are different, and the difference is load-bearing. ingest_time is
nullable with exactly one meaning (ADR-0022, L-11) and REQUIRED as a field, so absence
cannot be confused with unknown. value is nullable meaning observed-to-be-absent, never
imputed (ADR-0017, L-07).

THREE DEFECTS, ALL MINE, ALL FOUND BY TESTS

IMPL-009  Registry built with a nonexistent bulk constructor, and the no-domain-terms
          check scanned $id — which legitimately contains the project name. Both in the
          tests, not the contracts.

CONTRACT  `format: date-time` is ANNOTATION-ONLY in JSON Schema. No validator enforces
DEFECT    it unless explicitly configured with a format checker, so a timestamp
          constrained only by `format` is validated by some consumers and not others —
          worse than no constraint. The timestamp definition now carries a `pattern`
          as well, which every validator enforces.

IMPL-010  Two bugs in the additive-diff gate, both silently disabling a check:
            - walk() recursed into scalars inside lists instead of capturing them, so
              enum members, required names and union type arrays vanished from the
              flattened form entirely
            - the added-required-field check derived its container path by string
              splitting, producing "//required/" at the document root, matching nothing
          Neither could have been found against the real corpus, because every contract
          here is new and the gate compared nothing. They were found only because the
          diff logic is tested against synthetic baselines.

THE GATE NO LONGER OVERSTATES

With no baseline it now reports that the additive-only comparison was NOT exercised,
rather than printing "every change additive". A gate claiming a property it did not
check is the failure mode this project exists to refuse. A test pins that wording.

TESTS: 141 for contracts alone

Per contract: valid example, unknown-field rejection, closed-object assertion, and
every required field individually dropped to prove it is enforced. Plus 19 invalid
values, 5 boundary values, nullability semantics, the five pinned inputs of ADR-0021
individually, the three reproduction classes, the three supersession severities, and
a check that no domain term appears in any constraint.

The diff logic has 12 breaking cases and 7 additive cases, all synthetic.

  tests/architecture  352 passed
  tests/integration    24 passed
  kernel               78 passed
  research/tests/v2   188 passed
  links / imports / contracts gates  clean

CI: `jsonschema` added to the architecture job. No new job, no new context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Rexy-5097
Rexy-5097 merged commit 6c26b33 into main Jul 30, 2026
8 checks passed
@Rexy-5097
Rexy-5097 deleted the feat/m2-e4-issue-11-contracts branch July 30, 2026 06:59
Rexy-5097 added a commit that referenced this pull request Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #12. Deps: #11 (complete, merged as 6c26b33).
Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set was
{#12 order 12, #14 order 14, #15 order 15}; lowest Order selects #12.

WHAT THIS IS

14 entities, 8 value types and 19 invariants, under domain/. Imports the standard
library and nothing else — no third party, no kernel, no context — so every test here
runs with no fixtures, no mocks and no infrastructure (ADR-0026, TIS E4 §11(i), §16).

TWO IDENTITY REGIMES, WHICH IS THE POINT (ADR-0005)

  names.py        Dataset · Method · Source · Instrument · LabelSource
                  Mutable identifiers, permitted only because no Evaluation
                  references them. TIS E4 §11(ii) states the list; the module is
                  the list, and a sixth fails a test.

  everything else Immutable, addressed by content. What an Evaluation pins.

THE DOMAIN CANNOT MINT A DIGEST

ADR-0005 reserves minting to the provenance kernel; ADR-0026 forbids importing it. Both
hold at once because holding a content address and computing one are different
operations. `domain.values.Digest` validates the 64-hex form and computes nothing —
`hashlib` is stdlib and would import cleanly, so the absence is a decision, and
test_the_domain_cannot_mint_a_digest pins it. Same shape for the clock: `Timestamp`
parses, and there is no `now()` (TIS §0.4).

THREE FIELDS ENFORCED BY THE SHAPE OF THE TYPE, NOT BY A CHECK

  ingest_time      has NO DEFAULT. `= None` would default a provenance value for any
                   caller who omitted it, which is precisely what ADR-0022 forbids.
                   Omitting it raises TypeError from Python itself.
  Score.interval   required. If the type cannot represent a score without its
                   uncertainty, no code path can produce one (STD-05).
  the five inputs  positional fields on Evaluation. ADR-0009 claimed four sufficed;
                   ADR-0021 superseded it. The type cannot be built with four.

19 INVARIANTS, EVERY ONE WITH A PROPERTY TEST

ALL_INVARIANTS pairs each predicate with the decision it enforces, and
test_every_invariant_has_a_property_test reads that tuple — exporting a predicate
without a test is a red build, not a gap. Predicates return bool and never raise: a
rule that raises cannot be quantified over.

"Property" here means quantified over the finite space each rule is defined on — three
reproduction classes, three severities, both settings of requires_bitemporal, both
states of a nullable field, and the full cross-product where two interact. There is no
hypothesis in this suite and the docstring says so rather than letting "property test"
imply more than it is.

FEATURE-LEVEL INTEGRATION

tests/integration/test_domain_contract_conformance.py is the first test to cross
domain + contracts + kernel. Each subsystem was green on its own and nothing had ever
exercised a path through all three.

  - every entity's to_dict() validates against its real contract (ADR-0019)
  - the two agree on REJECTION, not only acceptance — agreement on the happy path
    alone would be satisfied by a schema that accepted everything
  - a serialised observation walks the full path: contract-validated, registered in
    the kernel's store, chained into a provenance record, recovered via the DAG
  - the two Digest types, separate by architecture, agree on form
  - a backfilled ingest_time passes the contract and fails the invariant — the
    division of labour between the layers, shown on the case that motivated ADR-0022

DEFECT FOUND IN #11's MERGED CONTRACT — REPORTED, NOT REPAIRED

ADR-0024 §2 states supersession records are "themselves immutable and
content-addressed". supersession.schema.json carries no digest field. The gap is real.
A contract is #11's to change and #12 may not edit one silently, so the identity
invariant records Supersession under ADDRESSED_BY_CONTAINMENT with the discrepancy
named in the comment, and the report classifies it.

WORKFLOW: TWO ONE-LINE CHANGES, BOTH SO THIS ISSUE'S TESTS RUN AT ALL

  unit         pytest kernel  →  pytest kernel domain
  integration  pip install pytest  →  pip install pytest jsonschema

Without the first, 244 tests would exist and never execute — the exact defect recorded
as EPD-004 when the kernel landed, repeating one issue later. Reported as EPD-006.
No new job, no new required context; still eight.

conftest.py at the root: tests/integration/ is not a package, so pytest inserted that
directory rather than the repo root and `import domain` failed at collection. The root
conftest is pytest's own mechanism for this and fixes it for every suite at once.

VERIFICATION

  domain              244 passed
  kernel               78 passed
  tests/architecture  405 passed  (+53)
  tests/integration    57 passed  (+33)
  research/tests/v2   188 passed
  links / imports / contracts gates       clean
  clean export (STD-12)                   all suites green

The contracts gate now reports "every change additive within its major" for the first
time — #11's schemas are on origin/main, so the comparison ran against a real baseline
instead of reporting that it had not been exercised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097 added a commit that referenced this pull request Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #12. Deps: #11 (complete, merged as 6c26b33).
Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set was
{#12 order 12, #14 order 14, #15 order 15}; lowest Order selects #12.

WHAT THIS IS

14 entities, 8 value types and 19 invariants, under domain/. Imports the standard
library and nothing else — no third party, no kernel, no context — so every test here
runs with no fixtures, no mocks and no infrastructure (ADR-0026, TIS E4 §11(i), §16).

TWO IDENTITY REGIMES, WHICH IS THE POINT (ADR-0005)

  names.py        Dataset · Method · Source · Instrument · LabelSource
                  Mutable identifiers, permitted only because no Evaluation
                  references them. TIS E4 §11(ii) states the list; the module is
                  the list, and a sixth fails a test.

  everything else Immutable, addressed by content. What an Evaluation pins.

THE DOMAIN CANNOT MINT A DIGEST

ADR-0005 reserves minting to the provenance kernel; ADR-0026 forbids importing it. Both
hold at once because holding a content address and computing one are different
operations. `domain.values.Digest` validates the 64-hex form and computes nothing —
`hashlib` is stdlib and would import cleanly, so the absence is a decision, and
test_the_domain_cannot_mint_a_digest pins it. Same shape for the clock: `Timestamp`
parses, and there is no `now()` (TIS §0.4).

THREE FIELDS ENFORCED BY THE SHAPE OF THE TYPE, NOT BY A CHECK

  ingest_time      has NO DEFAULT. `= None` would default a provenance value for any
                   caller who omitted it, which is precisely what ADR-0022 forbids.
                   Omitting it raises TypeError from Python itself.
  Score.interval   required. If the type cannot represent a score without its
                   uncertainty, no code path can produce one (STD-05).
  the five inputs  positional fields on Evaluation. ADR-0009 claimed four sufficed;
                   ADR-0021 superseded it. The type cannot be built with four.

19 INVARIANTS, EVERY ONE WITH A PROPERTY TEST

ALL_INVARIANTS pairs each predicate with the decision it enforces, and
test_every_invariant_has_a_property_test reads that tuple — exporting a predicate
without a test is a red build, not a gap. Predicates return bool and never raise: a
rule that raises cannot be quantified over.

"Property" here means quantified over the finite space each rule is defined on — three
reproduction classes, three severities, both settings of requires_bitemporal, both
states of a nullable field, and the full cross-product where two interact. There is no
hypothesis in this suite and the docstring says so rather than letting "property test"
imply more than it is.

FEATURE-LEVEL INTEGRATION

tests/integration/test_domain_contract_conformance.py is the first test to cross
domain + contracts + kernel. Each subsystem was green on its own and nothing had ever
exercised a path through all three.

  - every entity's to_dict() validates against its real contract (ADR-0019)
  - the two agree on REJECTION, not only acceptance — agreement on the happy path
    alone would be satisfied by a schema that accepted everything
  - a serialised observation walks the full path: contract-validated, registered in
    the kernel's store, chained into a provenance record, recovered via the DAG
  - the two Digest types, separate by architecture, agree on form
  - a backfilled ingest_time passes the contract and fails the invariant — the
    division of labour between the layers, shown on the case that motivated ADR-0022

DEFECT FOUND IN #11's MERGED CONTRACT — REPORTED, NOT REPAIRED

ADR-0024 §2 states supersession records are "themselves immutable and
content-addressed". supersession.schema.json carries no digest field. The gap is real.
A contract is #11's to change and #12 may not edit one silently, so the identity
invariant records Supersession under ADDRESSED_BY_CONTAINMENT with the discrepancy
named in the comment, and the report classifies it.

WORKFLOW: TWO ONE-LINE CHANGES, BOTH SO THIS ISSUE'S TESTS RUN AT ALL

  unit         pytest kernel  →  pytest kernel domain
  integration  pip install pytest  →  pip install pytest jsonschema

Without the first, 244 tests would exist and never execute — the exact defect recorded
as EPD-004 when the kernel landed, repeating one issue later. Reported as EPD-006.
No new job, no new required context; still eight.

conftest.py at the root: tests/integration/ is not a package, so pytest inserted that
directory rather than the repo root and `import domain` failed at collection. The root
conftest is pytest's own mechanism for this and fixes it for every suite at once.

VERIFICATION

  domain              244 passed
  kernel               78 passed
  tests/architecture  405 passed  (+53)
  tests/integration    57 passed  (+33)
  research/tests/v2   188 passed
  links / imports / contracts gates       clean
  clean export (STD-12)                   all suites green

The contracts gate now reports "every change additive within its major" for the first
time — #11's schemas are on origin/main, so the comparison ran against a real baseline
instead of reporting that it had not been exercised.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097 added a commit that referenced this pull request Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #13. Deps: #6 ✔, #12 ✔ (530a412).
Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15: eligible set
was {#13 order 13, #14 order 14, #15 order 15}; lowest Order selects #13.

ADR-0026's Verification asks for "a kernel-zero-imports test plus six import-direction
tests, each with a deliberate-violation case". The kernel test landed with #10 and the
domain stdlib-only rule with #12. This is the six.

THE RULE WAS INEXPRESSIBLE, NOT MERELY UNENFORCED

`check` permitted an import whose top-level root matched `policy.root`. For
`contexts.evaluation` that root is `contexts` — so importing `contexts.method` counted
as importing itself, and ADR-0026's fifth dependency rule could not be *stated*, let
alone violated. Demonstrated before changing anything:

    policy.package : contexts.evaluation
    policy.root    : contexts
    permitted      : ['contexts', 'contracts', 'domain']

Fixed by matching self-imports against the policy's dotted package rather than its root,
via a new `imported_paths` that keeps full dotted paths. `imported_roots` is retained —
root granularity is still correct for classifying stdlib and third party.

This is a strict tightening. #6's own 17 self-tests pass unchanged, and the real gate
still exits 0.

THE SIX RULES, ENCODED AS ADR-0026 WRITES THEM

  ingest · curation · groundtruth · method   contracts, domain, kernel
  evidence                                   contracts, domain, kernel  (+ writes nothing)
  evaluation                                 contracts, domain          ← as written

No policy grants the `contexts` root, so no context may import another. Third-party roots
are not pre-granted: the ADR grants none, and a context needing astropy should add one
reviewable line rather than inherit a permission nobody voted for (STD-11).

ARCHITECTURE CONTRADICTION — REPORTED, NOT RESOLVED (AC-001)

ADR-0026 says "Evaluation imports contracts and domain only". TIS E10 §7 requires an
Evaluation to carry its own digest; ADR-0005 reserves minting to the kernel. Those three
cannot all hold. Encoded exactly as frozen and reported for resolution by ADR before
M7/#30. Quietly adding `kernel` to R5 would be weakening a gate to fit an unwritten need,
which is the one thing this project has repeatedly refused to do.

Separately, ADR-0026 states R5 twice and inconsistently: the context table says
"Depends on contracts only" and the Decision says "contracts and domain only". The
precise Decision wording is taken as operative, following the DR-006 precedent.

EMPTY CONTEXTS, AND WHY THESE RULES ARE NOT DECORATION

contexts/* holds six READMEs and no Python, so none of the 55 architecture tests assert
over the real tree — each one builds a violating module on disk and asserts the analyser
reports it. The verdict is about the analyser, which exists.

Two tests make the rules bind on the day code arrives rather than the day someone
remembers them. Every context declares populated=False, and the gate rejects that
declaration the moment a module appears — so the first .py file in any context goes red
until that issue states what it may import. And a seventh context added without a policy
fails, because `undeclared` finds packages by __init__.py and these directories have none.

FEATURE-LEVEL INTEGRATION

Six rejection tests are all satisfied by a rule set that rejects everything. So
tests/integration/test_context_rules_with_platform.py asks the complementary question,
which needs three real subsystems at once: does the rule set PERMIT the composition the
architecture calls for?

A plausible Ingest module builds a domain Observation, validates it against the real
contract, and registers it in the kernel's provenance store. The gate permits it; the
same source then genuinely runs, producing a schema-valid, DAG-linked artifact with
ingest_time null and nothing fabricated.

Then the point of the whole gate, as a test: the violating variant differs by one import
and EXECUTES PERFECTLY. Strip that line and the remainder is byte-identical. No test that
runs the context would ever notice the coupling.

And the same text is permitted under R1 and refused under R5 — the verdict changes with
the context, which is what makes these rules directional rather than one allow-list.

VERIFICATION

  unit (kernel + domain)   322 passed
  tests/architecture       460 passed  (+55)
  tests/integration         63 passed  (+6)
  research/tests/v2        188 passed
  links / imports / contracts gates    clean
  imports gate: 30 modules, 134 imports, 9 policies

CI: no workflow change. Both new files land in jobs that already run. Still eight
contexts.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Rexy-5097 added a commit that referenced this pull request Jul 30, 2026
Canonical: Milestone M2 · Epic E4 · Issue #14. Deps: #11 (complete).
Eligibility recomputed by parsing origin/main:docs/tis/TIS-v1.0.md §15 — complete
{1..13}, eligible {#14 order 14, #15 order 15}; lowest Order selects #14.

Completes Epic E4. The twelfth contract, and the one git actually carries: a Tier 2
manifest is what the repository holds in place of ~600 MB it deliberately does not
(ADR-0023, E6 §19).

ALMOST EVERYTHING THIS SCHEMA DOES IS A REFUSAL

ADR-0023 defines three tiers with three different homes, and each must forbid the
others. Expressed as prose, "a Tier 0 manifest must not carry a deposition" is a
sentence someone has to read. Expressed as `if tier = 0 then not deposition`, a manifest
that would redistribute another organisation's raw archive CANNOT BE WRITTEN.

  tier 0   retrieval descriptor required; deposition and path impossible
           (STD-23, E6 §11(iii) — identified, never stored, never redistributed)
  tier 1   deposition required; retrieval and path impossible; retention must be
           permanent, because a prunable Tier 1 release makes a DOI resolve to nothing
  tier 2   path required, and constrained so it cannot leave the repository root

RETENTION IS UNREPRESENTABLE WHEN IT WOULD BE WRONG

E6 §11(iv): an artifact referenced by an Evidence Binding is NEVER pruned. So
`prunable` with a non-empty `referenced_by` is refused by the contract rather than
discouraged in a comment — it is the one combination that would let a published claim
lose the bytes underneath it. A `permanent` entry likewise cannot carry a `prune_after`:
permitting both would let a manifest state a contradiction that a retention run would
then arbitrate silently, the same way every time.

DOI IS REQUIRED AS A FIELD AND NULLABLE IN VALUE

ADR-0023's fallback degrades citability, and says the degradation is published rather
than hidden. Optional would make "no DOI" and "nobody recorded whether there is one"
the same document — the ingest_time reasoning of ADR-0022 applied to citability.

FEATURE-LEVEL INTEGRATION — THE DIGEST IS THE SAME DIGEST

tests/integration/test_manifest_with_platform.py checks the one thing no schema can:
that the digest in a manifest is the digest of the bytes it claims to describe. It
needs four components at once — the kernel mints it (#10, the only thing permitted to),
the contract validates the release (#11), the domain holds the digest without being
able to compute one (#12), and the import rules say who may do all of it (#13).

Every guarantee downstream — citation, re-acquisition, supersession, evidence binding —
rests on that equality, and nothing had ever checked it end to end. Also included: a
manifest that is schema-valid and FALSE, pointing at bytes the store has never seen,
and the demonstration that the store refuses it while the schema cannot.

A FINDING ABOUT MY OWN TEST, NOT THE SCHEMA

`tier: 1.0` validates, and that is correct. JSON has one number type and JSON Schema
compares enum members by value, so 1.0 and 1 are the same number. The first version of
that test asserted rejection and was wrong; it now asserts acceptance and records why.

NO PRODUCER, NO PARSER, NO REGISTRY, NO INGEST

The schema and its tests. `contexts/curation/manifest` is E6/#20's; registry/*/*.json
instances are E6/#21, E7/#23, E8/#25-#26, E9/#27's; the retention gate is E6/#22's.
The manifest gets no domain entity, deliberately — row 14's Unit column reads "manifest
validity", a schema test, and an entity would be a producer.

Two existing assertions updated because #14 changes the contract set: the expected
count 11 -> 12, and manifest added to the documented no-domain-entity exclusions.

VERIFICATION

  unit (kernel + domain)   322 passed
  tests/architecture       562 passed  (+102)
  tests/integration         71 passed  (+8)
  research/tests/v2        188 passed
  contracts gate: 12 schemas, 11 compared, every change additive
  links / imports gates    clean

CI: no workflow change. Both new files land in jobs that already run. Still eight.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant