pilotswarm-sdk 0.5.30 → 0.5.32
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/api/index.d.ts +40 -0
- package/api/index.js +8 -0
- package/api/src/session-authz.d.ts +76 -0
- package/api/src/session-authz.js +240 -0
- package/dist/agent-fqn.d.ts +72 -0
- package/dist/agent-fqn.d.ts.map +1 -0
- package/dist/agent-fqn.js +137 -0
- package/dist/agent-fqn.js.map +1 -0
- package/dist/agent-loader.d.ts.map +1 -1
- package/dist/agent-loader.js +22 -6
- package/dist/agent-loader.js.map +1 -1
- package/dist/agent-manager-tools.d.ts +135 -0
- package/dist/agent-manager-tools.d.ts.map +1 -0
- package/dist/agent-manager-tools.js +813 -0
- package/dist/agent-manager-tools.js.map +1 -0
- package/dist/agent-package-diff.d.ts +97 -0
- package/dist/agent-package-diff.d.ts.map +1 -0
- package/dist/agent-package-diff.js +203 -0
- package/dist/agent-package-diff.js.map +1 -0
- package/dist/agent-package-format.d.ts +2 -0
- package/dist/agent-package-format.d.ts.map +1 -1
- package/dist/agent-package-format.js +15 -0
- package/dist/agent-package-format.js.map +1 -1
- package/dist/agent-package-import-fetch.d.ts +68 -0
- package/dist/agent-package-import-fetch.d.ts.map +1 -0
- package/dist/agent-package-import-fetch.js +156 -0
- package/dist/agent-package-import-fetch.js.map +1 -0
- package/dist/agent-package-import-policy.d.ts +133 -0
- package/dist/agent-package-import-policy.d.ts.map +1 -0
- package/dist/agent-package-import-policy.js +347 -0
- package/dist/agent-package-import-policy.js.map +1 -0
- package/dist/agent-package-installer.d.ts.map +1 -1
- package/dist/agent-package-installer.js +30 -4
- package/dist/agent-package-installer.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +653 -1
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +103 -10
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +85 -10
- package/dist/cms.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect-tools.d.ts +68 -8
- package/dist/inspect-tools.d.ts.map +1 -1
- package/dist/inspect-tools.js +282 -21
- package/dist/inspect-tools.js.map +1 -1
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +87 -0
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +20 -1
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +25 -0
- package/dist/management-client.js.map +1 -1
- package/dist/session-manager.d.ts +61 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +143 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +9 -1
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +345 -12
- package/dist/session-proxy.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/web-management-client.d.ts +2 -0
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +6 -0
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts +5 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +28 -0
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/plugins/mgmt/agents/pilotswarm.agent.md +2 -2
- package/plugins/mgmt/skills/cost-latency-analysis/SKILL.md +1 -1
- package/plugins/mgmt/skills/graph-debug/SKILL.md +4 -4
- package/plugins/mgmt/skills/orchestration-session-lifecycle/SKILL.md +1 -1
- package/plugins/mgmt/agents/agent-tuner.agent.md +0 -316
package/dist/cms-migrations.js
CHANGED
|
@@ -217,6 +217,16 @@ export function CMS_MIGRATIONS(schema) {
|
|
|
217
217
|
name: "agent_package_owner_identity",
|
|
218
218
|
sql: migration_0041_agent_package_owner_identity(schema),
|
|
219
219
|
},
|
|
220
|
+
{
|
|
221
|
+
version: "0042",
|
|
222
|
+
name: "user_role_from_signin",
|
|
223
|
+
sql: migration_0042_user_role_from_signin(schema),
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
version: "0043",
|
|
227
|
+
name: "agent_package_namespaces",
|
|
228
|
+
sql: migration_0043_agent_package_namespaces(schema),
|
|
229
|
+
},
|
|
220
230
|
];
|
|
221
231
|
}
|
|
222
232
|
// ─── Migration 0040: worker registry ─────────────────────────────
|
|
@@ -8122,7 +8132,7 @@ $$ LANGUAGE plpgsql;
|
|
|
8122
8132
|
//
|
|
8123
8133
|
// An agent package stored only its owner PRINCIPAL (provider + subject —
|
|
8124
8134
|
// an opaque directory id) plus a created_by email, so the UI had no human
|
|
8125
|
-
// name to render and fell back to the email's local part: "
|
|
8135
|
+
// name to render and fell back to the email's local part: "ada@…"
|
|
8126
8136
|
// became "DA" where the same person's sessions correctly showed "AD" for
|
|
8127
8137
|
// "Affan Dar".
|
|
8128
8138
|
//
|
|
@@ -8196,4 +8206,646 @@ CREATE OR REPLACE FUNCTION ${s}.cms_get_agent_package(
|
|
|
8196
8206
|
$$ LANGUAGE sql;
|
|
8197
8207
|
`;
|
|
8198
8208
|
}
|
|
8209
|
+
// ─── Migration 0042: persist the authorization role at sign-in ───
|
|
8210
|
+
//
|
|
8211
|
+
// The worker had no way to know whether a session's owner is an admin.
|
|
8212
|
+
// The portal resolves the role from the request JWT (Entra app roles, or the
|
|
8213
|
+
// email allowlist) and never persisted it, so a worker — which holds an
|
|
8214
|
+
// owner, not a token, and runs turns on the far side of a durable queue where
|
|
8215
|
+
// no request exists at all (cron, sub-agent turns, crash recovery, replay) —
|
|
8216
|
+
// had nothing to read. Agent inspect tools were therefore user-scoped for
|
|
8217
|
+
// everyone, admins included.
|
|
8218
|
+
//
|
|
8219
|
+
// This stores the LAST OBSERVED role for a principal, refreshed on every
|
|
8220
|
+
// authenticated portal request. Two properties make that safe to rely on:
|
|
8221
|
+
//
|
|
8222
|
+
// - `role` is OVERWRITTEN, never COALESCE'd. A demotion (admin → user) must
|
|
8223
|
+
// land, and a COALESCE-style merge like cms_register_user's display-field
|
|
8224
|
+
// rule would silently preserve the higher privilege forever. This is why
|
|
8225
|
+
// the role does NOT ride along on cms_register_user: that function is
|
|
8226
|
+
// called by sightings which carry no role at all (share grants, session
|
|
8227
|
+
// creates), and any of them would either wipe a good role or force the
|
|
8228
|
+
// merge semantics we must not have here.
|
|
8229
|
+
//
|
|
8230
|
+
// - `role_seen_at` records when the value was last CONFIRMED, not when it
|
|
8231
|
+
// last changed, so readers can fail closed on a stale row. It is bumped
|
|
8232
|
+
// on every write even when the role is unchanged.
|
|
8233
|
+
//
|
|
8234
|
+
// Unrecognized role text normalizes to NULL rather than being stored verbatim:
|
|
8235
|
+
// an unknown role must read as "no privilege", never as an opaque value some
|
|
8236
|
+
// later caller might compare loosely.
|
|
8237
|
+
function migration_0042_user_role_from_signin(schema) {
|
|
8238
|
+
const s = `"${schema}"`;
|
|
8239
|
+
return `
|
|
8240
|
+
-- 0042_user_role_from_signin: last-observed authorization role per user.
|
|
8241
|
+
|
|
8242
|
+
ALTER TABLE ${s}.users ADD COLUMN IF NOT EXISTS role TEXT;
|
|
8243
|
+
ALTER TABLE ${s}.users ADD COLUMN IF NOT EXISTS role_seen_at TIMESTAMPTZ;
|
|
8244
|
+
|
|
8245
|
+
-- ── cms_set_user_role ────────────────────────────────────────────
|
|
8246
|
+
-- Upserts the user (reusing the standard sighting path so email/display_name
|
|
8247
|
+
-- stay fresh) and then REPLACES the role outright.
|
|
8248
|
+
--
|
|
8249
|
+
-- Returns the stored role so the caller can cheaply detect a change without a
|
|
8250
|
+
-- second round-trip.
|
|
8251
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_set_user_role(
|
|
8252
|
+
p_provider TEXT,
|
|
8253
|
+
p_subject TEXT,
|
|
8254
|
+
p_email TEXT,
|
|
8255
|
+
p_display_name TEXT,
|
|
8256
|
+
p_role TEXT
|
|
8257
|
+
) RETURNS TEXT AS $$
|
|
8258
|
+
DECLARE
|
|
8259
|
+
v_user_id BIGINT;
|
|
8260
|
+
v_role TEXT := LOWER(NULLIF(BTRIM(p_role), ''));
|
|
8261
|
+
BEGIN
|
|
8262
|
+
-- Anything outside the known vocabulary is stored as NULL (= no
|
|
8263
|
+
-- privilege). 'anonymous' is a real value: it is what an auth-disabled
|
|
8264
|
+
-- deployment issues, and the portal treats it as full access, so the
|
|
8265
|
+
-- worker must be able to see the same thing rather than guess.
|
|
8266
|
+
IF v_role IS NOT NULL AND v_role NOT IN ('admin', 'user', 'anonymous') THEN
|
|
8267
|
+
v_role := NULL;
|
|
8268
|
+
END IF;
|
|
8269
|
+
|
|
8270
|
+
v_user_id := ${s}.cms_register_user(p_provider, p_subject, p_email, p_display_name);
|
|
8271
|
+
|
|
8272
|
+
UPDATE ${s}.users
|
|
8273
|
+
SET role = v_role,
|
|
8274
|
+
role_seen_at = now(),
|
|
8275
|
+
updated_at = now()
|
|
8276
|
+
WHERE user_id = v_user_id;
|
|
8277
|
+
|
|
8278
|
+
RETURN v_role;
|
|
8279
|
+
END;
|
|
8280
|
+
$$ LANGUAGE plpgsql;
|
|
8281
|
+
|
|
8282
|
+
-- ── cms_get_user_role ────────────────────────────────────────────
|
|
8283
|
+
-- Narrow read for the worker's viewer resolver. Deliberately NOT folded into
|
|
8284
|
+
-- cms_get_user_profile: that would change an existing return type (requiring
|
|
8285
|
+
-- a DROP) and would hand the role to every profile reader, including the
|
|
8286
|
+
-- management surface, where it has no business being.
|
|
8287
|
+
--
|
|
8288
|
+
-- Returns no row for an unknown principal — the caller must treat "no row"
|
|
8289
|
+
-- and "NULL role" alike, as no privilege.
|
|
8290
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_get_user_role(
|
|
8291
|
+
p_provider TEXT,
|
|
8292
|
+
p_subject TEXT
|
|
8293
|
+
) RETURNS TABLE (
|
|
8294
|
+
role TEXT,
|
|
8295
|
+
role_seen_at TIMESTAMPTZ
|
|
8296
|
+
) AS $$
|
|
8297
|
+
DECLARE
|
|
8298
|
+
v_provider TEXT := NULLIF(BTRIM(p_provider), '');
|
|
8299
|
+
v_subject TEXT := NULLIF(BTRIM(p_subject), '');
|
|
8300
|
+
BEGIN
|
|
8301
|
+
IF v_provider IS NULL OR v_subject IS NULL THEN
|
|
8302
|
+
RETURN;
|
|
8303
|
+
END IF;
|
|
8304
|
+
|
|
8305
|
+
RETURN QUERY
|
|
8306
|
+
SELECT u.role, u.role_seen_at
|
|
8307
|
+
FROM ${s}.users u
|
|
8308
|
+
WHERE u.provider = v_provider AND u.subject = v_subject;
|
|
8309
|
+
END;
|
|
8310
|
+
$$ LANGUAGE plpgsql;
|
|
8311
|
+
`;
|
|
8312
|
+
}
|
|
8313
|
+
// ─── Migration 0043: per-user agent package namespaces ───────────
|
|
8314
|
+
//
|
|
8315
|
+
// Package identity was globally unique on `name` alone, so the first person
|
|
8316
|
+
// to publish "triager" owned that word for the whole deployment. Identity
|
|
8317
|
+
// becomes the triple (scope, owner, name): every user gets their own
|
|
8318
|
+
// namespace, and `shared` is the deployment-wide one.
|
|
8319
|
+
//
|
|
8320
|
+
// Two rules carry the whole design:
|
|
8321
|
+
//
|
|
8322
|
+
// 1. RESOLUTION — a viewer's own ENABLED copy shadows the shared copy.
|
|
8323
|
+
// That single rule delivers download-modify-independently, and doubles
|
|
8324
|
+
// as recovery: disable a broken personal copy and the shared one takes
|
|
8325
|
+
// over again with no other action.
|
|
8326
|
+
//
|
|
8327
|
+
// 2. PROMOTION IS EXCLUSIVE — user→shared refuses when a shared package
|
|
8328
|
+
// already holds the name, because `shared` is the one namespace that
|
|
8329
|
+
// still has to be globally unique.
|
|
8330
|
+
//
|
|
8331
|
+
// Every mutation proc previously keyed off `p_name` alone, which is now
|
|
8332
|
+
// ambiguous (your "triager" and mine). They all gain an explicit SELECTOR —
|
|
8333
|
+
// (scope, owner_provider, owner_subject) — where a NULL scope means "resolve
|
|
8334
|
+
// it for the actor the same way resolution works everywhere else". Adding
|
|
8335
|
+
// parameters creates an OVERLOAD rather than replacing the function, so each
|
|
8336
|
+
// old signature is dropped explicitly first.
|
|
8337
|
+
//
|
|
8338
|
+
// The install manifest also starts returning `package_id`. Workers install
|
|
8339
|
+
// every enabled package to disk, and with namespaces two packages can share
|
|
8340
|
+
// a name — a stable per-row key is what keeps them from colliding in the
|
|
8341
|
+
// cache directory.
|
|
8342
|
+
function migration_0043_agent_package_namespaces(schema) {
|
|
8343
|
+
const s = `"${schema}"`;
|
|
8344
|
+
return `
|
|
8345
|
+
-- 0043_agent_package_namespaces: identity becomes (scope, owner, name).
|
|
8346
|
+
|
|
8347
|
+
-- ── The uniqueness swap ──────────────────────────────────────────
|
|
8348
|
+
--
|
|
8349
|
+
-- The old constraint came from \`name TEXT NOT NULL UNIQUE\` in the 0038
|
|
8350
|
+
-- CREATE TABLE, so PostgreSQL named it. Look it up by DEFINITION rather than
|
|
8351
|
+
-- by guessing the generated name: a schema restored from a dump, or created
|
|
8352
|
+
-- by a different PostgreSQL version, may not have named it identically.
|
|
8353
|
+
DO $mig$
|
|
8354
|
+
DECLARE
|
|
8355
|
+
v_con TEXT;
|
|
8356
|
+
BEGIN
|
|
8357
|
+
SELECT c.conname INTO v_con
|
|
8358
|
+
FROM pg_constraint c
|
|
8359
|
+
WHERE c.conrelid = '${s}.agent_packages'::regclass
|
|
8360
|
+
AND c.contype = 'u'
|
|
8361
|
+
AND pg_get_constraintdef(c.oid) = 'UNIQUE (name)';
|
|
8362
|
+
IF v_con IS NOT NULL THEN
|
|
8363
|
+
EXECUTE format('ALTER TABLE ${s}.agent_packages DROP CONSTRAINT %I', v_con);
|
|
8364
|
+
END IF;
|
|
8365
|
+
END
|
|
8366
|
+
$mig$;
|
|
8367
|
+
|
|
8368
|
+
-- Shared is still globally unique — it is the deployment's own namespace.
|
|
8369
|
+
CREATE UNIQUE INDEX IF NOT EXISTS agent_packages_shared_name_uniq
|
|
8370
|
+
ON ${s}.agent_packages (name) WHERE scope = 'shared';
|
|
8371
|
+
|
|
8372
|
+
-- User scope is unique PER OWNER. Existing rows were globally unique, so this
|
|
8373
|
+
-- index cannot conflict on the way in — there is nothing to reconcile.
|
|
8374
|
+
CREATE UNIQUE INDEX IF NOT EXISTS agent_packages_user_name_uniq
|
|
8375
|
+
ON ${s}.agent_packages (owner_provider, owner_subject, name) WHERE scope = 'user';
|
|
8376
|
+
|
|
8377
|
+
-- ── The resolver ─────────────────────────────────────────────────
|
|
8378
|
+
--
|
|
8379
|
+
-- One place decides which copy a name refers to, so reads, writes and the
|
|
8380
|
+
-- worker's agent lookup can never disagree about it.
|
|
8381
|
+
--
|
|
8382
|
+
-- p_sel_scope NULL → resolve: the viewer's own copy, else shared.
|
|
8383
|
+
-- p_sel_scope given → pin exactly that copy (this is what an FQN compiles to).
|
|
8384
|
+
--
|
|
8385
|
+
-- p_require_enabled exists because SHADOWING IS ENABLE-SENSITIVE: a disabled
|
|
8386
|
+
-- personal copy must fall through to shared (that is the recovery path),
|
|
8387
|
+
-- while a mutation has to find the disabled row in order to re-enable it.
|
|
8388
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_resolve_agent_package_id(
|
|
8389
|
+
p_name TEXT,
|
|
8390
|
+
p_viewer_provider TEXT,
|
|
8391
|
+
p_viewer_subject TEXT,
|
|
8392
|
+
p_sel_scope TEXT,
|
|
8393
|
+
p_sel_owner_provider TEXT,
|
|
8394
|
+
p_sel_owner_subject TEXT,
|
|
8395
|
+
p_require_enabled BOOLEAN
|
|
8396
|
+
) RETURNS TEXT AS $$
|
|
8397
|
+
DECLARE
|
|
8398
|
+
v_viewer_p TEXT := NULLIF(BTRIM(p_viewer_provider), '');
|
|
8399
|
+
v_viewer_s TEXT := NULLIF(BTRIM(p_viewer_subject), '');
|
|
8400
|
+
v_sel_p TEXT := NULLIF(BTRIM(p_sel_owner_provider), '');
|
|
8401
|
+
v_sel_s TEXT := NULLIF(BTRIM(p_sel_owner_subject), '');
|
|
8402
|
+
v_id TEXT;
|
|
8403
|
+
BEGIN
|
|
8404
|
+
IF p_sel_scope = 'shared' THEN
|
|
8405
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p
|
|
8406
|
+
WHERE p.name = p_name AND p.scope = 'shared'
|
|
8407
|
+
AND (NOT p_require_enabled OR p.enabled);
|
|
8408
|
+
RETURN v_id;
|
|
8409
|
+
END IF;
|
|
8410
|
+
|
|
8411
|
+
IF p_sel_scope = 'user' THEN
|
|
8412
|
+
-- An explicit user-scope selection with no owner named means "mine".
|
|
8413
|
+
IF v_sel_p IS NULL OR v_sel_s IS NULL THEN
|
|
8414
|
+
v_sel_p := v_viewer_p;
|
|
8415
|
+
v_sel_s := v_viewer_s;
|
|
8416
|
+
END IF;
|
|
8417
|
+
IF v_sel_p IS NULL OR v_sel_s IS NULL THEN
|
|
8418
|
+
RETURN NULL;
|
|
8419
|
+
END IF;
|
|
8420
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p
|
|
8421
|
+
WHERE p.name = p_name AND p.scope = 'user'
|
|
8422
|
+
AND p.owner_provider = v_sel_p AND p.owner_subject = v_sel_s
|
|
8423
|
+
AND (NOT p_require_enabled OR p.enabled);
|
|
8424
|
+
RETURN v_id;
|
|
8425
|
+
END IF;
|
|
8426
|
+
|
|
8427
|
+
-- Unpinned: own copy shadows shared.
|
|
8428
|
+
IF v_viewer_p IS NOT NULL AND v_viewer_s IS NOT NULL THEN
|
|
8429
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p
|
|
8430
|
+
WHERE p.name = p_name AND p.scope = 'user'
|
|
8431
|
+
AND p.owner_provider = v_viewer_p AND p.owner_subject = v_viewer_s
|
|
8432
|
+
AND (NOT p_require_enabled OR p.enabled);
|
|
8433
|
+
IF v_id IS NOT NULL THEN
|
|
8434
|
+
RETURN v_id;
|
|
8435
|
+
END IF;
|
|
8436
|
+
END IF;
|
|
8437
|
+
|
|
8438
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p
|
|
8439
|
+
WHERE p.name = p_name AND p.scope = 'shared'
|
|
8440
|
+
AND (NOT p_require_enabled OR p.enabled);
|
|
8441
|
+
RETURN v_id;
|
|
8442
|
+
END;
|
|
8443
|
+
$$ LANGUAGE plpgsql;
|
|
8444
|
+
|
|
8445
|
+
-- ── Mutations: every one now takes a selector ────────────────────
|
|
8446
|
+
--
|
|
8447
|
+
-- Dropped first because the argument lists grew; CREATE OR REPLACE would
|
|
8448
|
+
-- leave the old arity behind as a live overload, and a caller that missed
|
|
8449
|
+
-- the update would silently keep hitting the name-only version.
|
|
8450
|
+
DROP FUNCTION IF EXISTS ${s}.cms_agent_package_authz(TEXT, TEXT, TEXT, BOOLEAN);
|
|
8451
|
+
DROP FUNCTION IF EXISTS ${s}.cms_set_agent_package_scope(TEXT, TEXT, TEXT, TEXT, BOOLEAN);
|
|
8452
|
+
DROP FUNCTION IF EXISTS ${s}.cms_set_agent_package_enabled(TEXT, BOOLEAN, TEXT, TEXT, BOOLEAN);
|
|
8453
|
+
DROP FUNCTION IF EXISTS ${s}.cms_pin_agent_package_version(TEXT, TEXT, TEXT, TEXT, BOOLEAN);
|
|
8454
|
+
DROP FUNCTION IF EXISTS ${s}.cms_delete_agent_package(TEXT, TEXT, TEXT, BOOLEAN);
|
|
8455
|
+
|
|
8456
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_agent_package_authz(
|
|
8457
|
+
p_name TEXT, p_actor_provider TEXT, p_actor_subject TEXT, p_is_admin BOOLEAN,
|
|
8458
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8459
|
+
) RETURNS ${s}.agent_packages AS $$
|
|
8460
|
+
DECLARE
|
|
8461
|
+
v_pkg ${s}.agent_packages;
|
|
8462
|
+
v_id TEXT;
|
|
8463
|
+
v_count INT;
|
|
8464
|
+
BEGIN
|
|
8465
|
+
-- require_enabled = FALSE: a disabled copy must still be reachable, or
|
|
8466
|
+
-- disabling one would be irreversible.
|
|
8467
|
+
v_id := ${s}.cms_resolve_agent_package_id(
|
|
8468
|
+
p_name, p_actor_provider, p_actor_subject,
|
|
8469
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject, FALSE);
|
|
8470
|
+
|
|
8471
|
+
-- ADMIN FALLBACK, and it is load-bearing rather than a convenience.
|
|
8472
|
+
--
|
|
8473
|
+
-- Ordinary resolution walks "my copy, then shared", which is right for a
|
|
8474
|
+
-- user: a name they do not own and that is not shared genuinely does not
|
|
8475
|
+
-- exist in their namespace. But that rule would strand two real classes
|
|
8476
|
+
-- of row with no owner triple to select them by:
|
|
8477
|
+
--
|
|
8478
|
+
-- * NULL-owner packages, which pre-date owner stamping or were minted
|
|
8479
|
+
-- by an admin in a no-auth deployment;
|
|
8480
|
+
-- * another user's private package, which an admin must still be able
|
|
8481
|
+
-- to disable or delete during an incident.
|
|
8482
|
+
--
|
|
8483
|
+
-- Without this they would be invisible AND undeletable after 0043 — a
|
|
8484
|
+
-- migration that quietly orphans existing data. Ambiguity is refused
|
|
8485
|
+
-- rather than guessed: if several copies share the name, the admin has
|
|
8486
|
+
-- to say which one.
|
|
8487
|
+
IF v_id IS NULL AND p_is_admin AND p_sel_scope IS NULL THEN
|
|
8488
|
+
SELECT count(*) INTO v_count FROM ${s}.agent_packages p WHERE p.name = p_name;
|
|
8489
|
+
IF v_count > 1 THEN
|
|
8490
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_AMBIGUOUS: % copies of "%" exist; name an owner or scope to pick one', v_count, p_name;
|
|
8491
|
+
END IF;
|
|
8492
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p WHERE p.name = p_name;
|
|
8493
|
+
END IF;
|
|
8494
|
+
|
|
8495
|
+
IF v_id IS NULL THEN
|
|
8496
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_NOT_FOUND: package "%" does not exist', p_name;
|
|
8497
|
+
END IF;
|
|
8498
|
+
|
|
8499
|
+
SELECT * INTO v_pkg FROM ${s}.agent_packages WHERE package_id = v_id FOR UPDATE;
|
|
8500
|
+
IF NOT FOUND THEN
|
|
8501
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_NOT_FOUND: package "%" does not exist', p_name;
|
|
8502
|
+
END IF;
|
|
8503
|
+
|
|
8504
|
+
IF NOT p_is_admin AND (
|
|
8505
|
+
v_pkg.owner_provider IS NULL
|
|
8506
|
+
OR v_pkg.owner_provider IS DISTINCT FROM NULLIF(BTRIM(p_actor_provider), '')
|
|
8507
|
+
OR v_pkg.owner_subject IS DISTINCT FROM NULLIF(BTRIM(p_actor_subject), '')
|
|
8508
|
+
) THEN
|
|
8509
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_FORBIDDEN: only the package creator or an admin can modify "%"', p_name;
|
|
8510
|
+
END IF;
|
|
8511
|
+
RETURN v_pkg;
|
|
8512
|
+
END;
|
|
8513
|
+
$$ LANGUAGE plpgsql;
|
|
8514
|
+
|
|
8515
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_set_agent_package_scope(
|
|
8516
|
+
p_name TEXT, p_scope TEXT, p_actor_provider TEXT, p_actor_subject TEXT, p_is_admin BOOLEAN,
|
|
8517
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8518
|
+
) RETURNS VOID AS $$
|
|
8519
|
+
DECLARE
|
|
8520
|
+
v_pkg ${s}.agent_packages;
|
|
8521
|
+
v_clash TEXT;
|
|
8522
|
+
BEGIN
|
|
8523
|
+
IF p_scope NOT IN ('shared', 'user') THEN
|
|
8524
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_BAD_SCOPE: scope must be shared or user, got "%"', p_scope;
|
|
8525
|
+
END IF;
|
|
8526
|
+
v_pkg := ${s}.cms_agent_package_authz(
|
|
8527
|
+
p_name, p_actor_provider, p_actor_subject, p_is_admin,
|
|
8528
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject);
|
|
8529
|
+
|
|
8530
|
+
IF p_scope = 'shared' AND v_pkg.scope <> 'shared' THEN
|
|
8531
|
+
-- Promotion is exclusive: shared is the one namespace still globally
|
|
8532
|
+
-- unique, so refuse with a legible error rather than letting the
|
|
8533
|
+
-- partial unique index raise a raw constraint violation.
|
|
8534
|
+
SELECT p.package_id INTO v_clash FROM ${s}.agent_packages p
|
|
8535
|
+
WHERE p.name = p_name AND p.scope = 'shared';
|
|
8536
|
+
IF v_clash IS NOT NULL THEN
|
|
8537
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_NAME_TAKEN: a shared package named "%" already exists; rename before promoting', p_name;
|
|
8538
|
+
END IF;
|
|
8539
|
+
END IF;
|
|
8540
|
+
|
|
8541
|
+
IF p_scope = 'user' AND v_pkg.scope <> 'user' THEN
|
|
8542
|
+
-- Demotion needs an owner to land on, and must not collide with a
|
|
8543
|
+
-- copy that owner already has.
|
|
8544
|
+
IF v_pkg.owner_provider IS NULL OR v_pkg.owner_subject IS NULL THEN
|
|
8545
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_NO_OWNER: "%" has no owner identity to demote to', p_name;
|
|
8546
|
+
END IF;
|
|
8547
|
+
SELECT p.package_id INTO v_clash FROM ${s}.agent_packages p
|
|
8548
|
+
WHERE p.name = p_name AND p.scope = 'user'
|
|
8549
|
+
AND p.owner_provider = v_pkg.owner_provider
|
|
8550
|
+
AND p.owner_subject = v_pkg.owner_subject;
|
|
8551
|
+
IF v_clash IS NOT NULL THEN
|
|
8552
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_NAME_TAKEN: that owner already has a user-scope package named "%"', p_name;
|
|
8553
|
+
END IF;
|
|
8554
|
+
END IF;
|
|
8555
|
+
|
|
8556
|
+
UPDATE ${s}.agent_packages SET scope = p_scope WHERE package_id = v_pkg.package_id;
|
|
8557
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8558
|
+
END;
|
|
8559
|
+
$$ LANGUAGE plpgsql;
|
|
8560
|
+
|
|
8561
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_set_agent_package_enabled(
|
|
8562
|
+
p_name TEXT, p_enabled BOOLEAN, p_actor_provider TEXT, p_actor_subject TEXT, p_is_admin BOOLEAN,
|
|
8563
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8564
|
+
) RETURNS VOID AS $$
|
|
8565
|
+
DECLARE
|
|
8566
|
+
v_pkg ${s}.agent_packages;
|
|
8567
|
+
BEGIN
|
|
8568
|
+
v_pkg := ${s}.cms_agent_package_authz(
|
|
8569
|
+
p_name, p_actor_provider, p_actor_subject, p_is_admin,
|
|
8570
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject);
|
|
8571
|
+
UPDATE ${s}.agent_packages SET enabled = p_enabled WHERE package_id = v_pkg.package_id;
|
|
8572
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8573
|
+
END;
|
|
8574
|
+
$$ LANGUAGE plpgsql;
|
|
8575
|
+
|
|
8576
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_pin_agent_package_version(
|
|
8577
|
+
p_name TEXT, p_semver TEXT, p_actor_provider TEXT, p_actor_subject TEXT, p_is_admin BOOLEAN,
|
|
8578
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8579
|
+
) RETURNS VOID AS $$
|
|
8580
|
+
DECLARE
|
|
8581
|
+
v_pkg ${s}.agent_packages;
|
|
8582
|
+
v_version_id TEXT;
|
|
8583
|
+
BEGIN
|
|
8584
|
+
v_pkg := ${s}.cms_agent_package_authz(
|
|
8585
|
+
p_name, p_actor_provider, p_actor_subject, p_is_admin,
|
|
8586
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject);
|
|
8587
|
+
SELECT version_id INTO v_version_id FROM ${s}.agent_package_versions
|
|
8588
|
+
WHERE package_id = v_pkg.package_id AND semver = p_semver;
|
|
8589
|
+
IF NOT FOUND THEN
|
|
8590
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_VERSION_NOT_FOUND: %@% is not a published version', p_name, p_semver;
|
|
8591
|
+
END IF;
|
|
8592
|
+
UPDATE ${s}.agent_packages SET active_version_id = v_version_id WHERE package_id = v_pkg.package_id;
|
|
8593
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8594
|
+
END;
|
|
8595
|
+
$$ LANGUAGE plpgsql;
|
|
8596
|
+
|
|
8597
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_delete_agent_package(
|
|
8598
|
+
p_name TEXT, p_actor_provider TEXT, p_actor_subject TEXT, p_is_admin BOOLEAN,
|
|
8599
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8600
|
+
) RETURNS TABLE(artifact_filename TEXT) AS $$
|
|
8601
|
+
DECLARE
|
|
8602
|
+
v_pkg ${s}.agent_packages;
|
|
8603
|
+
BEGIN
|
|
8604
|
+
v_pkg := ${s}.cms_agent_package_authz(
|
|
8605
|
+
p_name, p_actor_provider, p_actor_subject, p_is_admin,
|
|
8606
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject);
|
|
8607
|
+
RETURN QUERY
|
|
8608
|
+
SELECT v.artifact_filename FROM ${s}.agent_package_versions v
|
|
8609
|
+
WHERE v.package_id = v_pkg.package_id;
|
|
8610
|
+
DELETE FROM ${s}.agent_packages WHERE package_id = v_pkg.package_id;
|
|
8611
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8612
|
+
END;
|
|
8613
|
+
$$ LANGUAGE plpgsql;
|
|
8614
|
+
|
|
8615
|
+
-- ── Publish: locks the (name, scope, owner) row, not the name ────
|
|
8616
|
+
--
|
|
8617
|
+
-- The scope-mismatch error from 0038 is deliberately GONE. Publishing
|
|
8618
|
+
-- \`user\` when a \`shared\` package holds the name is no longer a conflict —
|
|
8619
|
+
-- it is exactly how a user takes a personal copy of a shared package, which
|
|
8620
|
+
-- is the headline feature of this migration.
|
|
8621
|
+
DROP FUNCTION IF EXISTS ${s}.cms_publish_agent_package(
|
|
8622
|
+
TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, BIGINT, TEXT, TEXT, JSONB, TEXT, BOOLEAN);
|
|
8623
|
+
|
|
8624
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_publish_agent_package(
|
|
8625
|
+
p_package_id TEXT, p_version_id TEXT, p_name TEXT, p_scope TEXT,
|
|
8626
|
+
p_owner_provider TEXT, p_owner_subject TEXT, p_source_id TEXT,
|
|
8627
|
+
p_semver TEXT, p_sha256 TEXT, p_size_bytes BIGINT, p_artifact_filename TEXT,
|
|
8628
|
+
p_commit_sha TEXT, p_manifest JSONB, p_created_by TEXT, p_is_admin BOOLEAN
|
|
8629
|
+
) RETURNS TABLE(status TEXT, package_id TEXT, version_id TEXT) AS $$
|
|
8630
|
+
DECLARE
|
|
8631
|
+
v_pkg RECORD;
|
|
8632
|
+
v_ver RECORD;
|
|
8633
|
+
v_owner_provider TEXT := NULLIF(BTRIM(p_owner_provider), '');
|
|
8634
|
+
v_owner_subject TEXT := NULLIF(BTRIM(p_owner_subject), '');
|
|
8635
|
+
|
|
8636
|
+
BEGIN
|
|
8637
|
+
IF p_scope NOT IN ('shared', 'user') THEN
|
|
8638
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_BAD_SCOPE: scope must be shared or user, got "%"', p_scope;
|
|
8639
|
+
END IF;
|
|
8640
|
+
|
|
8641
|
+
-- The "__" prefix is reserved for platform sentinels (\`__shared\`, and
|
|
8642
|
+
-- whatever comes later). Enforced HERE, in the database, because it is the
|
|
8643
|
+
-- one gate every publish path goes through — CLI push, portal upload and
|
|
8644
|
+
-- the manager's own import all land on this function. A TypeScript-only
|
|
8645
|
+
-- check would be one forgotten caller away from letting somebody mint a
|
|
8646
|
+
-- package that captures every reference meaning "the deployment's copy".
|
|
8647
|
+
IF LOWER(BTRIM(p_name)) LIKE '\\_\\_%' THEN
|
|
8648
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_RESERVED_NAME: "%" uses the reserved "__" prefix', p_name;
|
|
8649
|
+
END IF;
|
|
8650
|
+
|
|
8651
|
+
-- An owner-less publish is admin-only: a NULL-owner package would be
|
|
8652
|
+
-- unmanageable by its (anonymous) creator afterwards.
|
|
8653
|
+
IF NOT p_is_admin AND (v_owner_provider IS NULL OR v_owner_subject IS NULL) THEN
|
|
8654
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_FORBIDDEN: publishing without an owner identity requires the admin role';
|
|
8655
|
+
END IF;
|
|
8656
|
+
|
|
8657
|
+
<<retry>>
|
|
8658
|
+
LOOP
|
|
8659
|
+
SELECT * INTO v_pkg FROM ${s}.agent_packages p
|
|
8660
|
+
WHERE p.name = p_name
|
|
8661
|
+
AND p.scope = p_scope
|
|
8662
|
+
AND (p_scope = 'shared'
|
|
8663
|
+
OR (p.owner_provider IS NOT DISTINCT FROM v_owner_provider
|
|
8664
|
+
AND p.owner_subject IS NOT DISTINCT FROM v_owner_subject))
|
|
8665
|
+
FOR UPDATE;
|
|
8666
|
+
IF NOT FOUND THEN
|
|
8667
|
+
-- FOR UPDATE on a missing row takes no lock, so a concurrent first
|
|
8668
|
+
-- publish can beat this INSERT — catch the unique violation and
|
|
8669
|
+
-- loop back to lock the winner's row.
|
|
8670
|
+
BEGIN
|
|
8671
|
+
INSERT INTO ${s}.agent_packages
|
|
8672
|
+
(package_id, source_id, name, scope, owner_provider, owner_subject, created_by)
|
|
8673
|
+
VALUES (p_package_id, p_source_id, p_name, p_scope,
|
|
8674
|
+
v_owner_provider, v_owner_subject, p_created_by);
|
|
8675
|
+
EXCEPTION WHEN unique_violation THEN
|
|
8676
|
+
CONTINUE retry;
|
|
8677
|
+
END;
|
|
8678
|
+
INSERT INTO ${s}.agent_package_versions
|
|
8679
|
+
(version_id, package_id, semver, sha256, size_bytes, artifact_filename, commit_sha, manifest, created_by)
|
|
8680
|
+
VALUES (p_version_id, p_package_id, p_semver, p_sha256, p_size_bytes,
|
|
8681
|
+
p_artifact_filename, p_commit_sha, p_manifest, p_created_by);
|
|
8682
|
+
UPDATE ${s}.agent_packages SET active_version_id = p_version_id WHERE ${s}.agent_packages.package_id = p_package_id;
|
|
8683
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8684
|
+
RETURN QUERY SELECT 'published'::TEXT, p_package_id, p_version_id;
|
|
8685
|
+
RETURN;
|
|
8686
|
+
END IF;
|
|
8687
|
+
EXIT retry;
|
|
8688
|
+
END LOOP;
|
|
8689
|
+
|
|
8690
|
+
IF NOT p_is_admin AND (
|
|
8691
|
+
v_pkg.owner_provider IS NULL
|
|
8692
|
+
OR v_pkg.owner_provider IS DISTINCT FROM v_owner_provider
|
|
8693
|
+
OR v_pkg.owner_subject IS DISTINCT FROM v_owner_subject
|
|
8694
|
+
) THEN
|
|
8695
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_FORBIDDEN: only the package creator or an admin can publish new versions of "%"', p_name;
|
|
8696
|
+
END IF;
|
|
8697
|
+
|
|
8698
|
+
SELECT * INTO v_ver FROM ${s}.agent_package_versions v
|
|
8699
|
+
WHERE v.package_id = v_pkg.package_id AND v.semver = p_semver;
|
|
8700
|
+
IF FOUND THEN
|
|
8701
|
+
IF v_ver.sha256 = p_sha256 THEN
|
|
8702
|
+
RETURN QUERY SELECT 'noop'::TEXT, v_pkg.package_id, v_ver.version_id;
|
|
8703
|
+
RETURN;
|
|
8704
|
+
END IF;
|
|
8705
|
+
RAISE EXCEPTION 'AGENT_PACKAGE_SEMVER_CONFLICT: %@% is already published with different content — published versions are immutable, bump the version', p_name, p_semver;
|
|
8706
|
+
END IF;
|
|
8707
|
+
|
|
8708
|
+
INSERT INTO ${s}.agent_package_versions
|
|
8709
|
+
(version_id, package_id, semver, sha256, size_bytes, artifact_filename, commit_sha, manifest, created_by)
|
|
8710
|
+
VALUES (p_version_id, v_pkg.package_id, p_semver, p_sha256, p_size_bytes,
|
|
8711
|
+
p_artifact_filename, p_commit_sha, p_manifest, p_created_by);
|
|
8712
|
+
UPDATE ${s}.agent_packages
|
|
8713
|
+
SET active_version_id = p_version_id,
|
|
8714
|
+
source_id = COALESCE(p_source_id, ${s}.agent_packages.source_id)
|
|
8715
|
+
WHERE ${s}.agent_packages.package_id = v_pkg.package_id;
|
|
8716
|
+
PERFORM ${s}.cms_agent_registry_bump();
|
|
8717
|
+
RETURN QUERY SELECT 'published'::TEXT, v_pkg.package_id, p_version_id;
|
|
8718
|
+
END;
|
|
8719
|
+
$$ LANGUAGE plpgsql;
|
|
8720
|
+
|
|
8721
|
+
-- ── Reads: a name can now legitimately return two rows ───────────
|
|
8722
|
+
--
|
|
8723
|
+
-- \`shadowed\` tells a UI that this shared package is currently overridden by
|
|
8724
|
+
-- the viewer's own copy, which is the difference between "you have two
|
|
8725
|
+
-- packages" and "you have one package with a fallback".
|
|
8726
|
+
DROP FUNCTION IF EXISTS ${s}.cms_list_agent_packages(TEXT, TEXT, BOOLEAN);
|
|
8727
|
+
DROP FUNCTION IF EXISTS ${s}.cms_get_agent_package(TEXT, TEXT, TEXT, BOOLEAN);
|
|
8728
|
+
|
|
8729
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_list_agent_packages(
|
|
8730
|
+
p_viewer_provider TEXT, p_viewer_subject TEXT, p_is_admin BOOLEAN
|
|
8731
|
+
) RETURNS TABLE(
|
|
8732
|
+
package_id TEXT, source_id TEXT, name TEXT, scope TEXT,
|
|
8733
|
+
owner_provider TEXT, owner_subject TEXT,
|
|
8734
|
+
owner_email TEXT, owner_display_name TEXT,
|
|
8735
|
+
enabled BOOLEAN, created_by TEXT, created_at TIMESTAMPTZ,
|
|
8736
|
+
active_version_id TEXT, semver TEXT, sha256 TEXT, size_bytes BIGINT,
|
|
8737
|
+
artifact_filename TEXT, commit_sha TEXT, manifest JSONB,
|
|
8738
|
+
version_created_at TIMESTAMPTZ, version_created_by TEXT,
|
|
8739
|
+
shadowed BOOLEAN
|
|
8740
|
+
) AS $$
|
|
8741
|
+
SELECT p.package_id, p.source_id, p.name, p.scope,
|
|
8742
|
+
p.owner_provider, p.owner_subject,
|
|
8743
|
+
u.email, u.display_name,
|
|
8744
|
+
p.enabled, p.created_by, p.created_at,
|
|
8745
|
+
v.version_id, v.semver, v.sha256, v.size_bytes,
|
|
8746
|
+
v.artifact_filename, v.commit_sha, v.manifest, v.created_at, v.created_by,
|
|
8747
|
+
(p.scope = 'shared' AND EXISTS (
|
|
8748
|
+
SELECT 1 FROM ${s}.agent_packages o
|
|
8749
|
+
WHERE o.name = p.name AND o.scope = 'user' AND o.enabled
|
|
8750
|
+
AND o.owner_provider = BTRIM(p_viewer_provider)
|
|
8751
|
+
AND o.owner_subject = BTRIM(p_viewer_subject)
|
|
8752
|
+
)) AS shadowed
|
|
8753
|
+
FROM ${s}.agent_packages p
|
|
8754
|
+
LEFT JOIN ${s}.agent_package_versions v ON v.version_id = p.active_version_id
|
|
8755
|
+
LEFT JOIN ${s}.users u
|
|
8756
|
+
ON u.provider = p.owner_provider AND u.subject = p.owner_subject
|
|
8757
|
+
WHERE p.scope = 'shared'
|
|
8758
|
+
OR p_is_admin
|
|
8759
|
+
OR (p.owner_provider = BTRIM(p_viewer_provider) AND p.owner_subject = BTRIM(p_viewer_subject))
|
|
8760
|
+
ORDER BY p.name, p.scope, p.owner_provider, p.owner_subject;
|
|
8761
|
+
$$ LANGUAGE sql;
|
|
8762
|
+
|
|
8763
|
+
-- Selector-aware single read. With no selector it returns the copy the viewer
|
|
8764
|
+
-- would actually GET, which is what makes "show me package X" agree with
|
|
8765
|
+
-- "run agent from package X".
|
|
8766
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_get_agent_package(
|
|
8767
|
+
p_name TEXT, p_viewer_provider TEXT, p_viewer_subject TEXT, p_is_admin BOOLEAN,
|
|
8768
|
+
p_sel_scope TEXT, p_sel_owner_provider TEXT, p_sel_owner_subject TEXT
|
|
8769
|
+
) RETURNS TABLE(
|
|
8770
|
+
package_id TEXT, source_id TEXT, name TEXT, scope TEXT,
|
|
8771
|
+
owner_provider TEXT, owner_subject TEXT,
|
|
8772
|
+
owner_email TEXT, owner_display_name TEXT,
|
|
8773
|
+
enabled BOOLEAN, created_by TEXT, created_at TIMESTAMPTZ,
|
|
8774
|
+
active_version_id TEXT, version_id TEXT, semver TEXT, sha256 TEXT,
|
|
8775
|
+
size_bytes BIGINT, artifact_filename TEXT, commit_sha TEXT, manifest JSONB,
|
|
8776
|
+
version_created_at TIMESTAMPTZ, version_created_by TEXT
|
|
8777
|
+
) AS $$
|
|
8778
|
+
DECLARE
|
|
8779
|
+
v_id TEXT;
|
|
8780
|
+
v_count INT;
|
|
8781
|
+
BEGIN
|
|
8782
|
+
-- Two-pass on purpose. A read should answer "which copy do I actually
|
|
8783
|
+
-- GET", and shadowing is enable-sensitive — so try the live-resolution
|
|
8784
|
+
-- rule first (enabled only). Falling back to the disabled row second
|
|
8785
|
+
-- means a package whose ONLY copy is disabled is still inspectable;
|
|
8786
|
+
-- without that, disabling your one copy would make it invisible as well
|
|
8787
|
+
-- as inert, and there would be no way back through this API.
|
|
8788
|
+
v_id := ${s}.cms_resolve_agent_package_id(
|
|
8789
|
+
p_name, p_viewer_provider, p_viewer_subject,
|
|
8790
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject, TRUE);
|
|
8791
|
+
IF v_id IS NULL THEN
|
|
8792
|
+
v_id := ${s}.cms_resolve_agent_package_id(
|
|
8793
|
+
p_name, p_viewer_provider, p_viewer_subject,
|
|
8794
|
+
p_sel_scope, p_sel_owner_provider, p_sel_owner_subject, FALSE);
|
|
8795
|
+
END IF;
|
|
8796
|
+
|
|
8797
|
+
-- Same admin fallback the mutation path uses, and for the same reason:
|
|
8798
|
+
-- an admin must be able to READ the row they are allowed to delete.
|
|
8799
|
+
-- Keeping the two in step matters more than the duplication — a package
|
|
8800
|
+
-- an admin can destroy but cannot inspect is the worse outcome.
|
|
8801
|
+
IF v_id IS NULL AND p_is_admin AND p_sel_scope IS NULL THEN
|
|
8802
|
+
SELECT count(*) INTO v_count FROM ${s}.agent_packages p WHERE p.name = p_name;
|
|
8803
|
+
IF v_count = 1 THEN
|
|
8804
|
+
SELECT p.package_id INTO v_id FROM ${s}.agent_packages p WHERE p.name = p_name;
|
|
8805
|
+
END IF;
|
|
8806
|
+
END IF;
|
|
8807
|
+
|
|
8808
|
+
IF v_id IS NULL THEN
|
|
8809
|
+
RETURN;
|
|
8810
|
+
END IF;
|
|
8811
|
+
|
|
8812
|
+
-- Re-apply the visibility rule against the RESOLVED row. The resolver is
|
|
8813
|
+
-- about which copy a name means, never about whether you may see it —
|
|
8814
|
+
-- keeping those separate is what stops a selector from being a way to
|
|
8815
|
+
-- read someone else's private package by naming their owner triple.
|
|
8816
|
+
RETURN QUERY
|
|
8817
|
+
SELECT p.package_id, p.source_id, p.name, p.scope,
|
|
8818
|
+
p.owner_provider, p.owner_subject,
|
|
8819
|
+
u.email, u.display_name,
|
|
8820
|
+
p.enabled, p.created_by, p.created_at, p.active_version_id,
|
|
8821
|
+
v.version_id, v.semver, v.sha256, v.size_bytes,
|
|
8822
|
+
v.artifact_filename, v.commit_sha, v.manifest, v.created_at, v.created_by
|
|
8823
|
+
FROM ${s}.agent_packages p
|
|
8824
|
+
LEFT JOIN ${s}.agent_package_versions v ON v.package_id = p.package_id
|
|
8825
|
+
LEFT JOIN ${s}.users u
|
|
8826
|
+
ON u.provider = p.owner_provider AND u.subject = p.owner_subject
|
|
8827
|
+
WHERE p.package_id = v_id
|
|
8828
|
+
AND (p.scope = 'shared' OR p_is_admin
|
|
8829
|
+
OR (p.owner_provider = BTRIM(p_viewer_provider) AND p.owner_subject = BTRIM(p_viewer_subject)))
|
|
8830
|
+
ORDER BY v.created_at DESC;
|
|
8831
|
+
END;
|
|
8832
|
+
$$ LANGUAGE plpgsql;
|
|
8833
|
+
|
|
8834
|
+
-- ── Install manifest: carries package_id so disk keys stay unique ─
|
|
8835
|
+
DROP FUNCTION IF EXISTS ${s}.cms_get_agent_packages_install_manifest();
|
|
8836
|
+
|
|
8837
|
+
CREATE OR REPLACE FUNCTION ${s}.cms_get_agent_packages_install_manifest()
|
|
8838
|
+
RETURNS TABLE(
|
|
8839
|
+
package_id TEXT, name TEXT, scope TEXT, owner_provider TEXT, owner_subject TEXT,
|
|
8840
|
+
semver TEXT, sha256 TEXT, size_bytes BIGINT, artifact_filename TEXT, manifest JSONB
|
|
8841
|
+
) AS $$
|
|
8842
|
+
SELECT p.package_id, p.name, p.scope, p.owner_provider, p.owner_subject,
|
|
8843
|
+
v.semver, v.sha256, v.size_bytes, v.artifact_filename, v.manifest
|
|
8844
|
+
FROM ${s}.agent_packages p
|
|
8845
|
+
JOIN ${s}.agent_package_versions v ON v.version_id = p.active_version_id
|
|
8846
|
+
WHERE p.enabled
|
|
8847
|
+
ORDER BY p.name, p.scope, p.owner_provider, p.owner_subject;
|
|
8848
|
+
$$ LANGUAGE sql;
|
|
8849
|
+
`;
|
|
8850
|
+
}
|
|
8199
8851
|
//# sourceMappingURL=cms-migrations.js.map
|