Skip to content

@shutter-network/concorde/decisions

The HTTP routes this component serves: 3 on the Agent server and 2 on the Public server.

Every path is relative. Each one is printed as the plugin declares it, under whatever prefix the component's constructor registers that plugin at. This page states no mount point, because the constructor is where the mount point is decided.

Generated from what the route plugins in src/decisions/routes.ts declare, which is what a running Gateway serves at GET /openapi.json. Never edited by hand.

Agent server

The Agent server has no authentication of any kind. Everything below is reachable by the agent, and therefore by an injected prompt (ADR-0003). Nothing on it names a credential.

GET /

Read the Decision log

Every Decision this shared agent has published. It is the same log a User reads and the same one this agent published into. A Session is a lossy cache, so an agent with no memory of what it decided reads it here. One global log, the same for every reader. A Decision is addressed to nobody. There is no recipient, no group and no parameter naming a User anywhere on this route. seq numbers the one log rather than anybody's slice of it. So two Users reading the same window get the same records in the same order. A seq that moved is somebody else's activity. This surface is published to everyone on purpose, so that is the function rather than a leak. Three cursor cases, one order. No cursor answers the newest page, which is what a client opening a conversation wants. before=N answers the newest page strictly below N, which is scrolling back. after=N walks forwards from N, which is polling, and after=0 is how a log is read from its beginning, since nothing is numbered 0 and no cursor at all means the newest page instead. All three answer ascending by seq, so a client concatenates pages without reversing anything. Passing after and before together is a 400, because it describes two windows rather than one. The envelope carries no more-results flag, because a full page is one. decisions.length === limit means there can be more. The next request is this one with the cursor moved on. Walking forwards, set after to the largest seq received. Walking back, set before to the smallest. A short page is the end of that direction for now. There is no read state of any kind, so the cursor a client needs is one it already holds. limit defaults to 50 and is capped at 200. A larger value is refused with a 400 rather than quietly reduced. The Decisions past the cap are reachable by paging rather than lost. The Decision log is read by cursor and cannot be searched or filtered. The parameters are a window over seq. There is no full-text or field matching of any kind. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.

Tagged Decisions.

Query parameters

  • after integer, optional, 0 to 2147483647

    Walk forwards from this seq, exclusive: the poll. after=0 reads the log from its beginning, oldest first, which no other spelling expresses.

  • before integer, optional, 0 to 2147483647

    The newest page strictly below this seq: scrolling back. Answers ascending like every other case, so the page before the one in hand arrives the same way up.

  • limit integer, optional, 1 to 200, default 50

Responses

200 application/json

The window that matched, ascending by seq.

  • decisions array of object, required
    • seq integer, required

      This Decision's number in the one global log, from 1. It is the cursor: the largest one held is what after takes to read whatever has been published since. Gaps are expected and mean nothing, a rolled-back publish burning a number, so a missing number is not a withheld Decision and nothing anywhere could tell you if it were.

    • statement string, required

    • jws string, required

      The Decision itself: a compact JWS, header.payload.signature, base64url. Its payload carries this record's seq, createdAt and statement, so everything above can be read back out of it by anybody holding the public key, which is what makes handing this one string to a third party the whole point. Its protected header carries typ: "concorde-decision+jws", covered by the signature, so an artifact of another kind cannot be presented as a Decision.

    • createdAt string, required

      When it was published, ISO 8601. Generated by the Gateway when the artifact was signed rather than by the database, because the signed timestamp and the stored one are the same value: this is the timestamp inside jws.

400 application/json

A cursor or limit is not an integer or is out of range, both cursors were passed, or a parameter this route does not take was written.

  • statusCode integer, required
  • error string, required
  • message string, required

POST /

Publish a Decision

Commit to something, on the record, to everybody. The Statement is signed with the shared agent's key and kept in the one global log. The record answered carries the artifact, so there is no read-back to do, and the agent can quote it to a User in the same Run.

There is no field for the number, the timestamp or the signature. The number is drawn first, the timestamp second and the artifact last. All three happen inside one transaction, because the signature binds the first two. Nothing is notified: publishing wakes no Signal and no Handler. So a Decision published during a Run cannot queue work for the Run that published it. Each record carries jws, a compact JWS (RFC 7515) over { seq, createdAt, statement }. It is one URL-safe string, and any off-the-shelf JOSE library in any language verifies it. Take it away and check it against the public key at GET /jwks.json on the Public server. That check is offline and asks this Gateway nothing. It is the only verification worth something to somebody who does not trust the Operator. What it proves is narrow. The Operator committed to this Statement on the shared agent's behalf. It says nothing whatever about how the agent behaved. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.

Tagged Decisions.

Request body

application/json, required.

  • statement string, required, at least 1 character

Responses

201 application/json

