@vectros-ai/cli 0.10.0 → 0.11.0

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.
package/AUTHORING.md CHANGED
@@ -244,6 +244,13 @@ principal's `externalId`, the per-machine key name, and each seed record's
244
244
  `externalId`. Bump `version` when you evolve a blueprint. (Note: schema
245
245
  `typeName` is immutable after first apply.)
246
246
 
247
+ **Pair `:r` with every `:c` you request.** Being handed back an existing item
248
+ on a re-apply collision is a read of that item, not part of create authority —
249
+ so a `records:c`-only `accessProfile.allowedActions` converges on first apply
250
+ but fails on every re-apply with an "already exists" error instead of the
251
+ existing record. Request `:r` alongside `:c` on every resource your blueprint
252
+ creates (the bundled blueprints all do this — see the example above).
253
+
247
254
  ## Distribution
248
255
 
249
256
  - **Bundled** — reviewed, versioned blueprints shipped in the library
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to `@vectros-ai/cli` are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.11.0
7
+
8
+ ### Fixed
9
+
10
+ - **`access explain` no longer previews access that the platform denies.** A clause keyed on an
11
+ unrecognized ownership dimension — a retired `orgId`/`clientId`, or an invalid
12
+ `scope:<namespace>` — now shows as granting nothing, matching how the server evaluates it,
13
+ instead of appearing to grant records the credential cannot actually reach. The dead clause is
14
+ shown explicitly rather than silently dropped.
15
+
16
+ ### Changed
17
+
18
+ - **Clearer errors for expired sessions and rate limits.** When a command fails because your
19
+ login has expired, the CLI now tells you to run `vectros login` and retry; a rate-limited
20
+ request tells you to wait and retry. Failures that carry a typed error code surface it on the
21
+ support line alongside the request id.
22
+ - **`bootstrap` warns when seeded sample data could not be indexed.** If a seed record or document
23
+ is stored but not (fully) searchable, the run summary now says so — and why — instead of
24
+ reporting it as fully seeded.
25
+
6
26
  ## 0.10.0
7
27
 
8
28
  ### Changed (breaking)
