@hasna/instructions 0.4.35 → 0.4.39

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.
Files changed (35) hide show
  1. package/README.md +4 -2
  2. package/assets/skills/inbox/SKILL.md +86 -0
  3. package/dashboard/README.md +34 -70
  4. package/dist/cli/index.js +1136 -526
  5. package/dist/cli/raw-store-root.test.d.ts +2 -0
  6. package/dist/cli/raw-store-root.test.d.ts.map +1 -0
  7. package/dist/db/configs.d.ts.map +1 -1
  8. package/dist/generated/storage-kit/index.d.ts +1 -1
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1276 -754
  12. package/dist/lib/apply.d.ts.map +1 -1
  13. package/dist/lib/cursor-authority.d.ts +36 -3
  14. package/dist/lib/cursor-authority.d.ts.map +1 -1
  15. package/dist/lib/managed-skill-runtimes.d.ts +80 -0
  16. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -0
  17. package/dist/lib/managed-skill-runtimes.test.d.ts +2 -0
  18. package/dist/lib/managed-skill-runtimes.test.d.ts.map +1 -0
  19. package/dist/lib/raw-store-root.d.ts +17 -0
  20. package/dist/lib/raw-store-root.d.ts.map +1 -0
  21. package/dist/lib/session-apply.d.ts.map +1 -1
  22. package/dist/lib/session-authority.d.ts +27 -0
  23. package/dist/lib/session-authority.d.ts.map +1 -0
  24. package/dist/lib/session-authority.test.d.ts +2 -0
  25. package/dist/lib/session-authority.test.d.ts.map +1 -0
  26. package/dist/lib/session-render.d.ts +5 -3
  27. package/dist/lib/session-render.d.ts.map +1 -1
  28. package/dist/mcp/index.js +290 -233
  29. package/dist/server/index.js +425 -231
  30. package/dist/status.d.ts +11 -1
  31. package/dist/status.d.ts.map +1 -1
  32. package/dist/storage/cloud-store.d.ts.map +1 -1
  33. package/dist/storage/cloud-store.test.d.ts +2 -0
  34. package/dist/storage/cloud-store.test.d.ts.map +1 -0
  35. package/package.json +9 -7
@@ -1258,78 +1258,6 @@ var RegExpRouter = class {
1258
1258
  }
1259
1259
  };
1260
1260
 
1261
- // ../../node_modules/.bun/hono@4.13.1/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
1262
- var PreparedRegExpRouter = class {
1263
- name = "PreparedRegExpRouter";
1264
- #matchers;
1265
- #relocateMap;
1266
- constructor(matchers, relocateMap) {
1267
- this.#matchers = matchers;
1268
- this.#relocateMap = relocateMap;
1269
- }
1270
- #addWildcard(method, handlerData) {
1271
- const matcher = this.#matchers[method];
1272
- matcher[1].forEach((list) => list && list.push(handlerData));
1273
- Object.values(matcher[2]).forEach((list) => list[0].push(handlerData));
1274
- }
1275
- #addPath(method, path, handler, indexes, map) {
1276
- const matcher = this.#matchers[method];
1277
- if (!map) {
1278
- matcher[2][path][0].push([handler, {}]);
1279
- } else {
1280
- indexes.forEach((index) => {
1281
- if (typeof index === "number") {
1282
- matcher[1][index].push([handler, map]);
1283
- } else {
1284
- matcher[2][index || path][0].push([handler, map]);
1285
- }
1286
- });
1287
- }
1288
- }
1289
- add(method, path, handler) {
1290
- if (!this.#matchers[method]) {
1291
- const all = this.#matchers[METHOD_NAME_ALL];
1292
- const staticMap = {};
1293
- for (const key in all[2]) {
1294
- staticMap[key] = [all[2][key][0].slice(), emptyParam];
1295
- }
1296
- this.#matchers[method] = [
1297
- all[0],
1298
- all[1].map((list) => Array.isArray(list) ? list.slice() : 0),
1299
- staticMap
1300
- ];
1301
- }
1302
- if (path === "/*" || path === "*") {
1303
- const handlerData = [handler, {}];
1304
- if (method === METHOD_NAME_ALL) {
1305
- for (const m in this.#matchers) {
1306
- this.#addWildcard(m, handlerData);
1307
- }
1308
- } else {
1309
- this.#addWildcard(method, handlerData);
1310
- }
1311
- return;
1312
- }
1313
- const data = this.#relocateMap[path];
1314
- if (!data) {
1315
- throw new Error(`Path ${path} is not registered`);
1316
- }
1317
- for (const [indexes, map] of data) {
1318
- if (method === METHOD_NAME_ALL) {
1319
- for (const m in this.#matchers) {
1320
- this.#addPath(m, path, handler, indexes, map);
1321
- }
1322
- } else {
1323
- this.#addPath(method, path, handler, indexes, map);
1324
- }
1325
- }
1326
- }
1327
- buildAllMatchers() {
1328
- return this.#matchers;
1329
- }
1330
- match = match;
1331
- };
1332
-
1333
1261
  // ../../node_modules/.bun/hono@4.13.1/node_modules/hono/dist/router/smart-router/router.js
1334
1262
  var SmartRouter = class {
1335
1263
  name = "SmartRouter";
@@ -1744,14 +1672,90 @@ class ProfileNotFoundError extends Error {
1744
1672
  }
1745
1673
  }
1746
1674
 
