Targets and credentials
The target registry
Touchstone tests only servers registered in advance. targets.yaml maps a short id to a server. Every front end takes that id and never a URL: the --target option, the MCP tools’ targetId argument, and the GitHub Action, which writes a registry for you.
This is a security boundary rather than a convenience. Touchstone sends malformed and hostile requests on purpose. If it accepted arbitrary URLs, anyone who could reach it could aim it at someone else’s server. Only the person who edits the registry decides which servers can be tested.
targets:
ref:
baseUrl: http://localhost:4711/
adapter: env
capabilities: []
properties: {}
mine:
storage: https://storage.my-lws-server.test/
| Key | Required | Meaning |
|---|---|---|
storage | one of the two | The storage’s URL, the id of its storage description. Touchstone starts where a client that knows only the storage does: it reads the storage description and creates its run root in the storage root that the StorageRoot service names, so alice must be allowed to create containers there. |
baseUrl | one of the two | A container in the storage under test, which Touchstone creates its run root inside without reading the storage description: for a server that has none yet, or to keep runs out of the storage root. alice must be allowed to create containers there. |
Tests read the container the run root is created in as ${target.baseUrl}, whichever key gave it. | ||
adapter | no | The provisioning adapter. Default env, currently the only one. |
capabilities | no | What the deployment provides that the server cannot reveal about itself: Authentication, HarnessIssuedTokens, ReachableFixtures, SamlTrust. A test that needs one the target does not list is inapplicable. Authentication says what each means. |
properties | no | Settings, all strings. See below. |
run reads targets.yaml from the working directory. Use --targets <file> to point at another registry.
Identities
Tests never carry credentials. They name identities from definitions/lws10/identities.yamlld:
anonymoussends no credentials;aliceowns the storage. She creates the run root and every test’s container, and a step that names no identity acts as her;bobis a second authenticated agent, with no access to alice’s resources unless a test grants it;- fault identities, such as
alice-expired, are a basis identity with one defect; - subject credentials, such as
didkeyandoidc, are the credentials the authentication suites exchange for access tokens. The harness mints them.
Properties
| Property | Meaning |
|---|---|
token.<name> | A literal access token for alice or bob. Use this only for throwaway tokens; otherwise use the environment (below). |
cidUrl.<name> | The CID URL of alice or bob, their agent IRI, which grants name and harness-issued tokens carry as sub. Also TOUCHSTONE_CID_URL_<NAME>. The names before format 0.15.0, webid.<name> and TOUCHSTONE_WEBID_<NAME>, still count when these are unset. |
as.signingKey | The authorization server’s private JWK, with HarnessIssuedTokens. as.clientId sets the client_id its tokens carry (default touchstone). |
didkey.jwk.<name> | A P-256 private JWK. For alice or bob, the engine exchanges a CID-suite credential for that key’s did:key subject for their token; for didkey, it pins the key the did:key-subject tests use. |
fixtures.baseUrl, fixtures.bind | With ReachableFixtures: the URL the server reaches the harness’s fixture host at, and where the harness listens if that differs (host:port). |
saml.idpKey, saml.idpCertificate | With SamlTrust: the harness identity provider’s RSA private JWK, and its PEM certificate if the server trusts it by certificate. |
timeout | Seconds before a request is abandoned. Default 30. |
parallelism | How many tests run at once. Default 16. |
retryAfter.maxWait | The longest Retry-After, in seconds, the harness waits for when a server answers 429 or 503. Default 30. |
retryAfter.retries | How many times the harness sends a request again after such a refusal. Default 3. |
Tokens are looked up in the target property token.<name>, then in the environment variable TOUCHSTONE_TOKEN_<NAME>: the name upper-cased, - replaced by _. On a target that does not declare Authentication, alice and bob send nothing when neither is set. Authentication gives the full order, including tokens the harness mints or exchanges.
Testing a protected server
Most real storages do not let anonymous clients create containers. Give alice a token, and keep it out of the file:
targets:
mine:
storage: https://storage.my-lws-server.test/alice/
adapter: env
capabilities: []
export TOUCHSTONE_TOKEN_ALICE='<token>'
touchstone run --target mine
Without a token for alice, the run stops before any test: the run root cannot be created, and the exit code is 2. The storage description is read anonymously first, and with alice’s token only if the server answers that with 401. With a second agent’s token in TOUCHSTONE_TOKEN_BOB, add Authentication to capabilities, and the access-control tests apply too.
Never write a real credential into targets.yaml. The file is checked in and ships with the harness. Supply tokens through the environment, for example as CI secrets.
For an LWS OpenID Connect server, the token the storage expects is an OpenID Connect ID Token, not an access token. Its sub must be the agent’s CID URL, and it must come from the OpenID Provider that the controlled identifier document at that URL names. The storage follows that chain to decide whether to trust the issuer. Which provider, which client and which grant to use are facts about your deployment. They belong in your environment, not in the repository.
ID Tokens are short-lived; five minutes is a common default. A run of every definition takes seconds, so one token covers a run; mint a fresh one for each session.
What the server must allow
alice must be able to:
- read the storage description, for a target registered by its
storage: a GET of the storage URL, anonymously or as alice, that answers 200 with aStorageRootservice; - create a container in the storage root (or under
baseUrl), by POSTing with thelws#Containertype link and receiving201with aLocationheader; - delete what she created. Each test deletes its own container at the end, with
Depth: infinityandIf-Match. Recursive delete is optional in the draft; on a server without it, the harness deletes bottom-up by listing, and logs a warning naming anything left behind.
Touchstone also sends a Slug header when it creates a container, so run roots are easy to recognise in your storage. The current draft does not define Slug, and nothing depends on the server honouring it.