@openengio/cli 0.23.3 → 0.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,6 +98,7 @@ var RULE_ENTRIES = {
98
98
  "max-session-out-of-range": entry("The sign-in length in the request is outside the allowed range.", FIX_REQUEST),
99
99
  "invalid-email": entry("The email address is not one the platform takes: it must have one @, and no space or non-ASCII character, and be at most 254 characters.", FIX_REQUEST),
100
100
  "already-invited": entry("The organisation already has a pending invitation to this address.", { option: "List the pending invitations.", read: "listInvitations" }),
101
+ "no-such-client": entry("The client the request names is not an active client of your organisation.", { option: "List the organisation's clients.", read: "listClients" }),
101
102
  "no-such-invitation": entry("The invitation the request names is not one of your organisation.", { option: "List the pending invitations.", read: "listInvitations" }),
102
103
  "invitation-not-pending": entry("The invitation was already accepted or revoked.", { option: "List the pending invitations.", read: "listInvitations" }),
103
104
  "invalid-domain": entry("A domain in the rule is not a valid domain name: it needs letters, digits, hyphens and at least one dot.", FIX_REQUEST),
@@ -131,6 +132,17 @@ var RULE_ENTRIES = {
131
132
  "over-bound": entry("The request is larger than the platform allows.", FIX_REQUEST),
132
133
  "malformed-history-request": entry("The history request is not one the platform can run.", FIX_REQUEST),
133
134
  "protected-ref": entry("This branch is protected, so an agent cannot write to it.", { option: "Write to another branch." }),
135
+ immutable: entry("The record's type is immutable, so it cannot be changed or deleted on any branch.", { option: "Leave the record unchanged." }),
136
+ owned: entry("The record belongs to a connected system, which alone can write it.", { option: "Ask the connected system to make the change." }),
137
+ frozen: entry("The record is locked; it cannot be changed until someone allowed to lock records lifts the lock.", { option: "Ask someone allowed to lock records to lift the lock." }),
138
+ protected: entry("A rule protects this record on this branch, so it changes only by a merge from another branch.", { option: "Make the change on another branch and merge it." }),
139
+ "not-a-ref-head": entry("The commit is not the latest commit of a branch you can use.", { option: "Read the branches you can use and choose one." }),
140
+ "branch-needs-schema-update": entry("This branch's schema is older than the platform's, so it must be updated before it can be written.", { option: "Request a schema update for the branch." }),
141
+ "organisation-not-ready": entry("The organisation's trunk is not ready yet.", { option: "Try again shortly." }),
142
+ "read-only-ref": entry("The branch is read-only, so nothing can be written or merged into it.", { option: "Choose a writable branch." }),
143
+ "ref-exists": entry("An organisation ref of that name already exists.", { option: "List the organisation's refs.", read: "listOrganisationRefs" }),
144
+ "concurrent-change": entry("Another write changed the record first, so nothing was applied.", { option: "Try again shortly." }),
145
+ "lock-unavailable": entry("The lock could not be taken now, so nothing was applied.", { option: "Try again shortly." }),
134
146
  "organisation-not-active": entry("The organisation is not active.", ASK_ADMINISTRATOR),
135
147
  "duplicate-project": entry("A project with this identifier already exists.", { option: "List the projects.", read: "listProjects" }),
136
148
  "duplicate-organisation": entry("An organisation with this identifier already exists.", { option: "Send it again as a new request." }),
@@ -175,11 +187,15 @@ var CLASS_ENTRIES = {
175
187
  };
176
188
  var API_RULE_ENTRIES = {
177
189
  "not-found": KIND_ENTRIES["not-found"],
190
+ immutable: RULE_ENTRIES.immutable,
191
+ owned: RULE_ENTRIES.owned,
192
+ frozen: RULE_ENTRIES.frozen,
193
+ protected: RULE_ENTRIES.protected,
194
+ "branch-needs-schema-update": RULE_ENTRIES["branch-needs-schema-update"],
178
195
  conflict: entry("A property's current value differs from the 'from' value the request expected; the refusal names each such property and its current value.", { option: "Read the current properties, reconcile the changes and send the update again with the from values you now expect." }),
179
196
  "unknown-type": entry("The requested type is not a current object or edge type in this namespace.", { option: "Read the current types and use an object or edge type they contain.", read: "readCatalogue" }),
180
197
  "type-version-precondition": entry("The type version the request expected differs from the type's current version.", { option: "Read the type's current version and send the request with that version." }),
181
198
  "invalid-properties": entry("The property document failed validation; the violations name each offending path and rule.", { option: "Correct the properties at the paths the violations name and send the request again." }),
182
- "trunk-write": entry("The trunk takes changes only by a merge from a branch, so this write was refused.", { option: "Make the change on a branch you can write." }),
183
199
  "blocked-by-edge": entry("The object has a link you may not remove, so nothing was deleted.", { option: "Remove that link first, or ask someone who may to remove it." }),
184
200
  "endpoint-type-mismatch": entry("An endpoint's object type does not match the source or target type required by the link's type version.", { option: "Use endpoints whose object types match the link's source and target types." }),
185
201
  "cross-organisation-edge": entry("The link's endpoints belong to projects in different organisations.", { option: "Choose endpoints whose projects belong to the same organisation." }),
@@ -212,7 +228,11 @@ var API_REFUSAL_RULES = Object.freeze([
212
228
  "unknown-type",
213
229
  "type-version-precondition",
214
230
  "invalid-properties",
215
- "trunk-write",
231
+ "immutable",
232
+ "owned",
233
+ "frozen",
234
+ "protected",
235
+ "branch-needs-schema-update",
216
236
  "blocked-by-edge",
217
237
  "write-refused",
218
238
  "endpoint-type-mismatch",
@@ -224,9 +244,44 @@ var API_REFUSAL_RULES = Object.freeze([
224
244
  var SHIM_COLLIDING_API_RULES = Object.freeze(["write-refused"]);
225
245
 
226
246
  // packages/api-contract/dist/route-docs.js
247
+ function need(capability, scope) {
248
+ return Object.freeze({ capability, scope });
249
+ }
250
+ var PROTECTION_ROUTE_CAPABILITIES = Object.freeze({
251
+ addRefRule: Object.freeze([need("administer projects", "project")]),
252
+ listRefRules: Object.freeze([need("read", "project")]),
253
+ removeRefRule: Object.freeze([need("administer projects", "project")]),
254
+ addOrganisationRule: Object.freeze([need("administer projects", "organisation")]),
255
+ listOrganisationRules: Object.freeze([need("read", "organisation")]),
256
+ removeOrganisationRule: Object.freeze([need("administer projects", "organisation")]),
257
+ createOrganisationRef: Object.freeze([need("administer projects", "organisation")]),
258
+ listOrganisationRefs: Object.freeze([need("read", "organisation")]),
259
+ deleteOrganisationRef: Object.freeze([need("administer projects", "organisation")]),
260
+ markOrganisationRefReadOnly: Object.freeze([need("administer projects", "organisation")]),
261
+ setObjectLock: Object.freeze([need("lock objects", "project")]),
262
+ readAgentCeiling: Object.freeze([need("read", "project")]),
263
+ setAgentCeiling: Object.freeze([need("administer projects", "project")]),
264
+ requestSchemaUpdate: Object.freeze([
265
+ need("write ref", "branch-home-project"),
266
+ need("administer projects", "organisation-ref")
267
+ ]),
268
+ readSchemaUpdateRequest: Object.freeze([Object.freeze({ visibility: "requester-in-token-organisation" })])
269
+ });
227
270
  var SIGNED_IN = ["unauthenticated", "invalid-request", "bound-exceeded", "internal"];
228
271
  var SCOPED = [...SIGNED_IN, "not-found", "not-permitted"];
229
272
  var WRITE = [...SCOPED, "in-flight", "unknown-outcome"];
273
+ var REF_READ = [...SCOPED, "organisation-not-ready", "branch-needs-schema-update"];
274
+ var REF_WRITE = [...REF_READ, "in-flight", "unknown-outcome"];
275
+ var OBJECT_WRITE = [
276
+ ...REF_WRITE,
277
+ "immutable",
278
+ "owned",
279
+ "frozen",
280
+ "protected",
281
+ "read-only-ref",
282
+ "concurrent-change",
283
+ "lock-unavailable"
284
+ ];
230
285
  var PUBLIC = ["invalid-request", "bound-exceeded", "internal"];
231
286
  var CHANGE_SET = [...SIGNED_IN, "not-permitted", "stale-revision", "not-open"];
232
287
  var QUERY_PROJECT_SCOPE = 'Without input.projects, it reads every project the caller can read on the selected ref; selector.projectId does not narrow the set. To limit it to one project, include "projects":["<projectId>"] in input.';
@@ -246,15 +301,15 @@ var ROUTE_DOCS = Object.freeze({
246
301
  serviceCredential: doc("Exchanges a service credential for an access token.", [...PUBLIC, "unknown-credential", "secret", "kind", "lifecycle"]),
247
302
  registerClient: doc("Registers a third-party application as a client of the caller's organisation.", [...SIGNED_IN, "not-permitted", "capability-missing"]),
248
303
  listClients: doc("Lists the clients of the caller's organisation, for a caller who administers them.", SCOPED),
249
- revokeClient: doc("Revokes a client; its grants and tokens stop on their next use.", SCOPED),
250
- replaceClientKeys: doc("Replaces a confidential client's public key set.", SCOPED),
304
+ revokeClient: doc("Revokes a client; its grants and tokens stop on their next use.", [...SCOPED, "no-such-client"]),
305
+ replaceClientKeys: doc("Replaces a confidential client's public key set.", [...SCOPED, "no-such-client"]),
251
306
  readConsentRequest: doc("Reads a pending authorization request for the person who signed in inside it.", SCOPED),
252
307
  decideConsent: doc("Approves or denies a pending authorization request.", SCOPED),
253
308
  listConnectedApps: doc("Lists the applications the caller has connected.", SIGNED_IN),
254
309
  revokeConnectedApp: doc("Disconnects an application: every grant the caller holds for it stops.", SCOPED),
255
- readObject: doc("Reads one object at a ref.", SCOPED),
310
+ readObject: doc("Reads one object at a ref.", REF_READ),
256
311
  queryObjects: doc(`Runs a filtered, ordered, paged query over the objects of a ref. ${QUERY_PROJECT_SCOPE}`, [
257
- ...SCOPED,
312
+ ...REF_READ,
258
313
  "malformed-query",
259
314
  "over-bound",
260
315
  "text-search-unavailable",
@@ -263,11 +318,11 @@ var ROUTE_DOCS = Object.freeze({
263
318
  "stale-continuation",
264
319
  "head-moved"
265
320
  ]),
266
- aggregateObjects: doc(`Runs an aggregate over the objects of a ref: a count, or a minimum, maximum or sum of one numeric property, answered as one value, or, with groupBy, a count or a sum grouped by type or by a property's value, or a count grouped by structural parent, answered as groups. ${QUERY_PROJECT_SCOPE}`, [...SCOPED, "malformed-query", "over-bound", "text-search-unavailable", "search-too-wide"]),
267
- discoverVerbs: doc("Lists the verbs an object may be given.", SCOPED),
268
- readHistory: doc("Reads one object's history of changes.", [...SCOPED, "invalid-continuation", "stale-continuation", "malformed-history-request"]),
269
- mutate: doc('Creates, changes or deletes an object on a branch. For a property update, `from` is the value the caller expects now; a mismatch is refused as `conflict`, and `{"absent": true}` clears the property.', [...WRITE, "plan-closed", "protected-ref"]),
270
- mutateBatch: doc('Applies a list of object and edge operations to a branch in one save, one commit and one audit record, or none of them: a refused operation refuses the whole batch and writes nothing. An edge may name an object created earlier in the same batch. A batch with more operations than the maximum is refused as invalid-request naming input.operations, before any write. For a property update, `from` is the value the caller expects now; a mismatch is refused as `conflict`, and `{"absent": true}` clears the property.', [...WRITE, "plan-closed", "protected-ref"]),
321
+ aggregateObjects: doc(`Runs an aggregate over the objects of a ref: a count, or a minimum, maximum or sum of one numeric property, answered as one value, or, with groupBy, a count or a sum grouped by type or by a property's value, or a count grouped by structural parent, answered as groups. ${QUERY_PROJECT_SCOPE}`, [...REF_READ, "malformed-query", "over-bound", "text-search-unavailable", "search-too-wide"]),
322
+ discoverVerbs: doc("Lists the verbs an object may be given.", REF_READ),
323
+ readHistory: doc("Reads one object's history of changes.", [...REF_READ, "invalid-continuation", "stale-continuation", "malformed-history-request"]),
324
+ mutate: doc('Creates, changes or deletes an object on the selected ref. A protected refusal means the change needs a branch and a merge. For a property update, `from` is the value the caller expects now; a mismatch is refused as `conflict`, and `{"absent": true}` clears the property.', [...OBJECT_WRITE, "plan-closed"]),
325
+ mutateBatch: doc('Applies a list of object and edge operations to a branch in one save, one commit and one audit record, or none of them: a refused operation refuses the whole batch and writes nothing. An edge may name an object created earlier in the same batch. A batch with more operations than the maximum is refused as invalid-request naming input.operations, before any write. For a property update, `from` is the value the caller expects now; a mismatch is refused as `conflict`, and `{"absent": true}` clears the property.', [...OBJECT_WRITE, "plan-closed"]),
271
326
  createConnection: doc("Creates a connection to an external source system for the signed-in person: its mirror project and its service identity. The connection commits whether or not its types could be drafted, and the answer says which.", [...SIGNED_IN, "not-permitted", "organisation-not-active", "connection-exists"]),
272
327
  listConnections: doc("Lists the connections whose mirror project the caller reads, each with its last run and whether the caller may start a run.", SIGNED_IN),
273
328
  setConnectionCredential: doc("Stores the credential the connection reads the source system with, after the source system accepts it, and answers the upstream account and the time it was stored. The credential is never answered.", [...SCOPED, "credential-not-valid"]),
@@ -277,36 +332,48 @@ var ROUTE_DOCS = Object.freeze({
277
332
  startImportRun: doc("Starts a run that imports the connection's source into its mirror project, and answers the run once its row has committed.", [...SCOPED, "import-running", "connection-revoked", "types-not-approved"]),
278
333
  listImportRuns: doc("Lists the runs of a connection, newest first, or the one run named.", SCOPED),
279
334
  closeImportRun: doc("Ends a running run: the connection's service identity as succeeded or failed, or the person who started it as failed.", [...SCOPED, "run-not-running"]),
280
- previewDelete: doc("Previews deleting an object: the links the delete would remove, or the refusal it would meet. Changes nothing.", [...WRITE, "plan-closed", "protected-ref"]),
281
- recover: doc("Finds what an interrupted mutation did and finishes its record.", SCOPED),
282
- executeVerb: doc("Runs a verb on an object and records its result.", [...WRITE, "plan-closed", "protected-ref"]),
283
- createBranch: doc("Creates a branch from a ref.", [...WRITE, "plan-closed", "duplicate-name", "protected-ref"]),
284
- merge: doc("Merges one branch into another.", [...WRITE, "protected-ref"]),
285
- readMergeConflicts: doc("Reads the conflicts a merge refused on.", SCOPED),
286
- disposeRef: doc("Disposes a branch that is no longer wanted.", [...WRITE, "protected-ref"]),
335
+ previewDelete: doc("Previews deleting an object: the links the delete would remove, or the refusal it would meet. Changes nothing.", [...OBJECT_WRITE, "plan-closed"]),
336
+ recover: doc("Finds what an interrupted mutation did and finishes its record.", REF_READ),
337
+ executeVerb: doc("Runs a verb on an object and records its result.", [...OBJECT_WRITE, "plan-closed"]),
338
+ createBranch: doc("Creates a branch from a ref or an admitted current ref head.", [
339
+ ...REF_WRITE,
340
+ "plan-closed",
341
+ "ref-exists",
342
+ "read-only-ref",
343
+ "concurrent-change",
344
+ "lock-unavailable",
345
+ "not-a-ref-head"
346
+ ]),
347
+ merge: doc("Merges a reviewed head of a project branch or an organisation-ref source into main, or into the ref the optional target names.", [...REF_WRITE, "read-only-ref", "concurrent-change", "lock-unavailable", "not-a-ref-head"]),
348
+ readMergeConflicts: doc("Reads the conflicts a merge refused on.", REF_READ),
349
+ disposeRef: doc("Disposes a branch that is no longer wanted.", [
350
+ ...WRITE,
351
+ "concurrent-change",
352
+ "lock-unavailable"
353
+ ]),
287
354
  listProjects: doc("Lists every project the caller may read in the organisation the session is attached to, with its namespace.", SIGNED_IN),
288
- listRefs: doc("Lists the refs of a project, each with its head: readable with a hash, withheld when the caller may not read the ref, or unread past the first 50 refs.", SCOPED),
355
+ listRefs: doc("Lists the refs of a project, each with its head: readable with a hash, withheld when the caller may not read the ref, or unread past the first 50 refs.", [...SCOPED, "organisation-not-ready"]),
289
356
  readCatalogue: doc("Reads the published catalogue of a project's namespace.", SCOPED),
290
357
  readPendingProposal: doc("Reads the proposal waiting for review on a branch, if any.", SCOPED),
291
- submitProposal: doc("Submits a branch for review.", [...SCOPED, "proposal-open", "no-commits"]),
358
+ submitProposal: doc("Submits a branch for review.", [...REF_READ, "proposal-open", "no-commits"]),
292
359
  createServiceIdentity: doc("Creates a service identity in an organisation.", [...SIGNED_IN, "not-permitted", "capability-missing", "duplicate-name"]),
293
360
  revokeServiceIdentity: doc("Revokes a service identity.", [...SIGNED_IN, "not-permitted", "service_identity.revoke"]),
294
361
  issueServiceCredential: doc("Issues a credential for a service identity.", [...SIGNED_IN, "not-permitted", "capability-missing"]),
295
362
  revokeServiceCredential: doc("Revokes a service identity's credential.", [...SIGNED_IN, "not-permitted", "capability-missing"]),
296
363
  grantMembership: doc("Grants a principal a role in a scope.", [...SIGNED_IN, "not-permitted", "above-caller-role", "membership-exists", "no-such-principal"]),
297
- revokeMembership: doc("Revokes a principal's membership of a scope.", [...SIGNED_IN, "not-permitted", "last-administrator", "no-such-membership", "erasure-not-permitted"]),
364
+ revokeMembership: doc("Revokes a principal's membership of a scope; at organisation scope, also revokes their memberships in its projects.", [...SIGNED_IN, "not-permitted", "last-administrator", "no-such-membership", "erasure-not-permitted"]),
298
365
  setModelConfiguration: doc("Sets the model configuration an organisation's agents use.", [...SIGNED_IN, "not-permitted", "capability-missing", "window-out-of-range", "max-session-out-of-range"]),
299
366
  readDisplayNames: doc("Reads the display names of principals the caller may see.", SIGNED_IN),
300
367
  listMembers: doc("Lists the members of a project.", SCOPED),
301
368
  changeMembershipRole: doc("Changes a principal's role in a scope.", [...SIGNED_IN, "not-permitted", "above-caller-role", "last-administrator", "no-such-membership"]),
302
369
  listOrganisationMembers: doc("Lists the memberships of the caller's organisation and of the projects of it the caller may read.", SCOPED),
303
370
  searchPeople: doc("Finds the people of the caller's organisation whose name has a word starting with the query.", [...SIGNED_IN, "invalid-query"]),
304
- searchObjectReaders: doc("Finds the people who can read an object on a ref, by a name prefix.", [...SCOPED, "invalid-query", "invalid-ref"]),
305
- listSubscribers: doc("Lists the subscriptions on an object.", SCOPED),
306
- addSubscriber: doc("Adds a person to an object's subscriptions as owner, approver or watcher, when the type's `subscriptionRoles` admits that role. The declaration may set maxima; owner is limited to one. With no declaration, only watchers are admitted.", [...SCOPED, "limit-reached", "role-not-admitted", "unknown-person", "not-permitted", "invalid-ref"]),
307
- removeSubscriber: doc("Removes a person's subscription to an object in a role.", [...SCOPED, "subscription-absent", "not-permitted", "invalid-ref"]),
308
- listComments: doc("Lists the comments on an object, oldest first, a page at a time.", [...SCOPED, "invalid-query"]),
309
- addComment: doc("Adds a comment to an object, naming the people it mentions.", [...WRITE, "invalid-body", "invalid-mention", "unknown-person", "invalid-ref"]),
371
+ searchObjectReaders: doc("Finds the people who can read an object on a ref, by a name prefix.", [...REF_READ, "invalid-query", "invalid-ref"]),
372
+ listSubscribers: doc("Lists the subscriptions on an object.", REF_READ),
373
+ addSubscriber: doc("Adds a person to an object's subscriptions as owner, approver or watcher, when the type's `subscriptionRoles` admits that role. The declaration may set maxima; owner is limited to one. With no declaration, only watchers are admitted.", [...REF_READ, "limit-reached", "role-not-admitted", "unknown-person", "not-permitted", "invalid-ref"]),
374
+ removeSubscriber: doc("Removes a person's subscription to an object in a role.", [...REF_READ, "subscription-absent", "not-permitted", "invalid-ref"]),
375
+ listComments: doc("Lists the comments on an object, oldest first, a page at a time.", [...REF_READ, "invalid-query"]),
376
+ addComment: doc("Adds a comment to an object, naming the people it mentions.", [...REF_WRITE, "invalid-body", "invalid-mention", "unknown-person", "invalid-ref"]),
310
377
  listServiceIdentities: doc("Lists the service identities of the caller's organisation, for a caller who administers them.", SCOPED),
311
378
  createInvitation: doc("Invites an email address to the caller's organisation in a role, for a caller who administers its members.", [...SIGNED_IN, "capability-missing", "above-caller-role", "invalid-email", "already-invited", "domain-not-allowed"]),
312
379
  listInvitations: doc("Lists the pending invitations of the caller's organisation, for a caller who administers its members.", [...SIGNED_IN, "capability-missing"]),
@@ -320,25 +387,43 @@ var ROUTE_DOCS = Object.freeze({
320
387
  reissuePlatformInvitation: doc("Replaces the code and the expiry of a pending platform invitation and answers the new link, for a platform administrator.", [...SIGNED_IN, "capability-missing", "no-such-invitation", "invitation-not-pending"]),
321
388
  readWebOrigin: doc("Reads the origin of the deployment's web application, or null when the host is given none.", SIGNED_IN),
322
389
  readCallerCapabilities: doc("Lists the capabilities the caller holds in the token's organisation and its projects, and the platform capability for a platform administrator.", [...SIGNED_IN]),
323
- queryGraph: doc(`Walks the edges from a start of addresses or an object set, and answers the objects reached with every hop among them. One start address with one hop step of depth 1 answers that object's connections, with a marker for each kind of connection whose far object the caller cannot read, and pages with a continuation. For an object-set start, omitting input.start.set.projects reads every project the caller can read on the selected ref; selector.projectId does not narrow the start set. To limit it to one project, include "projects":["<projectId>"] in input.start.set.`, [...SCOPED, "malformed-query", "over-bound", "text-search-unavailable", "search-too-wide", "invalid-continuation", "stale-continuation", "head-moved"]),
390
+ queryGraph: doc(`Walks the edges from a start of addresses or an object set, and answers the objects reached with every hop among them. One start address with one hop step of depth 1 answers that object's connections, with a marker for each kind of connection whose far object the caller cannot read, and pages with a continuation. For an object-set start, omitting input.start.set.projects reads every project the caller can read on the selected ref; selector.projectId does not narrow the start set. To limit it to one project, include "projects":["<projectId>"] in input.start.set.`, [...REF_READ, "malformed-query", "over-bound", "text-search-unavailable", "search-too-wide", "invalid-continuation", "stale-continuation", "head-moved"]),
324
391
  readStructure: doc("Reads the structure of a type's objects as a tree: every root, one object's subtree or one object's ancestors, each placement an occurrence. A chosen relationship or one-step view places the type itself, and the types above it follow their own declared relationships.", [
325
- ...SCOPED,
392
+ ...REF_READ,
326
393
  "malformed-query",
327
394
  "view-not-drawable",
328
395
  "over-bound",
329
396
  "head-moved"
330
397
  ]),
331
- findPaths: doc("Finds every shortest path between two objects within a length, answered as the hops each path follows.", [...SCOPED, "malformed-query", "over-bound", "head-moved"]),
398
+ findPaths: doc("Finds every shortest path between two objects within a length, answered as the hops each path follows.", [...REF_READ, "malformed-query", "over-bound", "head-moved"]),
332
399
  listProposals: doc("Lists the proposals of a project.", SCOPED),
333
400
  readProposal: doc("Reads one proposal.", SCOPED),
334
401
  refuseProposal: doc("Refuses a proposal and returns its branch to its author.", [...SCOPED, "proposal-not-pending"]),
335
- readBranchDiff: doc("Reads what a proposal's branch changes against its target.", SCOPED),
336
- diffBranches: doc("Reads what one ref changes against another.", SCOPED),
402
+ readBranchDiff: doc("Reads what a proposal's branch changes against its target.", REF_READ),
403
+ diffBranches: doc("Reads what one ref changes against another.", REF_READ),
337
404
  readBranchHistory: doc("Reads a branch's history of commits.", [...SCOPED, "invalid-continuation", "stale-continuation", "malformed-history-request"]),
338
405
  listAudit: doc("Lists the audit records of a project.", [...SCOPED, "invalid-continuation"]),
339
- readRefHead: doc("Reads the head of a ref.", SCOPED),
406
+ readRefHead: doc("Reads the head of a ref.", REF_READ),
340
407
  inspectBranchOperation: doc("Reads the state of a branch operation the caller started.", SCOPED),
341
- abandonBranch: doc("Abandons a branch and records why.", [...WRITE, "protected-ref"]),
408
+ abandonBranch: doc("Refuses branch abandonment because this operation is not available in this release.", [
409
+ ...WRITE,
410
+ "not-in-this-release"
411
+ ]),
412
+ addRefRule: doc("Adds a ref protection rule to the selected project and answers the full rule. Repeating the same pattern and consequence returns the existing rule, ignoring pattern letter case. Consequences are merge-only and locked-rows-only; an invalid pattern or consequence answers invalid-request naming that field.", SCOPED),
413
+ listRefRules: doc("Lists only the selected project's visible ref protection rules, including each rule's identifier, pattern and consequence.", SCOPED),
414
+ removeRefRule: doc("Removes a rule by identifier from the selected project and answers its ruleId. An absent or unreadable identifier, or one belonging to another project, answers not-found.", SCOPED),
415
+ addOrganisationRule: doc("Adds a ref protection rule in the attached organisation and answers the full rule. Repeating the same pattern and consequence returns the existing rule, ignoring pattern letter case. An invalid pattern or consequence answers invalid-request naming that field.", SCOPED),
416
+ listOrganisationRules: doc("Lists the attached organisation's visible ref rules, including identifier, pattern and consequence.", SCOPED),
417
+ removeOrganisationRule: doc("Removes a rule by identifier from the attached organisation and answers its ruleId. An absent, unreadable or project-rule identifier answers not-found.", SCOPED),
418
+ createOrganisationRef: doc("Creates a ref at the organisation's trunk head and answers its name, readOnly and creation attribution. The optional readOnly flag defaults to false; the name is one non-reserved ref segment.", [...WRITE, "ref-exists", "read-only-ref", "organisation-not-ready"]),
419
+ listOrganisationRefs: doc("Lists visible refs in the attached organisation, including each name, readOnly and creation attribution.", SCOPED),
420
+ deleteOrganisationRef: doc("Deletes a writable ref from the attached organisation and answers its name. A read-only ref cannot be deleted.", [...REF_WRITE, "read-only-ref"]),
421
+ markOrganisationRefReadOnly: doc("Marks a ref in the attached organisation read-only and answers the full ref. An absent or unreadable name answers not-found.", SCOPED),
422
+ setObjectLock: doc("Sets an object's lock on the selected project ref to none, merge-only or frozen and answers objectId and lock. The lock also applies to its existing structural child edges in that project; none lifts the lock. New children on a live ref are not thereby frozen. Use a read-only organisation ref for a fixed baseline. An unknown lock answers invalid-request at lock.", [...REF_WRITE, "read-only-ref", "concurrent-change", "lock-unavailable"]),
423
+ readAgentCeiling: doc("Reads the selected project's maximum agent role as { ceiling }: contributor or maintainer. An absent or unreadable project answers not-found.", SCOPED),
424
+ setAgentCeiling: doc("Sets the selected project's maximum agent role to contributor or maintainer and answers { ceiling }. An unknown role answers invalid-request at ceiling.", SCOPED),
425
+ requestSchemaUpdate: doc("Records a schema update request for a project branch or organisation ref and answers its state and attribution. Execution is separate. A branch whose name contains a slash cannot request a schema update today. The trunk main answers invalid-request at ref; an unreachable ref answers not-found.", SCOPED),
426
+ readSchemaUpdateRequest: doc("Reads the caller's latest schema update request for the ref in the token's organisation, or null. Project read access is not required. A branch whose name contains a slash cannot read its request today. The trunk main answers invalid-request at ref.", SCOPED),
342
427
  openAgentSession: doc("Opens an agent session on a project.", [...SCOPED, "invalid-configuration"]),
343
428
  listAgentSessions: doc("Lists the caller's agent sessions on a project.", SCOPED),
344
429
  readAgentSession: doc("Reads one agent session and its runs.", SCOPED),
@@ -416,7 +501,7 @@ var SCHEMAS = {
416
501
  recover: { "type": "object", "properties": { "correlationId": { "type": "string" } }, "required": ["correlationId"], "additionalProperties": false },
417
502
  executeVerb: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "verb": { "type": "object", "properties": { "typeKey": { "type": "string" }, "typeVersion": { "type": "number" } }, "required": ["typeKey", "typeVersion"], "additionalProperties": false }, "subjectIds": { "type": "array", "items": { "type": "string" } }, "input": {} }, "required": ["correlation", "verb", "subjectIds", "input"], "additionalProperties": false },
418
503
  createBranch: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "name": { "type": "string" }, "from": { "oneOf": [{ "type": "object", "properties": { "ref": { "type": "string" } }, "required": ["ref"], "additionalProperties": false }, { "type": "object", "properties": { "hash": { "type": "string" } }, "required": ["hash"], "additionalProperties": false }] } }, "required": ["correlation", "name"], "additionalProperties": false },
419
- merge: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "sourceHash": { "type": "string" }, "message": { "type": "string" }, "name": { "type": "string" } }, "required": ["correlation", "sourceHash", "message"], "additionalProperties": false },
504
+ merge: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "sourceHash": { "type": "string" }, "message": { "type": "string" }, "name": { "type": "string" }, "source": { "type": "string" }, "target": { "type": "string" } }, "required": ["correlation", "sourceHash", "message"], "additionalProperties": false },
420
505
  readMergeConflicts: { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false },
421
506
  disposeRef: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "name": { "type": "string" }, "atHash": { "type": "string" } }, "required": ["correlation", "name", "atHash"], "additionalProperties": false },
422
507
  listProjects: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
@@ -461,6 +546,21 @@ var SCHEMAS = {
461
546
  readRefHead: { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false },
462
547
  inspectBranchOperation: { "type": "object", "properties": { "correlationId": { "type": "string" } }, "required": ["correlationId"], "additionalProperties": false },
463
548
  abandonBranch: { "type": "object", "properties": { "correlation": { "oneOf": [{ "type": "object", "properties": { "kind": { "const": "mint" } }, "required": ["kind"], "additionalProperties": false }, { "type": "object", "properties": { "kind": { "const": "sent" }, "correlationId": { "type": "string" } }, "required": ["kind", "correlationId"], "additionalProperties": false }] }, "name": { "type": "string" }, "atHash": { "type": "string" } }, "required": ["correlation", "name", "atHash"], "additionalProperties": false },
549
+ addRefRule: { "type": "object", "properties": { "pattern": { "type": "string" }, "consequence": { "enum": ["merge-only", "locked-rows-only"] } }, "required": ["pattern", "consequence"], "additionalProperties": false },
550
+ listRefRules: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
551
+ removeRefRule: { "type": "object", "properties": { "ruleId": { "type": "string" } }, "required": ["ruleId"], "additionalProperties": false },
552
+ addOrganisationRule: { "type": "object", "properties": { "pattern": { "type": "string" }, "consequence": { "enum": ["merge-only", "locked-rows-only"] } }, "required": ["pattern", "consequence"], "additionalProperties": false },
553
+ listOrganisationRules: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
554
+ removeOrganisationRule: { "type": "object", "properties": { "ruleId": { "type": "string" } }, "required": ["ruleId"], "additionalProperties": false },
555
+ createOrganisationRef: { "type": "object", "properties": { "name": { "type": "string" }, "readOnly": { "type": "boolean" } }, "required": ["name"], "additionalProperties": false },
556
+ listOrganisationRefs: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
557
+ deleteOrganisationRef: { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false },
558
+ markOrganisationRefReadOnly: { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"], "additionalProperties": false },
559
+ setObjectLock: { "type": "object", "properties": { "objectId": { "type": "string" }, "lock": { "enum": ["none", "merge-only", "frozen"] } }, "required": ["objectId", "lock"], "additionalProperties": false },
560
+ readAgentCeiling: { "type": "object", "properties": {}, "required": [], "additionalProperties": false },
561
+ setAgentCeiling: { "type": "object", "properties": { "ceiling": { "enum": ["contributor", "maintainer"] } }, "required": ["ceiling"], "additionalProperties": false },
562
+ requestSchemaUpdate: { "type": "object", "properties": { "ref": { "type": "string" } }, "required": ["ref"], "additionalProperties": false },
563
+ readSchemaUpdateRequest: { "type": "object", "properties": { "ref": { "type": "string" } }, "required": ["ref"], "additionalProperties": false },
464
564
  openAgentSession: { "type": "object", "properties": { "agent": { "enum": ["object", "ontology"] } }, "required": ["agent"], "additionalProperties": false },
465
565
  listAgentSessions: { "type": "object", "properties": { "agent": { "enum": ["object", "ontology"] } }, "required": ["agent"], "additionalProperties": false },
466
566
  readAgentSession: { "type": "object", "properties": { "agentSessionId": { "type": "string" } }, "required": ["agentSessionId"], "additionalProperties": false },
@@ -587,6 +687,21 @@ var EXAMPLES = {
587
687
  readRefHead: { "name": "<name>" },
588
688
  inspectBranchOperation: { "correlationId": "<correlationId>" },
589
689
  abandonBranch: { "correlation": { "kind": "mint" }, "name": "<name>", "atHash": "<atHash>" },
690
+ addRefRule: { "pattern": "<pattern>", "consequence": "merge-only" },
691
+ listRefRules: {},
692
+ removeRefRule: { "ruleId": "<ruleId>" },
693
+ addOrganisationRule: { "pattern": "<pattern>", "consequence": "merge-only" },
694
+ listOrganisationRules: {},
695
+ removeOrganisationRule: { "ruleId": "<ruleId>" },
696
+ createOrganisationRef: { "name": "<name>" },
697
+ listOrganisationRefs: {},
698
+ deleteOrganisationRef: { "name": "<name>" },
699
+ markOrganisationRefReadOnly: { "name": "<name>" },
700
+ setObjectLock: { "objectId": "<objectId>", "lock": "none" },
701
+ readAgentCeiling: {},
702
+ setAgentCeiling: { "ceiling": "contributor" },
703
+ requestSchemaUpdate: { "ref": "<ref>" },
704
+ readSchemaUpdateRequest: { "ref": "<ref>" },
590
705
  openAgentSession: { "agent": "object" },
591
706
  listAgentSessions: { "agent": "object" },
592
707
  readAgentSession: { "agentSessionId": "<agentSessionId>" },
@@ -634,7 +749,7 @@ var EXAMPLES = {
634
749
  var ROUTE_INPUT_EXAMPLES = deepFreeze(EXAMPLES);
635
750
 
636
751
  // packages/api-contract/dist/routes.js
637
- var PROTOCOL_VERSION = 23;
752
+ var PROTOCOL_VERSION = 24;
638
753
  var VERSION_HEADER = "oe2-protocol-version";
639
754
  var TOKEN_HEADER = "authorization";
640
755
  var IDEMPOTENCY_HEADER = "oe2-idempotency-key";
@@ -734,6 +849,21 @@ var ROUTES = Object.freeze({
734
849
  readRefHead: route({ name: "readRefHead", method: "POST", path: "/branches/head", group: "read", selector: "project", subject: "project", idempotent: false, admission: admitting("user", "service", "run-explore", "run-apply", "run-types", "client"), versioned: true, module: "branches-sdk", capability: "branches.head" }),
735
850
  inspectBranchOperation: route({ name: "inspectBranchOperation", method: "POST", path: "/branch-inspections", group: "branch", selector: "project", subject: "project", idempotent: false, admission: admitting("user", "service", "client"), versioned: true, module: "branches-sdk", capability: "branches.inspection" }),
736
851
  abandonBranch: route({ name: "abandonBranch", method: "POST", path: "/branch-abandonments", group: "branch", selector: "project", subject: "project", idempotent: true, admission: admitting("user", "service", "client"), versioned: true, module: "branches-sdk", capability: "branches.abandonment" }),
852
+ addRefRule: route({ name: "addRefRule", method: "POST", path: "/ref-rules", group: "administration", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.ref-rules" }),
853
+ listRefRules: route({ name: "listRefRules", method: "POST", path: "/projects/ref-rules", group: "read", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.ref-rules" }),
854
+ removeRefRule: route({ name: "removeRefRule", method: "POST", path: "/ref-rule-removals", group: "administration", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.ref-rules" }),
855
+ addOrganisationRule: route({ name: "addOrganisationRule", method: "POST", path: "/organisation-ref-rules", group: "administration", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-ref-rules" }),
856
+ listOrganisationRules: route({ name: "listOrganisationRules", method: "POST", path: "/organisation-ref-rules/list", group: "read", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-ref-rules" }),
857
+ removeOrganisationRule: route({ name: "removeOrganisationRule", method: "POST", path: "/organisation-ref-rule-removals", group: "administration", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-ref-rules" }),
858
+ createOrganisationRef: route({ name: "createOrganisationRef", method: "POST", path: "/organisation-refs", group: "administration", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-refs" }),
859
+ listOrganisationRefs: route({ name: "listOrganisationRefs", method: "POST", path: "/organisation-refs/list", group: "read", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-refs" }),
860
+ deleteOrganisationRef: route({ name: "deleteOrganisationRef", method: "POST", path: "/organisation-ref-deletions", group: "administration", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-refs" }),
861
+ markOrganisationRefReadOnly: route({ name: "markOrganisationRefReadOnly", method: "POST", path: "/organisation-ref-read-only", group: "administration", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.organisation-refs" }),
862
+ setObjectLock: route({ name: "setObjectLock", method: "POST", path: "/object-locks", group: "write", selector: "object", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "objects-sdk", capability: "objects.locks" }),
863
+ readAgentCeiling: route({ name: "readAgentCeiling", method: "POST", path: "/projects/agent-ceiling", group: "read", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.agent-ceiling" }),
864
+ setAgentCeiling: route({ name: "setAgentCeiling", method: "POST", path: "/agent-ceiling", group: "administration", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "admin-sdk", capability: "admin.agent-ceiling" }),
865
+ requestSchemaUpdate: route({ name: "requestSchemaUpdate", method: "POST", path: "/schema-update-requests", group: "branch", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "branches-sdk", capability: "branches.schema-update" }),
866
+ readSchemaUpdateRequest: route({ name: "readSchemaUpdateRequest", method: "POST", path: "/schema-update-requests/read", group: "branch", selector: "none", subject: "none", idempotent: false, admission: admitting("user"), versioned: true, module: "branches-sdk", capability: "branches.schema-update" }),
737
867
  openAgentSession: route({ name: "openAgentSession", method: "POST", path: "/agent-sessions", group: "agent", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "agents-sdk", capability: "agents.session" }),
738
868
  listAgentSessions: route({ name: "listAgentSessions", method: "POST", path: "/agent-sessions/list", group: "agent", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "agents-sdk", capability: "agents.sessionList" }),
739
869
  readAgentSession: route({ name: "readAgentSession", method: "POST", path: "/agent-sessions/read", group: "agent", selector: "project", subject: "project", idempotent: false, admission: admitting("user"), versioned: true, module: "agents-sdk", capability: "agents.sessionRead" }),
@@ -853,6 +983,21 @@ var ROUTE_CARRIES_CREDENTIAL = Object.freeze({
853
983
  readRefHead: false,
854
984
  inspectBranchOperation: false,
855
985
  abandonBranch: false,
986
+ addRefRule: false,
987
+ listRefRules: false,
988
+ removeRefRule: false,
989
+ addOrganisationRule: false,
990
+ listOrganisationRules: false,
991
+ removeOrganisationRule: false,
992
+ createOrganisationRef: false,
993
+ listOrganisationRefs: false,
994
+ deleteOrganisationRef: false,
995
+ markOrganisationRefReadOnly: false,
996
+ setObjectLock: false,
997
+ readAgentCeiling: false,
998
+ setAgentCeiling: false,
999
+ requestSchemaUpdate: false,
1000
+ readSchemaUpdateRequest: false,
856
1001
  openAgentSession: false,
857
1002
  listAgentSessions: false,
858
1003
  readAgentSession: false,
@@ -908,7 +1053,7 @@ var CLIENT_SCOPES = Object.freeze({
908
1053
  "objects.write": Object.freeze({ capabilities: Object.freeze(["objects.write", "objects.verb"]), consentLine: "Create, change and delete objects, and run actions on them, as you." }),
909
1054
  "branches.read": Object.freeze({ capabilities: Object.freeze(["branches.head", "branches.diff", "branches.history", "branches.conflicts", "branches.inspection", "branches.pendingProposal", "branches.proposalList", "branches.proposalRead", "branches.proposalDiff", "branches.list"]), consentLine: "Read your branches, their history and proposals." }),
910
1055
  "branches.write": Object.freeze({ capabilities: Object.freeze(["branches.create", "branches.abandonment", "branches.proposalSubmit", "branches.proposalRefusal", "branches.head", "branches.diff", "branches.history", "branches.conflicts", "branches.inspection", "branches.pendingProposal", "branches.proposalList", "branches.proposalRead", "branches.proposalDiff", "branches.list"]), consentLine: "Create and abandon branches, submit and refuse proposals, and read your branches, as you." }),
911
- "branches.manage": Object.freeze({ capabilities: Object.freeze(["branches.merge", "branches.disposal", "objects.recovery"]), consentLine: "Merge branches into a project's trunk, delete branches and other refs, and recover changes, as you." }),
1056
+ "branches.manage": Object.freeze({ capabilities: Object.freeze(["branches.merge", "branches.disposal", "objects.recovery"]), consentLine: "Merge branches into the branches and trunks you may change, delete branches and other refs, and recover changes, as you." }),
912
1057
  offline_access: Object.freeze({ capabilities: Object.freeze([]), consentLine: "Stay connected when you are away, until you disconnect it." })
913
1058
  });
914
1059
 
@@ -1120,7 +1265,7 @@ function createHostFetch(socketPath, deadlineMs = REQUEST_DEADLINE_MS) {
1120
1265
  let written = false;
1121
1266
  let settled = false;
1122
1267
  let deadline = null;
1123
- const settle = (answer) => {
1268
+ const settle2 = (answer) => {
1124
1269
  if (settled) {
1125
1270
  return;
1126
1271
  }
@@ -1131,7 +1276,7 @@ function createHostFetch(socketPath, deadlineMs = REQUEST_DEADLINE_MS) {
1131
1276
  answer();
1132
1277
  };
1133
1278
  const lost = (error) => {
1134
- settle(() => {
1279
+ settle2(() => {
1135
1280
  reject(written ? new HostOutcomeUnknown(path, "connection-lost", `the connection to the host was lost after the request to ${path} was sent`) : error);
1136
1281
  });
1137
1282
  };
@@ -1152,7 +1297,7 @@ function createHostFetch(socketPath, deadlineMs = REQUEST_DEADLINE_MS) {
1152
1297
  }
1153
1298
  const status = incoming.statusCode;
1154
1299
  const reason = incoming.statusMessage;
1155
- settle(() => {
1300
+ settle2(() => {
1156
1301
  if (status === void 0) {
1157
1302
  reject(new Error("the host's response carried no status"));
1158
1303
  return;
@@ -1162,7 +1307,7 @@ function createHostFetch(socketPath, deadlineMs = REQUEST_DEADLINE_MS) {
1162
1307
  });
1163
1308
  });
1164
1309
  deadline = setTimeout(() => {
1165
- settle(() => {
1310
+ settle2(() => {
1166
1311
  reject(new HostOutcomeUnknown(path, "deadline", `the host did not answer ${path} within the deadline of ${String(deadlineMs)} ms`));
1167
1312
  });
1168
1313
  outgoing.destroy();
@@ -1498,7 +1643,7 @@ function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLIN
1498
1643
  let settled = false;
1499
1644
  let deadline = null;
1500
1645
  const path = `${url.pathname}${url.search}`;
1501
- const settle = (answer) => {
1646
+ const settle2 = (answer) => {
1502
1647
  if (settled) {
1503
1648
  return;
1504
1649
  }
@@ -1509,7 +1654,7 @@ function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLIN
1509
1654
  answer();
1510
1655
  };
1511
1656
  const lost = (error) => {
1512
- settle(() => {
1657
+ settle2(() => {
1513
1658
  reject(written ? new HostOutcomeUnknown(path, "connection-lost", `the connection to the host was lost after the request to ${path} was sent`) : error);
1514
1659
  });
1515
1660
  };
@@ -1529,7 +1674,7 @@ function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLIN
1529
1674
  incoming.on("end", () => {
1530
1675
  const status = incoming.statusCode;
1531
1676
  const reason = incoming.statusMessage;
1532
- settle(() => {
1677
+ settle2(() => {
1533
1678
  if (status === void 0) {
1534
1679
  reject(new Error("the host's response carried no status"));
1535
1680
  return;
@@ -1539,7 +1684,7 @@ function createHttpsTransport(origin, options = {}, deadlineMs = REQUEST_DEADLIN
1539
1684
  });
1540
1685
  });
1541
1686
  deadline = setTimeout(() => {
1542
- settle(() => {
1687
+ settle2(() => {
1543
1688
  reject(new HostOutcomeUnknown(path, "deadline", `the host did not answer ${path} within the deadline of ${String(deadlineMs)} ms`));
1544
1689
  });
1545
1690
  outgoing.destroy();
@@ -1610,6 +1755,18 @@ var ADMIN_ROUTE_NAMES = Object.freeze([
1610
1755
  "listAudit",
1611
1756
  "createProject",
1612
1757
  "createOrganisation",
1758
+ "addRefRule",
1759
+ "listRefRules",
1760
+ "removeRefRule",
1761
+ "addOrganisationRule",
1762
+ "listOrganisationRules",
1763
+ "removeOrganisationRule",
1764
+ "createOrganisationRef",
1765
+ "listOrganisationRefs",
1766
+ "deleteOrganisationRef",
1767
+ "markOrganisationRefReadOnly",
1768
+ "readAgentCeiling",
1769
+ "setAgentCeiling",
1613
1770
  "createConnection",
1614
1771
  "listConnections",
1615
1772
  "setConnectionCredential",
@@ -1668,7 +1825,9 @@ var BRANCHES_ROUTE_NAMES = Object.freeze([
1668
1825
  "readBranchHistory",
1669
1826
  "readRefHead",
1670
1827
  "inspectBranchOperation",
1671
- "abandonBranch"
1828
+ "abandonBranch",
1829
+ "requestSchemaUpdate",
1830
+ "readSchemaUpdateRequest"
1672
1831
  ]);
1673
1832
  function entries3() {
1674
1833
  return BRANCHES_ROUTE_NAMES.map((name) => ROUTES[name]);
@@ -1739,7 +1898,15 @@ function exploreViewAddress(address, state) {
1739
1898
  function projectAddress(organisationId, projectId, ref) {
1740
1899
  return withSubject(projectBase(organisationId, projectId), { ref, q: null });
1741
1900
  }
1742
- var ORGANISATION_ADMIN_PAGES = Object.freeze(["members", "service-identities", "clients", "agent-model", "connections", "projects/new"]);
1901
+ var ORGANISATION_ADMIN_PAGES = Object.freeze([
1902
+ "members",
1903
+ "service-identities",
1904
+ "clients",
1905
+ "agent-model",
1906
+ "connections",
1907
+ "refs",
1908
+ "projects/new"
1909
+ ]);
1743
1910
  function organisationAdminAddress(organisationId, page) {
1744
1911
  return `/orgs/${encodeURIComponent(organisationId)}/${page}`;
1745
1912
  }
@@ -1756,6 +1923,12 @@ function branchDiffAddress(organisationId, projectId, ref) {
1756
1923
 
1757
1924
  // packages/addresses/dist/links.js
1758
1925
  var ORGANISATION_PAGES = Object.freeze([...ORGANISATION_ADMIN_PAGES, "settings"]);
1926
+ function isLinkRef(ref, projectId) {
1927
+ if (ref === "main")
1928
+ return true;
1929
+ const parts = ref.split("/");
1930
+ return parts.length === 2 && parts[0] !== "" && parts[1] !== "" && (projectId === void 0 || parts[0] === projectId);
1931
+ }
1759
1932
  var ADMITTED_PAGES = new Set(ORGANISATION_PAGES);
1760
1933
  function isOrganisationPage(value) {
1761
1934
  return ADMITTED_PAGES.has(value);
@@ -1872,7 +2045,8 @@ var OBJECTS_ROUTE_NAMES = [
1872
2045
  "listComments",
1873
2046
  "addComment",
1874
2047
  "readApp",
1875
- "listApps"
2048
+ "listApps",
2049
+ "setObjectLock"
1876
2050
  ];
1877
2051
  function entries4() {
1878
2052
  return OBJECTS_ROUTE_NAMES.map((name) => ROUTES[name]);
@@ -2804,14 +2978,14 @@ function explanationLines(heading, explanation) {
2804
2978
  }
2805
2979
  return lines;
2806
2980
  }
2807
- function refusalLines(refusal2, settle, request3, host, reference) {
2981
+ function refusalLines(refusal2, settle2, request3, host, reference) {
2808
2982
  const lines = explanationLines(refusalHeading(refusal2), refusal2);
2809
2983
  const allowed = allowedFieldOf(refusal2, request3);
2810
2984
  if (request3 !== void 0 && allowed !== null && allowed.allowedKinds.length > 0 && allowed.discriminator !== void 0) {
2811
2985
  lines.push(`allowed ${allowed.discriminator}: ${allowed.allowedKinds.join(", ")}`, `${request3.command} --help shows each shape`);
2812
2986
  }
2813
2987
  if (refusal2.kind === "unknown-outcome") {
2814
- lines.push(`The operation may or may not have taken effect, and openeng will not repeat it. Settle it with: ${settle}`);
2988
+ lines.push(`The operation may or may not have taken effect, and openeng will not repeat it. Settle it with: ${settle2}`);
2815
2989
  }
2816
2990
  if (refusal2.kind === "protocol-mismatch" && host !== void 0) {
2817
2991
  const versions = `this CLI speaks protocol ${PROTOCOL_VERSION}; the host at ${host} speaks ${refusal2.versions.host}`;
@@ -2827,13 +3001,13 @@ function refusalLines(refusal2, settle, request3, host, reference) {
2827
3001
  }
2828
3002
  return lines;
2829
3003
  }
2830
- function refusalValue(refusal2, settle, request3, host, reference) {
3004
+ function refusalValue(refusal2, settle2, request3, host, reference) {
2831
3005
  const referenced = reference === void 0 ? {} : { reference };
2832
3006
  if (refusal2.kind === "protocol-mismatch") {
2833
3007
  return { ...refusal2, clientVersion: PROTOCOL_VERSION, ...host === void 0 ? {} : { host }, ...referenced };
2834
3008
  }
2835
3009
  const allowed = allowedFieldOf(refusal2, request3);
2836
- const settled = refusal2.kind === "unknown-outcome" ? { ...refusal2, settle } : refusal2;
3010
+ const settled = refusal2.kind === "unknown-outcome" ? { ...refusal2, settle: settle2 } : refusal2;
2837
3011
  return allowed === null || allowed.allowedKinds.length === 0 ? { ...settled, ...referenced } : { ...settled, allowedKinds: allowed.allowedKinds, ...referenced };
2838
3012
  }
2839
3013
  function jsonLine(value) {
@@ -2937,12 +3111,12 @@ var render = (outcome, json, scope, host) => {
2937
3111
  function failure(kind, exitCode, message, json) {
2938
3112
  return json ? { exitCode, stdout: jsonLine({ error: { kind, message } }), stderr: "" } : { exitCode, stdout: "", stderr: textBlock([message]) };
2939
3113
  }
2940
- var outcomeOfEnvelope = (envelope, settle, text, request3) => {
3114
+ var outcomeOfEnvelope = (envelope, settle2, text, request3) => {
2941
3115
  if ("refusal" in envelope) {
2942
3116
  return {
2943
3117
  kind: "refusal",
2944
3118
  refusal: envelope.refusal,
2945
- settle,
3119
+ settle: settle2,
2946
3120
  ...request3 === void 0 ? {} : { request: request3 },
2947
3121
  ...typeof envelope.correlationId === "string" ? { reference: envelope.correlationId } : {}
2948
3122
  };
@@ -2960,9 +3134,9 @@ var outcomeOfRefusedResult = (result) => isRefusedResult2(result) ? { kind: "ref
2960
3134
  function isRefusedResult2(value) {
2961
3135
  return typeof value === "object" && value !== null && "kind" in value && value.kind === "refused" && "explanation" in value && "explains" in value;
2962
3136
  }
2963
- function outcomeOfRouteRefusal(refusal2, settle, correlationId) {
3137
+ function outcomeOfRouteRefusal(refusal2, settle2, correlationId) {
2964
3138
  if ("explanation" in refusal2 && "explains" in refusal2) {
2965
- return { kind: "refusal", refusal: refusal2, settle, ...correlationId === null ? {} : { reference: correlationId } };
3139
+ return { kind: "refusal", refusal: refusal2, settle: settle2, ...correlationId === null ? {} : { reference: correlationId } };
2966
3140
  }
2967
3141
  return { kind: "internal-failure", message: `the host refused with ${refusal2.kind} and the SDK attached no explanation` };
2968
3142
  }
@@ -2988,18 +3162,18 @@ var STORE_NEXT_STEPS = {
2988
3162
  function storeFailureMessage(store, operation, nextStep) {
2989
3163
  return `the ${STORE_LABELS[store]} session store ${STORE_OPERATION_FAILURES[operation]}; ${STORE_NEXT_STEPS[nextStep]}`;
2990
3164
  }
2991
- var outcomeOfSessionFailure = (failure2, settle) => {
3165
+ var outcomeOfSessionFailure = (failure2, settle2) => {
2992
3166
  switch (failure2.reason) {
2993
3167
  case "not-signed-in":
2994
3168
  return { kind: "unauthenticated", message: notSignedInMessage(failure2.hostKey, failure2.store) };
2995
3169
  case "refresh-refused":
2996
- return outcomeOfRouteRefusal(failure2.refusal, settle, failure2.correlationId);
3170
+ return outcomeOfRouteRefusal(failure2.refusal, settle2, failure2.correlationId);
2997
3171
  case "store-failed":
2998
3172
  return { kind: "client-refusal", message: storeFailureMessage(failure2.store, failure2.operation, failure2.nextStep) };
2999
3173
  case "transport-failed":
3000
3174
  return { kind: "transport-failure", message: failure2.message };
3001
3175
  case "outcome-unknown":
3002
- return { kind: "unknown-outcome-no-answer", message: failure2.message, settle };
3176
+ return { kind: "unknown-outcome-no-answer", message: failure2.message, settle: settle2 };
3003
3177
  }
3004
3178
  };
3005
3179
  var outcomeOfHostRefusal = (refusal2) => ({
@@ -3051,7 +3225,7 @@ var outcomeOfLogout = (outcome) => {
3051
3225
  return outcomeOfSessionFailure(outcome.failure, "openeng whoami");
3052
3226
  }
3053
3227
  };
3054
- var outcomeOfOrganisationSwitch = (outcome, settle) => {
3228
+ var outcomeOfOrganisationSwitch = (outcome, settle2) => {
3055
3229
  switch (outcome.kind) {
3056
3230
  case "switched":
3057
3231
  return {
@@ -3060,9 +3234,9 @@ var outcomeOfOrganisationSwitch = (outcome, settle) => {
3060
3234
  text: outcome.organisation === null ? "detached from its organisation" : `attached to ${outcome.organisation.name} (${outcome.organisation.id})`
3061
3235
  };
3062
3236
  case "refused":
3063
- return outcomeOfRouteRefusal(outcome.refusal, settle, outcome.correlationId);
3237
+ return outcomeOfRouteRefusal(outcome.refusal, settle2, outcome.correlationId);
3064
3238
  case "failed":
3065
- return outcomeOfSessionFailure(outcome.failure, settle);
3239
+ return outcomeOfSessionFailure(outcome.failure, settle2);
3066
3240
  case "unexpected":
3067
3241
  return { kind: "internal-failure", message: outcome.message };
3068
3242
  }
@@ -4013,7 +4187,7 @@ async function runGenerated(entry2, context) {
4013
4187
  if (body.correlationMinted) {
4014
4188
  context.note("correlation: minted by the host");
4015
4189
  }
4016
- const settle = settleOf(entry2, key.key);
4190
+ const settle2 = settleOf(entry2, key.key);
4017
4191
  let run2;
4018
4192
  try {
4019
4193
  run2 = await runAuthenticated(context.host, context.deps, async (clients) => {
@@ -4046,12 +4220,12 @@ async function runGenerated(entry2, context) {
4046
4220
  message: REFRESH_UNANSWERED_ADVICE
4047
4221
  };
4048
4222
  }
4049
- return { kind: "unknown-outcome-no-answer", message: error.message, settle };
4223
+ return { kind: "unknown-outcome-no-answer", message: error.message, settle: settle2 };
4050
4224
  }
4051
4225
  throw error;
4052
4226
  }
4053
4227
  if (!run2.ok) {
4054
- return outcomeOfSessionFailure(run2.failure, settle);
4228
+ return outcomeOfSessionFailure(run2.failure, settle2);
4055
4229
  }
4056
4230
  const answer = run2.answer;
4057
4231
  if ("unavailable" in answer && typeof answer.unavailable === "string") {
@@ -4064,7 +4238,7 @@ async function runGenerated(entry2, context) {
4064
4238
  if (replayed) {
4065
4239
  context.note("the host answered a replay of an earlier request that carried this idempotency key");
4066
4240
  }
4067
- return outcomeOfEnvelope(replayed ? withoutReplay(answer) : answer, settle, (result) => JSON.stringify(result, null, 2), {
4241
+ return outcomeOfEnvelope(replayed ? withoutReplay(answer) : answer, settle2, (result) => JSON.stringify(result, null, 2), {
4068
4242
  command: `openeng ${entry2.module} ${entry2.name}`,
4069
4243
  schemas: schemasOf(entry2),
4070
4244
  sent: body.body
@@ -4166,13 +4340,13 @@ var orgCreate = {
4166
4340
  if (options.idempotencyKey === void 0) {
4167
4341
  note(`idempotency key: ${key}`);
4168
4342
  }
4169
- const settle = `repeat this command with --idempotency-key ${key}, to retrieve a recorded outcome or retry an attempt that never reached the host`;
4343
+ const settle2 = `repeat this command with --idempotency-key ${key}, to retrieve a recorded outcome or retry an attempt that never reached the host`;
4170
4344
  const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.createOrganisation({ input: { name } }, key));
4171
4345
  if (!run2.ok) {
4172
- return outcomeOfSessionFailure(run2.failure, settle);
4346
+ return outcomeOfSessionFailure(run2.failure, settle2);
4173
4347
  }
4174
4348
  if ("refusal" in run2.answer) {
4175
- return outcomeOfRouteRefusal(run2.answer.refusal, settle, run2.answer.correlationId);
4349
+ return outcomeOfRouteRefusal(run2.answer.refusal, settle2, run2.answer.correlationId);
4176
4350
  }
4177
4351
  return { kind: "success", value: run2.answer.result, text: `created organisation ${name} (${run2.answer.result.organisationId})` };
4178
4352
  }
@@ -8925,6 +9099,20 @@ var DemoForeignChangeSetError = class extends Error {
8925
9099
  this.name = "DemoForeignChangeSetError";
8926
9100
  }
8927
9101
  };
9102
+ var CATALOGUE_CATEGORY_LABELS = {
9103
+ object: "an object type",
9104
+ edge: "an edge type",
9105
+ verb: "a verb type"
9106
+ };
9107
+ var DemoCatalogueClashError = class extends Error {
9108
+ clashes;
9109
+ constructor(clashes) {
9110
+ const categories = clashes.map(({ typeKey, typeCategory, version }) => `${typeKey} is ${CATALOGUE_CATEGORY_LABELS[typeCategory]} at version ${String(version)}`).join(", ");
9111
+ super(`the organisation's catalogue already holds demo types the demo world cannot play into (${categories}); seed into an organisation whose catalogue does not hold these types`);
9112
+ this.name = "DemoCatalogueClashError";
9113
+ this.clashes = clashes;
9114
+ }
9115
+ };
8928
9116
  function demoRailGroups() {
8929
9117
  return DEMO_RAIL_GROUPS.map(({ name }) => name);
8930
9118
  }
@@ -9061,6 +9249,22 @@ async function playDemoWorld(clients, target, size, data) {
9061
9249
  throw new Error("the demo world declares no object type to check for an existing play");
9062
9250
  }
9063
9251
  const published = resultOf(await clients.objects.readCatalogue({ selector: { projectId: target.projectId }, input: {} }), "readCatalogue");
9252
+ const catalogueTypes = [...published.objects, ...published.edges, ...published.verbs];
9253
+ const demoTypes = [
9254
+ ...DEMO_OBJECT_TYPES.map(({ typeKey }) => ({ typeKey, typeCategory: "object" })),
9255
+ ...DEMO_EDGE_TYPES.map(({ typeKey }) => ({ typeKey, typeCategory: "edge" }))
9256
+ ];
9257
+ const clashes = [];
9258
+ for (const demoType of demoTypes) {
9259
+ for (const catalogueType of catalogueTypes) {
9260
+ if (catalogueType.typeKey === demoType.typeKey && (catalogueType.typeCategory !== demoType.typeCategory || catalogueType.version !== DEMO_TYPE_VERSION)) {
9261
+ clashes.push({ typeKey: demoType.typeKey, typeCategory: catalogueType.typeCategory, version: catalogueType.version });
9262
+ }
9263
+ }
9264
+ }
9265
+ if (clashes.length > 0) {
9266
+ throw new DemoCatalogueClashError(clashes);
9267
+ }
9064
9268
  if (!published.objects.some((one) => one.typeKey === firstObjectType.typeKey)) {
9065
9269
  await publishDemoTypes(clients.ontology, target.organisationId);
9066
9270
  }
@@ -9104,10 +9308,10 @@ async function playDemoWorld(clients, target, size, data) {
9104
9308
  // packages/cli/src/commands/demo-shared.ts
9105
9309
  async function runDemoSeed(context, projectId, size, world, data) {
9106
9310
  const { host, deps } = context;
9107
- const settle = `openeng types list --project ${projectId}, then repeat openeng demo seed, which completes what is left`;
9311
+ const settle2 = `openeng types list --project ${projectId}, then repeat openeng demo seed, which completes what is left`;
9108
9312
  const signedIn = await freshSession(host, deps);
9109
9313
  if (!signedIn.ok) {
9110
- return outcomeOfSessionFailure(signedIn.failure, settle);
9314
+ return outcomeOfSessionFailure(signedIn.failure, settle2);
9111
9315
  }
9112
9316
  const ownerPersonId = signedIn.session.principalId;
9113
9317
  const run2 = await runAuthenticated(host, deps, async (clients) => {
@@ -9134,15 +9338,18 @@ async function runDemoSeed(context, projectId, size, world, data) {
9134
9338
  if (error instanceof DemoForeignChangeSetError) {
9135
9339
  return { foreignChangeSet: error.message };
9136
9340
  }
9341
+ if (error instanceof DemoCatalogueClashError) {
9342
+ return { catalogueClash: error.message };
9343
+ }
9137
9344
  throw error;
9138
9345
  }
9139
9346
  });
9140
9347
  if (!run2.ok) {
9141
- return outcomeOfSessionFailure(run2.failure, settle);
9348
+ return outcomeOfSessionFailure(run2.failure, settle2);
9142
9349
  }
9143
9350
  const answer = run2.answer;
9144
9351
  if ("refusal" in answer) {
9145
- return outcomeOfRouteRefusal(answer.refusal, settle, answer.correlationId);
9352
+ return outcomeOfRouteRefusal(answer.refusal, settle2, answer.correlationId);
9146
9353
  }
9147
9354
  if ("refusedResult" in answer) {
9148
9355
  return outcomeOfRefusedResult(answer.refusedResult);
@@ -9150,6 +9357,9 @@ async function runDemoSeed(context, projectId, size, world, data) {
9150
9357
  if ("foreignChangeSet" in answer) {
9151
9358
  return { kind: "client-refusal", message: answer.foreignChangeSet };
9152
9359
  }
9360
+ if ("catalogueClash" in answer) {
9361
+ return { kind: "client-refusal", message: answer.catalogueClash };
9362
+ }
9153
9363
  if ("absent" in answer) {
9154
9364
  return { kind: "client-refusal", message: `the project ${projectId} is not among the projects you may list; create it with openeng projects create` };
9155
9365
  }
@@ -9216,13 +9426,13 @@ function createSecretPrompt(input, output) {
9216
9426
  return new Promise((resolve, reject) => {
9217
9427
  let value = "";
9218
9428
  let finished = false;
9219
- const finish = (settle) => {
9429
+ const finish = (settle2) => {
9220
9430
  finished = true;
9221
9431
  input.off("data", onData);
9222
9432
  input.setRawMode(false);
9223
9433
  input.pause();
9224
9434
  output.write("\n");
9225
- settle();
9435
+ settle2();
9226
9436
  };
9227
9437
  const consume = (text) => {
9228
9438
  const characters = [...text];
@@ -9331,14 +9541,14 @@ var connectorCreate = {
9331
9541
  if (!chosen.ok) {
9332
9542
  return chosen.outcome;
9333
9543
  }
9334
- const settle = "openeng admin-sdk listConnections, which only reads, shows whether the connection exists";
9544
+ const settle2 = "openeng admin-sdk listConnections, which only reads, shows whether the connection exists";
9335
9545
  context.note(CONNECTOR_DISCLOSURE);
9336
9546
  const run2 = await runAuthenticated(
9337
9547
  context.host,
9338
9548
  context.deps,
9339
9549
  (clients) => clients.admin.createConnection({ input: { name, system, stack, companyId: company === void 0 ? null : company, selection: chosen.selection } })
9340
9550
  );
9341
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, createdText) : outcomeOfSessionFailure(run2.failure, settle);
9551
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, createdText) : outcomeOfSessionFailure(run2.failure, settle2);
9342
9552
  }
9343
9553
  };
9344
9554
  function selectorOf(context) {
@@ -9381,14 +9591,14 @@ var connectorSetCredential = {
9381
9591
  if (secretKey === null || secretKey === "") {
9382
9592
  return { kind: "client-refusal", message: "openeng connector set-credential needs the secret key typed at a terminal prompt" };
9383
9593
  }
9384
- const settle = "openeng connector set-credential again; a repeat replaces the stored credential";
9594
+ const settle2 = "openeng connector set-credential again; a repeat replaces the stored credential";
9385
9595
  const run2 = await runAuthenticated(
9386
9596
  context.host,
9387
9597
  context.deps,
9388
9598
  (clients) => clients.admin.setConnectionCredential({ selector, input: { credential: { kind: "api-key", accessKey: toCredential(accessKey), secretKey: toCredential(secretKey) } } })
9389
9599
  );
9390
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (set) => `credential set for Onshape account ${set.upstreamIdentity} at ${set.setAt}
9391
- ${CONNECTOR_DISCLOSURE}`) : outcomeOfSessionFailure(run2.failure, settle);
9600
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (set) => `credential set for Onshape account ${set.upstreamIdentity} at ${set.setAt}
9601
+ ${CONNECTOR_DISCLOSURE}`) : outcomeOfSessionFailure(run2.failure, settle2);
9392
9602
  }
9393
9603
  };
9394
9604
  var connectorDraftTypes = {
@@ -9406,9 +9616,9 @@ var connectorDraftTypes = {
9406
9616
  ],
9407
9617
  run: async (context) => {
9408
9618
  const selector = selectorOf(context);
9409
- const settle = "openeng types draft current, which only reads, shows whether a change set is open";
9619
+ const settle2 = "openeng types draft current, which only reads, shows whether a change set is open";
9410
9620
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.admin.draftConnectorTypes({ selector, input: {} }));
9411
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, typesText) : outcomeOfSessionFailure(run2.failure, settle);
9621
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, typesText) : outcomeOfSessionFailure(run2.failure, settle2);
9412
9622
  }
9413
9623
  };
9414
9624
  var connectorRevoke = {
@@ -9427,9 +9637,9 @@ var connectorRevoke = {
9427
9637
  run: async (context) => {
9428
9638
  const selector = selectorOf(context);
9429
9639
  const { connectionId } = selector;
9430
- const settle = "openeng admin-sdk listConnections, which only reads, shows the connection's state";
9640
+ const settle2 = "openeng admin-sdk listConnections, which only reads, shows the connection's state";
9431
9641
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.admin.revokeConnection({ selector, input: {} }));
9432
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, () => `revoked connection ${connectionId}`) : outcomeOfSessionFailure(run2.failure, settle);
9642
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, () => `revoked connection ${connectionId}`) : outcomeOfSessionFailure(run2.failure, settle2);
9433
9643
  }
9434
9644
  };
9435
9645
  var connectorRun = {
@@ -9448,9 +9658,9 @@ var connectorRun = {
9448
9658
  run: async (context) => {
9449
9659
  const selector = selectorOf(context);
9450
9660
  const { connectionId } = selector;
9451
- const settle = `openeng connector runs ${connectionId}, which only reads`;
9661
+ const settle2 = `openeng connector runs ${connectionId}, which only reads`;
9452
9662
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.admin.startImportRun({ selector, input: {} }));
9453
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (started) => `started run ${started.runId}: read it with openeng connector runs ${connectionId}`) : outcomeOfSessionFailure(run2.failure, settle);
9663
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (started) => `started run ${started.runId}: read it with openeng connector runs ${connectionId}`) : outcomeOfSessionFailure(run2.failure, settle2);
9454
9664
  }
9455
9665
  };
9456
9666
  var runText = (record) => {
@@ -9474,9 +9684,9 @@ var connectorRuns = {
9474
9684
  ],
9475
9685
  run: async (context) => {
9476
9686
  const selector = selectorOf(context);
9477
- const settle = "repeat openeng connector runs, which only reads";
9687
+ const settle2 = "repeat openeng connector runs, which only reads";
9478
9688
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.admin.listImportRuns({ selector, input: {} }));
9479
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (records) => records.length === 0 ? "no runs" : records.map(runText).join("\n")) : outcomeOfSessionFailure(run2.failure, settle);
9689
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (records) => records.length === 0 ? "no runs" : records.map(runText).join("\n")) : outcomeOfSessionFailure(run2.failure, settle2);
9480
9690
  }
9481
9691
  };
9482
9692
 
@@ -9514,6 +9724,9 @@ var SCOPE_OF_KIND = Object.freeze({
9514
9724
  });
9515
9725
  var UNREAD = "";
9516
9726
  function scopeOf2(kind, project, ref) {
9727
+ if (SCOPE_OF_KIND[kind] !== "none" && ref !== void 0 && !isLinkRef(ref, SCOPE_OF_KIND[kind] === "project-and-ref" ? project : void 0)) {
9728
+ return "--ref takes main or <projectId>/<branch>, the ref the platform returns; its project must match --project when given";
9729
+ }
9517
9730
  switch (SCOPE_OF_KIND[kind]) {
9518
9731
  case "project-and-ref":
9519
9732
  return project === void 0 || project === "" || ref === void 0 || ref === "" ? "needs --project <id> and --ref <ref>" : { project, ref };
@@ -9526,6 +9739,64 @@ function scopeOf2(kind, project, ref) {
9526
9739
  return project !== void 0 || ref !== void 0 ? "takes neither --project nor --ref" : { project: UNREAD, ref: UNREAD };
9527
9740
  }
9528
9741
  }
9742
+ function checkedRead(pending, reason, contains) {
9743
+ return pending.then((answer) => {
9744
+ if ("refusal" in answer) {
9745
+ return { answer, missing: answer.refusal.kind === "not-found" ? reason : null };
9746
+ }
9747
+ if (answer.replayed === true) return { answer, missing: null };
9748
+ return { answer, missing: contains !== void 0 && !contains(answer.result) ? reason : null };
9749
+ });
9750
+ }
9751
+ function subjectReads(clients, subject, scope) {
9752
+ const reads = [];
9753
+ if (scope.ref !== "main" && SCOPE_OF_KIND[subject.kind] !== "none") {
9754
+ const slash = scope.ref.indexOf("/");
9755
+ const projectId = scope.ref.slice(0, slash);
9756
+ const name = scope.ref.slice(slash + 1);
9757
+ reads.push(checkedRead(
9758
+ clients.branches.readRefHead({ selector: { projectId }, input: { name } }),
9759
+ `no ref ${scope.ref} in project ${projectId}`
9760
+ ));
9761
+ } else if (SCOPE_OF_KIND[subject.kind] === "project-and-ref") {
9762
+ reads.push(checkedRead(
9763
+ clients.branches.readRefHead({ selector: { projectId: scope.project }, input: { name: "main" } }),
9764
+ `no ref main in project ${scope.project}`
9765
+ ));
9766
+ }
9767
+ switch (subject.kind) {
9768
+ case "object":
9769
+ reads.push(checkedRead(
9770
+ clients.objects.readObject({ selector: { projectId: scope.project, ref: scope.ref }, input: { objectId: subject.objectId } }),
9771
+ `no object ${subject.objectId} in project ${scope.project} on ref ${scope.ref}`
9772
+ ));
9773
+ break;
9774
+ case "type":
9775
+ reads.push(checkedRead(
9776
+ clients.objects.readCatalogue({ selector: { projectId: scope.project }, input: {} }),
9777
+ `no type ${subject.typeKey} in project ${scope.project}`,
9778
+ (catalogue) => catalogue.objects.some((type) => type.typeKey === subject.typeKey)
9779
+ ));
9780
+ break;
9781
+ case "app":
9782
+ reads.push(checkedRead(clients.objects.readApp({ input: { appKey: subject.appKey } }), `no app ${subject.appKey}`));
9783
+ break;
9784
+ case "app-page":
9785
+ reads.push(checkedRead(
9786
+ clients.objects.readApp({ input: { appKey: subject.appKey } }),
9787
+ `no page ${subject.pageKey} in app ${subject.appKey}`,
9788
+ (app) => app.document.pages.some((page) => page.key === subject.pageKey)
9789
+ ));
9790
+ break;
9791
+ case "proposal":
9792
+ reads.push(checkedRead(
9793
+ clients.branches.readProposal({ selector: { projectId: scope.project }, input: { proposalId: subject.proposalId } }),
9794
+ `no proposal ${subject.proposalId} in project ${scope.project}`
9795
+ ));
9796
+ break;
9797
+ }
9798
+ return reads;
9799
+ }
9529
9800
  async function link(context) {
9530
9801
  const { host, deps, options, args } = context;
9531
9802
  const read = await readStoredEntry(host, deps.store);
@@ -9567,13 +9838,28 @@ async function link(context) {
9567
9838
  return refusal(`openeng link ${subject.kind} ${scope}`);
9568
9839
  }
9569
9840
  const path = linkPath(organisation.id, scope.project, subject, scope.ref);
9570
- const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.readWebOrigin({ input: {} }));
9841
+ const run2 = await runAuthenticated(host, deps, async (clients) => {
9842
+ const [origin, checks] = await Promise.all([
9843
+ clients.admin.readWebOrigin({ input: {} }),
9844
+ Promise.all(subjectReads(clients, subject, scope))
9845
+ ]);
9846
+ const blocked = checks.find((check) => check.missing === null && ("refusal" in check.answer || check.answer.replayed === true));
9847
+ if (blocked !== void 0) return blocked.answer;
9848
+ if ("refusal" in origin) {
9849
+ return origin.refusal.kind === "not-found" ? refusal("the host's web application was not found") : origin;
9850
+ }
9851
+ if (origin.replayed === true) return origin;
9852
+ const missing = checks.find((check) => check.missing !== null);
9853
+ if (missing !== void 0 && missing.missing !== null) return refusal(missing.missing);
9854
+ return { kind: "link-ready", webOrigin: origin.result.webOrigin };
9855
+ });
9571
9856
  if (!run2.ok) {
9572
9857
  return outcomeOfSessionFailure(run2.failure, SETTLE2);
9573
9858
  }
9574
9859
  const { answer } = run2;
9575
- if (!("refusal" in answer) && answer.replayed !== true) {
9576
- const { webOrigin } = answer.result;
9860
+ if ("kind" in answer) {
9861
+ if (answer.kind === "client-refusal") return answer;
9862
+ const { webOrigin } = answer;
9577
9863
  if (webOrigin === null) {
9578
9864
  return refusal("this host has no web application configured, so it has no page to link to");
9579
9865
  }
@@ -9590,7 +9876,9 @@ var spec = (argumentCount, usage) => ({
9590
9876
  usage,
9591
9877
  help: [
9592
9878
  ...LINK_HELP,
9593
- "does: builds the address of a page of the web application for the attached organisation",
9879
+ "does: reads the page's subject before printing its web address for the attached organisation",
9880
+ "ref: --ref takes main or <projectId>/<branch>, the ref the platform returns; match --project when given",
9881
+ "refusal: a subject not found by the page's own read prints no address",
9594
9882
  "example: openeng link explore --project proj_abc123 --ref main",
9595
9883
  "output: the address on one line; under --json an object with link"
9596
9884
  ],
@@ -9649,6 +9937,347 @@ var projectsCreate = {
9649
9937
  }
9650
9938
  };
9651
9939
 
9940
+ // packages/cli/src/commands/protection-help.ts
9941
+ var SCOPE_LABELS = Object.freeze({
9942
+ project: "on the selected project",
9943
+ organisation: "in the attached organisation",
9944
+ "branch-home-project": "on the branch's home project",
9945
+ "organisation-ref": "for an organisation ref",
9946
+ ref: "on the selected ref"
9947
+ });
9948
+ function protectionCapabilityHelp(...routes) {
9949
+ const requirements = routes.flatMap((route2) => PROTECTION_ROUTE_CAPABILITIES[route2].map((requirement) => "visibility" in requirement ? "none; only the caller's own requests in the token's organisation; no project read required" : `${requirement.capability} ${SCOPE_LABELS[requirement.scope]}`));
9950
+ return `capability: ${[...new Set(requirements)].join(" or ")}`;
9951
+ }
9952
+
9953
+ // packages/cli/src/commands/rules.ts
9954
+ var CONSEQUENCES = ["merge-only", "locked-rows-only"];
9955
+ var settleWith = (project) => project === void 0 ? "repeat openeng rules list --organisation, which only reads" : `repeat openeng rules list --project ${project}, which only reads`;
9956
+ var ruleText = (rule) => `rule ${rule.ruleId} ${rule.pattern} ${rule.consequence}`;
9957
+ var rulesOutcome = (outcome) => outcome.kind === "success" ? { ...outcome, value: { value: outcome.value } } : outcome;
9958
+ var scopeMessage = "choose exactly one of --project <id> or --organisation";
9959
+ var rulesAdd = {
9960
+ words: ["rules", "add"],
9961
+ organisation: "required",
9962
+ usage: "openeng rules add (--project <id>|--organisation) --pattern <pattern> --consequence <value>",
9963
+ help: [
9964
+ "openeng rules add (--project <id>|--organisation) --pattern <pattern> --consequence <merge-only|locked-rows-only>",
9965
+ "does: adds a project or organisation ref rule; repeating the same content returns the existing rule",
9966
+ protectionCapabilityHelp("addRefRule", "addOrganisationRule"),
9967
+ "example: openeng rules add --organisation --pattern release-v1 --consequence merge-only",
9968
+ "output: the rule id, pattern and consequence; under --json { scope, value }, with the full RefRule in value"
9969
+ ],
9970
+ run: async ({ host, deps, options }) => {
9971
+ const { project, organisation, pattern } = options;
9972
+ if (project !== void 0 === organisation) {
9973
+ return { kind: "client-refusal", message: scopeMessage };
9974
+ }
9975
+ if (pattern === void 0 || options.consequence === void 0) {
9976
+ return {
9977
+ kind: "client-refusal",
9978
+ message: "openeng rules add needs --pattern <pattern> and --consequence <merge-only|locked-rows-only>"
9979
+ };
9980
+ }
9981
+ const consequence = CONSEQUENCES.find((value) => value === options.consequence);
9982
+ if (consequence === void 0) {
9983
+ return { kind: "client-refusal", message: "--consequence must be merge-only or locked-rows-only" };
9984
+ }
9985
+ const settle2 = settleWith(project);
9986
+ const run2 = await runAuthenticated(host, deps, (clients) => project === void 0 ? clients.admin.addOrganisationRule({ input: { pattern, consequence } }) : clients.admin.addRefRule({ selector: { projectId: project }, input: { pattern, consequence } }));
9987
+ return run2.ok ? rulesOutcome(outcomeOfEnvelope(run2.answer, settle2, ruleText)) : outcomeOfSessionFailure(run2.failure, settle2);
9988
+ }
9989
+ };
9990
+ var rulesList = {
9991
+ words: ["rules", "list"],
9992
+ organisation: "required",
9993
+ usage: "openeng rules list (--project <id>|--organisation)",
9994
+ help: [
9995
+ "openeng rules list (--project <id>|--organisation)",
9996
+ "does: lists visible ref protection rules for the project or attached organisation",
9997
+ protectionCapabilityHelp("listRefRules", "listOrganisationRules"),
9998
+ "example: openeng rules list --organisation --json",
9999
+ "output: id, pattern and consequence per rule, or no rules; --json { scope, value }, value is a RefRule array"
10000
+ ],
10001
+ run: async ({ host, deps, options }) => {
10002
+ const { project, organisation } = options;
10003
+ if (project !== void 0 === organisation) {
10004
+ return { kind: "client-refusal", message: scopeMessage };
10005
+ }
10006
+ const settle2 = settleWith(project);
10007
+ const run2 = await runAuthenticated(host, deps, (clients) => project === void 0 ? clients.admin.listOrganisationRules({ input: {} }) : clients.admin.listRefRules({ selector: { projectId: project }, input: {} }));
10008
+ return run2.ok ? rulesOutcome(outcomeOfEnvelope(
10009
+ run2.answer,
10010
+ settle2,
10011
+ (rules) => rules.length === 0 ? "no rules" : rules.map(ruleText).join("\n")
10012
+ )) : outcomeOfSessionFailure(run2.failure, settle2);
10013
+ }
10014
+ };
10015
+ var rulesRemove = {
10016
+ words: ["rules", "remove"],
10017
+ organisation: "required",
10018
+ usage: "openeng rules remove (--project <id>|--organisation) --rule-id <id>",
10019
+ help: [
10020
+ "openeng rules remove (--project <id>|--organisation) --rule-id <id>",
10021
+ "does: removes one rule from the project or attached organisation by its identifier",
10022
+ protectionCapabilityHelp("removeRefRule", "removeOrganisationRule"),
10023
+ "example: openeng rules remove --organisation --rule-id rule-one",
10024
+ "output: the removed rule id; under --json { scope, value }, with { ruleId } in value"
10025
+ ],
10026
+ run: async ({ host, deps, options }) => {
10027
+ const { project, organisation, ruleId } = options;
10028
+ if (project !== void 0 === organisation) {
10029
+ return { kind: "client-refusal", message: scopeMessage };
10030
+ }
10031
+ if (ruleId === void 0) {
10032
+ return { kind: "client-refusal", message: "openeng rules remove needs --rule-id <id>" };
10033
+ }
10034
+ const settle2 = settleWith(project);
10035
+ const run2 = await runAuthenticated(host, deps, (clients) => project === void 0 ? clients.admin.removeOrganisationRule({ input: { ruleId } }) : clients.admin.removeRefRule({ selector: { projectId: project }, input: { ruleId } }));
10036
+ return run2.ok ? rulesOutcome(outcomeOfEnvelope(run2.answer, settle2, (removed) => `removed rule ${removed.ruleId}`)) : outcomeOfSessionFailure(run2.failure, settle2);
10037
+ }
10038
+ };
10039
+
10040
+ // packages/cli/src/commands/org-refs.ts
10041
+ var settle = "repeat openeng org-refs list, which only reads";
10042
+ var refText = (ref) => `ref ${ref.name} ${ref.readOnly ? "read-only" : "writable"}`;
10043
+ var refsOutcome = (outcome) => outcome.kind === "success" ? { ...outcome, value: { value: outcome.value } } : outcome;
10044
+ var orgRefsCreate = {
10045
+ words: ["org-refs", "create"],
10046
+ organisation: "required",
10047
+ usage: "openeng org-refs create --name <name> [--read-only]",
10048
+ help: [
10049
+ "openeng org-refs create --name <name> [--read-only]",
10050
+ "does: creates a ref in the attached organisation, optionally read-only",
10051
+ protectionCapabilityHelp("createOrganisationRef"),
10052
+ "example: openeng org-refs create --name release-v1 --read-only",
10053
+ "output: the ref name and write state; under --json { scope, value }, with the full OrganisationRef in value"
10054
+ ],
10055
+ run: async ({ host, deps, options }) => {
10056
+ const { name, readOnly } = options;
10057
+ if (name === void 0) {
10058
+ return { kind: "client-refusal", message: "openeng org-refs create needs --name <name>" };
10059
+ }
10060
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.createOrganisationRef({
10061
+ input: { name, readOnly }
10062
+ }));
10063
+ return run2.ok ? refsOutcome(outcomeOfEnvelope(run2.answer, settle, refText)) : outcomeOfSessionFailure(run2.failure, settle);
10064
+ }
10065
+ };
10066
+ var orgRefsList = {
10067
+ words: ["org-refs", "list"],
10068
+ organisation: "required",
10069
+ usage: "openeng org-refs list",
10070
+ help: [
10071
+ "openeng org-refs list",
10072
+ "does: lists visible refs in the attached organisation",
10073
+ protectionCapabilityHelp("listOrganisationRefs"),
10074
+ "example: openeng org-refs list --json",
10075
+ "output: each ref's name and write state, or no refs; under --json { scope, value }, value is an array"
10076
+ ],
10077
+ run: async ({ host, deps }) => {
10078
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.listOrganisationRefs({ input: {} }));
10079
+ return run2.ok ? refsOutcome(outcomeOfEnvelope(
10080
+ run2.answer,
10081
+ settle,
10082
+ (refs) => refs.length === 0 ? "no refs" : refs.map(refText).join("\n")
10083
+ )) : outcomeOfSessionFailure(run2.failure, settle);
10084
+ }
10085
+ };
10086
+ var orgRefsDelete = {
10087
+ words: ["org-refs", "delete"],
10088
+ organisation: "required",
10089
+ usage: "openeng org-refs delete --name <name>",
10090
+ help: [
10091
+ "openeng org-refs delete --name <name>",
10092
+ "does: deletes a writable ref from the attached organisation",
10093
+ protectionCapabilityHelp("deleteOrganisationRef"),
10094
+ "example: openeng org-refs delete --name review",
10095
+ "output: the deleted ref name; under --json { scope, value }, with { name } in value"
10096
+ ],
10097
+ run: async ({ host, deps, options }) => {
10098
+ const { name } = options;
10099
+ if (name === void 0) {
10100
+ return { kind: "client-refusal", message: "openeng org-refs delete needs --name <name>" };
10101
+ }
10102
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.deleteOrganisationRef({
10103
+ input: { name }
10104
+ }));
10105
+ return run2.ok ? refsOutcome(outcomeOfEnvelope(run2.answer, settle, (deleted) => `deleted ref ${deleted.name}`)) : outcomeOfSessionFailure(run2.failure, settle);
10106
+ }
10107
+ };
10108
+ var orgRefsMarkReadOnly = {
10109
+ words: ["org-refs", "mark-read-only"],
10110
+ organisation: "required",
10111
+ usage: "openeng org-refs mark-read-only --name <name>",
10112
+ help: [
10113
+ "openeng org-refs mark-read-only --name <name>",
10114
+ "does: marks a ref in the attached organisation read-only",
10115
+ protectionCapabilityHelp("markOrganisationRefReadOnly"),
10116
+ "example: openeng org-refs mark-read-only --name release-v1",
10117
+ "output: the ref name and write state; under --json { scope, value }, with the full OrganisationRef in value"
10118
+ ],
10119
+ run: async ({ host, deps, options }) => {
10120
+ const { name } = options;
10121
+ if (name === void 0) {
10122
+ return { kind: "client-refusal", message: "openeng org-refs mark-read-only needs --name <name>" };
10123
+ }
10124
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.markOrganisationRefReadOnly({
10125
+ input: { name }
10126
+ }));
10127
+ return run2.ok ? refsOutcome(outcomeOfEnvelope(run2.answer, settle, refText)) : outcomeOfSessionFailure(run2.failure, settle);
10128
+ }
10129
+ };
10130
+
10131
+ // packages/cli/src/commands/locks.ts
10132
+ var LOCKS = ["none", "merge-only", "frozen"];
10133
+ var lockOutcome = (outcome) => outcome.kind === "success" ? { ...outcome, value: { value: outcome.value } } : outcome;
10134
+ var locksSet = {
10135
+ words: ["locks", "set"],
10136
+ organisation: "required",
10137
+ usage: "openeng locks set --project <id> --ref <ref> --object <objectId> --lock <none|merge-only|frozen>",
10138
+ help: [
10139
+ "openeng locks set --project <id> --ref <ref> --object <objectId> --lock <none|merge-only|frozen>",
10140
+ "does: sets or lifts an object's lock on the selected project ref",
10141
+ protectionCapabilityHelp("setObjectLock"),
10142
+ "example: openeng locks set --project project-one --ref main --object object-one --lock frozen",
10143
+ "output: the object identifier and lock; under --json { scope, value }, with { objectId, lock } in value"
10144
+ ],
10145
+ run: async ({ host, deps, options }) => {
10146
+ const { project, ref, object } = options;
10147
+ if (project === void 0 || ref === void 0 || object === void 0 || options.lock === void 0) {
10148
+ return {
10149
+ kind: "client-refusal",
10150
+ message: "openeng locks set needs --project <id>, --ref <ref>, --object <objectId> and --lock <none|merge-only|frozen>"
10151
+ };
10152
+ }
10153
+ const lock = LOCKS.find((value) => value === options.lock);
10154
+ if (lock === void 0) {
10155
+ return { kind: "client-refusal", message: "--lock must be none, merge-only or frozen" };
10156
+ }
10157
+ const selector = { projectId: project, ref };
10158
+ const input = { objectId: object, lock };
10159
+ const settle2 = `repeat openeng locks set --project ${project} --ref ${ref} --object ${object} --lock ${lock}, which sets the same lock again`;
10160
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.objects.setObjectLock({ selector, input }));
10161
+ return run2.ok ? lockOutcome(outcomeOfEnvelope(run2.answer, settle2, (value) => `object ${value.objectId} lock ${value.lock}`)) : outcomeOfSessionFailure(run2.failure, settle2);
10162
+ }
10163
+ };
10164
+
10165
+ // packages/cli/src/commands/ceiling.ts
10166
+ var CEILINGS = ["contributor", "maintainer"];
10167
+ var settleWith2 = (project) => `repeat openeng ceiling show --project ${project}, which only reads`;
10168
+ var ceilingText = (value) => `ceiling ${value.ceiling}`;
10169
+ var ceilingOutcome = (outcome) => outcome.kind === "success" ? { ...outcome, value: { value: outcome.value } } : outcome;
10170
+ var ceilingShow = {
10171
+ words: ["ceiling", "show"],
10172
+ organisation: "required",
10173
+ usage: "openeng ceiling show --project <id>",
10174
+ help: [
10175
+ "openeng ceiling show --project <id>",
10176
+ "does: reads the maximum role an agent can hold in the project",
10177
+ protectionCapabilityHelp("readAgentCeiling"),
10178
+ "example: openeng ceiling show --project project-one --json",
10179
+ "output: the ceiling role; under --json { scope, value }, with { ceiling } in value"
10180
+ ],
10181
+ run: async ({ host, deps, options }) => {
10182
+ const { project } = options;
10183
+ if (project === void 0) {
10184
+ return { kind: "client-refusal", message: "openeng ceiling show needs --project <id>" };
10185
+ }
10186
+ const settle2 = settleWith2(project);
10187
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.readAgentCeiling({
10188
+ selector: { projectId: project },
10189
+ input: {}
10190
+ }));
10191
+ return run2.ok ? ceilingOutcome(outcomeOfEnvelope(run2.answer, settle2, ceilingText)) : outcomeOfSessionFailure(run2.failure, settle2);
10192
+ }
10193
+ };
10194
+ var ceilingSet = {
10195
+ words: ["ceiling", "set"],
10196
+ organisation: "required",
10197
+ usage: "openeng ceiling set --project <id> --ceiling <contributor|maintainer>",
10198
+ help: [
10199
+ "openeng ceiling set --project <id> --ceiling <contributor|maintainer>",
10200
+ "does: sets the maximum role an agent can hold in the project",
10201
+ protectionCapabilityHelp("setAgentCeiling"),
10202
+ "example: openeng ceiling set --project project-one --ceiling contributor",
10203
+ "output: the ceiling role; under --json { scope, value }, with { ceiling } in value"
10204
+ ],
10205
+ run: async ({ host, deps, options }) => {
10206
+ const { project } = options;
10207
+ if (project === void 0 || options.ceiling === void 0) {
10208
+ return {
10209
+ kind: "client-refusal",
10210
+ message: "openeng ceiling set needs --project <id> and --ceiling <contributor|maintainer>"
10211
+ };
10212
+ }
10213
+ const ceiling = CEILINGS.find((value) => value === options.ceiling);
10214
+ if (ceiling === void 0) {
10215
+ return { kind: "client-refusal", message: "--ceiling must be contributor or maintainer" };
10216
+ }
10217
+ const settle2 = settleWith2(project);
10218
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.admin.setAgentCeiling({
10219
+ selector: { projectId: project },
10220
+ input: { ceiling }
10221
+ }));
10222
+ return run2.ok ? ceilingOutcome(outcomeOfEnvelope(run2.answer, settle2, ceilingText)) : outcomeOfSessionFailure(run2.failure, settle2);
10223
+ }
10224
+ };
10225
+
10226
+ // packages/cli/src/commands/schema-update.ts
10227
+ var settleWith3 = (ref) => `repeat openeng schema-update show --ref ${ref}, which only reads`;
10228
+ var requestText = (value) => `schema update ${value.ref} ${value.state}`;
10229
+ var schemaOutcome = (outcome) => outcome.kind === "success" ? { ...outcome, value: { value: outcome.value } } : outcome;
10230
+ var schemaUpdateRequest = {
10231
+ words: ["schema-update", "request"],
10232
+ organisation: "required",
10233
+ usage: "openeng schema-update request --ref <ref>",
10234
+ help: [
10235
+ "openeng schema-update request --ref <ref>",
10236
+ "does: records a schema update request for the ref; execution is separate",
10237
+ protectionCapabilityHelp("requestSchemaUpdate"),
10238
+ "example: openeng schema-update request --ref project-one/work",
10239
+ "output: the ref and request state; under --json { scope, value }, with the full SchemaUpdateRequest in value"
10240
+ ],
10241
+ run: async ({ host, deps, options }) => {
10242
+ const { ref } = options;
10243
+ if (ref === void 0) {
10244
+ return { kind: "client-refusal", message: "openeng schema-update request needs --ref <ref>" };
10245
+ }
10246
+ const settle2 = settleWith3(ref);
10247
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.branches.requestSchemaUpdate({
10248
+ input: { ref }
10249
+ }));
10250
+ return run2.ok ? schemaOutcome(outcomeOfEnvelope(run2.answer, settle2, requestText)) : outcomeOfSessionFailure(run2.failure, settle2);
10251
+ }
10252
+ };
10253
+ var schemaUpdateShow = {
10254
+ words: ["schema-update", "show"],
10255
+ organisation: "required",
10256
+ usage: "openeng schema-update show --ref <ref>",
10257
+ help: [
10258
+ "openeng schema-update show --ref <ref>",
10259
+ "does: reads the latest visible schema update request for the ref",
10260
+ protectionCapabilityHelp("readSchemaUpdateRequest"),
10261
+ "example: openeng schema-update show --ref project-one/work --json",
10262
+ "output: the ref and state, or no request; under --json { scope, value }, value is SchemaUpdateRequest or null"
10263
+ ],
10264
+ run: async ({ host, deps, options }) => {
10265
+ const { ref } = options;
10266
+ if (ref === void 0) {
10267
+ return { kind: "client-refusal", message: "openeng schema-update show needs --ref <ref>" };
10268
+ }
10269
+ const settle2 = settleWith3(ref);
10270
+ const run2 = await runAuthenticated(host, deps, (clients) => clients.branches.readSchemaUpdateRequest({
10271
+ input: { ref }
10272
+ }));
10273
+ return run2.ok ? schemaOutcome(outcomeOfEnvelope(
10274
+ run2.answer,
10275
+ settle2,
10276
+ (value) => value === null ? "no request" : requestText(value)
10277
+ )) : outcomeOfSessionFailure(run2.failure, settle2);
10278
+ }
10279
+ };
10280
+
9652
10281
  // packages/cli/src/commands/types.ts
9653
10282
  import { randomUUID as randomUUID4 } from "node:crypto";
9654
10283
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -9766,9 +10395,9 @@ var typesList = {
9766
10395
  return flags.outcome;
9767
10396
  }
9768
10397
  const projectId = flags.values.project;
9769
- const settle = "repeat openeng types list, which only reads";
10398
+ const settle2 = "repeat openeng types list, which only reads";
9770
10399
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.objects.readCatalogue({ selector: { projectId }, input: {} }));
9771
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, catalogueText) : outcomeOfSessionFailure(run2.failure, settle);
10400
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, catalogueText) : outcomeOfSessionFailure(run2.failure, settle2);
9772
10401
  }
9773
10402
  };
9774
10403
  var typesDraftOpen = {
@@ -9782,9 +10411,9 @@ var typesDraftOpen = {
9782
10411
  "output: one line with the change set id and revision; under --json an object with changeSetId and revision"
9783
10412
  ],
9784
10413
  run: async (context) => {
9785
- const settle = "openeng types draft current";
10414
+ const settle2 = "openeng types draft current";
9786
10415
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.openChangeSet({ input: {} }));
9787
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (opened) => `opened change set ${opened.changeSetId} at revision ${opened.revision}`) : outcomeOfSessionFailure(run2.failure, settle);
10416
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (opened) => `opened change set ${opened.changeSetId} at revision ${opened.revision}`) : outcomeOfSessionFailure(run2.failure, settle2);
9788
10417
  }
9789
10418
  };
9790
10419
  var typesDraftCurrent = {
@@ -9798,9 +10427,9 @@ var typesDraftCurrent = {
9798
10427
  "output: one line naming the change set and revision, or that none is open; under --json an object with changeSetId, revision and scope, or {scope, value: null} when none is open"
9799
10428
  ],
9800
10429
  run: async (context) => {
9801
- const settle = "repeat openeng types draft current, which only reads";
10430
+ const settle2 = "repeat openeng types draft current, which only reads";
9802
10431
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.readCurrentChangeSet({ input: {} }));
9803
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (current) => current === null ? "no change set is open" : `change set ${current.changeSetId} at revision ${current.revision}`) : outcomeOfSessionFailure(run2.failure, settle);
10432
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (current) => current === null ? "no change set is open" : `change set ${current.changeSetId} at revision ${current.revision}`) : outcomeOfSessionFailure(run2.failure, settle2);
9804
10433
  }
9805
10434
  };
9806
10435
  function listingOf(catalogue) {
@@ -9869,7 +10498,7 @@ var typesDraftShow = {
9869
10498
  }
9870
10499
  const changeSetId = flags.values.changeSet;
9871
10500
  const projectId = context.options.project;
9872
- const settle = "repeat openeng types draft show, which only reads";
10501
+ const settle2 = "repeat openeng types draft show, which only reads";
9873
10502
  const run2 = await runAuthenticated(context.host, context.deps, async (clients) => {
9874
10503
  const read2 = await clients.ontology.readChangeSet({ input: { changeSetId } });
9875
10504
  if ("refusal" in read2) {
@@ -9891,11 +10520,11 @@ var typesDraftShow = {
9891
10520
  return { read: read2.result, catalogue: catalogue2.result };
9892
10521
  });
9893
10522
  if (!run2.ok) {
9894
- return outcomeOfSessionFailure(run2.failure, settle);
10523
+ return outcomeOfSessionFailure(run2.failure, settle2);
9895
10524
  }
9896
10525
  const answer = run2.answer;
9897
10526
  if ("refusal" in answer) {
9898
- return outcomeOfRouteRefusal(answer.refusal, settle, answer.correlationId);
10527
+ return outcomeOfRouteRefusal(answer.refusal, settle2, answer.correlationId);
9899
10528
  }
9900
10529
  const { read, catalogue } = answer;
9901
10530
  if (catalogue === null) {
@@ -9944,14 +10573,14 @@ var typesExport = {
9944
10573
  return flags.outcome;
9945
10574
  }
9946
10575
  const projectId = flags.values.project;
9947
- const settle = "repeat openeng types export, which only reads";
10576
+ const settle2 = "repeat openeng types export, which only reads";
9948
10577
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.objects.readCatalogue({ selector: { projectId }, input: {} }));
9949
10578
  if (!run2.ok) {
9950
- return outcomeOfSessionFailure(run2.failure, settle);
10579
+ return outcomeOfSessionFailure(run2.failure, settle2);
9951
10580
  }
9952
10581
  const envelope = run2.answer;
9953
10582
  if ("refusal" in envelope) {
9954
- return outcomeOfRouteRefusal(envelope.refusal, settle, envelope.correlationId);
10583
+ return outcomeOfRouteRefusal(envelope.refusal, settle2, envelope.correlationId);
9955
10584
  }
9956
10585
  if (envelope.replayed === true) {
9957
10586
  return { kind: "internal-failure", message: "the host answered a replay to a read that carried no idempotency key" };
@@ -9980,9 +10609,9 @@ var typesDraftState = {
9980
10609
  return flags.outcome;
9981
10610
  }
9982
10611
  const changeSetId = flags.values.changeSet;
9983
- const settle = "repeat openeng types draft state, which only reads";
10612
+ const settle2 = "repeat openeng types draft state, which only reads";
9984
10613
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.readChangeSetState({ input: { changeSetId } }));
9985
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (state) => `change set ${changeSetId} is ${state.state} at revision ${state.revision}`) : outcomeOfSessionFailure(run2.failure, settle);
10614
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (state) => `change set ${changeSetId} is ${state.state} at revision ${state.revision}`) : outcomeOfSessionFailure(run2.failure, settle2);
9986
10615
  }
9987
10616
  };
9988
10617
  function changesOfFile(path) {
@@ -10056,13 +10685,13 @@ var typesDraftRevise = {
10056
10685
  return groups.outcome;
10057
10686
  }
10058
10687
  const { railGroups } = groups;
10059
- const settle = settleChangeSet(changeSetId);
10688
+ const settle2 = settleChangeSet(changeSetId);
10060
10689
  const run2 = await runAuthenticated(
10061
10690
  context.host,
10062
10691
  context.deps,
10063
10692
  (clients) => clients.ontology.reviseChangeSet({ input: { changeSetId, expectedRevision, changes, ...railGroups === void 0 ? {} : { railGroups } } })
10064
10693
  );
10065
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (revised) => `revised change set ${changeSetId} to revision ${revised.revision}`) : outcomeOfSessionFailure(run2.failure, settle);
10694
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (revised) => `revised change set ${changeSetId} to revision ${revised.revision}`) : outcomeOfSessionFailure(run2.failure, settle2);
10066
10695
  }
10067
10696
  };
10068
10697
  var typesDraftValidate = {
@@ -10081,13 +10710,13 @@ var typesDraftValidate = {
10081
10710
  return flags.outcome;
10082
10711
  }
10083
10712
  const { changeSet: changeSetId, revision } = flags.values;
10084
- const settle = "repeat openeng types draft validate, which only reads";
10713
+ const settle2 = "repeat openeng types draft validate, which only reads";
10085
10714
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.validateChangeSet({ input: { changeSetId, revision } }));
10086
10715
  if (!run2.ok) {
10087
- return outcomeOfSessionFailure(run2.failure, settle);
10716
+ return outcomeOfSessionFailure(run2.failure, settle2);
10088
10717
  }
10089
10718
  let violated = false;
10090
- const outcome = outcomeOfEnvelope(run2.answer, settle, (validated) => {
10719
+ const outcome = outcomeOfEnvelope(run2.answer, settle2, (validated) => {
10091
10720
  violated = validated.violations.length > 0;
10092
10721
  return violated ? [`${String(validated.violations.length)} ${validated.violations.length === 1 ? "violation" : "violations"}`, ...validated.violations.map((violation) => ` ${JSON.stringify(violation)}`)].join("\n") : `change set ${changeSetId} at revision ${revision} has no violations`;
10093
10722
  });
@@ -10110,9 +10739,9 @@ var typesDraftDiscard = {
10110
10739
  return flags.outcome;
10111
10740
  }
10112
10741
  const changeSetId = flags.values.changeSet;
10113
- const settle = settleChangeSet(changeSetId);
10742
+ const settle2 = settleChangeSet(changeSetId);
10114
10743
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.discardChangeSet({ input: { changeSetId } }));
10115
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, () => `discarded change set ${changeSetId}`) : outcomeOfSessionFailure(run2.failure, settle);
10744
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, () => `discarded change set ${changeSetId}`) : outcomeOfSessionFailure(run2.failure, settle2);
10116
10745
  }
10117
10746
  };
10118
10747
  var typesDraftAccept = {
@@ -10131,10 +10760,10 @@ var typesDraftAccept = {
10131
10760
  return flags.outcome;
10132
10761
  }
10133
10762
  const { changeSet: changeSetId, revision } = flags.values;
10134
- const settle = settleChangeSet(changeSetId);
10763
+ const settle2 = settleChangeSet(changeSetId);
10135
10764
  const key = randomUUID4();
10136
10765
  const run2 = await runAuthenticated(context.host, context.deps, (clients) => clients.ontology.acceptChangeSet({ input: { changeSetId, revision } }, key));
10137
- return run2.ok ? outcomeOfEnvelope(run2.answer, settle, (accepted) => `accepted change set ${changeSetId}: ${String(accepted.versionIds.length)} type versions published`) : outcomeOfSessionFailure(run2.failure, settle);
10766
+ return run2.ok ? outcomeOfEnvelope(run2.answer, settle2, (accepted) => `accepted change set ${changeSetId}: ${String(accepted.versionIds.length)} type versions published`) : outcomeOfSessionFailure(run2.failure, settle2);
10138
10767
  }
10139
10768
  };
10140
10769
 
@@ -10200,6 +10829,18 @@ var PUBLIC_HAND_WRITTEN_COMMANDS = [
10200
10829
  orgCurrent,
10201
10830
  projectsList,
10202
10831
  projectsCreate,
10832
+ rulesAdd,
10833
+ rulesList,
10834
+ rulesRemove,
10835
+ orgRefsCreate,
10836
+ orgRefsList,
10837
+ orgRefsDelete,
10838
+ orgRefsMarkReadOnly,
10839
+ locksSet,
10840
+ ceilingShow,
10841
+ ceilingSet,
10842
+ schemaUpdateRequest,
10843
+ schemaUpdateShow,
10203
10844
  typesList,
10204
10845
  typesExport,
10205
10846
  typesDraftOpen,
@@ -10240,6 +10881,14 @@ var CLI_OPTIONS = {
10240
10881
  json: { type: "boolean" },
10241
10882
  "no-open": { type: "boolean" },
10242
10883
  project: { type: "string" },
10884
+ organisation: { type: "boolean" },
10885
+ "read-only": { type: "boolean" },
10886
+ object: { type: "string" },
10887
+ lock: { type: "string" },
10888
+ ceiling: { type: "string" },
10889
+ pattern: { type: "string" },
10890
+ consequence: { type: "string" },
10891
+ "rule-id": { type: "string" },
10243
10892
  ref: { type: "string" },
10244
10893
  name: { type: "string" },
10245
10894
  "change-set": { type: "string" },
@@ -10377,6 +11026,14 @@ function parseInvocation(argv) {
10377
11026
  json: values.json === true,
10378
11027
  open: values["no-open"] !== true,
10379
11028
  project: values.project,
11029
+ organisation: values.organisation === true,
11030
+ readOnly: values["read-only"] === true,
11031
+ object: values.object,
11032
+ lock: values.lock,
11033
+ ceiling: values.ceiling,
11034
+ pattern: values.pattern,
11035
+ consequence: values.consequence,
11036
+ ruleId: values["rule-id"],
10380
11037
  ref: values.ref,
10381
11038
  name: values.name,
10382
11039
  changeSet: values["change-set"],