permdock 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/active-tenant-DVee32-T.js +39 -0
  2. package/dist/{adoption-scan-Ch07MdxN.js → adoption-scan-DKCOsfcF.js} +1 -1
  3. package/dist/{arazzo-BpS7LnV0.js → arazzo-BXaEhGHM.js} +1 -1
  4. package/dist/{arazzo-CmQaGPkM.js → arazzo-CTH5iN--.js} +1 -1
  5. package/dist/better-supabase/index.d.ts +188 -23
  6. package/dist/better-supabase/index.js +246 -62
  7. package/dist/{casbin-B7_yS-oo.js → casbin-BuTZnSPv.js} +1 -1
  8. package/dist/{casbin-bHyIxTNQ.js → casbin-DTi6HSsl.js} +1 -1
  9. package/dist/cli/bin.js +1 -1
  10. package/dist/cli/index.d.ts +6 -1
  11. package/dist/cli/index.js +10 -2
  12. package/dist/{cloud-3kWfjnYy.js → cloud-DiAiYFRo.js} +2 -2
  13. package/dist/{codemod-Bv5i-Mzo.js → codemod-C1U8_Zu3.js} +1 -1
  14. package/dist/{codemod-D9Aiii1r.js → codemod-_q_ZI01S.js} +2 -2
  15. package/dist/{collect-B80GYEot.js → collect-D9Tt0Bcx.js} +1 -1
  16. package/dist/{completion-_bc0OUQ-.js → completion-DLTuCf53.js} +1 -1
  17. package/dist/{config-BwPBfN0t.js → config-BR23fL8m.js} +2 -2
  18. package/dist/{config-D5XrLWpk.js → config-CmydKZr2.js} +1 -1
  19. package/dist/{config-report-BmWIskD7.js → config-report-5AwptLr2.js} +2 -2
  20. package/dist/{config-shape-ykV0Vm2n.js → config-shape-tsxHx71E.js} +2 -1
  21. package/dist/{create-BamXmeob.d.ts → create-CDd9c22W.d.ts} +10 -1
  22. package/dist/{doctor-LPMYjSqO.js → doctor-DxyVeUEE.js} +2 -2
  23. package/dist/{doctor-ClRVAtXQ.js → doctor-ktitU5Dl.js} +142 -14
  24. package/dist/{fields-D0vUI6dq.js → fields-DI8s3cPl.js} +1 -1
  25. package/dist/{gen-BtfFWGbz.js → gen-DuAhghDw.js} +1 -1
  26. package/dist/{gen-BFfzZSLH.js → gen-DwgCSC6V.js} +1 -1
  27. package/dist/{import-CST57XLp.js → import-CNBF96hB.js} +2 -2
  28. package/dist/{import-ACkOrv6g.js → import-t1yqnCyG.js} +1 -1
  29. package/dist/{index-vYjMrXyK.d.ts → index-Cay5-Kvx.d.ts} +3 -51
  30. package/dist/index.d.ts +2 -1
  31. package/dist/{init-txYTuW9u.js → init-CY7THM2v.js} +3 -3
  32. package/dist/{init-BjuFDYT1.js → init-DFwylmiN.js} +1 -1
  33. package/dist/{login-CgfCTO3F.js → login-B92-dZMK.js} +1 -1
  34. package/dist/{login-CfImTJGb.js → login-C5XLYZSF.js} +3 -3
  35. package/dist/{manifest-CzzWtnAb.d.ts → manifest-B5yF3CIq.d.ts} +19 -0
  36. package/dist/{manifest-wg8WkkpD.js → manifest-BXTbHNIV.js} +1 -1
  37. package/dist/{manifest-B2QPh19C.js → manifest-Cav1rjNQ.js} +33 -33
  38. package/dist/{manifest-schema-2r37XRhD.js → manifest-schema-DQkBK2Mr.js} +28 -1
  39. package/dist/{migrate-B7oP_sQU.js → migrate-BJHxQqT5.js} +2 -2
  40. package/dist/{module-values-BJTBdo6y.js → module-values-B1EVTkHP.js} +1 -1
  41. package/dist/next/index.d.ts +16 -3
  42. package/dist/next/index.js +1 -1
  43. package/dist/next/plugin.d.ts +1 -1
  44. package/dist/next/plugin.js +1 -1
  45. package/dist/{openapi-BGeuunWC.js → openapi-CuePQL4z.js} +1 -1
  46. package/dist/{openapi-BIpw3Mb7.js → openapi-gMQQSzNn.js} +2 -2
  47. package/dist/{output-DV5dwOTs.js → output-CtyBwTNB.js} +1 -1
  48. package/dist/{ownership-DA5YdYTm.js → ownership-qqBuGJXr.js} +3 -3
  49. package/dist/{postgrest-5Dr3W64L.js → postgrest-DAxFltvO.js} +1 -1
  50. package/dist/powersync-6vgKevSi.js +1 -0
  51. package/dist/{powersync-Bg8u-JcR.js → powersync-CBcsEvdr.js} +2 -2
  52. package/dist/{powersync-CJrT1DSc.js → powersync-CD8-Ypao.js} +188 -36
  53. package/dist/{pull-2nJr0qg_.js → pull-CAeaAioC.js} +2 -2
  54. package/dist/pull-DfJALUbS.js +1 -0
  55. package/dist/{push-Bn-lMEmI.js → push-Dq_ca4-j.js} +1 -1
  56. package/dist/{resolve-names-ConCSj1y.js → resolve-names-Y1Cb22cp.js} +4 -4
  57. package/dist/{rls-Ba9wN0SP.js → rls-BYHN5bcJ.js} +1 -1
  58. package/dist/{rls-Cx0dtsB7.js → rls-CtRaRrn3.js} +22 -15
  59. package/dist/run-0vOholnN.js +1 -0
  60. package/dist/{run-PbRlXTfh.js → run-B5nyNKe8.js} +3 -3
  61. package/dist/{run-ticket-Dv7aF7Dk.js → run-ticket-DfnZCh-u.js} +19 -6
  62. package/dist/{run-ticket-B2o2Kxz8.d.ts → run-ticket-ylovhyFQ.d.ts} +16 -1
  63. package/dist/{scan-CRTj6fke.js → scan-CvFWW_fU.js} +1 -1
  64. package/dist/{schema-BTsE1WRT.js → schema-CQzIJdEd.js} +1 -1
  65. package/dist/server/index.d.ts +2 -2
  66. package/dist/server/index.js +1 -1
  67. package/dist/{shared-Afctg7B0.js → shared-DrGLwM3s.js} +1 -1
  68. package/dist/{skills-C9uKfSF5.js → skills-BZc81VLt.js} +1 -1
  69. package/dist/{skills-CQQcuGBi.js → skills-Bar7ZqwM.js} +1 -1
  70. package/dist/{sources-CMAM-2A3.js → sources-C4vkzZJV.js} +12 -7
  71. package/dist/{types-BMVDHvI0.d.ts → sources-oB1Hg1Fm.d.ts} +316 -2
  72. package/dist/supabase/index.d.ts +10 -3
  73. package/dist/supabase/index.js +4 -4
  74. package/dist/{supabase-SBs3MYUE.js → supabase-CBEK6KEo.js} +2 -2
  75. package/dist/{supabase-DnMMg5Fn.js → supabase-CC6rIri4.js} +3 -2
  76. package/dist/supabase-hook-CWvEpdCh.js +1 -0
  77. package/dist/{supabase-hook-CrgU06WH.js → supabase-hook-CyuD9Wau.js} +110 -47
  78. package/dist/{supautils-ByezN3b8.js → supautils-C-_jqylY.js} +8 -8
  79. package/dist/{tables-BqrxBCir.js → tables-DimBcVWY.js} +2 -2
  80. package/dist/testing/fixtures/index.d.ts +1 -1
  81. package/dist/testing/fixtures/index.js +26 -28
  82. package/dist/testing/index.d.ts +4 -3
  83. package/dist/testing/index.js +3 -2
  84. package/dist/{types-CzOeVvcC.d.ts → types-Cklw5hJo.d.ts} +23 -2
  85. package/dist/unplugin/index.d.ts +1 -1
  86. package/dist/unplugin/index.js +1 -1
  87. package/dist/usage-CNca1i1W.js +1 -0
  88. package/dist/{usage-B9EtHWLa.js → usage-Y6xZUfE-.js} +2 -2
  89. package/dist/{usage-CBzgwfIs.js → usage-heV5KOiU.js} +1 -1
  90. package/dist/{verify-DlUcTDBz.js → verify-6EauWrkP.js} +6 -6
  91. package/dist/version-cache-Df11C2Qp.d.ts +51 -0
  92. package/dist/{watch-BFDKqmUa.js → watch-Bpci3hBY.js} +1 -1
  93. package/dist/watch-COuvly20.js +1 -0
  94. package/dist/workflow/index.d.ts +1 -1
  95. package/dist/workflow/index.js +22 -6
  96. package/dist/workflow-sdk/index.d.ts +1 -1
  97. package/package.json +2 -2
  98. package/schemas/run-v1.json +16 -0
  99. package/schemas/supabase-manifest-v1.json +28 -0
  100. package/skills/permdock-agents/references/operations-automations-chat.md +1 -0
  101. package/skills/permdock-approvals/references/stores-and-resume.md +1 -1
  102. package/skills/permdock-wire/references/adapters.md +2 -2
  103. package/dist/powersync-B701tvhE.js +0 -1
  104. package/dist/pull-C3EEGzSw.js +0 -1
  105. package/dist/run-Ck22anC2.js +0 -1
  106. package/dist/sources-CWG32C0G.d.ts +0 -312
  107. package/dist/supabase-hook-C0FyEACZ.js +0 -1
  108. package/dist/usage-0hjZPIRX.js +0 -1
  109. package/dist/watch-ClLuO2TL.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { t as compact } from "./compact-CAf557ZB.js";