1747
- // ../../node_modules/.bun/@hasna+contracts@0.4.2/node_modules/@hasna/contracts/dist/auth/index.js
1675
+ // ../contracts/dist/auth/index.js
1748
1676
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
1677
+ var MAX_TENANT_ID_LENGTH = 64;
1678
+ var TENANT_ID_PATTERN = new RegExp(`^[A-Za-z0-9][A-Za-z0-9._-]{0,${MAX_TENANT_ID_LENGTH - 1}}$`);
1679
+ var UUID_HEX = "[0-9a-fA-F]";
1680
+ var UUID_PATTERN = new RegExp(`^\\{?(?:${UUID_HEX}{8}-${UUID_HEX}{4}-${UUID_HEX}{4}-${UUID_HEX}{4}-${UUID_HEX}{12}|${UUID_HEX}{32})\\}?$`);
1681
+ function isValidTenantId(value) {
1682
+ return typeof value === "string" && TENANT_ID_PATTERN.test(value);
1683
+ }
1684
+ function isUuidTenantId(value) {
1685
+ return typeof value === "string" && UUID_PATTERN.test(value);
1686
+ }
1687
+ function canonicalizeTenantId(value) {
1688
+ if (!isUuidTenantId(value))
1689
+ return value;
1690
+ const hex = value.replace(/[{}-]/g, "").toLowerCase();
1691
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1692
+ }
1693
+ function normalizeTenantId(value) {
1694
+ const trimmed = typeof value === "string" ? value.trim() : "";
1695
+ const canonical = canonicalizeTenantId(trimmed);
1696
+ if (!isValidTenantId(canonical)) {
1697
+ throw new Error(`Invalid tenant id '${value}'. Expected 1-${MAX_TENANT_ID_LENGTH} characters matching ${TENANT_ID_PATTERN} (a UUID, ULID, slug, or prefixed id).`);
1698
+ }
1699
+ return canonical;
1700
+ }
1701
+ function tenantIdsEqual(left, right) {
1702
+ const canonical = (value) => {
1703
+ if (typeof value !== "string")
1704
+ return null;
1705
+ const folded = canonicalizeTenantId(value.trim());
1706
+ return isValidTenantId(folded) ? folded : null;
1707
+ };
1708
+ const a = canonical(left);
1709
+ const b = canonical(right);
1710
+ return a !== null && b !== null && a === b;
1711
+ }
1712
+ function ownTenantId(source) {
1713
+ return Object.hasOwn(source, "tid") ? source.tid : undefined;
1714
+ }
1749
1715
  var API_KEY_TOKEN_VERSION = 1;
1750
1716
  var API_KEY_NAMESPACE = "hasna";
