Skip to content

feat: syspi — SysML Package Installer (identity, VCS-neutral resolution, verification, install, lock, index) - #1

Merged
HuiJun merged 17 commits into
developfrom
feat/syspi-cli
Oct 27, 2026
Merged

HuiJun merged 17 commits into
developfrom
feat/syspi-cli

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The syspi command-line package manager for KerML / SysML v2 project-interchange packages — Phase 1. SysPI fetches, verifies, installs and locks packages; OpenSysML only reads what it installs, and nothing here is shared code with OpenSysML or sysand.

Contract. A package is any git, svn or hg repository (or #<subpath> within one) whose tagged tree has .project.json (KerML 1.1 §10.3) and whose version equals the tag (v<semver> preferred, bare <semver> accepted). No .project.json is an error; nothing is synthesised.

Identity (internal/origin, hand-parsed, no purl library; pkg:git is the registered Package-URL type git of ECMA-427 / purl-spec types/git-definition.json with the version restricted to a tag naming a semver — a commit sha is refused saying why, the other official examples parse unchanged (TestPurlGitExamples); pkg:svn/pkg:hg/pkg:sysand are not registered types and use the same shape by analogy):
pkg:<vcs>/<host>/<path>[@<version>][#<subpath>]. Plain clone addresses (https://…/repo.git@1.0.0, ssh://git@host/path@1.0.0, git@host:path@1.0.0, file:///path@1.0.0, svn+ssh://…) normalise to it; pkg:github/…/pkg:gitlab/… are refused pointing at pkg:git/<host>/<path>; pkg:cvs/… is refused naming git, svn, hg.

Resolution (internal/vcs): one Source interface — ListVersions, Export(version, dst), Revision(version) — with a git (ls-remote --tags peeled, clone --depth 1 --branch), hg (identify --debug -r, clone -r <node>, hg archive minus .hg_archival.txt/.hgtags) and svn (svn ls <url>/tags, svn info --show-item last-changed-revision, svn export <url>/tags/<tag>@<rev>) implementation, each shelling out to the user's executable; a missing executable is an error naming the binary. Constraints (internal/version): exact, ^, ~, comparison ranges; @<version> without versionConstraint is exact; the looser npm forms are refused.

Verification before use (internal/project, internal/digest): .meta.json checksums (list or sysand's object shape; algorithm: NONE counts as unrecorded; Table 13's "some or all" coverage is allowed and the uncovered count is reported), tag == version, recorded revision, path-traversal / symlink / duplicate / size checks on every archive and export, and the tree hash — sorted relative paths, length-prefixed path and content, mode-free, VCS metadata skipped (documented in docs/packages.md), identical for the same package served from any VCS.

Install and lock (internal/install, internal/lock): trees go to lib/<publisher>-<name>_<version>/, archives to lib/<publisher>-<name>_<version>.kpar, beside the consuming .project.json; an existing .sysand/lib/ satisfies a locked package and is never written. .project.lock.json is deterministic (sorted, fixed key order) with VCS-neutral entries:

{ "resource": "pkg:git/github.com/MBSE4U/sysmod-sysmlv2", "kind": "vcs", "publisher": "mbse4u", "name": "sysmod",
  "version": "5.1.5", "vcs": "git", "source": "https://github.com/MBSE4U/sysmod-sysmlv2.git", "tag": "5.1.5",
  "revision": "07286ecd2128b64f53fc3d05aee811e98b991a85", "sha256": "38455a9a…" }

A lock entry wins over a constraint; usage per entry lets an offline run walk the closure from the lock alone. Other sources: local directory (editable, kind: local), local/remote .kpar, OMG standard-library IRIs (kind: standard, never installed), pkg:sysand/<publisher>/<name> through a static index (internal/index, which also emits that layout).

Commands (cmd/syspi, standard-library flag): init, add, install, sync, lock, bundle (byte-identical, SOURCE_DATE_EPOCH, checks tag == version in a tagged checkout), index emit. -offline, -C, -index/$SYSPI_INDEX on project commands.

Repository hygiene: README (contract on one screen, Future section), docs/packages.md, AGENTS.md, CONTRIBUTING.md, changes/unreleased/ fragment, SPDX headers, CI on Linux and macOS with mercurial and subversion installed and SYSPI_REQUIRE_VCS=1, release workflow on v* building linux/darwin/windows × amd64/arm64 with SHA256SUMS.

Gate

$ go build ./... && go vet ./... && gofmt -l .
$ SYSPI_REQUIRE_VCS=1 go test -count=1 ./...
ok  	github.com/Open-MBEE/SysPI/internal/digest
ok  	github.com/Open-MBEE/SysPI/internal/install
ok  	github.com/Open-MBEE/SysPI/internal/lock
ok  	github.com/Open-MBEE/SysPI/internal/origin
ok  	github.com/Open-MBEE/SysPI/internal/project
ok  	github.com/Open-MBEE/SysPI/internal/vcs
ok  	github.com/Open-MBEE/SysPI/internal/version
ok  	github.com/Open-MBEE/SysPI/tests/e2e

tests/e2e (local fixtures only, no network): bare git repository with v1.0.0/v1.1.0 plus a #packages/gears sub-package, a .kpar built by syspi bundle (byte-identical twice) and served over httptest; init → add pkg:git//<path>@1.0.0 → add #packages/gears ~1.1.0 → add <url>.kpar → add <OMG IRI> → install → lock; lib/ layout as above; lock byte-identical on a fresh clone with and without the lock; offline install/lock/sync after the server and repository are gone; refusals for missing .project.json at the tag, tag/version mismatch, checksum mismatch in an archive, path traversal in an archive, pkg:github/…, pkg:cvs/…, an unknown host, and no git on PATH (error names the binary, no panic), each leaving .project.json and the lock untouched. TestSameTreeFromGitHgSvn: the same package in a git, an hg (hg init + hg tag) and an svn repository (svnadmin create, svn import to trunk/, svn copy to tags/v1.1.0) installs byte-identical lib/ trees with the same sha256 and different revisions. TestIndexEmitAndResolve: index emit then add -index <url> pkg:sysand/acme/tools ^3.0.0.

Interop gate (OpenSysML sysml -validate, no flags)

sysml built from OpenSysML develop (15ac97cff) and, for comparison, from the head of OpenSysML PR #1517 (6df2fca8f). Project: syspi init, add pkg:git//…/widgets.git@1.0.0, add pkg:git//…/widgets.git#packages/gears 1.0.0, App.sysml importing Widgets::* and Gears::* and typing parts by Frame and Gear.

$ sysml -validate App.sysml          # develop build, lib/ present
✓ package App
✓ package Gears
note: Gears is opened by more than one loaded file; …
✓ package Gears
✓ package Widgets
✓ App.sysml: no errors
$ mv lib /tmp/lib.off && sysml -validate App.sysml
App.sysml:2:18: error[unresolved]: unresolved reference: Widgets
App.sysml:3:18: error[unresolved]: unresolved reference: Gears
App.sysml:4:12: error[unresolved]: unresolved reference: Frame
App.sysml:5:12: error[unresolved]: unresolved reference: Gear
sysml: App.sysml did not analyse cleanly; no check was made

The PR #1517 build gives the same two results. Every usage resolves from lib/ with no OpenSysML flags; see the first note below on how the develop build finds it.

Manual check: a real public package, read-only

$ syspi init -publisher acme -name rover
initialised acme-rover_0.1.0 in . (.project.json, .meta.json)
$ syspi add pkg:git/github.com/MBSE4U/sysmod-sysmlv2@5.1.5
pkg:git/github.com/MBSE4U/sysmod-sysmlv2: tag 5.1.5: .meta.json records no checksum for 48 of 52 files; their integrity rests on the tree hash
installed sysmod 5.1.5 (git 5.1.5 07286ecd2128b64f53fc3d05aee811e98b991a85) -> lib/mbse4u-sysmod_5.1.5
1 package(s) locked: 1 installed, 0 already present, 0 not installed (standard libraries and local directories); .project.lock.json updated
$ cat .project.lock.json
{
  "lockVersion": 1,
  "packages": [
    {
      "resource": "pkg:git/github.com/MBSE4U/sysmod-sysmlv2",
      "kind": "vcs",
      "publisher": "mbse4u",
      "name": "sysmod",
      "version": "5.1.5",
      "vcs": "git",
      "source": "https://github.com/MBSE4U/sysmod-sysmlv2.git",
      "tag": "5.1.5",
      "revision": "07286ecd2128b64f53fc3d05aee811e98b991a85",
      "sha256": "38455a9a4b5642295d0d1283f2c8e3f6d025e3d10a42275386ac1d76d3f9a272"
    }
  ]
}
$ ls lib lib/mbse4u-sysmod_5.1.5
lib:
mbse4u-sysmod_5.1.5

lib/mbse4u-sysmod_5.1.5:
CHANGELOG.md  LICENSE  LICENSES  README.md  SYSMOD.sysml  SYSMOD4AI.sysml  SYSMODRequirementsBoilerPlates.sysml  SYSMODViews.md  SYSMODViews.sysml  examples  scripts
$ git ls-remote --tags https://github.com/MBSE4U/sysmod-sysmlv2.git 5.1.5
07286ecd2128b64f53fc3d05aee811e98b991a85	refs/tags/5.1.5
$ syspi install -offline
1 package(s) locked: 0 installed, 1 already present, 0 not installed (standard libraries and local directories)

(The repository is MBSE4U/sysmod-sysmlv2; its tags are bare semver, which the tag rule accepts.)

Credentials follow through each backend

SysPI never prompts and stores no credentials of its own; the user's git, hg and svn run with
the user's environment, and SysPI's own HTTP downloads (index files, .kpar URLs) take HTTP Basic
from the netrc ($NETRC, else ~/.netrc / %USERPROFILE%\_netrc), matched per request host so a
cross-host redirect never carries them (internal/netrc, asserted in netrc_test.go). Each
authentication failure ends in one hint (vcs.AuthHint, index.AuthHint). tests/e2e/auth_test.go
proves the paths against local servers (SYSPI_REQUIRE_VCS=1 go test -v ./tests/e2e -run TestAuth):

=== RUN   TestAuthGitCredentialHelper      dumb-HTTP bare repo behind Basic auth; helper via GIT_CONFIG_COUNT/KEY_0/VALUE_0
--- PASS: TestAuthGitCredentialHelper      (without helper: "hint: git needs credentials ... credential helper"; with it: lib/ populated, server saw Authorization, no secret in the lock)
=== RUN   TestAuthHgAuthSection            static-http:// repo behind Basic auth; [auth] from HGRCPATH, HGPLAIN=1 set
--- PASS: TestAuthHgAuthSection            (without [auth]: "hint: hg needs credentials"; with it: export OK, server saw Authorization)
=== RUN   TestAuthSvnNetrc                 svnserve anon-access=none, passwd file; fresh HOME (no auth cache); netrc entry for 127.0.0.1
--- PASS: TestAuthSvnNetrc                 (no netrc: "hint: svn needs credentials ... netrc ... svn+ssh://" at once, no hang; with netrc: svn run as
                                            --username alice --password-from-stdin --no-auth-cache, password on stdin, lib/acme-widgets_1.0.0 populated,
                                            ~/.subversion/auth left empty, no credential in .project.json or the lock; wrong password: hint, secret absent
                                            from the error text, argv shows --password-from-stdin and no --password)
=== RUN   TestAuthHTTPNetrc                index + .kpar behind Basic auth
--- PASS: TestAuthHTTPNetrc                (no netrc: "HTTP 401 ... hint: ... netrc"; with NETRC: versions.json and project.kpar fetched with credentials, lib/acme-tools_3.0.0.kpar, no secret in the lock)

Credentials in a URL stay refused; URL user-info is redacted from error text. docs/packages.md
and the README carry a Credentials section with the per-backend fix and what is deliberately not
done. svn: a netrc entry for the repository host is passed as --username <login> --password-from-stdin --no-auth-cache when svn is 1.14 or newer (svn --version --quiet, checked
once; older svn runs as before and the hint names the cache and svn+ssh://), so hosts whose svn
stores no plaintext password (Debian, Ubuntu, CI containers) install non-interactively over
svn:// and https://; svn+ssh:// and file:// take no netrc. internal/vcs/svn_auth_test.go
pins the argument shape (password never in argv). The svnserve proof no longer skips anywhere.

sysand index discovery and credentials (sensmetry/sysand read, nothing copied)

Behaviour matched from sysand's sources: core/src/env/discovery.rs (discovery document, index_root/api_root validation and error texts, 404 → flat root), core/src/index_location.rs ({path}/{path_raw} URL templates, absolute http(s) only, no userinfo, redaction), core/src/env/index.rs (versions.json rules: non-semver, build metadata, sha256:<64-hex>, duplicates, descending precedence, removed hard error, yanked not newly selected), core/src/auth.rs (RestrictAuthentication / SequenceAuthentication: anonymous first, retry on 4xx except 429, ambiguous globs warned and tried in order, same-host redirects only, literal-separator globs), sysand/src/cred_env.rs and sysand/src/cli.rs (SYSAND_CRED_* grammar and validation messages; SYSAND_INDEX / SYSAND_DEFAULT_INDEX comma-delimited, empty-entry error), design/index-protocol.md and design/credential-storage.md (precedence of SYSAND_CRED_* over anonymous; default-index chain).

  • internal/index/location.go: ParseLocation, Discover, Endpoints (api_root recorded, unused by design); internal/index/index.go: Open, ReadVersions with sysand's messages, Lookup (removed → error).
  • internal/httpauth: FromEnv (sysand grammar), glob compiler, Transport (env credential → netrc → anonymous), Hint naming both mechanisms.
  • syspi: -index/$SYSPI_INDEX comma-separated, then $SYSAND_INDEX, then $SYSAND_DEFAULT_INDEX else https://sysand.com; -no-index. The e2e harness pins SYSAND_DEFAULT_INDEX to an empty local directory so no test reaches sysand.com.
TestSysandDiscoveryDocument                    PASS  index_root on a second server; relative index_root refused
TestSysandEnvCredentials/bearer                PASS  anonymous request, then `Bearer …`
TestSysandEnvCredentials/basic                 PASS  anonymous request, then Basic
TestSysandEnvCredentials/glob_does_not_match   PASS  one anonymous request, 401, hint names SYSAND_CRED_<LABEL> and netrc
TestSysandEnvCredentials/malformed_group       PASS  `please specify URL pattern SYSAND_CRED_IDX`, token not echoed
TestSysandCredentialDroppedOnCrossHostRedirect PASS  redirect target saw no Authorization header
TestSysandDefaultIndexEnv                      PASS  SYSAND_DEFAULT_INDEX with nothing configured; SYSAND_INDEX; empty entry error; -no-index

Unit tests: internal/httpauth (grammar errors, globs, anonymous-first/retry/429/ambiguity, same-origin redirects — another host, another port or an https → http downgrade never sees a credential, URL query redaction), internal/index (locations incl. a plain root with a query, templates, discovery errors, versions.json rules incl. digest case, bounded local reads). Review round 2 (11 findings) fixed in ccb9bc2: atomic publication with rollback and a temp-file lock write, lazy HTTP client (offline never reads credentials), lazy per-index discovery (a down default never blocks a configured index), add validates its options before writing .project.json, legacy string usage entries in a lock still parse. Gate on the head: go build ./..., go vet ./..., gofmt -l . empty, SYSPI_REQUIRE_VCS=1 go test ./... all ok.

Notes for OpenSysML

  • wellKnownLibraryDirs on develop (15ac97cff) still lists only .sysand/lib; lib/ arrives with PR #1517. The develop build above resolves lib/ because sysml loads every model file under the project directory, not through the well-known-directory mechanism — the result is the same, but a lib/ outside the directory tree (or -library-root) needs #1517.
  • A #<subpath> package is also contained in its parent's tagged tree. When both are installed (lib/acme-widgets_1.0.0/packages/gears/ and lib/acme-gears_1.0.0/), sysml loads Gears.sysml twice and reports "Gears is opened by more than one loaded file". The reader could skip a nested directory that has its own .project.json when loading an installed project (SysPI must not alter the tree: its hash is the tagged tree).
  • .meta.json as sysand writes it (and as MBSE4U/sysmod-sysmlv2 ships it) records checksum as path → {value, algorithm} — one object, not the list Table 13 specifies — with "algorithm": "NONE" and an empty value, and covers only the four .sysml files. internal/workspace/project/archive.go decodes map[string][]Checksum, so such a .meta.json would be rejected; SysPI reads both shapes and treats NONE as unrecorded.
  • With sysmod 5.1.5 installed and a model that does private import SYSMOD::*;, sysml -validate (both builds) reports error[unresolved] for SYSMODViews::ProjectView, ::ProjectAIView, ::ProblemStatementView, ::ProblemStatementAIView at SYSMOD.sysml:50–76, although view def ProblemStatementAIView is declared at the top level of library package SYSMODViews (SYSMODViews.sysml:504) and SYSMODViews itself does private import SYSMOD::*; — a mutual import between two installed library packages that looks like a resolution gap rather than an installation problem (the files are all present and loaded, as the earlier lines show).

Devin Review

Hand-parse pkg:<vcs>/<host>/<path>[@<version>][#<subpath>] for git, svn
and hg, normalise plain clone URLs to it, refuse provider types
(pkg:github, pkg:gitlab) and unsupported systems (pkg:cvs). Add strict
semver and constraint parsing (exact, caret, tilde, comparison ranges),
one Source interface with a git, svn and hg implementation shelling out
to the user's executables, and the deterministic tree digest a lock
records. Fixture tests prove the same package served from the three
systems exports identical trees.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 5 commits October 26, 2026 00:22
…ndles and lock

Read and validate .project.json (KerML 10.3 Table 12) and .meta.json
(Table 13), generate metadata with a package index and SHA-256 member
checksums, verify checksums before any content is used, open .kpar
archives with path-traversal, symlink, duplicate and size checks, write
byte-identical bundles honouring SOURCE_DATE_EPOCH, and define the
VCS-neutral .project.lock.json with deterministic bytes.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…e syspi commands

Resolve the usages of a project to exact packages from git, svn or hg
tags, local directories, local and remote .kpar files, OMG standard
library IRIs and a configured sysand static index; verify every tree and
archive before use; install trees under lib/<publisher>-<name>_<version>/
and archives beside them; record the closure in .project.lock.json with
a lock entry winning over a constraint; honour an existing .sysand/lib.
Add the syspi command with init, add, install, sync, lock, bundle and
index emit, and an end-to-end suite on local git, hg and svn fixtures.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…guides, changelog fragment

ci: Go gate and end-to-end suite on Linux and macOS with mercurial and
subversion installed, and a release workflow on v* tags building
linux/darwin/windows x amd64/arm64 binaries with a SHA256SUMS file.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…al coverage

KerML 10.3 Table 13 lets the checksum dictionary cover some or all of
the files and specifies a list per path; sysand and the packages built
with it write a single object with algorithm NONE. Read both shapes,
treat NONE as unrecorded, verify what is recorded and report the files
whose integrity rests on the tree hash.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration devin-ai-integration Bot changed the title feat: syspi package manager — identity, VCS-neutral resolution, install, lock feat: syspi — SysML Package Installer (identity, VCS-neutral resolution, verification, install, lock, index) Oct 26, 2026
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 26, 2026 00:40
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 5 commits October 26, 2026 00:50
…y sysand copies, reject install-slot collisions

Lock usages record the resource and versionConstraint as declared, so a
reused package keeps its constraints and transport when the closure is
walked from the lock. New packages are staged under lib/ and published
only after every usage resolved; a failing run leaves lib/ and the lock
untouched. Two resources installing as the same <publisher>-<name>_<version>
with different digests are an error. A .sysand/lib copy is reused only
with the locked tree digest. Archives read every member so the ZIP CRC
covers unrecorded ones; bundle excludes its own output and takes its
created time from the checkout when SOURCE_DATE_EPOCH is unset; clone
URLs accept @Version with #subpath; credentials never reach error text.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ory constraints, index package identity and exact tag text

A locked local .kpar already in lib/ is reused without reading its
source; a second usage of an editable directory is checked against the
resolved version; an index archive must carry the publisher and name it
was requested as; the tag text must equal .project.json's version
exactly, build metadata included.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…d, credential proof tests

Index files and .kpar URLs take HTTP Basic credentials from the netrc
(NETRC, else ~/.netrc or %USERPROFILE%\_netrc) matched per request host,
so a cross-host redirect never carries them. Authentication failures of
git, svn, hg and HTTP each end in one actionable hint. End-to-end tests
prove the user's credentials follow through each backend: git over dumb
HTTP behind Basic auth with a credential helper from GIT_CONFIG_*, hg
over static-http with an [auth] section from HGRCPATH under HGPLAIN=1,
svn against svnserve with a seeded auth cache, and the index and .kpar
downloads with a netrc. git falls back to a full clone where a remote
cannot serve a shallow one; static-http:// is accepted as an hg address.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…X and SYSAND_CRED_* credentials

Index locations are parsed and discovered as sysand does (sysand-index-config.json,
{path}/{path_raw} templates, absolute http(s) only), versions.json is validated
with sysand's rules (descending semver, no build metadata, sha256:<64-hex>,
duplicates, removed/yanked status), the index chain reads -index/SYSPI_INDEX,
SYSAND_INDEX and SYSAND_DEFAULT_INDEX (default https://sysand.com; -no-index).
HTTP downloads take SYSAND_CRED_<LABEL> glob credentials (bearer or Basic,
anonymous first, retry on 4xx except 429, ambiguity warned and tried in order,
same-host redirects only) ahead of netrc.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
A netrc entry for the repository host runs svn with --username <login>
--password-from-stdin --no-auth-cache (svn 1.14+), the password on stdin only,
so non-interactive installs work where svn stores no plaintext passwords.
The svnserve proof test now runs everywhere instead of skipping.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ry and credentials

Publication parks each replaced package and restores it when a later rename
or the lock write fails (lock written via temp file + rename); the HTTP
client and each index are opened lazily so offline runs never read
credentials and a down default index cannot block a configured one; cmdAdd
validates its index options before writing .project.json; version-1 locks
with string usages still parse; local index reads are bounded; versions.json
digests are normalised to lowercase; plain index roots keep their query;
URL queries are redacted from diagnostics; a credential never follows a
redirect to another port or an https->http downgrade.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 4 commits October 26, 2026 01:33
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ids refused with a reason

README, docs/packages.md and the origin package comment cite ECMA-427 /
purl-spec types/git-definition.json for pkg:git and state that pkg:svn, pkg:hg
and pkg:sysand are not registered types used by analogy; a commit id after @
is refused with an error saying SysPI resolves only a tag naming a semantic
version. The four official examples are pinned in TestPurlGitExamples.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
… design

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 5467c58 into develop Oct 27, 2026
3 checks passed
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