package/dist/cli.js CHANGED
@@ -1614,7 +1614,7 @@ var require_proper_lockfile = __commonJS({
1614
1614
  // src/cli-bootstrap.ts
1615
1615
  var readline = __toESM(require("readline/promises"));
1616
1616
 
1617
- // node_modules/@vectros-ai/sdk/dist/index.mjs
1617
+ // ../../node_modules/@vectros-ai/sdk/dist/index.mjs
1618
1618
  var __defProp2 = Object.defineProperty;
1619
1619
  var __export = (target, all) => {
1620
1620
  for (var name in all)
@@ -1649,6 +1649,7 @@ __export(api_exports, {
1649
1649
  FileUploadRequest: () => FileUploadRequest,
1650
1650
  ForbiddenError: () => ForbiddenError,
1651
1651
  IdentityLookupRequest: () => IdentityLookupRequest,
1652
+ IndexFailure: () => IndexFailure,
1652
1653
  InternalServerError: () => InternalServerError,
1653
1654
  ListEntitiesRequestOrder: () => ListEntitiesRequestOrder,
1654
1655
  ListUsersRequestOrder: () => ListUsersRequestOrder,
@@ -2170,6 +2171,17 @@ var IdentityLookupRequest;
2170
2171
  Desc: "desc"
2171
2172
  };
2172
2173
  })(IdentityLookupRequest || (IdentityLookupRequest = {}));
2174
+ var IndexFailure;
2175
+ ((IndexFailure2) => {
2176
+ IndexFailure2.Code = {
2177
+ SourceUnavailable: "SOURCE_UNAVAILABLE",
2178
+ TextIndexFailed: "TEXT_INDEX_FAILED",
2179
+ EmbeddingFailed: "EMBEDDING_FAILED",
2180
+ IndexingFailed: "INDEXING_FAILED",
2181
+ VectorLimitExceeded: "VECTOR_LIMIT_EXCEEDED",
2182
+ Internal: "INTERNAL"
2183
+ };
2184
+ })(IndexFailure || (IndexFailure = {}));
2173
2185
  var ModelDataVersionResponse;
2174
2186
  ((ModelDataVersionResponse2) => {
2175
2187
  ModelDataVersionResponse2.ChangeType = {
@@ -5686,7 +5698,7 @@ var AuthClient = class {
5686
5698
  );
5687
5699
  }
5688
5700
  /**
5689
- * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user, organization, or client. Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
5701
+ * Creates a short-lived JWT bearer token restricted to specific actions and, optionally, to a particular user or identity entity (in any namespace). Use this to hand a narrowly-scoped credential to a browser or downstream service so it never sees your root API key. Only callable with a root API key (`sk_*`).
5690
5702
  *
5691
5703
  * @param {Vectros.TokenRequest} request
5692
5704
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5903,7 +5915,7 @@ var ComplianceClient = class {
5903
5915
  this._options = normalizeClientOptionsWithAuth(options);
5904
5916
  }
5905
5917
  /**
5906
- * Submits a right-to-erasure request for a single end-subject (a user, client, or organization). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
5918
+ * Submits a right-to-erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure removes exactly the data the subject solely owns across the declared contexts, plus the subject's identity and lookup rows. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
5907
5919
  *
5908
5920
  * @param {Vectros.ErasureRequest} request
5909
5921
  * @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -12480,19 +12492,21 @@ function extractServerError(err) {
12480
12492
  const statusCode = typeof e.statusCode === "number" ? e.statusCode : void 0;
12481
12493
  let serverMessage;
12482
12494
  let requestId;
12495
+ let errorCode;
12483
12496
  if (e.body && typeof e.body === "object") {
12484
12497
  const b = e.body;
12485
12498
  if (typeof b.message === "string" && b.message.trim()) serverMessage = b.message.trim();
12486
12499
  if (typeof b.requestId === "string" && b.requestId.trim()) requestId = b.requestId.trim();
12500
+ if (typeof b.errorCode === "string" && b.errorCode.trim()) errorCode = b.errorCode.trim();
12487
12501
  } else if (typeof e.body === "string" && e.body.trim()) {
12488
12502
  serverMessage = e.body.trim();
12489
12503
  }
12490
- if (serverMessage) return { statusCode, message: serverMessage, requestId };
12504
+ if (serverMessage) return { statusCode, message: serverMessage, requestId, errorCode };
12491
12505
  const message = typeof e.message === "string" ? e.message : "";
12492
12506
  const isSdkEnvelope = /\nStatus code:/.test(message);
12493
- if (message && !isSdkEnvelope) return { statusCode, message, requestId };
12494
- if (statusCode) return { statusCode, message: `HTTP ${statusCode}`, requestId };
12495
- if (message) return { statusCode, message, requestId };
12507
+ if (message && !isSdkEnvelope) return { statusCode, message, requestId, errorCode };
12508
+ if (statusCode) return { statusCode, message: `HTTP ${statusCode}`, requestId, errorCode };
12509
+ if (message) return { statusCode, message, requestId, errorCode };
12496
12510
  }
12497
12511
  return { message: String(err) };
12498
12512
  }
@@ -12503,15 +12517,17 @@ async function devRouteError(res, action) {
12503
12517
  const raw = await res.text().catch(() => "");
12504
12518
  let message;
12505
12519
  let requestId;
12520
+ let errorCode;
12506
12521
  try {
12507
12522
  const parsed = JSON.parse(raw);
12508
12523
  if (typeof parsed.message === "string" && parsed.message.trim()) message = parsed.message.trim();
12509
12524
  if (typeof parsed.requestId === "string" && parsed.requestId.trim()) requestId = parsed.requestId.trim();
12525
+ if (typeof parsed.errorCode === "string" && parsed.errorCode.trim()) errorCode = parsed.errorCode.trim();
12510
12526
  } catch {
12511
12527
  }
12512
12528
  const err = new Error(`${action}: HTTP ${res.status}`);
12513
12529
  err.statusCode = res.status;
12514
- err.body = { message: message ?? `HTTP ${res.status}`, requestId };
12530
+ err.body = { message: message ?? `HTTP ${res.status}`, requestId, errorCode };
12515
12531
  return err;
12516
12532
  }
12517
12533
  function formatCliError(err, opts) {
@@ -12520,14 +12536,28 @@ function formatCliError(err, opts) {
12520
12536
  const block = actionable ?? ` \u2716 ${opts.action}: ${info.message}
12521
12537
  `;
12522
12538
  const debug = opts.debug ?? Boolean(process.env.VECTROS_MCP_DEBUG);
12523
- if (debug && info.requestId) {
12524
- return `${block} (requestId: ${info.requestId} \u2014 quote this to support)
12539
+ if (debug && (info.requestId || info.errorCode)) {
12540
+ const parts = [
12541
+ info.errorCode ? `errorCode: ${info.errorCode}` : null,
12542
+ info.requestId ? `requestId: ${info.requestId}` : null
12543
+ ].filter(Boolean);
12544
+ return `${block} (${parts.join(", ")} \u2014 quote this to support)
12525
12545
  `;
12526
12546
  }
12527
12547
  return block;
12528
12548
  }
12529
12549
  function renderActionableError(info) {
12530
12550
  const msg = info.message;
12551
+ switch (info.errorCode) {
12552
+ case "SESSION_REFRESH_REQUIRED":
12553
+ return ` \u2716 Your session has expired.
12554
+ Run 'vectros login' to re-authenticate, then retry.
12555
+ `;
12556
+ case "RATE_LIMITED":
12557
+ return ` \u2716 ${msg}
12558
+ You're being rate-limited \u2014 wait a moment and retry.
12559
+ `;
12560
+ }
12531
12561
  if (/scoped-key limit reached/i.test(msg)) {
12532
12562
  const plan = /plan '([^']+)'/i.exec(msg)?.[1];
12533
12563
  const count = /\((\d+) active keys\)/i.exec(msg)?.[1];
@@ -13076,8 +13106,13 @@ var KNOWN_CONTROL_PLANE_RESOURCES = [
13076
13106
  "users",
13077
13107
  "billing",
13078
13108
  "admin",
13079
- "clients",
13080
- "orgs"
13109
+ // Identity/compartment provisioning. The flat `orgs`/`clients` resources were
13110
+ // folded into the generic `entities` (per-namespace) + `namespaces` surfaces,
13111
+ // which is what the API now scopes on — so those are the current spellings a
13112
+ // pack could name. (Cosmetic: any non-data-plane resource hard-rejects whether
13113
+ // or not it is listed here; this list only sharpens the error message.)
13114
+ "entities",
13115
+ "namespaces"
13081
13116
  ];
13082
13117
  var VALID_OPS = /* @__PURE__ */ new Set(["c", "r", "u", "d"]);
13083
13118
  var DATA_PLANE_SET = new Set(DATA_PLANE_ALLOWLIST);
@@ -13524,6 +13559,21 @@ async function runLoader(client, input) {
13524
13559
  let seededCount = 0;
13525
13560
  const seededRecordIds = [];
13526
13561
  const seededDocumentIds = [];
13562
+ const seedIndexFailures = [];
13563
+ const noteIfIndexFailed = (created, rec, surface) => {
13564
+ if (created.indexStatus !== "FAILED") return;
13565
+ seedIndexFailures.push({
13566
+ externalId: rec.externalId,
13567
+ typeName: rec.typeName,
13568
+ surface,
13569
+ code: created.indexFailure?.code,
13570
+ message: created.indexFailure?.message
13571
+ });
13572
+ log.warn(
13573
+ { component: "loader", typeName: rec.typeName, externalId: rec.externalId, code: created.indexFailure?.code },
13574
+ "seed stored but failed to index"
13575
+ );
13576
+ };
13527
13577
  for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
13528
13578
  const schemaId = schemaIds[rec.typeName];
13529
13579
  if (!schemaId) {
@@ -13544,6 +13594,7 @@ async function runLoader(client, input) {
13544
13594
  }
13545
13595
  });
13546
13596
  if (created.id) seededDocumentIds.push(created.id);
13597
+ noteIfIndexFailed(created, rec, "document");
13547
13598
  } else {
13548
13599
  const created = await ctxClient.records.createRecord({
13549
13600
  body: {
@@ -13555,6 +13606,7 @@ async function runLoader(client, input) {
13555
13606
  }
13556
13607
  });
13557
13608
  if (created.id) seededRecordIds.push(created.id);
13609
+ noteIfIndexFailed(created, rec, "record");
13558
13610
  }
13559
13611
  seededCount++;
13560
13612
  }
@@ -13573,7 +13625,8 @@ async function runLoader(client, input) {
13573
13625
  roleIds,
13574
13626
  seededCount,
13575
13627
  seededRecordIds,
13576
- seededDocumentIds
13628
+ seededDocumentIds,
13629
+ seedIndexFailures
13577
13630
  };
13578
13631
  }
13579
13632
  function buildPlan(blueprint, opts = {}) {
@@ -13828,6 +13881,17 @@ function seedSummaryLine(opts) {
13828
13881
  return opts.seededCount ? ` \u2714 Seeded ${opts.seededCount} record(s)
13829
13882
  ` : "";
13830
13883
  }
13884
+ function seedIndexWarningBlock(failures) {
13885
+ if (failures.length === 0) return "";
13886
+ const lines = failures.map((f) => {
13887
+ const reason = f.message ?? f.code ?? "indexing failed";
13888
+ return ` \u2022 ${f.typeName}/${f.externalId} (${f.surface}): ${reason}
13889
+ `;
13890
+ });
13891
+ return ` \u26A0 ${failures.length} seed(s) stored but not fully searchable:
13892
+ ` + lines.join("") + ` They're stored and retrievable by id, but won't appear in search until the issue above is resolved.
13893
+ `;
13894
+ }
13831
13895
  function reconcileTally(items, noun) {
13832
13896
  if (items.length === 0) return null;
13833
13897
  const created = items.filter((i) => i.action === "created").length;
@@ -14084,7 +14148,7 @@ ${renderPlan(blueprint, planSteps)}
14084
14148
  noSeed,
14085
14149
  declinedInteractively: seedDeclinedInteractively,
14086
14150
  seededCount: result.seededCount
14087
- })
14151
+ }) + seedIndexWarningBlock(result.seedIndexFailures)
14088
14152
  );