The Decision as it was published, including the number it was given and the artifact signed over it.

  • seq integer, required

    This Decision's number in the one global log, from 1. It is the cursor: the largest one held is what after takes to read whatever has been published since. Gaps are expected and mean nothing, a rolled-back publish burning a number, so a missing number is not a withheld Decision and nothing anywhere could tell you if it were.

  • statement string, required

  • jws string, required

    The Decision itself: a compact JWS, header.payload.signature, base64url. Its payload carries this record's seq, createdAt and statement, so everything above can be read back out of it by anybody holding the public key, which is what makes handing this one string to a third party the whole point. Its protected header carries typ: "concorde-decision+jws", covered by the signature, so an artifact of another kind cannot be presented as a Decision.

  • createdAt string, required

    When it was published, ISO 8601. Generated by the Gateway when the artifact was signed rather than by the database, because the signed timestamp and the stored one are the same value: this is the timestamp inside jws.

400 application/json

statement is missing or empty, or a query parameter was written.

  • statusCode integer, required
  • error string, required
  • message string, required

GET /{seq}

Read one Decision by number

One Decision, so a number the agent holds can be quoted without working out a cursor. The number is its own from an earlier Run, or one a User cited at it. Citing a Decision is a route rather than a cursor query. GET /decisions/7 is the Decision numbered 7, which is what ?after=6&limit=1 says the long way round. It is the same read the log is paged with. The cursor is the one just below the number, so a citation and a page can never answer with different records. A number nobody has is a 404, and it is not evidence of a Decision withheld. A rolled-back publish burns a number, so gaps in seq are expected and mean nothing. Detecting a withheld Decision is not something this log can do or claims to. This route takes no query parameters at all. The number is the whole request, and the window belongs to the read beside it. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.

Tagged Decisions.

Path parameters

  • seq integer, required, 1 to 2147483647

    The Decision's number in the one global log, from 1.

Responses

200 application/json

The Decision with that number, exactly as the log read answers with it.

  • seq integer, required

    This Decision's number in the one global log, from 1. It is the cursor: the largest one held is what after takes to read whatever has been published since. Gaps are expected and mean nothing, a rolled-back publish burning a number, so a missing number is not a withheld Decision and nothing anywhere could tell you if it were.

  • statement string, required

  • jws string, required

    The Decision itself: a compact JWS, header.payload.signature, base64url. Its payload carries this record's seq, createdAt and statement, so everything above can be read back out of it by anybody holding the public key, which is what makes handing this one string to a third party the whole point. Its protected header carries typ: "concorde-decision+jws", covered by the signature, so an artifact of another kind cannot be presented as a Decision.

  • createdAt string, required

    When it was published, ISO 8601. Generated by the Gateway when the artifact was signed rather than by the database, because the signed timestamp and the stored one are the same value: this is the timestamp inside jws.

400 application/json

The number in the path is not a positive integer this log could have reached, or a query parameter was written.

  • statusCode integer, required
  • error string, required
  • message string, required

404 application/json

No Decision has that number.

  • statusCode integer, required
  • error string, required
  • message string, required

Public server

The Public server is what a User's client calls. Which credential each route wants, and which wants none, is in that route's own description below.

GET /

Read the Decision log

Everything this shared agent has committed to. This is what it said on everybody's behalf rather than what it said to you. Reading it is the first half of the only thing this log is for. The second half is taking a jws away and showing it to somebody who does not trust this Gateway. One global log, the same for every reader. A Decision is addressed to nobody. There is no recipient, no group and no parameter naming a User anywhere on this route. seq numbers the one log rather than anybody's slice of it. So two Users reading the same window get the same records in the same order. A seq that moved is somebody else's activity. This surface is published to everyone on purpose, so that is the function rather than a leak. Each record carries jws, a compact JWS (RFC 7515) over { seq, createdAt, statement }. It is one URL-safe string, and any off-the-shelf JOSE library in any language verifies it. Take it away and check it against the public key at GET /jwks.json on the Public server. That check is offline and asks this Gateway nothing. It is the only verification worth something to somebody who does not trust the Operator. What it proves is narrow. The Operator committed to this Statement on the shared agent's behalf. It says nothing whatever about how the agent behaved. Three cursor cases, one order. No cursor answers the newest page, which is what a client opening a conversation wants. before=N answers the newest page strictly below N, which is scrolling back. after=N walks forwards from N, which is polling, and after=0 is how a log is read from its beginning, since nothing is numbered 0 and no cursor at all means the newest page instead. All three answer ascending by seq, so a client concatenates pages without reversing anything. Passing after and before together is a 400, because it describes two windows rather than one. The envelope carries no more-results flag, because a full page is one. decisions.length === limit means there can be more. The next request is this one with the cursor moved on. Walking forwards, set after to the largest seq received. Walking back, set before to the smallest. A short page is the end of that direction for now. There is no read state of any kind, so the cursor a client needs is one it already holds. limit defaults to 50 and is capped at 200. A larger value is refused with a 400 rather than quietly reduced. The Decisions past the cap are reachable by paging rather than lost. The Decision log is read by cursor and cannot be searched or filtered. The parameters are a window over seq. There is no full-text or field matching of any kind. Requires a bearer Token, presented as Authorization: Bearer <token> and obtained from POST /auth/tokens. The User acted on is the one the Gateway authenticated, and no parameter anywhere names another. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.

Tagged Decisions.

