@tablation/crew 0.79.1 → 0.79.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.
- package/dist/cli.js +64 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13147,10 +13147,19 @@ async function canMintAgentKeys(route, o = {}) {
|
|
|
13147
13147
|
|
|
13148
13148
|
// src/seat-read-probe.ts
|
|
13149
13149
|
var SEAT_NO_READ_REMEDY = "the project has no default role for agent keys \u2014 update the Issues app's template, then re-mint with `crew agents sync --revoke-seat-keys`, or assign the Crew seat role to the agent members";
|
|
13150
|
-
|
|
13151
|
-
const
|
|
13152
|
-
if (!
|
|
13153
|
-
const
|
|
13150
|
+
function probeTables(route) {
|
|
13151
|
+
const m = route.resolved?.models;
|
|
13152
|
+
if (!m) return [];
|
|
13153
|
+
const r = route.resolved;
|
|
13154
|
+
return [
|
|
13155
|
+
{ name: "Issues", modelId: m.issues },
|
|
13156
|
+
{ name: "Comments", modelId: m.comments },
|
|
13157
|
+
{ name: "Crew", modelId: m.crew },
|
|
13158
|
+
{ name: "Ships", modelId: r?.shipsModelId ?? "" }
|
|
13159
|
+
].filter((t) => !!t.modelId);
|
|
13160
|
+
}
|
|
13161
|
+
async function probeTableRead(route, modelId, apiKey, o = {}) {
|
|
13162
|
+
const url = `${route.baseUrl.replace(/\/+$/, "")}/api/data-models/${modelId}/records?limit=1`;
|
|
13154
13163
|
try {
|
|
13155
13164
|
const res = await (o.fetchImpl ?? fetch)(url, {
|
|
13156
13165
|
headers: { Authorization: `Bearer ${apiKey}`, "User-Agent": o.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" }
|
|
@@ -13168,12 +13177,46 @@ async function probeSeatReads(route, o = {}) {
|
|
|
13168
13177
|
for (const role of roles) {
|
|
13169
13178
|
const id = role === "release" ? await resolveReleaseKey(route, o.getStore) : await resolveSeatKey(route, role, o.getStore);
|
|
13170
13179
|
if (id.kind !== "agent") continue;
|
|
13171
|
-
|
|
13180
|
+
const tables = [];
|
|
13181
|
+
for (const t of probeTables(route)) tables.push({ table: t.name, ...await probeTableRead(route, t.modelId, id.apiKey, o) });
|
|
13182
|
+
const bad = tables.find((t) => t.state === "forbidden") ?? tables.find((t) => t.state === "unknown");
|
|
13183
|
+
out.push({ role, state: bad?.state ?? (tables.length ? "ok" : "unknown"), detail: bad?.detail ?? (tables.length ? void 0 : "route has no resolved tables"), tables });
|
|
13172
13184
|
}
|
|
13173
13185
|
return out;
|
|
13174
13186
|
}
|
|
13175
|
-
function
|
|
13176
|
-
|
|
13187
|
+
function forbiddenDetail(e, route) {
|
|
13188
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13189
|
+
const m = /^([A-Z]+) (\S+) -> 403/.exec(msg);
|
|
13190
|
+
const verb = /No (READ|WRITE) access/i.exec(msg)?.[1]?.toUpperCase();
|
|
13191
|
+
const modelId = /\/data-models\/([\w-]+)/i.exec(m?.[2] ?? "")?.[1];
|
|
13192
|
+
const r = route?.resolved;
|
|
13193
|
+
const known = [
|
|
13194
|
+
[r?.models?.issues, "Issues"],
|
|
13195
|
+
[r?.models?.comments, "Comments"],
|
|
13196
|
+
[r?.models?.crew, "Crew"],
|
|
13197
|
+
[r?.shipsModelId, "Ships"],
|
|
13198
|
+
[r?.locksModelId, "Locks"],
|
|
13199
|
+
[r?.epicsModelId, "Epics"],
|
|
13200
|
+
[r?.reposModelId, "Repos"]
|
|
13201
|
+
];
|
|
13202
|
+
const table2 = modelId ? known.find(([id]) => id && id === modelId)?.[1] ?? `table ${modelId}` : void 0;
|
|
13203
|
+
return { method: m?.[1], path: m?.[2], table: table2, verb };
|
|
13204
|
+
}
|
|
13205
|
+
function remedyFor(d, roleName = "the agent role") {
|
|
13206
|
+
if (d.table && d.verb && !(d.table === "Issues" && d.verb === "READ")) {
|
|
13207
|
+
return `grant ${d.verb} on ${d.table} to ${roleName}`;
|
|
13208
|
+
}
|
|
13209
|
+
return SEAT_NO_READ_REMEDY;
|
|
13210
|
+
}
|
|
13211
|
+
function describeSeatForbidden(role, tables = []) {
|
|
13212
|
+
const bad = tables.filter((t) => t.state === "forbidden").map((t) => t.table);
|
|
13213
|
+
const what = bad.length ? bad.join(", ") : "Issues";
|
|
13214
|
+
const remedy = bad.length && !(bad.length === 1 && bad[0] === "Issues") ? bad.map((t) => remedyFor({ table: t, verb: "READ" })).join("; ") : SEAT_NO_READ_REMEDY;
|
|
13215
|
+
return `seat read: ERROR ${role} agent key cannot read ${what} (403) \u2014 ${remedy}`;
|
|
13216
|
+
}
|
|
13217
|
+
function describeSeatTables(role, tables) {
|
|
13218
|
+
const cells = tables.map((t) => `${t.table} ${t.state === "ok" ? "ok" : t.state === "forbidden" ? "403" : `error (${t.detail ?? "?"})`}`);
|
|
13219
|
+
return `seat read: ${role}: ${cells.join(", ")}`;
|
|
13177
13220
|
}
|
|
13178
13221
|
function isForbiddenError(e) {
|
|
13179
13222
|
const status2 = e?.status;
|
|
@@ -13181,16 +13224,18 @@ function isForbiddenError(e) {
|
|
|
13181
13224
|
return /\b403\b/.test(e instanceof Error ? e.message : String(e));
|
|
13182
13225
|
}
|
|
13183
13226
|
var SEAT_FORBIDDEN_KEY = "seat_forbidden:release";
|
|
13184
|
-
function seatForbiddenItem(now = /* @__PURE__ */ new Date()) {
|
|
13227
|
+
function seatForbiddenItem(now = /* @__PURE__ */ new Date(), e, route) {
|
|
13228
|
+
const d = e === void 0 ? {} : forbiddenDetail(e, route);
|
|
13229
|
+
const what = d.method && d.table ? `${d.method} ${d.table}${d.verb ? ` -> 403 No ${d.verb} access` : " -> 403"}` : "cannot read the Issues table (403)";
|
|
13185
13230
|
return {
|
|
13186
13231
|
kind: "seat_forbidden",
|
|
13187
13232
|
key: SEAT_FORBIDDEN_KEY,
|
|
13188
|
-
message: `release seat
|
|
13233
|
+
message: `release seat: ${what} \u2014 ${remedyFor(d)}`,
|
|
13189
13234
|
since: now.toISOString()
|
|
13190
13235
|
};
|
|
13191
13236
|
}
|
|
13192
13237
|
function seatForbiddenRepoLine(scope) {
|
|
13193
|
-
return `${scope}: skipped \u2014 release seat
|
|
13238
|
+
return `${scope}: skipped \u2014 release seat was refused by the tracker (see attention)`;
|
|
13194
13239
|
}
|
|
13195
13240
|
|
|
13196
13241
|
// src/cli.ts
|
|
@@ -18263,7 +18308,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18263
18308
|
return summary;
|
|
18264
18309
|
} catch (e) {
|
|
18265
18310
|
if (isForbiddenError(e)) {
|
|
18266
|
-
await raiseShipAttention({ state, emit: remit, route: c, ship: cfg.ship }, seatForbiddenItem());
|
|
18311
|
+
await raiseShipAttention({ state, emit: remit, route: c, ship: cfg.ship }, seatForbiddenItem(/* @__PURE__ */ new Date(), e, c));
|
|
18267
18312
|
const line = seatForbiddenRepoLine(scope);
|
|
18268
18313
|
remit.warn(line, { step: "release" });
|
|
18269
18314
|
return { scope, tests: "skipped", outcome: "error", detail: line };
|
|
@@ -19024,7 +19069,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
19024
19069
|
if (!dryRun) {
|
|
19025
19070
|
for (const p of await probeSeatReads(target, { userAgent: cfg.ship.userAgent })) {
|
|
19026
19071
|
if (p.state === "forbidden") {
|
|
19027
|
-
process.stdout.write(`${describeSeatForbidden(p.role)}
|
|
19072
|
+
process.stdout.write(`${describeSeatForbidden(p.role, p.tables)}
|
|
19028
19073
|
`);
|
|
19029
19074
|
process.exitCode = 1;
|
|
19030
19075
|
}
|
|
@@ -20007,7 +20052,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20007
20052
|
for (const o of mintOutcomes) process.stderr.write(`${describeMintOutcome(o)}
|
|
20008
20053
|
`);
|
|
20009
20054
|
for (const p of await probeSeatReads({ ...existingRoute, resolved: { ...existingRoute.resolved, seats: found.seats } }, { userAgent: cfg.ship.userAgent })) {
|
|
20010
|
-
if (p.state === "forbidden") process.stderr.write(`${describeSeatForbidden(p.role)}
|
|
20055
|
+
if (p.state === "forbidden") process.stderr.write(`${describeSeatForbidden(p.role, p.tables)}
|
|
20011
20056
|
`);
|
|
20012
20057
|
}
|
|
20013
20058
|
} catch (e) {
|
|
@@ -20186,7 +20231,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
20186
20231
|
for (const m of minted) process.stderr.write(` ${describeMintOutcome(m)}
|
|
20187
20232
|
`);
|
|
20188
20233
|
for (const p of await probeSeatReads(syncRoute, { userAgent: cfg.ship.userAgent })) {
|
|
20189
|
-
if (p.state === "forbidden") process.stderr.write(` ${describeSeatForbidden(p.role)}
|
|
20234
|
+
if (p.state === "forbidden") process.stderr.write(` ${describeSeatForbidden(p.role, p.tables)}
|
|
20190
20235
|
`);
|
|
20191
20236
|
}
|
|
20192
20237
|
if (run2.readOnly) {
|
|
@@ -21016,7 +21061,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
21016
21061
|
baseUrl,
|
|
21017
21062
|
workspaceId: route.resolved.workspaceId,
|
|
21018
21063
|
apiKey,
|
|
21019
|
-
|
|
21064
|
+
// Only the seat's reason: runSelftest appends the remedy itself, as for every other class.
|
|
21065
|
+
keyProblem: keychainDown && seatKey.kind === "ship" ? seatKey.reason : void 0,
|
|
21020
21066
|
workspaceName
|
|
21021
21067
|
}));
|
|
21022
21068
|
}
|
|
@@ -21313,7 +21359,7 @@ watermark: ${state.watermark()}
|
|
|
21313
21359
|
if (!flag("offline")) {
|
|
21314
21360
|
for (const p of await probeSeatReads(route, { userAgent: cfg.ship.userAgent })) {
|
|
21315
21361
|
if (p.state === "forbidden") {
|
|
21316
|
-
problems.push({ severity: "blocker", kind: "seat_cannot_read_issues", route: route.route, message: `${p.role} agent key
|
|
21362
|
+
problems.push({ severity: "blocker", kind: "seat_cannot_read_issues", route: route.route, message: `${p.role} agent key refused on ${p.tables.filter((t) => t.state === "forbidden").map((t) => t.table).join(", ")} (403)`, remedy: describeSeatForbidden(p.role, p.tables).replace(/^.*?— /, "") });
|
|
21317
21363
|
} else if (p.state === "ok") passed++;
|
|
21318
21364
|
}
|
|
21319
21365
|
}
|
|
@@ -21459,11 +21505,11 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
21459
21505
|
if (!flag("offline")) {
|
|
21460
21506
|
for (const p of await probeSeatReads(roleRoute, { userAgent: cfg.ship.userAgent })) {
|
|
21461
21507
|
if (p.state === "forbidden") {
|
|
21462
|
-
process.stdout.write(`${describeSeatForbidden(p.role)}
|
|
21508
|
+
process.stdout.write(`${describeSeatForbidden(p.role, p.tables)}
|
|
21463
21509
|
`);
|
|
21464
21510
|
process.exitCode = 1;
|
|
21465
21511
|
} else {
|
|
21466
|
-
process.stdout.write(p.state === "ok" ?
|
|
21512
|
+
process.stdout.write(p.state === "ok" ? `${describeSeatTables(p.role, p.tables)}
|
|
21467
21513
|
` : `seat read: ${p.role} could not be checked (${p.detail})
|
|
21468
21514
|
`);
|
|
21469
21515
|
}
|
package/package.json
CHANGED