14089
14153
  const entry = buildVectrosServerEntry({ apiKey: result.rawKey, apiBaseUrl: baseUrl });
14090
14154
  const isCode = args.client === "code";
@@ -15365,6 +15429,25 @@ function canonicalDim(key) {
15365
15429
  if (key === "userId" || key === "partnerUserId") return "user";
15366
15430
  return key;
15367
15431
  }
15432
+ var FORBIDDEN_SCOPE_NAMESPACES = /* @__PURE__ */ new Set([
15433
+ "user",
15434
+ "record",
15435
+ "document",
15436
+ "entity",
15437
+ "self",
15438
+ "tenant",
15439
+ "context",
15440
+ "scope",
15441
+ "versions",
15442
+ "lookup"
15443
+ ]);
15444
+ var SCOPE_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
15445
+ function isRecognizedOwnershipKey(key) {
15446
+ if (key === "userId" || key === "partnerUserId") return true;
15447
+ if (!key.startsWith("scope:")) return false;
15448
+ const ns = key.slice("scope:".length);
15449
+ return !FORBIDDEN_SCOPE_NAMESPACES.has(ns) && SCOPE_NAMESPACE_RE.test(ns);
15450
+ }
15368
15451
  var SELF_TOKEN_RE2 = /^\$\{\{\s*self\.[a-zA-Z.]+\s*\}\}$/;