Query parameters

  • after integer, optional, 0 to 2147483647

    Walk forwards from this seq, exclusive: the poll. after=0 reads the log from its beginning, oldest first, which no other spelling expresses.

  • before integer, optional, 0 to 2147483647

    The newest page strictly below this seq: scrolling back. Answers ascending like every other case, so the page before the one in hand arrives the same way up.

  • limit integer, optional, 1 to 200, default 50

Responses

200 application/json

The window that matched, ascending by seq.

  • decisions array of object, required
    • seq integer, required

      This Decision's number in the one global log, from 1. It is the cursor: the largest one held is what after takes to read whatever has been published since. Gaps are expected and mean nothing, a rolled-back publish burning a number, so a missing number is not a withheld Decision and nothing anywhere could tell you if it were.

    • statement string, required

    • jws string, required

      The Decision itself: a compact JWS, header.payload.signature, base64url. Its payload carries this record's seq, createdAt and statement, so everything above can be read back out of it by anybody holding the public key, which is what makes handing this one string to a third party the whole point. Its protected header carries typ: "concorde-decision+jws", covered by the signature, so an artifact of another kind cannot be presented as a Decision.

    • createdAt string, required

      When it was published, ISO 8601. Generated by the Gateway when the artifact was signed rather than by the database, because the signed timestamp and the stored one are the same value: this is the timestamp inside jws.

400 application/json

A cursor or limit is not an integer or is out of range, both cursors were passed, or a parameter this route does not take was written, user among them, since this log has no owner.

  • statusCode integer, required
  • error string, required
  • message string, required

401 application/json

Authentication failed, which is the whole of what is said: a wrong password, an id nobody holds, a User with no password, and a Token that is missing, malformed, unknown or expired are one status and one message, so nothing here answers who exists. This part authenticates nobody: the refusal is publicServer.requireUser, taken as one option on the route, so it is the same 401 every protected route on this server answers, whichever scheme the deployment accepts.

  • statusCode integer, required
  • error string, required
  • message string, required

GET /{seq}

Read one Decision by number

The Decision somebody cited at you, fetched by the number they cited. This is where an artifact worth handing onward comes from: one record, one jws, and nothing else to hold. Citing a Decision is a route rather than a cursor query. GET /decisions/7 is the Decision numbered 7, which is what ?after=6&limit=1 says the long way round. It is the same read the log is paged with. The cursor is the one just below the number, so a citation and a page can never answer with different records. Each record carries jws, a compact JWS (RFC 7515) over { seq, createdAt, statement }. It is one URL-safe string, and any off-the-shelf JOSE library in any language verifies it. Take it away and check it against the public key at GET /jwks.json on the Public server. That check is offline and asks this Gateway nothing. It is the only verification worth something to somebody who does not trust the Operator. What it proves is narrow. The Operator committed to this Statement on the shared agent's behalf. It says nothing whatever about how the agent behaved. A number nobody has is a 404, and it is not evidence of a Decision withheld. A rolled-back publish burns a number, so gaps in seq are expected and mean nothing. Detecting a withheld Decision is not something this log can do or claims to. Requires a bearer Token, presented as Authorization: Bearer <token> and obtained from POST /auth/tokens. The User acted on is the one the Gateway authenticated, and no parameter anywhere names another. This route takes no query parameters at all. The number is the whole request, and the window belongs to the read beside it. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.

Tagged Decisions.

Path parameters

  • seq integer, required, 1 to 2147483647

    The Decision's number in the one global log, from 1.

Responses

200 application/json

The Decision with that number, exactly as the log read answers with it.

  • seq integer, required

    This Decision's number in the one global log, from 1. It is the cursor: the largest one held is what after takes to read whatever has been published since. Gaps are expected and mean nothing, a rolled-back publish burning a number, so a missing number is not a withheld Decision and nothing anywhere could tell you if it were.

  • statement string, required

  • jws string, required

    The Decision itself: a compact JWS, header.payload.signature, base64url. Its payload carries this record's seq, createdAt and statement, so everything above can be read back out of it by anybody holding the public key, which is what makes handing this one string to a third party the whole point. Its protected header carries typ: "concorde-decision+jws", covered by the signature, so an artifact of another kind cannot be presented as a Decision.

  • createdAt string, required

    When it was published, ISO 8601. Generated by the Gateway when the artifact was signed rather than by the database, because the signed timestamp and the stored one are the same value: this is the timestamp inside jws.

400 application/json

The number in the path is not a positive integer this log could have reached, or a query parameter was written.

  • statusCode integer, required
  • error string, required
  • message string, required

401 application/json

Authentication failed, which is the whole of what is said: a wrong password, an id nobody holds, a User with no password, and a Token that is missing, malformed, unknown or expired are one status and one message, so nothing here answers who exists. This part authenticates nobody: the refusal is publicServer.requireUser, taken as one option on the route, so it is the same 401 every protected route on this server answers, whichever scheme the deployment accepts.

  • statusCode integer, required
  • error string, required
  • message string, required

404 application/json

No Decision has that number.

  • statusCode integer, required
  • error string, required
  • message string, required