2
- import { c as isConditionDate, l as isConditionRef } from "./refs-C8BKxc_e.js";
2
+ import { c as isConditionDate, l as isConditionRef, o as hasConditionOp } from "./refs-C8BKxc_e.js";
3
3
  import { g as localSnapshotManifest, j as memoryCustomRoleSource } from "./src-C_pi2Rnh.js";
4
4
  import { n as findPermission } from "./permission-registry-B8tYxPIt.js";
5
5
  import { f as scopeList, s as resolveScope } from "./scopes-0Up_UA2t.js";
@@ -10,16 +10,16 @@ import { u as usageResult } from "./errors-Vpu6rKHl.js";
10
10
  import { c as requirePolicy } from "./load-DjSU5KXO.js";
11
11
  import { s as quoteSqlLiteral, t as SQL_IDENT } from "./sql-0WjxIp9g.js";
12
12
  import { a as scopeMembershipTable, i as scopeColumn } from "./compile-EzEMB-7v.js";
13
- import "./sources-CMAM-2A3.js";
13
+ import "./sources-C4vkzZJV.js";
14
14
  import { t as applyFiles } from "./files-B3AA05f1.js";
15
15
  import { i as fileDrift } from "./output-files-BIQ_9pIs.js";
16
16
  import { n as fixtureSubject, r as loadFixtures, t as fixtureRow } from "./fixtures-A1_sp_tR.js";
17
- import { O as quoteIdent, a as customRolesOn, t as CUSTOM_ROLES } from "./shared-Afctg7B0.js";
18
- import { r as tableNames } from "./tables-BqrxBCir.js";
19
- import { d as andConditions, l as collectGrants, o as assignableRoles, p as contextRefs, t as ownershipRules, u as roleNames } from "./ownership-DA5YdYTm.js";
17
+ import { O as quoteIdent, a as customRolesOn, t as CUSTOM_ROLES } from "./shared-DrGLwM3s.js";
18
+ import { r as tableNames } from "./tables-DimBcVWY.js";
19
+ import { d as andConditions, l as collectGrants, o as assignableRoles, p as contextRefs, t as ownershipRules, u as roleNames } from "./ownership-qqBuGJXr.js";
20
20
  import { t as membershipSourcesOf } from "./sources-qoIJQrJv.js";
21
21
  import { n as pgConnector } from "./pg-DQTqRrou.js";
22
- import { m as tableFor, t as canFixture } from "./verify-DlUcTDBz.js";
22
+ import { m as tableFor, t as canFixture } from "./verify-6EauWrkP.js";
23
23
  import { resolve } from "node:path";
24
24
  //#region src/cli/powersync/names.ts
25
25
  function ident(name) {
@@ -87,7 +87,7 @@ function scopeSourcesOf(sources, name) {
87
87
  /** The source's stream form, or why its rows cannot filter a stream. */
88
88
  function streamOf(source) {
89
89
  const { stream } = source.sql;
90
- if (stream === void 0) return `the ${source.sql.table} membership source has no stream form: build it with tableMembershipSource or junctionMembershipSource`;
90
+ if (stream === void 0) return `the ${source.sql.table} membership source has no stream form: build it with tableMembershipSource, junctionMembershipSource or blockMembershipSource`;
91
91
  if (stream.expiresAt !== void 0) return `the ${stream.table} membership source has no stream: it has an expiry`;
92
92
  if (stream.disabledAt !== void 0) return `the ${stream.table} membership source has no stream: it has disabledAt`;
93
93
  return stream;
@@ -399,6 +399,65 @@ function customRoleItems(ctx, grants, resource, key, table) {
399
399
  }) };
400
400
  }
401
401
  //#endregion