15369
15452
  function normalizeClauses(raw) {
15370
15453
  const out = [];
@@ -15374,15 +15457,18 @@ function normalizeClauses(raw) {
15374
15457
  const actions = obj.allowed_actions ?? obj.allowedActions ?? [];
15375
15458
  const ds = obj.data_scope ?? obj.dataScope ?? {};
15376
15459
  const dataScope = {};
15460
+ const unrecognizedDims = [];
15377
15461
  for (const [k, v] of Object.entries(ds)) {
15462
+ const dim = canonicalDim(k);
15378
15463
  const values = Array.isArray(v) ? v : [v];
15379
- dataScope[canonicalDim(k)] = values.map((val) => {
15464
+ dataScope[dim] = values.map((val) => {
15380
15465
  if (val === null) return null;
15381
15466
  const s = String(val);
15382
15467
  return SELF_TOKEN_RE2.test(s) ? SELF : s;
15383
15468
  });
15469
+ if (!isRecognizedOwnershipKey(k)) unrecognizedDims.push(k);
15384
15470
  }
15385
- out.push({ actions: Array.isArray(actions) ? actions.map(String) : [], dataScope });
15471
+ out.push({ actions: Array.isArray(actions) ? actions.map(String) : [], dataScope, unrecognizedDims });
15386
15472
  }
15387
15473
  return out;
15388
15474
  }
@@ -15393,6 +15479,7 @@ function buildArchetypes(clauses) {
15393
15479
  ];
15394
15480
  const scopeDims = /* @__PURE__ */ new Map();
15395
15481
  for (const c of clauses) {
15482
+ if (c.unrecognizedDims.length > 0) continue;
15396
15483
  for (const [dim, values] of Object.entries(c.dataScope)) {
15397
15484
  if (dim === "user") continue;
15398
15485
  const set = scopeDims.get(dim) ?? /* @__PURE__ */ new Set();
@@ -15416,6 +15503,7 @@ function buildArchetypes(clauses) {
15416
15503
  return rows;
15417
15504
  }
15418
15505
  function clauseMatches(clause, arch) {
15506
+ if (clause.unrecognizedDims.length > 0) return false;
15419
15507
  for (const [dim, allowed] of Object.entries(clause.dataScope)) {
15420
15508
  const recordValue = arch.owners[dim];
15421
15509
  const ok = allowed.some((a) => {
@@ -15481,7 +15569,14 @@ async function runAccessExplain(opts, deps = {}) {
15481
15569
  context: opts.context,
15482
15570
  via,
15483
15571
  identityOverrides: profile.identityOverrides,
15484
- clauses: clauses.map((c, i) => ({ n: i + 1, actions: c.actions, dataScope: renderDataScope(c.dataScope) })),
15572
+ clauses: clauses.map((c, i) => ({
15573
+ n: i + 1,
15574
+ actions: c.actions,
15575
+ dataScope: renderDataScope(c.dataScope),
15576
+ // Present only on a dead clause — an unrecognized dim the platform
15577
+ // denies (a retired key or a bad scope:<ns>); the matrix omits it.
15578
+ ...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {}
15579
+ })),
15485
15580
  matrix: rows,
15486
15581
  note: "Ownership dimensions only: per-type action qualifiers narrow further, and search/RAG visibility is bounded per row by the same clause's typed read grants. Client-side preview; the platform decides at request time."
15487
15582
  })}
@@ -15500,6 +15595,12 @@ async function runAccessExplain(opts, deps = {}) {
15500
15595
  clauses.forEach((c, i) => {
15501
15596
  process.stdout.write(` ${i + 1}. [${c.actions.join(", ")}] where ${renderDataScope(c.dataScope)}
15502
15597
  `);
15598
+ if (c.unrecognizedDims.length > 0) {
15599
+ process.stdout.write(
15600
+ ` \u2716 grants nothing \u2014 unrecognized dimension${c.unrecognizedDims.length > 1 ? "s" : ""}: ${c.unrecognizedDims.join(", ")} (the platform denies a clause keyed on any dim that is not userId or a valid scope:<namespace>)
15601
+ `
15602
+ );
15603
+ }
15503
15604
  });
15504
15605
  process.stdout.write(
15505
15606
  "\n" + formatTable(rows, [
@@ -18245,8 +18346,8 @@ function emitJoined(alias, context, principalId, role, keyId, reused) {
18245
18346
 
18246
18347
  // src/build-info.ts
18247
18348
  var BUILD_INFO = {
18248
- cli: true ? "0.10.0" : "dev",
18249
- sdk: true ? "0.35.0" : "dev",
18349
+ cli: true ? "0.11.0" : "dev",
18350
+ sdk: true ? "0.36.0" : "dev",
18250
18351
  blueprints: true ? "0.8.0" : "dev"
18251
18352
  };
18252
18353
  function formatBuildInfo() {