1751
- var TOKEN_PATTERN = /^hasna_([a-z][a-z0-9-]*)_([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$/;
1717
+ var API_KEY_TOKEN_PATTERN = /^hasna_([a-z][a-z0-9-]*)_([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]+)$/;
1718
+ var TOKEN_PATTERN = API_KEY_TOKEN_PATTERN;
1752
1719
  var DEFAULT_API_KEY_TTL_SECONDS = 90 * 24 * 60 * 60;
1720
+ function ownAgentClaim(source) {
1721
+ return Object.hasOwn(source, "agent") && typeof source.agent === "string" ? source.agent : null;
1722
+ }
1723
+ function ownScopesClaim(source) {
1724
+ return Object.hasOwn(source, "scopes") && Array.isArray(source.scopes) ? source.scopes : null;
1725
+ }
1726
+ function ownOption(options, name) {
1727
+ return Object.hasOwn(options, name) ? options[name] : undefined;
1728
+ }
1729
+ var typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
1730
+ var intrinsicViewBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
1731
+ var intrinsicViewByteOffset = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset").get;
1732
+ var intrinsicViewByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
1733
+ var intrinsicDataViewBuffer = Object.getOwnPropertyDescriptor(DataView.prototype, "buffer").get;
1734
+ var intrinsicDataViewByteOffset = Object.getOwnPropertyDescriptor(DataView.prototype, "byteOffset").get;
1735
+ var intrinsicDataViewByteLength = Object.getOwnPropertyDescriptor(DataView.prototype, "byteLength").get;
1736
+ function viewWindow(view) {
1737
+ try {
1738
+ return [
1739
+ intrinsicViewBuffer.call(view),
1740
+ intrinsicViewByteOffset.call(view),
1741
+ intrinsicViewByteLength.call(view)
1742
+ ];
1743
+ } catch {
1744
+ return [
1745
+ intrinsicDataViewBuffer.call(view),
1746
+ intrinsicDataViewByteOffset.call(view),
1747
+ intrinsicDataViewByteLength.call(view)
1748
+ ];
1749
+ }
1750
+ }
1753
1751
  function toBuffer(secret) {
1754
- return typeof secret === "string" ? Buffer.from(secret, "utf8") : secret;
1752
+ if (typeof secret === "string")
1753
+ return Buffer.from(secret, "utf8");
1754
+ if (ArrayBuffer.isView(secret)) {
1755
+ const [store, byteOffset, byteLength] = viewWindow(secret);
1756
+ return Buffer.from(store, byteOffset, byteLength);
1757
+ }
1758
+ return Buffer.from(secret);
1755
1759
  }
1756
1760
  function hmac(signingSecret, message) {
1757
1761
  return createHmac("sha256", toBuffer(signingSecret)).update(message, "utf8").digest();
@@ -1774,12 +1778,23 @@ function parseApiKey(token) {
1774
1778
  } catch {
1775
1779
  return null;
1776
1780
  }
1777
- if (typeof claims !== "object" || claims === null || typeof claims.kid !== "string" || typeof claims.app !== "string" || !Array.isArray(claims.scopes)) {
1781
+ if (typeof claims !== "object" || claims === null || typeof claims.kid !== "string" || typeof claims.app !== "string" || ownScopesClaim(claims) === null) {
1782
+ return null;
1783
+ }
1784
+ const claimedTid = ownTenantId(claims);
1785
+ if (claimedTid !== undefined && !isValidTenantId(claimedTid)) {
1778
1786
  return null;
1779
1787
  }
1780
1788
  return { app, body, sig, claims };
1781
1789
  }
1782
1790
  function verifyApiKeyToken(token, options) {
1791
+ const optSigningSecret = ownOption(options, "signingSecret");
1792
+ const optExpectedApp = ownOption(options, "expectedApp");
1793
+ const optNowMs = ownOption(options, "nowMs");
1794
+ const optLeewaySeconds = ownOption(options, "leewaySeconds");
1795
+ const optRequiredScopes = ownOption(options, "requiredScopes");
1796
+ const optRequireTenant = ownOption(options, "requireTenant");
1797
+ const optExpectedTid = ownOption(options, "expectedTid");
1783
1798
  const parsed = parseApiKey(token);
1784
1799
  if (!parsed) {
1785
1800
  return { ok: false, reason: "malformed", message: "Token is malformed." };
@@ -1791,10 +1806,10 @@ function verifyApiKeyToken(token, options) {
1791
1806
  if (claims.app !== app) {
1792
1807
  return { ok: false, reason: "app_mismatch", message: "Token prefix app does not match claims." };
1793
1808
  }
1794
- if (options.expectedApp !== undefined && app !== options.expectedApp) {
1795
- return { ok: false, reason: "app_mismatch", message: `Token is for app '${app}', expected '${options.expectedApp}'.` };
1809
+ if (optExpectedApp !== undefined && app !== optExpectedApp) {
1810
+ return { ok: false, reason: "app_mismatch", message: `Token is for app '${app}', expected '${optExpectedApp}'.` };
1796
1811
  }
1797
- const expected = hmac(options.signingSecret, `${apiKeyPrefix(app)}${body}`);
1812
+ const expected = hmac(optSigningSecret, `${apiKeyPrefix(app)}${body}`);
1798
1813
  let provided;
1799
1814
  try {
1800
1815
  provided = Buffer.from(sig, "base64url");
@@ -1804,16 +1819,41 @@ function verifyApiKeyToken(token, options) {
1804
1819
  if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) {
1805
1820
  return { ok: false, reason: "bad_signature", message: "Signature verification failed." };
1806
1821
  }
1807
- const now = Math.floor((options.nowMs ?? Date.now()) / 1000);
1808
- const leeway = options.leewaySeconds ?? 0;
1822
+ const agent = ownAgentClaim(claims);
1823
+ const now = Math.floor((optNowMs ?? Date.now()) / 1000);
1824
+ const leeway = optLeewaySeconds ?? 0;
1809
1825
  if (typeof claims.iat === "number" && now + leeway < claims.iat) {
1810
- return { ok: false, reason: "not_yet_valid", message: "Token is not yet valid." };
1826
+ return { ok: false, reason: "not_yet_valid", message: "Token is not yet valid.", agent };
1811
1827
  }
1812
1828
  if (claims.exp !== null && typeof claims.exp === "number" && now - leeway >= claims.exp) {
1813
- return { ok: false, reason: "expired", message: "Token has expired." };
1829
+ return { ok: false, reason: "expired", message: "Token has expired.", agent };
1830
+ }
1831
+ const verifiedTid = ownTenantId(claims);
1832
+ const tid = verifiedTid === undefined ? null : canonicalizeTenantId(verifiedTid);
1833
+ const tenantRequired = Boolean(optRequireTenant) || optExpectedTid !== undefined;
1834
+ if (tenantRequired && tid === null) {
1835
+ return {
1836
+ ok: false,
1837
+ reason: "tenant_required",
1838
+ message: "Token carries no tenant id ('tid') and this service requires one.",
1839
+ kid: claims.kid,
1840
+ tid: null,
1841
+ agent
1842
+ };
1814
1843
  }
1815
- if (options.requiredScopes && options.requiredScopes.length > 0) {
1816
- const granted = claims.scopes;
1844
+ if (optExpectedTid !== undefined && !tenantIdsEqual(tid, optExpectedTid)) {
1845
+ const expectationIsWellFormed = typeof optExpectedTid === "string" && isValidTenantId(optExpectedTid.trim());
1846
+ return {
1847
+ ok: false,
1848
+ reason: "tenant_mismatch",
1849
+ message: expectationIsWellFormed ? "Token is for a different tenant than the one this service accepts." : "Token tenant cannot be checked: the expected tenant id is not a valid tenant id.",
1850
+ kid: claims.kid,
1851
+ tid,
1852
+ agent
1853
+ };
1854
+ }
1855
+ if (optRequiredScopes && optRequiredScopes.length > 0) {
1856
+ const granted = ownScopesClaim(claims) ?? [];
1817
1857
  const satisfies = (required) => granted.some((g) => {
1818
1858
  if (g === "*")
1819
1859
  return true;
@@ -1827,15 +1867,16 @@ function verifyApiKeyToken(token, options) {
1827
1867
  const rAction = required.slice(ri + 1);
1828
1868
  return (gApp === "*" || gApp === rApp) && (gAction === "*" || gAction === rAction);
1829
1869
  });
1830
- for (const required of options.requiredScopes) {
1870
+ for (const required of optRequiredScopes) {
1831
1871
  if (!satisfies(required)) {
1832
- return { ok: false, reason: "insufficient_scope", message: `Missing required scope '${required}'.` };
1872
+ return { ok: false, reason: "insufficient_scope", message: `Missing required scope '${required}'.`, agent };
1833
1873
  }
1834
1874
  }
1835
1875
  }
1836
- return { ok: true, claims, kid: claims.kid, app };
1876
+ return { ok: true, claims, kid: claims.kid, app, tid, agent };
1837
1877
  }
1838
1878
  var DEFAULT_API_KEYS_TABLE = "api_keys";
1879
+ var API_KEY_ISSUANCE_PENDING_REASON = "credential_delivery_pending";
1839
1880
  function createTableSql(table) {
1840
1881
  return `CREATE TABLE IF NOT EXISTS ${table} (
1841
1882
  kid TEXT PRIMARY KEY,
@@ -1859,6 +1900,11 @@ function apiKeyMigrations(table = DEFAULT_API_KEYS_TABLE) {
1859
1900
  id: `hasna_auth_0002_${table}_indexes`,
1860
1901
  sql: `CREATE INDEX IF NOT EXISTS ${table}_app_idx ON ${table} (app);
1861
1902
  CREATE INDEX IF NOT EXISTS ${table}_token_hash_idx ON ${table} (token_hash);`
1903
+ },
1904
+ {
1905
+ id: `hasna_auth_0003_${table}_tenant`,
1906
+ sql: `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS tid TEXT;
1907
+ CREATE INDEX IF NOT EXISTS ${table}_tid_idx ON ${table} (tid);`
1862
1908
  }
1863
1909
  ];
1864
1910
  }
@@ -1883,10 +1929,13 @@ function parseScopes(value) {
1883
1929
  return [];
1884
1930
  }
1885
1931
  function rowToRecord(row) {
1932
+ const tid = ownTenantId(row);
1933
+ const agentValue = Object.hasOwn(row, "agent") ? row.agent : null;
1886
1934
  return {
1887
1935
  kid: String(row.kid),
1888
1936
  app: String(row.app),
1889
- agent: row.agent === null || row.agent === undefined ? null : String(row.agent),
1937
+ agent: agentValue === null || agentValue === undefined ? null : String(agentValue),
1938
+ tid: tid === null || tid === undefined ? null : String(tid),
1890
1939
  scopes: parseScopes(row.scopes),
1891
1940
  tokenHash: String(row.token_hash),
1892
1941
  issuedAt: toIso(row.issued_at) ?? new Date(0).toISOString(),
@@ -1917,31 +1966,63 @@ class ApiKeyStore {
1917
1966
  }
1918
1967
  }
1919
1968
  async insert(input) {
1969
+ await this.insertWithLifecycle(input, null, null);
1970
+ }
1971
+ async insertWithLifecycle(input, revokedAt, revokedReason) {
1972
+ const tid = ownTenantId(input);
1973
+ const agent = ownAgentClaim(input);
1920
1974
  await this.client.execute(`INSERT INTO ${this.table}
1921
- (kid, app, agent, scopes, token_hash, issued_at, expires_at, created_by)
1922
- VALUES ($1, $2, $3, $4::jsonb, $5, $6, $7, $8)`, [
1975
+ (kid, app, agent, tid, scopes, token_hash, issued_at, expires_at, created_by, revoked_at, revoked_reason)
1976
+ VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7, $8, $9, $10, $11)`, [
1923
1977
  input.kid,
1924
1978
  input.app,
1925
- input.agent ?? null,
1979
+ agent,
1980
+ tid === undefined || tid === null ? null : normalizeTenantId(tid),
1926
1981
  JSON.stringify(input.scopes),
1927
1982
  input.tokenHash,
1928
1983
  input.issuedAt.toISOString(),
1929
1984
  input.expiresAt ? input.expiresAt.toISOString() : null,
1930
- input.createdBy ?? null
1985
+ input.createdBy ?? null,
1986
+ revokedAt,
1987
+ revokedReason
1931
1988
  ]);
1932
1989
  }
1933
- async insertMinted(minted, createdBy) {
1990
+ mintedInput(minted, createdBy) {
1934
1991
  const claims = minted.claims;
1935
- await this.insert({
1992
+ return {
1936
1993
  kid: minted.kid,
1937
1994
  app: claims.app,
1938
- agent: claims.agent ?? null,
1995
+ agent: ownAgentClaim(claims),
1996
+ tid: ownTenantId(claims) ?? null,
1939
1997
  scopes: claims.scopes,
1940
1998
  tokenHash: minted.tokenHash,
1941
1999
  issuedAt: new Date(claims.iat * 1000),
1942
2000
  expiresAt: claims.exp === null ? null : new Date(claims.exp * 1000),
1943
2001
  createdBy: createdBy ?? null
1944
- });
2002
+ };
2003
+ }
2004
+ async insertMinted(minted, createdBy) {
2005
+ await this.insert(this.mintedInput(minted, createdBy));
2006
+ }
2007
+ async insertMintedPending(minted, createdBy, atMs = Date.now()) {
2008
+ await this.insertWithLifecycle(this.mintedInput(minted, createdBy), new Date(atMs).toISOString(), API_KEY_ISSUANCE_PENDING_REASON);
2009
+ }
2010
+ async activatePending(kid, tokenHash) {
2011
+ const row = await this.client.get(`UPDATE ${this.table}
2012
+ SET revoked_at = NULL, revoked_reason = NULL
2013
+ WHERE kid = $1
2014
+ AND revoked_at IS NOT NULL
2015
+ AND revoked_reason = $2
2016
+ AND token_hash = $3
2017
+ RETURNING kid`, [kid, API_KEY_ISSUANCE_PENDING_REASON, tokenHash]);
2018
+ if (row)
2019
+ return true;
2020
+ const active = await this.client.get(`SELECT kid FROM ${this.table}
2021
+ WHERE kid = $1
2022
+ AND token_hash = $2
2023
+ AND revoked_at IS NULL
2024
+ AND revoked_reason IS NULL`, [kid, tokenHash]);
2025
+ return active !== null;
1945
2026
  }
1946
2027
  async findByKid(kid) {
1947
2028
  const row = await this.client.get(`SELECT * FROM ${this.table} WHERE kid = $1`, [kid]);
@@ -1967,6 +2048,9 @@ class ApiKeyStore {
1967
2048
  return "expired";
1968
2049
  return "active";
1969
2050
  }
2051
+ keyStatus = async (kid) => {
2052
+ return this.status(kid);
2053
+ };
1970
2054
  statusChecker() {
1971
2055
  return async (kid) => {
1972
2056
  const status = await this.status(kid);
@@ -1993,11 +2077,16 @@ class ApiKeyStore {
1993
2077
  params.push(options.app);
1994
2078
  clauses.push(`app = $${params.length}`);
1995
2079
  }
2080
+ const tid = ownTenantId(options);
2081
+ if (tid !== undefined) {
2082
+ params.push(normalizeTenantId(tid));
2083
+ clauses.push(`tid = $${params.length}`);
2084
+ }
1996
2085
  if (!options.includeRevoked) {
1997
2086
  clauses.push("revoked_at IS NULL");
1998
2087
  }
1999
2088
  const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
2000
- const rows = await this.client.many(`SELECT * FROM ${this.table} ${where} ORDER BY issued_at DESC`);
2089
+ const rows = await this.client.many(`SELECT * FROM ${this.table} ${where} ORDER BY issued_at DESC`, params);
2001
2090
  return rows.map(rowToRecord);
2002
2091
  }
2003
2092
  async revokedKids() {
@@ -2034,27 +2123,65 @@ function extractToken(source, headerName = "x-api-key", scheme = "Bearer") {
2034
2123
  }
2035
2124
  return null;
2036
2125
  }
2126
+ function ownOption2(bag, name) {
2127
+ return Object.hasOwn(bag, name) ? bag[name] : undefined;
2128
+ }
2037
2129
  function verifyApiKey(options) {
2038
- if (!options.app)
2130
+ const optionApp = ownOption2(options, "app");
2131
+ const optionSigningSecret = ownOption2(options, "signingSecret");
2132
+ const optionExpectedTid = ownOption2(options, "expectedTid");
2133
+ const optionRequiredScopes = ownOption2(options, "requiredScopes");
2134
+ const optionRequireTenant = ownOption2(options, "requireTenant");
2135
+ const optionLeewaySeconds = ownOption2(options, "leewaySeconds");
2136
+ const audit = ownOption2(options, "audit");
2137
+ const headerName = ownOption2(options, "headerName") ?? "x-api-key";
2138
+ const scheme = ownOption2(options, "scheme") ?? "Bearer";
2139
+ const clock = ownOption2(options, "nowMs") ?? (() => Date.now());
2140
+ if (!optionApp)
2039
2141
  throw new Error("verifyApiKey requires an 'app' slug.");
2040
- if (!options.signingSecret) {
2142
+ if (!optionSigningSecret) {
2041
2143
  throw new Error("verifyApiKey requires a 'signingSecret'. Set it from HASNA_<APP>_API_SIGNING_KEY.");
2042
2144
  }
2043
- const headerName = options.headerName ?? "x-api-key";
2044
- const scheme = options.scheme ?? "Bearer";
2045
- const clock = options.nowMs ?? (() => Date.now());
2145
+ if (optionExpectedTid !== undefined && !isValidTenantId(optionExpectedTid)) {
2146
+ throw new Error(`verifyApiKey received an invalid 'expectedTid': '${optionExpectedTid}'.`);
2147
+ }
2148
+ const app = optionApp;
2149
+ const signingSecret = optionSigningSecret;
2150
+ const ownKeyStatus = ownOption2(options, "keyStatus");
2151
+ const ownIsRevoked = ownOption2(options, "isRevoked");
2152
+ const allowUnregistered = ownOption2(options, "allowUnregisteredKeys") === true;
2153
+ if (ownKeyStatus && ownIsRevoked) {
2154
+ throw new Error("verifyApiKey received both 'keyStatus' and 'isRevoked'. Supply exactly one \u2014 " + "letting one silently win would hide which check is actually guarding the service. " + "Use 'keyStatus' (store.keyStatus); drop 'isRevoked'.");
2155
+ }
2156
+ if (!ownKeyStatus && !allowUnregistered) {
2157
+ throw new Error(ownIsRevoked ? "verifyApiKey was given only 'isRevoked', which cannot refuse a key this service has " + "no record of: it returns false both for an active key and for one that was never " + "registered, so an unregistered key is irrevocable. Wire 'keyStatus: store.keyStatus' " + "(or 'isRevoked: store.statusChecker()'), or set 'allowUnregisteredKeys: true' to " + "accept that risk explicitly." : "verifyApiKey requires a key-status hook. Without one this service performs NO " + "revocation check and cannot turn any of its keys off. Wire " + "'keyStatus: store.keyStatus', or set 'allowUnregisteredKeys: true' to declare that " + "this service intentionally cannot revoke keys.");
2158
+ }
2046
2159
  async function emit(event) {
2047
- if (!options.audit)
2160
+ if (!audit)
2048
2161
  return;
2049
2162
  try {
2050
- await options.audit(event);
2163
+ await audit(event);
2051
2164
  } catch {}
2052
2165
  }
2053
2166
  async function authenticate(headers, context = {}) {
2054
- const method = context.method ?? null;
2055
- const path = context.path ?? null;
2056
- const requiredScopes = [...options.requiredScopes ?? [], ...context.requiredScopes ?? []];
2167
+ const method = ownOption2(context, "method") ?? null;
2168
+ const path = ownOption2(context, "path") ?? null;
2169
+ const requiredScopes = [
2170
+ ...optionRequiredScopes ?? [],
2171
+ ...ownOption2(context, "requiredScopes") ?? []
2172
+ ];
2057
2173
  const at = new Date(clock()).toISOString();
2174
+ const perCallTid = ownOption2(context, "expectedTid");
2175
+ const expectedTid = perCallTid !== undefined ? perCallTid : optionExpectedTid;
2176
+ if (perCallTid !== undefined && optionExpectedTid !== undefined && !tenantIdsEqual(perCallTid, optionExpectedTid)) {
2177
+ await emit({ outcome: "deny", app, kid: null, tid: null, reason: "tenant_mismatch", scopesRequired: requiredScopes, method, path, status: 403, at });
2178
+ return {
2179
+ ok: false,
2180
+ status: 403,
2181
+ reason: "tenant_mismatch",
2182
+ message: "This route addresses a tenant other than the one this service is pinned to."
2183
+ };
2184
+ }
2058
2185
  const token = extractToken(headers, headerName, scheme);
2059
2186
  if (!token) {
2060
2187
  const decision = {
@@ -2063,25 +2190,72 @@ function verifyApiKey(options) {
2063
2190
  reason: "missing_token",
2064
2191
  message: `Missing API key. Send it as '${headerName}: <key>' or 'Authorization: ${scheme} <key>'.`
2065
2192
  };
2066
- await emit({ outcome: "deny", app: options.app, kid: null, reason: "missing_token", scopesRequired: requiredScopes, method, path, status: 401, at });
2193
+ await emit({ outcome: "deny", app, kid: null, tid: null, reason: "missing_token", scopesRequired: requiredScopes, method, path, status: 401, at });
2067
2194
  return decision;
2068
2195
  }
2069
2196
  const verified = verifyApiKeyToken(token, {
2070
- signingSecret: options.signingSecret,
2071
- expectedApp: options.app,
2197
+ signingSecret,
2198
+ expectedApp: app,
2072
2199
  nowMs: clock(),
2073
- ...options.leewaySeconds !== undefined ? { leewaySeconds: options.leewaySeconds } : {},
2200
+ ...optionLeewaySeconds !== undefined ? { leewaySeconds: optionLeewaySeconds } : {},
2201
+ ...optionRequireTenant !== undefined ? { requireTenant: optionRequireTenant } : {},
2202
+ ...expectedTid !== undefined ? { expectedTid } : {},
2074
2203
  requiredScopes
2075
2204
  });
2076
2205
  if (!verified.ok) {
2077
- const status = verified.reason === "insufficient_scope" ? 403 : 401;
2078
- await emit({ outcome: "deny", app: options.app, kid: null, reason: verified.reason, scopesRequired: requiredScopes, method, path, status, at });
2206
+ const status = verified.reason === "insufficient_scope" || verified.reason === "tenant_mismatch" || verified.reason === "tenant_required" ? 403 : 401;
2207
+ await emit({
2208
+ outcome: "deny",
2209
+ app,
2210
+ kid: ownOption2(verified, "kid") ?? null,
2211
+ tid: ownTenantId(verified) ?? null,
2212
+ ...Object.hasOwn(verified, "agent") ? { agent: verified.agent } : {},
2213
+ reason: verified.reason,
2214
+ scopesRequired: requiredScopes,
2215
+ method,
2216
+ path,
2217
+ status,
2218
+ at
2219
+ });
2079
2220
  return { ok: false, status, reason: verified.reason, message: verified.message };
2080
2221
  }
2081
- if (options.isRevoked) {
2082
- const revoked = await options.isRevoked(verified.kid);
2222
+ if (ownKeyStatus) {
2223
+ let status;
2224
+ try {
2225
+ status = await ownKeyStatus(verified.kid);
2226
+ } catch {
2227
+ await emit({ outcome: "deny", app, kid: verified.kid, tid: verified.tid, agent: verified.agent, reason: "status_unavailable", scopesRequired: requiredScopes, method, path, status: 503, at });
2228
+ return {
2229
+ ok: false,
2230
+ status: 503,
2231
+ reason: "status_unavailable",
2232
+ message: "Could not verify API key status. Try again shortly."
2233
+ };
2234
+ }
2235
+ if (status !== "active") {
2236
+ const known = status === "revoked" || status === "expired" || status === "unknown";
2237
+ if (!(status === "unknown" && allowUnregistered)) {
2238
+ const reason = status === "revoked" || status === "expired" ? status : "unknown_key";
2239
+ const message = reason === "unknown_key" ? known ? "API key is not registered with this service." : "API key status could not be recognized." : status === "expired" ? "API key has expired." : "API key has been revoked.";
2240
+ await emit({ outcome: "deny", app, kid: verified.kid, tid: verified.tid, agent: verified.agent, reason, scopesRequired: requiredScopes, method, path, status: 401, at });
2241
+ return { ok: false, status: 401, reason, message };
2242
+ }
2243
+ }
2244
+ } else if (ownIsRevoked) {
2245
+ let revoked;
2246
+ try {
2247
+ revoked = await ownIsRevoked(verified.kid);
2248
+ } catch {
2249
+ await emit({ outcome: "deny", app, kid: verified.kid, tid: verified.tid, agent: verified.agent, reason: "status_unavailable", scopesRequired: requiredScopes, method, path, status: 503, at });
2250
+ return {
2251
+ ok: false,
2252
+ status: 503,
2253
+ reason: "status_unavailable",
2254
+ message: "Could not verify API key status. Try again shortly."
2255
+ };
2256
+ }
2083
2257
  if (revoked) {
2084
- await emit({ outcome: "deny", app: options.app, kid: verified.kid, reason: "revoked", scopesRequired: requiredScopes, method, path, status: 401, at });
2258
+ await emit({ outcome: "deny", app, kid: verified.kid, tid: verified.tid, agent: verified.agent, reason: "revoked", scopesRequired: requiredScopes, method, path, status: 401, at });
2085
2259
  return { ok: false, status: 401, reason: "revoked", message: "API key has been revoked." };
2086
2260
  }
2087
2261
  }
@@ -2089,13 +2263,14 @@ function verifyApiKey(options) {
2089
2263
  kid: verified.kid,
2090
2264
  app: verified.app,
2091
2265
  scopes: verified.claims.scopes,
2092
- agent: verified.claims.agent ?? null,
2266
+ agent: verified.agent,
2267
+ tid: verified.tid,
2093
2268
  claims: verified.claims
2094
2269
  };
2095
- await emit({ outcome: "allow", app: options.app, kid: verified.kid, reason: null, scopesRequired: requiredScopes, method, path, status: 200, at });
2270
+ await emit({ outcome: "allow", app, kid: verified.kid, tid: verified.tid, agent: verified.agent, reason: null, scopesRequired: requiredScopes, method, path, status: 200, at });
2096
2271
  return { ok: true, status: 200, principal };
2097
2272
  }
2098
- return { authenticate, app: options.app };
2273
+ return { authenticate, app };
2099
2274
  }
2100
2275
  function honoApiKey(options) {
2101
2276
  const verifier = verifyApiKey(options);
@@ -2111,6 +2286,7 @@ function honoApiKey(options) {
2111
2286
  return c.json({ error: decision.message, reason: decision.reason }, decision.status);
2112
2287
  };
2113
2288
  }
2289
+ var MAX_FLEET_TOKEN_TTL_SECONDS = 24 * 60 * 60;
2114
2290
 
2115
2291
  // src/generated/storage-kit/own.ts
2116
2292
  function ownProp(source, key) {
@@ -2936,113 +3112,113 @@ import { basename, dirname as dirname2, isAbsolute, join as join2, parse, relati
2936
3112
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
2937
3113
  var exports_external = {};
2938
3114
  __export(exports_external, {
2939
- void: () => voidType,
2940
- util: () => util,
2941
- unknown: () => unknownType,
2942
- union: () => unionType,
2943
- undefined: () => undefinedType,
2944
- tuple: () => tupleType,
2945
- transformer: () => effectsType,
2946
- symbol: () => symbolType,
2947
- string: () => stringType,
2948
- strictObject: () => strictObjectType,
2949
- setErrorMap: () => setErrorMap,
2950
- set: () => setType,
2951
- record: () => recordType,
2952
- quotelessJson: () => quotelessJson,
2953
- promise: () => promiseType,
2954
- preprocess: () => preprocessType,
2955
- pipeline: () => pipelineType,
2956
- ostring: () => ostring,
2957
- optional: () => optionalType,
2958
- onumber: () => onumber,
2959
- oboolean: () => oboolean,
2960
- objectUtil: () => objectUtil,
2961
- object: () => objectType,
2962
- number: () => numberType,
2963
- nullable: () => nullableType,
2964
- null: () => nullType,
2965
- never: () => neverType,
2966
- nativeEnum: () => nativeEnumType,
2967
- nan: () => nanType,
2968
- map: () => mapType,
2969
- makeIssue: () => makeIssue,
2970
- literal: () => literalType,
2971
- lazy: () => lazyType,
2972
- late: () => late,
2973
- isValid: () => isValid,
2974
- isDirty: () => isDirty,
2975
- isAsync: () => isAsync,
2976
- isAborted: () => isAborted,
2977
- intersection: () => intersectionType,
2978
- instanceof: () => instanceOfType,
2979
- getParsedType: () => getParsedType,
2980
- getErrorMap: () => getErrorMap,
2981
- function: () => functionType,
2982
- enum: () => enumType,
2983
- effect: () => effectsType,
2984
- discriminatedUnion: () => discriminatedUnionType,
2985
- defaultErrorMap: () => en_default,
2986
- datetimeRegex: () => datetimeRegex,
2987
- date: () => dateType,
2988
- custom: () => custom,
2989
- coerce: () => coerce,
2990
- boolean: () => booleanType,
2991
- bigint: () => bigIntType,
2992
- array: () => arrayType,
2993
- any: () => anyType,
2994
- addIssueToContext: () => addIssueToContext,
2995
- ZodVoid: () => ZodVoid,
2996
- ZodUnknown: () => ZodUnknown,
2997
- ZodUnion: () => ZodUnion,
2998
- ZodUndefined: () => ZodUndefined,
2999
- ZodType: () => ZodType,
3000
- ZodTuple: () => ZodTuple,
3001
- ZodTransformer: () => ZodEffects,
3002
- ZodSymbol: () => ZodSymbol,
3003
- ZodString: () => ZodString,
3004
- ZodSet: () => ZodSet,
3005
- ZodSchema: () => ZodType,
3006
- ZodRecord: () => ZodRecord,
3007
- ZodReadonly: () => ZodReadonly,
3008
- ZodPromise: () => ZodPromise,
3009
- ZodPipeline: () => ZodPipeline,
3010
- ZodParsedType: () => ZodParsedType,
3011
- ZodOptional: () => ZodOptional,
3012
- ZodObject: () => ZodObject,
3013
- ZodNumber: () => ZodNumber,
3014
- ZodNullable: () => ZodNullable,
3015
- ZodNull: () => ZodNull,
3016
- ZodNever: () => ZodNever,
3017
- ZodNativeEnum: () => ZodNativeEnum,
3018
- ZodNaN: () => ZodNaN,
3019
- ZodMap: () => ZodMap,
3020
- ZodLiteral: () => ZodLiteral,
3021
- ZodLazy: () => ZodLazy,
3022
- ZodIssueCode: () => ZodIssueCode,
3023
- ZodIntersection: () => ZodIntersection,
3024
- ZodFunction: () => ZodFunction,
3025
- ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
3026
- ZodError: () => ZodError,
3027
- ZodEnum: () => ZodEnum,
3028
- ZodEffects: () => ZodEffects,
3029
- ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
3030
- ZodDefault: () => ZodDefault,
3031
- ZodDate: () => ZodDate,
3032
- ZodCatch: () => ZodCatch,
3033
- ZodBranded: () => ZodBranded,
3034
- ZodBoolean: () => ZodBoolean,
3035
- ZodBigInt: () => ZodBigInt,
3036
- ZodArray: () => ZodArray,
3037
- ZodAny: () => ZodAny,
3038
- Schema: () => ZodType,
3039
- ParseStatus: () => ParseStatus,
3040
- OK: () => OK,
3041
- NEVER: () => NEVER,
3042
- INVALID: () => INVALID,
3043
- EMPTY_PATH: () => EMPTY_PATH,
3115
+ BRAND: () => BRAND,
3044
3116
  DIRTY: () => DIRTY,
3045
- BRAND: () => BRAND
3117
+ EMPTY_PATH: () => EMPTY_PATH,
3118
+ INVALID: () => INVALID,
3119
+ NEVER: () => NEVER,
3120
+ OK: () => OK,
3121
+ ParseStatus: () => ParseStatus,
3122
+ Schema: () => ZodType,
3123
+ ZodAny: () => ZodAny,
3124
+ ZodArray: () => ZodArray,
3125
+ ZodBigInt: () => ZodBigInt,
3126
+ ZodBoolean: () => ZodBoolean,
3127
+ ZodBranded: () => ZodBranded,
3128
+ ZodCatch: () => ZodCatch,
3129
+ ZodDate: () => ZodDate,
3130
+ ZodDefault: () => ZodDefault,
3131
+ ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
3132
+ ZodEffects: () => ZodEffects,
3133
+ ZodEnum: () => ZodEnum,
3134
+ ZodError: () => ZodError,
3135
+ ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
3136
+ ZodFunction: () => ZodFunction,
3137
+ ZodIntersection: () => ZodIntersection,
3138
+ ZodIssueCode: () => ZodIssueCode,
3139
+ ZodLazy: () => ZodLazy,
3140
+ ZodLiteral: () => ZodLiteral,
3141
+ ZodMap: () => ZodMap,
3142
+ ZodNaN: () => ZodNaN,
3143
+ ZodNativeEnum: () => ZodNativeEnum,
3144
+ ZodNever: () => ZodNever,
3145
+ ZodNull: () => ZodNull,
3146
+ ZodNullable: () => ZodNullable,
3147
+ ZodNumber: () => ZodNumber,
3148
+ ZodObject: () => ZodObject,
3149
+ ZodOptional: () => ZodOptional,
3150
+ ZodParsedType: () => ZodParsedType,
3151
+ ZodPipeline: () => ZodPipeline,
3152
+ ZodPromise: () => ZodPromise,
3153
+ ZodReadonly: () => ZodReadonly,
3154
+ ZodRecord: () => ZodRecord,
3155
+ ZodSchema: () => ZodType,
3156
+ ZodSet: () => ZodSet,
3157
+ ZodString: () => ZodString,
3158
+ ZodSymbol: () => ZodSymbol,
3159
+ ZodTransformer: () => ZodEffects,
3160
+ ZodTuple: () => ZodTuple,
3161
+ ZodType: () => ZodType,
3162
+ ZodUndefined: () => ZodUndefined,
3163
+ ZodUnion: () => ZodUnion,
3164
+ ZodUnknown: () => ZodUnknown,
3165
+ ZodVoid: () => ZodVoid,
3166
+ addIssueToContext: () => addIssueToContext,
3167
+ any: () => anyType,
3168
+ array: () => arrayType,
3169
+ bigint: () => bigIntType,
3170
+ boolean: () => booleanType,
3171
+ coerce: () => coerce,
3172
+ custom: () => custom,
3173
+ date: () => dateType,
3174
+ datetimeRegex: () => datetimeRegex,
3175
+ defaultErrorMap: () => en_default,
3176
+ discriminatedUnion: () => discriminatedUnionType,
3177
+ effect: () => effectsType,
3178
+ enum: () => enumType,
3179
+ function: () => functionType,
3180
+ getErrorMap: () => getErrorMap,
3181
+ getParsedType: () => getParsedType,
3182
+ instanceof: () => instanceOfType,
3183
+ intersection: () => intersectionType,
3184
+ isAborted: () => isAborted,
3185
+ isAsync: () => isAsync,
3186
+ isDirty: () => isDirty,
3187
+ isValid: () => isValid,
3188
+ late: () => late,
3189
+ lazy: () => lazyType,
3190
+ literal: () => literalType,
3191
+ makeIssue: () => makeIssue,
3192
+ map: () => mapType,
3193
+ nan: () => nanType,
3194
+ nativeEnum: () => nativeEnumType,
3195
+ never: () => neverType,
3196
+ null: () => nullType,
3197
+ nullable: () => nullableType,
3198
+ number: () => numberType,
3199
+ object: () => objectType,
3200
+ objectUtil: () => objectUtil,
3201
+ oboolean: () => oboolean,
3202
+ onumber: () => onumber,
3203
+ optional: () => optionalType,
3204
+ ostring: () => ostring,
3205
+ pipeline: () => pipelineType,
3206
+ preprocess: () => preprocessType,
3207
+ promise: () => promiseType,
3208
+ quotelessJson: () => quotelessJson,
3209
+ record: () => recordType,
3210
+ set: () => setType,
3211
+ setErrorMap: () => setErrorMap,
3212
+ strictObject: () => strictObjectType,
3213
+ string: () => stringType,
3214
+ symbol: () => symbolType,
3215
+ transformer: () => effectsType,
3216
+ tuple: () => tupleType,
3217
+ undefined: () => undefinedType,
3218
+ union: () => unionType,
3219
+ unknown: () => unknownType,
3220
+ util: () => util,
3221
+ void: () => voidType
3046
3222
  });
3047
3223
 
3048
3224
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
@@ -7145,6 +7321,9 @@ var DEPRECATED_CONFIG_AGENT_SET = new Set(DEPRECATED_CONFIG_AGENTS);
7145
7321
 
7146
7322
  // src/lib/cursor-authority.ts
7147
7323
  var CURSOR_GLOBAL_AUTHORITY_MAX_BYTES = 256 * 1024;
7324
+
7325
+ // src/lib/session-authority.ts
7326
+ var CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
7148
7327
  // src/lib/session-render.ts
7149
7328
  var SESSION_RENDER_PROFILE_ENTRYPOINTS = [
7150
7329
  ".claude/CLAUDE.md",
@@ -7602,9 +7781,15 @@ async function createConfig2(client, input) {
7602
7781
  throw new Error("category is required");
7603
7782
  const id = randomUUID();
7604
7783
  const slug = await uniqueSlug(client, input.name);
7605
- await client.execute(`INSERT INTO configs
7606
- (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at)
7607
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9,$10,$11,$12::jsonb,$13,1,now(),now())`, [
7784
+ const snapshotId = randomUUID();
7785
+ await client.execute(`WITH inserted_config AS (
7786
+ INSERT INTO configs
7787
+ (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at)
7788
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9,$10,$11,$12::jsonb,$13,1,now(),now())
7789
+ RETURNING id, content, version
7790
+ )
7791
+ INSERT INTO config_snapshots (id, config_id, content, version, created_at)
7792
+ SELECT $14, id, content, version, now() FROM inserted_config`, [
7608
7793
  id,
7609
7794
  input.name,
7610
7795
  slug,
@@ -7617,7 +7802,8 @@ async function createConfig2(client, input) {
7617
7802
  input.content ?? "",
7618
7803
  input.description ?? null,
7619
7804
  JSON.stringify(input.tags ?? []),
7620
- input.is_template ?? false
7805
+ input.is_template ?? false,
7806
+ snapshotId
7621
7807
  ]);
7622
7808
  return getConfig2(client, id);
7623
7809
  }
@@ -7656,7 +7842,15 @@ async function updateConfig2(client, idOrSlug, input) {
7656
7842
  if (input.synced_at !== undefined)
7657
7843
  set("synced_at", input.synced_at, "::timestamptz");
7658
7844
  params.push(existing.id);
7659
- await client.execute(`UPDATE configs SET ${sets.join(", ")} WHERE id = $${params.length}`, params);
7845
+ const configIdParam = params.length;
7846
+ params.push(randomUUID());
7847
+ const snapshotIdParam = params.length;
7848
+ await client.execute(`WITH updated_config AS (
7849
+ UPDATE configs SET ${sets.join(", ")} WHERE id = $${configIdParam}
7850
+ RETURNING id, content, version
7851
+ )
7852
+ INSERT INTO config_snapshots (id, config_id, content, version, created_at)
7853
+ SELECT $${snapshotIdParam}, id, content, version, now() FROM updated_config`, params);
7660
7854
  return getConfig2(client, existing.id);
7661
7855
  }
7662
7856
  async function deleteConfig2(client, idOrSlug) {