402
+ //#region src/cli/powersync/tables.ts
403
+ /**
404
+ * One stream per `powersync.tables` entry: the user's own rows, and the rows
405
+ * that point at a resource row the user reads. A stream of the user's own rows
406
+ * alone carries `user`, so `verify` checks it holds no other user's row.
407
+ */
408
+ function tableStreams(tables, dialect, taken, permitted) {
409
+ const streams = [];
410
+ const warnings = [];
411
+ const omitted = [];
412
+ for (const [source, spec] of Object.entries(tables)) {
413
+ const table = bare(source, dialect);
414
+ const from = fromTable(source, dialect);
415
+ const queries = [];
416
+ const follows = spec.follow ?? [];
417
+ const invalid = [...spec.user === void 0 ? [] : [spec.user], ...follows.flatMap((follow) => [follow.field, ...Object.keys(follow.match ?? {})])].find((column) => !SQL_IDENT.test(column));
418
+ if (taken.has(table) || invalid !== void 0) {
419
+ omitted.push({
420
+ resource: source,
421
+ reason: invalid === void 0 ? `a resource stream is named ${table}` : `${invalid} is not a column name`
422
+ });
423
+ continue;
424
+ }
425
+ if (spec.user !== void 0) queries.push(`SELECT * FROM ${from} WHERE ${dialect.column(table, spec.user)} = ${dialect.user}`);
426
+ for (const follow of follows) {
427
+ const { ids, gaps } = permitted(follow.resource);
428
+ for (const gap of gaps) warnings.push(`${source} under-syncs: ${gap}`);
429
+ if (ids === void 0) {
430
+ warnings.push(`${source} does not sync the rows of ${follow.resource}: no grant of its read permission compiles, or it has a deny grant`);
431
+ continue;
432
+ }
433
+ const match = Object.entries(follow.match ?? {}).map(([column, value]) => `${dialect.column(table, column)} = ${dialect.literal(value)}`);
434
+ for (const query of ids) queries.push(`SELECT * FROM ${from} WHERE ${[`${dialect.column(table, follow.field)} IN (${query})`, ...match].join(" AND ")}`);
435
+ }
436
+ if (queries.length === 0) {
437
+ omitted.push({
438
+ resource: source,
439
+ reason: "no query compiles"
440
+ });
441
+ continue;
442
+ }
443
+ streams.push(spec.user !== void 0 && follows.length === 0 ? {
444
+ name: table,
445
+ table,
446
+ user: spec.user,
447
+ queries
448
+ } : {
449
+ name: table,
450
+ table,
451
+ queries
452
+ });
453
+ }
454
+ return {
455
+ streams,
456
+ warnings,
457
+ omitted
458
+ };
459
+ }
460
+ //#endregion
402
461
  //#region src/cli/powersync/streams.ts
403
462
  const POWERSYNC = {
404
463
  user: "auth.user_id()",
@@ -419,6 +478,8 @@ const POSTGRES = {
419
478
  * than truncated.
420
479
  */
421
480
  const MAX_QUERIES = 32;
481
+ /** Nested `inherit()` grants a stream follows; a longer chain syncs nothing. */
482
+ const MAX_INHERIT_DEPTH = 4;
422
483
  /** Every combination of one clause list from each input, ANDed. */
423
484
  function product(lists) {
424
485
  let out = [[]];
@@ -502,15 +563,85 @@ function conditionSql(condition, table, ctx) {
502
563
  return membershipSql(scope, left, condition.roles, ctx);
503
564
  }
504
565
  case "sqlFunction": return conditionSql(condition.twin, table, ctx);
566
+ case "related": return relatedSql(condition, table, ctx);
505
567
  case "not":
506
568
  case "contains":
507
- case "related":
508
569
  case "opaque":
509
570
  case "liveSession": return;
510
571
  default: return condition;
511
572
  }
512
573
  }
513
574
  /**
575
+ * An `inherit()` condition: the row's `field` among the ids of the `resource`
576
+ * rows the user reaches with `permission`, one nested `IN (SELECT …)` per link
577
+ * hop. A relation reads the closure table, which Sync Streams cannot.
578
+ */
579
+ function relatedSql(condition, table, ctx) {
580
+ const { permission } = condition;
581
+ if (permission === void 0 || condition.relation !== "" || condition.ids !== void 0 || condition.depth > 0 || condition.restrictedAncestors !== void 0 || ctx.depth >= MAX_INHERIT_DEPTH || !SQL_IDENT.test(condition.field)) return;
582
+ let ids = permittedSql(condition.resource, permission, {
583
+ ...ctx,
584
+ depth: ctx.depth + 1
585
+ });
586
+ const hops = condition.hops ?? [];
587
+ for (let index = hops.length - 1; index > 0 && ids !== void 0; index -= 1) {
588
+ const hop = hops[index];
589
+ const node = ctx.policy.resources.get(hops[index - 1]?.resource ?? "");
590
+ const link = hop !== void 0 && node !== void 0 && Object.hasOwn(node.links, hop.link) ? node.links[hop.link] : void 0;
591
+ if (node === void 0 || link === void 0 || !SQL_IDENT.test(node.id) || !SQL_IDENT.test(link.field)) return;
592
+ const source = tableFor(node.name, tableNames(ctx.config.rls?.tables));
593
+ const name = bare(source, ctx.dialect);
594
+ ids = ids.map((query) => `SELECT ${ctx.dialect.column(name, node.id)} FROM ${fromTable(source, ctx.dialect)} WHERE ${ctx.dialect.column(name, link.field)} IN (${query})`);
595
+ }
596
+ const open = condition.restricted === void 0 ? [[]] : conditionSql({
597
+ op: "or",
598
+ conditions: [{
599
+ op: "isNull",
600
+ field: condition.restricted,
601
+ value: true
602
+ }, {
603
+ op: "eq",
604
+ field: condition.restricted,
605
+ value: false
606
+ }]
607
+ }, table, ctx);
608
+ if (ids === void 0 || open === void 0) return;
609
+ const left = ctx.dialect.column(table, condition.field);
610
+ return product([ids.map((query) => [`${left} IN (${query})`]), open]);
611
+ }
612
+ /**
613
+ * The `SELECT`s of the ids of the `resource` rows the user reaches with `key`:
614
+ * `[]` when no grant gives it, `undefined` when a deny grant could subtract
615
+ * rows or no grant compiles. A grant that does not compile leaves its rows out,
616
+ * which the caller reports as an under-sync.
617
+ */
618
+ function permittedSql(resource, key, ctx) {
619
+ const node = ctx.policy.resources.get(resource);
620
+ const items = ctx.grants.filter((item) => item.grant.permission.key === key);
621
+ if (node === void 0 || !SQL_IDENT.test(node.id) || items.some((item) => item.grant.effect === "deny")) return;
622
+ const source = tableFor(resource, tableNames(ctx.config.rls?.tables));
623
+ const table = bare(source, ctx.dialect);
624
+ const custom = customRoleItems(ctx, ctx.grants, resource, key, table);
625
+ if (custom.skipped !== void 0) ctx.gaps.push(custom.skipped);
626
+ const reaches = [...items.map((item) => ({
627
+ item,
628
+ access: unsupported(item) ?? accessSql(item, table, ctx)
629
+ })), ...custom.items.map((entry) => ({
630
+ item: entry.item,
631
+ access: entry.access
632
+ }))];
633
+ const queries = /* @__PURE__ */ new Set();
634
+ for (const { item, access } of reaches) {
635
+ const where = typeof access === "string" || item.where === void 0 ? [[]] : conditionSql(item.where, table, ctx);
636
+ if (typeof access === "string" || where === void 0) {
637
+ ctx.gaps.push(`inherit() of ${key} leaves out ${item.label}: ${typeof access === "string" ? access : "its condition has no Sync Streams form"}`);
638
+ continue;
639
+ }
640
+ for (const clauses of product([access, where])) queries.add(`SELECT ${ctx.dialect.column(table, node.id)} FROM ${fromTable(source, ctx.dialect)}${clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`}`);
641
+ }
642
+ return queries.size === 0 && reaches.length > 0 ? void 0 : [...queries];
643
+ }
644
+ /**
514
645
  * One `IN (SELECT …)` per role source of the membership table: the row's `left`
515
646
  * is an instance the user holds `roles` in.
516
647
  */
@@ -758,6 +889,7 @@ function powersyncPlan(policy, config, dialect = POWERSYNC) {
758
889
  }
759
890
  function planWith(policy, config, dialect) {
760
891
  const scopes = scopeList(policy.scopes);
892
+ const grants = collectGrants(policy);
761
893
  const ctx = {
762
894
  policy,
763
895
  config,
@@ -765,10 +897,11 @@ function planWith(policy, config, dialect) {
765
897
  kinds: ownershipRules(policy, scopes)?.kinds ?? {},
766
898
  dialect,
767
899
  sources: guardedSources(policy, config),
900
+ grants,
901
+ depth: 0,
768
902
  gaps: []
769
903
  };
770
904
  const action = config.powersync?.action ?? "read";
771
- const grants = collectGrants(policy);
772
905
  const resources = config.powersync?.resources ?? [...policy.resources.keys()].filter((name) => grants.some((item) => item.grant.permission.resource === name && item.grant.permission.action === action));
773
906
  const streams = [];
774
907
  const omitted = [];
@@ -849,11 +982,22 @@ function planWith(policy, config, dialect) {
849
982
  queries: [...queries]
850
983
  });
851
984
  }
985
+ const tables = tableStreams(config.powersync?.tables ?? {}, dialect, new Set(streams.map((stream) => stream.name)), (resource) => ({
986
+ ids: permittedSql(resource, `${resource}.${action}`, ctx),
987
+ gaps: [...new Set(ctx.gaps.splice(0))]
988
+ }));
989
+ warnings.push(...tables.warnings);
990
+ omitted.push(...tables.omitted);
852
991
  const subject = subjectStreams(ctx);
853
- const outside = [...new Set([...streams, ...subject].flatMap((stream) => stream.queries.flatMap((query) => [...query.matchAll(QUOTED_TABLE)].map(([, schema, name]) => `${schema ?? ""}.${name ?? ""}`))))].toSorted();
992
+ const outside = [...new Set([
993
+ ...streams,
994
+ ...subject,
995
+ ...tables.streams
996
+ ].flatMap((stream) => stream.queries.flatMap((query) => [...query.matchAll(QUOTED_TABLE)].map(([, schema, name]) => `${schema ?? ""}.${name ?? ""}`))))].toSorted();
854
997
  return {
855
998
  streams,
856
999
  subject,
1000
+ tables: tables.streams,
857
1001
  omitted,
858
1002
  warnings,
859
1003
  unsynced,
@@ -862,31 +1006,6 @@ function planWith(policy, config, dialect) {
862
1006
  ...outside.length === 0 ? {} : { publication: `alter publication powersync add table ${outside.join(", ")};` }
863
1007
  };
864
1008
  }
865
- /**
866
- * A YAML scalar quoted as oxfmt and Prettier write it: single quotes around a
867
- * `"`, unless a backslash needs a double-quoted escape.
868
- */
869
- function yamlString(text) {
870
- return text.includes("\"") && !text.includes("\\") ? `'${text.replaceAll("'", "''")}'` : JSON.stringify(text);
871
- }
872
- /**
873
- * `sync-config.yaml`: edition 3 Sync Streams, one auto-subscribed stream per
874
- * synced resource.
875
- */
876
- function powersyncYaml(plan) {
877
- const lines = [
878
- "# Generated by permdock powersync generate from the policy. Do not edit.",
879
- ...plan.publication === void 0 ? [] : ["# Tables outside public must be in the powersync publication:", `# ${plan.publication}`],
880
- "config:",
881
- " edition: 3",
882
- "",
883
- "streams:"
884
- ];
885
- if (plan.streams.length === 0 && plan.subject.length === 0) lines[lines.length - 1] = "streams: {}";
886
- for (const stream of [...plan.streams, ...plan.subject]) lines.push(` ${stream.name}:`, " auto_subscribe: true", " queries:", ...stream.queries.map((query) => ` - ${yamlString(query)}`));
887
- for (const item of plan.omitted) lines.push(`# ${item.resource}: not synced, ${item.reason}`);
888
- return `${lines.join("\n")}\n`;
889
- }
890
1009
  //#endregion
891
1010
  //#region src/cli/powersync/verify-gaps.ts
892
1011
  function heldRoles(subject) {
@@ -911,6 +1030,37 @@ function warnedFor(unsynced, permission, subject, customRoles) {
911
1030
  });
912
1031
  }
913
1032
  //#endregion
1033
+ //#region src/cli/powersync/yaml.ts
1034
+ /**
1035
+ * A YAML scalar quoted as oxfmt and Prettier write it: single quotes around a
1036
+ * `"`, unless a backslash needs a double-quoted escape.
1037
+ */
1038
+ function yamlString(text) {
1039
+ return text.includes("\"") && !text.includes("\\") ? `'${text.replaceAll("'", "''")}'` : JSON.stringify(text);
1040
+ }
1041
+ /**
1042
+ * `sync-config.yaml`: edition 3 Sync Streams, one auto-subscribed stream per
1043
+ * synced resource.
1044
+ */
1045
+ function powersyncYaml(plan) {
1046
+ const lines = [
1047
+ "# Generated by permdock powersync generate from the policy. Do not edit.",
1048
+ ...plan.publication === void 0 ? [] : ["# Tables outside public must be in the powersync publication:", `# ${plan.publication}`],
1049
+ "config:",
1050
+ " edition: 3",
1051
+ "",
1052
+ "streams:"
1053
+ ];
1054
+ if (plan.streams.length === 0 && plan.subject.length === 0 && plan.tables.length === 0) lines[lines.length - 1] = "streams: {}";
1055
+ for (const stream of [
1056
+ ...plan.streams,
1057
+ ...plan.subject,
1058
+ ...plan.tables
1059
+ ]) lines.push(` ${stream.name}:`, " auto_subscribe: true", " queries:", ...stream.queries.map((query) => ` - ${yamlString(query)}`));
1060
+ for (const item of plan.omitted) lines.push(`# ${item.resource}: not synced, ${item.reason}`);
1061
+ return `${lines.join("\n")}\n`;
1062
+ }
1063
+ //#endregion
914
1064
  //#region src/cli/powersync/powersync.ts
915
1065
  const POWERSYNC_HELP = `permdock powersync generate [--out sync-config.yaml] [--check]
916
1066
  permdock powersync verify [--db-url-stdin] [--fixtures rls.fixtures.json]`;
@@ -997,6 +1147,7 @@ async function synced(query, stream, fixture) {
997
1147
  }
998
1148
  async function verifyStreams(input) {
999
1149
  const plan = powersyncPlan(input.policy, input.config, POSTGRES);
1150
+ const inherited = new Set(collectGrants(input.policy).filter((item) => hasConditionOp(item.where, "related")).map((item) => item.grant.permission.key));
1000
1151
  const mismatches = [];
1001
1152
  const notes = [];
1002
1153
  const client = await input.connect(input.db);
@@ -1007,7 +1158,7 @@ async function verifyStreams(input) {
1007
1158
  for (const fixture of input.fixtures) {
1008
1159
  if (checked.has(fixture.subject.id)) continue;
1009
1160
  checked.add(fixture.subject.id);
1010
- for (const stream of plan.subject) if (stream.user !== void 0 && await leaks(query, stream, stream.user, fixture)) mismatches.push(`stream ${stream.name} syncs another user's ${stream.table} rows to ${fixture.subject.id}`);
1161
+ for (const stream of [...plan.subject, ...plan.tables]) if (stream.user !== void 0 && await leaks(query, stream, stream.user, fixture)) mismatches.push(`stream ${stream.name} syncs another user's ${stream.table} rows to ${fixture.subject.id}`);
1011
1162
  }
1012
1163
  for (const fixture of input.fixtures) {
1013
1164
  const permission = findPermission(input.policy.permissions, fixture.action);
@@ -1021,6 +1172,7 @@ async function verifyStreams(input) {
1021
1172
  const granted = canFixture(permdock, permission, fixtureRow(fixture, permission.kind));
1022
1173
  const holds = await synced(query, stream, fixture);
1023
1174
  if (holds === void 0) notes.push(`${fixture.action} for ${fixture.subject.id}: not checked against stream ${stream.name}: the fixture row has no ${stream.id}`);
1175
+ else if (holds && !granted && inherited.has(permission.key)) notes.push(`${fixture.action} for ${fixture.subject.id}: stream ${stream.name} syncs the row through inherit(), which verify does not check: can() here has no relation source`);
1024
1176
  else if (holds && !granted) mismatches.push(`${fixture.action} for ${fixture.subject.id}: stream ${stream.name} syncs a row the policy denies`);
1025
1177
  else if (granted && !holds && warnedFor(plan.unsynced, permission.key, fixture.subject, input.customRoles)) notes.push(`${fixture.action} for ${fixture.subject.id}: granted, not synced (the app reads it from the server)`);
1026
1178
  else if (granted && !holds) mismatches.push(`${fixture.action} for ${fixture.subject.id}: granted, but stream ${stream.name} does not sync the row and generate gave no warning for it`);
@@ -5,9 +5,9 @@ import { s as writeFileEnsuringDir } from "./files-BSjrtIHw.js";
5
5
  import { s as cliVersion } from "./version-DMQ0dnN5.js";
6
6
  import { o as formatCatalogJson } from "./catalog-doc-DjMKzJp9.js";
7
7
  import { t as catalogPath } from "./catalog-path-CnI0XTFK.js";
8
- import { n as parseSupabaseManifest } from "./manifest-wg8WkkpD.js";
8
+ import { n as parseSupabaseManifest } from "./manifest-BXTbHNIV.js";
9
9
  import { t as parseCatalog } from "./parse-1_ZheTW5.js";
10
- import { a as nonEmptyOr } from "./output-DV5dwOTs.js";
10
+ import { a as nonEmptyOr } from "./output-CtyBwTNB.js";
11
11
  import { t as POLICY_NOT_PUBLISHED } from "./problem-types-DSsRfONQ.js";
12
12
  import { n as httpRequest, t as authFailure } from "./http-DOL33cO0.js";
13
13
  import { l as cloudUrl, n as cliRuntime, o as readAuth } from "./auth--yPyZNgr.js";
@@ -0,0 +1 @@
1
+ export { n as runCloudPull } from "./pull-CAeaAioC.js";
@@ -4,7 +4,7 @@ import { t as catalogFingerprint } from "./catalog-fingerprint-e6aCWosn.js";
4
4
  import { a as describeError, n as CliError } from "./errors-Vpu6rKHl.js";
5
5
  import { s as cliVersion } from "./version-DMQ0dnN5.js";
6
6
  import { i as loadConfiguredPolicy } from "./load-DjSU5KXO.js";
7
- import { n as parseSupabaseManifest } from "./manifest-wg8WkkpD.js";
7
+ import { n as parseSupabaseManifest } from "./manifest-BXTbHNIV.js";
8
8
  import { t as parseCatalog } from "./parse-1_ZheTW5.js";
9
9
  import { r as pushedPolicy } from "./pushed-policy-CDuOL2de.js";
10
10
  import { n as runCatalog } from "./catalog-CGlSwU6j.js";
@@ -3,10 +3,10 @@ import { c as listPermissions } from "./permission-registry-B8tYxPIt.js";
3
3
  import { f as scopeList } from "./scopes-0Up_UA2t.js";
4
4
  import { i as configError } from "./errors-Vpu6rKHl.js";
5
5
  import "./budget-BELja5A9.js";
6
- import "./sources-CMAM-2A3.js";
7
- import { A as quoteQualified, D as qualified, I as scopeTypeOf, O as quoteIdent } from "./shared-Afctg7B0.js";
8
- import { F as globalRoleRows, Q as graphSqlName, Z as graphHelper, _ as PERMISSION_HELPERS, at as memberForHelper, c as holderSources, ct as memberIdsHelper, dt as permittedForHelper, ft as permittedIdsHelper, ht as scopeTable, lt as memberOfHelper, nt as inheritedRowsHelper, o as graphPlan, pt as restrictedHelper, r as tableNames, rt as isTeamScope, tt as hasPermittedUsers, v as checkName, x as permittedByPermissionHelper } from "./tables-BqrxBCir.js";
9
- import { t as FIELD_VIEWS } from "./fields-D0vUI6dq.js";
6
+ import "./sources-C4vkzZJV.js";
7
+ import { A as quoteQualified, D as qualified, I as scopeTypeOf, O as quoteIdent } from "./shared-DrGLwM3s.js";
8
+ import { F as globalRoleRows, Q as graphSqlName, Z as graphHelper, _ as PERMISSION_HELPERS, at as memberForHelper, c as holderSources, ct as memberIdsHelper, dt as permittedForHelper, ft as permittedIdsHelper, ht as scopeTable, lt as memberOfHelper, nt as inheritedRowsHelper, o as graphPlan, pt as restrictedHelper, r as tableNames, rt as isTeamScope, tt as hasPermittedUsers, v as checkName, x as permittedByPermissionHelper } from "./tables-DimBcVWY.js";
9
+ import { t as FIELD_VIEWS } from "./fields-DI8s3cPl.js";
10
10
  //#region src/cli/rls/permission-holders.ts
11
11
  /**
12
12
  * `permitted_<scope>_users(p_permission, p_id)`. Names are part of the SQL
@@ -192,7 +192,7 @@ async function rlsInput(ctx, verb, shape, options) {
192
192
  };
193
193
  }
194
194
  async function runRls(input) {
195
- return (await import("./rls-Cx0dtsB7.js")).runRls(input);
195
+ return (await import("./rls-CtRaRrn3.js")).runRls(input);
196
196
  }
197
197
  const generate = defineLeaf({
198
198
  flags: {
@@ -7,26 +7,26 @@ import { t as policyRowConditionKeys } from "./row-conditions-BAGHOmVq.js";
7
7
  import { o as sqlFiles } from "./files-BSjrtIHw.js";
8
8
  import { c as requirePolicy } from "./load-DjSU5KXO.js";
9
9
  import { i as qualifySqlTable, r as escapeSqlIdent } from "./sql-0WjxIp9g.js";
10
- import "./sources-CMAM-2A3.js";
10
+ import "./sources-C4vkzZJV.js";
11
11
  import { t as policyFromCatalog } from "./policy-from-catalog-CU3An8uP.js";
12
12
  import { t as beforeFirst } from "./text-BMFRfBqc.js";
13
13
  import { i as INDEXES_MARKER, n as GRANTS_MARKER, o as SEEDS_MARKER, t as BACKFILL_MARKER } from "./markers-BeHZ4x9Q.js";
14
- import { a as nonEmptyOr } from "./output-DV5dwOTs.js";
14
+ import { a as nonEmptyOr } from "./output-CtyBwTNB.js";
15
15
  import { i as requirePeer, n as packageManagerOf, t as addLine } from "./peer-Q0fuZ6hx.js";
16
16
  import { t as POLICY_NOT_PUBLISHED } from "./problem-types-DSsRfONQ.js";
17
- import { t as fetchProjectPolicy } from "./pull-2nJr0qg_.js";
17
+ import { t as fetchProjectPolicy } from "./pull-CAeaAioC.js";
18
18
  import { n as MIGRATION_DIRS } from "./constants-DcJABbLW.js";
19
19
  import { a as isSeedsDirectory, c as parseSplit, d as seedsMigration, f as writeOutputFiles, l as partPath, n as dataMigrationFile, r as driftOf, u as pgDeltaPath } from "./output-files-BIQ_9pIs.js";
20
- import { d as schemasRefusal, f as declarativeSql, l as isSupabaseSchemas, o as supabaseHookSql, s as approvalsSql, t as grantsLabel, u as resolveSchemas, v as parseRbacAuthorize, y as rbacScaffold } from "./supabase-hook-CrgU06WH.js";
21
- import { A as quoteQualified, D as qualified$1, I as scopeTypeOf, O as quoteIdent$1, _ as subjectClaimJsonSql, c as SESSION_LIVE_HELPER, g as sessionLiveHelperSql, j as quoteTable, k as quoteLiteral, r as HELPERS, t as CUSTOM_ROLES, y as subjectIdSql } from "./shared-Afctg7B0.js";
22
- import { C as accessSql, G as byKey, T as seedSql, Z as graphHelper, ct as memberIdsHelper, ft as permittedIdsHelper, ht as scopeTable, mt as scopeSources, r as tableNames, s as graphSql, w as helpersSql, x as permittedByPermissionHelper, y as permissionHelpersSql } from "./tables-BqrxBCir.js";
23
- import { c as breakGlassKey, i as assignmentSql, l as collectGrants, n as ownershipSql, s as breakGlassHolder } from "./ownership-DA5YdYTm.js";
20
+ import { b as rbacScaffold, d as resolveSchemas, f as schemasRefusal, o as supabaseHookSql, p as declarativeSql, s as approvalsSql, t as grantsLabel, u as isSupabaseSchemas, y as parseRbacAuthorize } from "./supabase-hook-CyuD9Wau.js";
21
+ import { A as quoteQualified, D as qualified$1, I as scopeTypeOf, O as quoteIdent$1, _ as subjectClaimJsonSql, c as SESSION_LIVE_HELPER, g as sessionLiveHelperSql, j as quoteTable, k as quoteLiteral, r as HELPERS, t as CUSTOM_ROLES, y as subjectIdSql } from "./shared-DrGLwM3s.js";
22
+ import { C as accessSql, G as byKey, T as seedSql, Z as graphHelper, ct as memberIdsHelper, ft as permittedIdsHelper, ht as scopeTable, mt as scopeSources, r as tableNames, s as graphSql, w as helpersSql, x as permittedByPermissionHelper, y as permissionHelpersSql } from "./tables-DimBcVWY.js";
23
+ import { c as breakGlassKey, i as assignmentSql, l as collectGrants, n as ownershipSql, s as breakGlassHolder } from "./ownership-qqBuGJXr.js";
24
24
  import { a as splitUndiffed } from "./deciding-columns-NB4wcyBf.js";
25
25
  import { t as connectPgPool } from "./pg-DQTqRrou.js";
26
- import { n as permissionHoldersSql } from "./resolve-names-ConCSj1y.js";
27
- import { a as fieldViewsSql, c as ACTOR_MAY_WRITE, f as readOnlyActorKinds, i as fieldViews, l as actorMayWriteSql, n as columnGrantSql, o as rowBranches, p as readOnlyActorPolicies, r as columnRevokeSql, u as assemblePolicies } from "./fields-D0vUI6dq.js";
28
- import { _ as indexTargets, a as verifyResult, b as readTableIndexFacts, c as callsHelper, g as INDEX_COLUMNS_SQL, h as COLUMNS_SQL, i as verifyIgnores, l as helperCallKeys, m as tableFor, n as runRlsVerify, o as HELPER_TABLES, p as compileGrants, r as rlsFinding, v as indexesSql, y as pruneIndexTargets } from "./verify-DlUcTDBz.js";
29
- import { a as buildContext, c as levelKeys, d as rebuildPlan, i as splitsPart, l as rowHelpersSql, n as generateOptions, o as CANDIDATES, r as isRefusal, s as inheritTargets, t as supautilsRejections, u as DROPPED_LIST } from "./supautils-ByezN3b8.js";
26
+ import { a as fieldViewsSql, c as ACTOR_MAY_WRITE, f as readOnlyActorKinds, i as fieldViews, l as actorMayWriteSql, n as columnGrantSql, o as rowBranches, p as readOnlyActorPolicies, r as columnRevokeSql, u as assemblePolicies } from "./fields-DI8s3cPl.js";
27
+ import { n as permissionHoldersSql } from "./resolve-names-Y1Cb22cp.js";
28
+ import { _ as indexTargets, a as verifyResult, b as readTableIndexFacts, c as callsHelper, g as INDEX_COLUMNS_SQL, h as COLUMNS_SQL, i as verifyIgnores, l as helperCallKeys, m as tableFor, n as runRlsVerify, o as HELPER_TABLES, p as compileGrants, r as rlsFinding, v as indexesSql, y as pruneIndexTargets } from "./verify-6EauWrkP.js";
29
+ import { a as buildContext, c as levelKeys, d as rebuildPlan, i as splitsPart, l as rowHelpersSql, n as generateOptions, o as CANDIDATES, r as isRefusal, s as inheritTargets, t as supautilsRejections, u as DROPPED_LIST } from "./supautils-C-_jqylY.js";
30
30
  import { readFileSync } from "node:fs";
31
31
  import { basename, delimiter, join } from "node:path";
32
32
  import { spawn } from "node:child_process";
@@ -1409,7 +1409,7 @@ function sectionsSql(input, policy, options, built, generated, jsonSchema, warni
1409
1409
  if (ctx.assignments !== void 0 && (ownership === void 0 || ownership.assigns.length === 0)) warnings.push("rls.assignments needs a role that declares assigns: no assignment trigger is written");
1410
1410
  let approvals;
1411
1411
  try {
1412
- approvals = approvalsSql(ctx, rls?.approvals, jsonSchema ?? false);
1412
+ approvals = approvalsSql(ctx, rls?.approvals, jsonSchema ?? false, readOnlyActorKinds(rls?.readOnlyActors) !== void 0);
1413
1413
  } catch (cause) {
1414
1414
  return { refusal: describeError(cause) };
1415
1415
  }
@@ -2232,6 +2232,13 @@ const WRITES = [
2232
2232
  "delete"
2233
2233
  ];
2234
2234
  /**
2235
+ * `rls generate` inlines the check in its `<table>_<command>_read_only_actors`
2236
+ * policies.
2237
+ */
2238
+ function inlinesActorCheck(expression) {
2239
+ return expression.includes("'act'") && expression.includes("'read_only'");
2240
+ }
2241
+ /**
2235
2242
  * Each table and write command whose permissive policies reach `authenticated`
2236
2243
  * (or `public`) without the read-only actor check: no restrictive policy for
2237
2244
  * the command calls `permdock_actor_may_write()`, and some permissive one does
@@ -2245,7 +2252,7 @@ function unguardedWrites(policies, fn) {
2245
2252
  const permissive = applying.filter((policy) => policy.restrictive !== true && (policy.roles ?? []).some((role) => role === "authenticated" || role === "public"));
2246
2253
  if (permissive.length === 0) continue;
2247
2254
  const calls = (expression) => expression.includes(fn);
2248
- if (!(applying.some((policy) => policy.restrictive === true && calls(policy.expression)) || permissive.every((policy) => calls(policy.expression)))) out.push(rlsFinding("write-unguarded", table, command, `${table}: ${command} policies for authenticated do not call ${fn}(), so a read-only support or impersonation session can write (rls.readOnlyActors)`));
2255
+ if (!(applying.some((policy) => policy.restrictive === true && (calls(policy.expression) || inlinesActorCheck(policy.expression))) || permissive.every((policy) => calls(policy.expression)))) out.push(rlsFinding("write-unguarded", table, command, `${table}: ${command} policies for authenticated do not call ${fn}(), so a read-only support or impersonation session can write (rls.readOnlyActors)`));
2249
2256
  }
2250
2257
  return out;
2251
2258
  }
@@ -2474,7 +2481,7 @@ async function migrate(input) {
2474
2481
  output: generated.output
2475
2482
  };
2476
2483
  await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls migrate", input.cwd, PGSQL_PARSER_MAJOR);
2477
- const { migrateTarget, runRlsMigrate } = await import("./migrate-B7oP_sQU.js");
2484
+ const { migrateTarget, runRlsMigrate } = await import("./migrate-BJHxQqT5.js");
2478
2485
  return runRlsMigrate({
2479
2486
  cwd: input.cwd,
2480
2487
  sql: input.sql,
@@ -2590,7 +2597,7 @@ async function runRls(input) {
2590
2597
  }
2591
2598
  case "import": {
2592
2599
  await requirePeer(() => import("pgsql-parser"), "pgsql-parser", "permdock rls import", input.cwd, PGSQL_PARSER_MAJOR);
2593
- const { runRlsImport } = await import("./import-CST57XLp.js");
2600
+ const { runRlsImport } = await import("./import-CNBF96hB.js");
2594
2601
  return runRlsImport({
2595
2602
  cwd: input.cwd,
2596
2603
  config: input.config,
@@ -0,0 +1 @@
1
+ export { t as run } from "./run-B5nyNKe8.js";
@@ -1,13 +1,13 @@
1
1
  import { i as dateAt } from "./time-nR1JesoH.js";
2
2
  import { a as describeError, l as problemOf, n as CliError, r as cliProblem, s as kindOfCode } from "./errors-Vpu6rKHl.js";
3
3
  import { s as cliVersion } from "./version-DMQ0dnN5.js";
4
- import { a as resolveCwd, i as readConfig } from "./config-BwPBfN0t.js";
4
+ import { a as resolveCwd, i as readConfig } from "./config-BR23fL8m.js";
5
5
  import { t as beforeFirst } from "./text-BMFRfBqc.js";
6
6
  import { a as suggestion, i as parseLeaf, n as flagSpellings, o as GLOBAL_FLAGS, r as parseFlags } from "./args-0FrVs5EH.js";
7
7
  import { r as progressReporter, t as redact } from "./credentials-CdDxp7jN.js";
8
- import { a as resolve, i as leaves, n as ROOT, r as SECTIONS, t as DOCS_BASE } from "./manifest-B2QPh19C.js";
8
+ import { a as resolve, i as leaves, n as ROOT, r as SECTIONS, t as DOCS_BASE } from "./manifest-Cav1rjNQ.js";
9
9
  import { t as createStyle } from "./style-Ds11Hmk4.js";
10
- import { i as githubAnnotations, n as envelope, o as sarifLog, t as dataOf } from "./output-DV5dwOTs.js";
10
+ import { i as githubAnnotations, n as envelope, o as sarifLog, t as dataOf } from "./output-CtyBwTNB.js";
11
11
  import { i as renameMessage, r as flagRename, t as COMMAND_MOVES } from "./renames-CGj7YwcQ.js";
12
12
  import { stripVTControlCharacters } from "node:util";
13
13
  import { existsSync, statSync } from "node:fs";
@@ -26,7 +26,8 @@ const RUN_TICKET_CLAIMS = Object.freeze([
26
26
  "automation",
27
27
  "tenant",
28
28
  "trigger",
29
- "binding"
29
+ "binding",
30
+ "resource"
30
31
  ]);
31
32
  const RUN_KINDS = [
32
33
  "automation",
@@ -97,6 +98,14 @@ function parseAutomation(value) {
97
98
  version: record["version"]
98
99
  };
99
100
  }
101
+ function parseResource(value) {
102
+ const record = ownRecord(value);
103
+ if (record === void 0 || !isId(record["type"]) || !isId(record["id"])) return;
104
+ return {
105
+ type: record["type"],
106
+ id: record["id"]
107
+ };
108
+ }
100
109
  function parseBinding(value) {
101
110
  const record = ownRecord(value);
102
111
  if (record === void 0) return;
@@ -140,7 +149,8 @@ function parseBody(record) {
140
149
  const automation = optional(record, "automation", parseAutomation);
141
150
  const trigger = optional(record, "trigger", parseTrigger);
142
151
  const binding = optional(record, "binding", parseBinding);
143
- if (!act.ok || !details.ok || !automation.ok || !trigger.ok || !binding.ok || scope === void 0 && details.value === void 0 || kind === "automation" !== (automation.value !== void 0)) return;
152
+ const resource = optional(record, "resource", parseResource);
153
+ if (!act.ok || !details.ok || !automation.ok || !trigger.ok || !binding.ok || !resource.ok || scope === void 0 && details.value === void 0 || kind === "automation" !== (automation.value !== void 0)) return;
144
154
  return compact({
145
155
  v: 1,
146
156
  sub,
@@ -151,7 +161,8 @@ function parseBody(record) {
151
161
  automation: automation.value,
152
162
  tenant,
153
163
  trigger: trigger.value,
154
- binding: binding.value
164
+ binding: binding.value,
165
+ resource: resource.value
155
166
  });
156
167
  }
157
168
  /**
@@ -218,9 +229,10 @@ function signRunTicket(input, signer, options = {}) {
218
229
  automation: input.automation,
219
230
  tenant: input.tenant,
220
231
  trigger: input.trigger,
221
- binding: input.binding
232
+ binding: input.binding,
233
+ resource: input.resource
222
234
  }));
223
- if (body === void 0) invalid("is invalid: check principal, kind, automation, delegation, trigger and binding");
235
+ if (body === void 0) invalid("is invalid: check principal, kind, automation, delegation, trigger, binding and resource");
224
236
  if (input.id !== void 0 && !isId(input.id)) invalid("id must be a non-empty string");
225
237
  return signer.sign(body, compact({
226
238
  typ: RUN_TICKET_TYP,
@@ -338,7 +350,8 @@ async function subjectFromRunTicket(token, options) {
338
350
  id: ticket.jti,
339
351
  kind: ticket.kind,
340
352
  automation: ticket.automation,
341
- binding: ticket.binding
353
+ binding: ticket.binding,
354
+ resource: ticket.resource
342
355
  });
343
356
  return Object.freeze(compact({
344
357
  principal: Object.freeze({
@@ -24,6 +24,14 @@ type RunAutomation = {
24
24
  readonly id: string;
25
25
  readonly version: number | string;
26
26
  };
27
+ /**
28
+ * The record a run is bound to. `authorization_details` carries the binding as
29
+ * `identifier`; this claim names it for the adapter and the audit trail.
30
+ */
31
+ type RunResource = {
32
+ readonly type: string;
33
+ readonly id: string;
34
+ };
27
35
  /**
28
36
  * The v1 claims of a `vnd.permdock.run+jwt`. At least one of `scope` and
29
37
  * `authorization_details` is present.
@@ -49,6 +57,7 @@ type RunTicket = {
49
57
  */
50
58
  readonly trigger?: Readonly<Record<string, unknown>>;
51
59
  readonly binding?: RunBinding;
60
+ readonly resource?: RunResource;
52
61
  };
53
62
  /**
54
63
  * Every v1 claim of a `vnd.permdock.run+jwt`, the set `parseRunTicket` keeps. A
@@ -70,6 +79,11 @@ type RunTicketInput = {
70
79
  readonly tenant?: string;
71
80
  readonly trigger?: Readonly<Record<string, unknown>>;
72
81
  readonly binding?: RunBinding;
82
+ /**
83
+ * The record the run is bound to. Narrow `delegation` to it with an
84
+ * `authorization_details` `identifier`; this claim grants nothing.
85
+ */
86
+ readonly resource?: RunResource;
73
87
  /** `jti`; a fresh UUID when absent. */
74
88
  readonly id?: string;
75
89
  /** Lifetime in whole seconds. Exactly one of `expiresIn` and `expiresAt`. */
@@ -113,6 +127,7 @@ type RunContext = {
113
127
  readonly kind: RunKind;
114
128
  readonly automation?: RunAutomation;
115
129
  readonly binding?: RunBinding;
130
+ readonly resource?: RunResource;
116
131
  };
117
132
  /** The principal of a run subject, with the verified ticket attached. */
118
133
  type RunPrincipal = Principal & {
@@ -141,4 +156,4 @@ declare function signRunTicket(input: RunTicketInput, signer: TokenSigner, optio
141
156
  */
142
157
  declare function subjectFromRunTicket(token: string | undefined | null, options: RunTicketSubjectOptions): Promise<Subject<RunPrincipal>>;
143
158
  //#endregion
144
- export { RunContext as a, RunTicket as c, RunTicketInput as d, RunTicketOptions as f, subjectFromRunTicket as g, signRunTicket as h, RunBinding as i, RunTicketAutomations as l, parseRunTicket as m, RunActClaim as n, RunKind as o, RunTicketSubjectOptions as p, RunAutomation as r, RunPrincipal as s, RUN_TICKET_CLAIMS as t, RunTicketFailureCause as u };
159
+ export { subjectFromRunTicket as _, RunContext as a, RunResource as c, RunTicketFailureCause as d, RunTicketInput as f, signRunTicket as g, parseRunTicket as h, RunBinding as i, RunTicket as l, RunTicketSubjectOptions as m, RunActClaim as n, RunKind as o, RunTicketOptions as p, RunAutomation as r, RunPrincipal as s, RUN_TICKET_CLAIMS as t, RunTicketAutomations as u };
@@ -16,7 +16,7 @@ const scan = defineLeaf({
16
16
  }
17
17
  },
18
18
  async run(ctx, { flags }) {
19
- const { runAdoptionScan } = await import("./adoption-scan-Ch07MdxN.js");
19
+ const { runAdoptionScan } = await import("./adoption-scan-DKCOsfcF.js");
20
20
  return runAdoptionScan({
21
21
  cwd: ctx.cwd,
22
22
  srcPath: flags.src.length > 0 ? flags.src : ctx.config.collect?.srcPath ?? ["src"],
@@ -1,7 +1,7 @@
1
1
  import { r as isRecord } from "./compact-CAf557ZB.js";
2
2
  import { l as packageRoot } from "./version-DMQ0dnN5.js";
3
3
  import { t as DRAFT_PINS } from "./pins-nqarl3oE.js";
4
- import { a as nonEmptyOr } from "./output-DV5dwOTs.js";
4
+ import { a as nonEmptyOr } from "./output-CtyBwTNB.js";
5
5
  import { t as require__2020 } from "./2020-CJbO8Bvz.js";
6
6
  import { readFileSync } from "node:fs";
7
7
  import { join } from "node:path";