@byollm/control-plane 0.1.0-alpha.60 → 0.1.0-alpha.61

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @byollm/control-plane
2
2
 
3
- > **Alpha (`0.1.0-alpha.60`) — under active development. Don't use this yet.**
3
+ > **Alpha (`0.1.0-alpha.61`) — under active development. Don't use this yet.**
4
4
 
5
5
  The reference control plane: it resolves a person's mapping and authors one
6
6
  signed grant per job, against a policy store it does not own and with a key it
@@ -61,6 +61,19 @@ interface PolicyStoreContractOptions {
61
61
  }) => Promise<void> | void;
62
62
  done: () => Promise<void>;
63
63
  }>;
64
+ /**
65
+ * Whether this store accepts arbitrary strings as ids.
66
+ *
67
+ * True for a store that keys by concatenation, where two ids differing only
68
+ * in where a separator falls is a real hazard and the case below is the
69
+ * point. False for one with typed columns — a Postgres `uuid` cannot hold
70
+ * `"b:c"`, so the confusion is structurally impossible and demanding the
71
+ * case would only stop that store running the contract at all.
72
+ *
73
+ * Named rather than skipped silently: an implementation that opts out is
74
+ * saying its ids are typed, which is a claim about its schema.
75
+ */
76
+ readonly opaqueIds?: boolean;
64
77
  }
65
78
  /** Run the contract. Call inside a suite; it declares its own `describe`. */
66
79
  declare function describePolicyStoreContract(name: string, options: PolicyStoreContractOptions): void;
@@ -1,14 +1,24 @@
1
1
  // src/store-contract.ts
2
2
  import { describe, expect, it } from "vitest";
3
- var SITE = "site_demo";
4
- var OWNER = "bob";
5
- var USER = "alice";
3
+ var SITE = "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e01";
4
+ var OWNER = "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e02";
5
+ var USER = "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e03";
6
+ var CAROL = "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e05";
6
7
  var MAPPING = {
7
8
  purpose: "writing_assistant",
8
9
  kind: "llm.generate",
9
10
  service: "qwen",
10
11
  owner: null
11
12
  };
13
+ var TEAMMATE_MAPPING = {
14
+ purpose: "writing_assistant",
15
+ kind: "llm.generate",
16
+ // Deliberately the same name as the mapper's own service above: a store
17
+ // keyed on the name alone would pass a case that used a distinct one.
18
+ service: "qwen",
19
+ // carol — a second teammate, whose qwen is not bob's.
20
+ owner: "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e04"
21
+ };
12
22
  function describePolicyStoreContract(name, options) {
13
23
  describe(`the policy store \u2014 ${name}`, () => {
14
24
  it("says a site nobody authorised is not consented", async () => {
@@ -34,6 +44,46 @@ function describePolicyStoreContract(name, options) {
34
44
  expect([...snapshot.mappings]).toEqual([MAPPING]);
35
45
  await s.done();
36
46
  });
47
+ it("round-trips whose service a mapping names", async () => {
48
+ const s = await options.make();
49
+ await s.consent({
50
+ siteId: SITE,
51
+ user: USER,
52
+ mappings: [TEAMMATE_MAPPING]
53
+ });
54
+ await s.addMember({ owner: OWNER, user: USER });
55
+ const snapshot = await s.store.read({
56
+ siteId: SITE,
57
+ user: USER,
58
+ owner: OWNER
59
+ });
60
+ expect(snapshot.mappings).toEqual([TEAMMATE_MAPPING]);
61
+ expect(snapshot.mappings[0]?.owner).toBe(TEAMMATE_MAPPING.owner);
62
+ await s.done();
63
+ });
64
+ it("keeps two same-named services apart by whose they are", async () => {
65
+ const s = await options.make();
66
+ await s.consent({
67
+ siteId: SITE,
68
+ user: USER,
69
+ mappings: [
70
+ { ...MAPPING, purpose: "mine" },
71
+ { ...TEAMMATE_MAPPING, purpose: "theirs" }
72
+ ]
73
+ });
74
+ await s.addMember({ owner: OWNER, user: USER });
75
+ const snapshot = await s.store.read({
76
+ siteId: SITE,
77
+ user: USER,
78
+ owner: OWNER
79
+ });
80
+ const byPurpose = new Map(
81
+ snapshot.mappings.map((m) => [m.purpose, m.owner])
82
+ );
83
+ expect(byPurpose.get("mine")).toBeNull();
84
+ expect(byPurpose.get("theirs")).toBe(TEAMMATE_MAPPING.owner);
85
+ await s.done();
86
+ });
37
87
  it("distinguishes consented-and-unmapped from never-consented", async () => {
38
88
  const s = await options.make();
39
89
  await s.consent({ siteId: SITE, user: USER, mappings: [] });
@@ -80,7 +130,7 @@ function describePolicyStoreContract(name, options) {
80
130
  await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });
81
131
  const other = await s.store.read({
82
132
  siteId: SITE,
83
- user: "carol",
133
+ user: CAROL,
84
134
  owner: OWNER
85
135
  });
86
136
  expect(other.consented).toBe("no");
@@ -108,7 +158,8 @@ function describePolicyStoreContract(name, options) {
108
158
  const theirs = await s.store.read({
109
159
  siteId: SITE,
110
160
  user: USER,
111
- owner: "carol"
161
+ // carol — a second teammate, whose qwen is not bob's.
162
+ owner: "5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e04"
112
163
  });
113
164
  expect(mine.member).toBe(true);
114
165
  expect(theirs.member).toBe(false);
@@ -136,27 +187,30 @@ function describePolicyStoreContract(name, options) {
136
187
  });
137
188
  expect(noConsent.member).toBe(true);
138
189
  expect(noConsent.consented).toBe("no");
139
- await s.consent({ siteId: SITE, user: "carol", mappings: [MAPPING] });
190
+ await s.consent({ siteId: SITE, user: CAROL, mappings: [MAPPING] });
140
191
  const noMembership = await s.store.read({
141
192
  siteId: SITE,
142
- user: "carol",
193
+ user: CAROL,
143
194
  owner: OWNER
144
195
  });
145
196
  expect(noMembership.consented).toBe("yes");
146
197
  expect(noMembership.member).toBe(false);
147
198
  await s.done();
148
199
  });
149
- it("does not let a site id and a user id be confused for each other", async () => {
150
- const s = await options.make();
151
- await s.consent({ siteId: "a", user: "b:c", mappings: [MAPPING] });
152
- const confusable = await s.store.read({
153
- siteId: "a:b",
154
- user: "c",
155
- owner: OWNER
156
- });
157
- expect(confusable.consented).toBe("no");
158
- await s.done();
159
- });
200
+ it.runIf(options.opaqueIds !== false)(
201
+ "does not let a site id and a user id be confused for each other",
202
+ async () => {
203
+ const s = await options.make();
204
+ await s.consent({ siteId: "a", user: "b:c", mappings: [MAPPING] });
205
+ const confusable = await s.store.read({
206
+ siteId: "a:b",
207
+ user: "c",
208
+ owner: OWNER
209
+ });
210
+ expect(confusable.consented).toBe("no");
211
+ await s.done();
212
+ }
213
+ );
160
214
  });
161
215
  }
162
216
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store-contract.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { Mapping, PolicyStore } from \"./store.js\";\n\n/**\n * The policy store's behaviour, written once and run against every\n * implementation.\n *\n * The same reasoning as `@byollm/relay/store-contract`, and more of it. The\n * implementation that matters most is byollm.cloud's, in another repository\n * and not open — so this is the only way a reader can know that the hosted\n * product's store answers the same questions the same way the reference one\n * does. A contract only its author can run is a description.\n *\n * ## What a store has to do to be tested\n *\n * `PolicyStore` has one method, and it reads. Everything that *writes* —\n * consenting, mapping, adding somebody to a team — is the deployment's own\n * surface, and no two will agree on it. So the contract takes a small harness\n * that performs those acts however the implementation performs them, and\n * asserts only what `read` must then answer.\n *\n * That is the honest boundary: this proves the store's *answers*, not its\n * admin API. A store that passes has the semantics the engine relies on.\n */\nexport interface PolicyStoreContractOptions {\n /**\n * A fresh, empty store, its write surface, and how to dispose of it.\n *\n * Arrow-typed rather than methods, for the reason the routing store's\n * contract gives: a method signature lets `this` travel with a call read\n * off an options object, which is exactly where that goes wrong.\n */\n readonly make: () => Promise<{\n store: PolicyStore;\n consent: (input: {\n siteId: string;\n user: string;\n mappings?: readonly Mapping[];\n }) => Promise<void> | void;\n revoke: (input: { siteId: string; user: string }) => Promise<void> | void;\n /**\n * Pause a consent without withdrawing it.\n *\n * Optional because not every deployment offers pausing. A store that does\n * not is not asked to prove it — but one that does must prove a paused\n * consent reads as not-consented, which is the case below.\n */\n pause?: (input: { siteId: string; user: string }) => Promise<void> | void;\n addMember: (input: { owner: string; user: string }) => Promise<void> | void;\n removeMember: (input: {\n owner: string;\n user: string;\n }) => Promise<void> | void;\n done: () => Promise<void>;\n }>;\n}\n\nconst SITE = \"site_demo\";\nconst OWNER = \"bob\";\nconst USER = \"alice\";\nconst MAPPING: Mapping = {\n purpose: \"writing_assistant\",\n kind: \"llm.generate\",\n service: \"qwen\",\n owner: null,\n};\n\n/** Run the contract. Call inside a suite; it declares its own `describe`. */\nexport function describePolicyStoreContract(\n name: string,\n options: PolicyStoreContractOptions,\n): void {\n describe(`the policy store — ${name}`, () => {\n it(\"says a site nobody authorised is not consented\", async () => {\n // The ordinary answer, not an error. A person who has never heard of a\n // site is the common case, and a store that threw here would make the\n // engine treat \"no\" as \"broken\".\n const { store, done } = await options.make();\n const snapshot = await store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"no\");\n expect(snapshot.mappings).toEqual([]);\n await done();\n });\n\n it(\"returns the mapping the consent carried\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"yes\");\n expect([...snapshot.mappings]).toEqual([MAPPING]);\n await s.done();\n });\n\n it(\"distinguishes consented-and-unmapped from never-consented\", async () => {\n // A real signup state: every slot had two candidates, so nothing\n // auto-mapped and the person has not chosen. It is not the same as\n // never having authorised the site, and it sends them somewhere else.\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [] });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"yes\");\n expect(snapshot.mappings).toEqual([]);\n await s.done();\n });\n\n it(\"deletes the mapping when consent is withdrawn\", async () => {\n // Hole 2: the mapping *is* the consent, so un-consenting unmaps. A\n // store that kept the mapping would hold a resolution pointing at a\n // service its author no longer authorises anybody to reach.\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n await s.revoke({ siteId: SITE, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"no\");\n expect(snapshot.mappings).toEqual([]);\n await s.done();\n });\n\n it(\"reads a paused consent as not consented\", async () => {\n // Three states, one boolean, on purpose: never authorised, revoked and\n // paused are different to a person and identical to a grant. A relay's\n // projection can lag by seconds, so a store that reported a paused\n // consent as live would let a grant be authored for work its owner had\n // just stopped.\n const s = await options.make();\n if (!s.pause) {\n await s.done();\n return;\n }\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n await s.pause({ siteId: SITE, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"paused\");\n\n /**\n * `\"paused\"`, not `\"no\"` — byollm-review 2026-08-27.\n *\n * The distinction is the whole reason this stopped being a boolean. A\n * store reporting a pause as \"no\" is not merely imprecise: the engine\n * declines `not-consented`, which is **permanent**, and the relay never\n * offers that job to that device again. The hosted product pauses a\n * consent the moment its author joins a team, so this is an everyday\n * path, and re-consenting minutes later did not bring the work back.\n *\n * A pause is somebody's own to lift, so the job has to still be there\n * when they do.\n */\n await s.done();\n });\n\n it(\"keeps one person's consent out of another's\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const other = await s.store.read({\n siteId: SITE,\n user: \"carol\",\n owner: OWNER,\n });\n expect(other.consented).toBe(\"no\");\n await s.done();\n });\n\n it(\"keeps one site's consent out of another's\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const other = await s.store.read({\n siteId: \"site_other\",\n user: USER,\n owner: OWNER,\n });\n expect(other.consented).toBe(\"no\");\n await s.done();\n });\n\n it(\"reports membership per owner, not per person\", async () => {\n // A team is one owner's, and being on one confers nothing about\n // anybody else's devices. A store that kept a global \"alice is a team\n // member\" flag would share every owner's hardware with her.\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n const mine = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n const theirs = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: \"carol\",\n });\n expect(mine.member).toBe(true);\n expect(theirs.member).toBe(false);\n await s.done();\n });\n\n it(\"stops reporting membership once somebody is removed\", async () => {\n // Removal is the whole of it — there is no blocked-but-still-a-member,\n // because a team *is* access to its owner's devices (Amendment J).\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n await s.removeMember({ owner: OWNER, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.member).toBe(false);\n await s.done();\n });\n\n it(\"keeps membership and consent independent\", async () => {\n // Two different questions, and both have to be yes. A store that\n // conflated them would let a team member's work run on a site they\n // never authorised, or refuse a person their own site because nobody\n // had added them to a team.\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n const noConsent = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(noConsent.member).toBe(true);\n expect(noConsent.consented).toBe(\"no\");\n\n await s.consent({ siteId: SITE, user: \"carol\", mappings: [MAPPING] });\n const noMembership = await s.store.read({\n siteId: SITE,\n user: \"carol\",\n owner: OWNER,\n });\n expect(noMembership.consented).toBe(\"yes\");\n expect(noMembership.member).toBe(false);\n await s.done();\n });\n\n it(\"does not let a site id and a user id be confused for each other\", async () => {\n // Composite keys are parsers waiting to meet an id containing their\n // separator. These two consents differ only in where the boundary\n // falls, and a store that joined them naively would answer one for the\n // other.\n const s = await options.make();\n await s.consent({ siteId: \"a\", user: \"b:c\", mappings: [MAPPING] });\n const confusable = await s.store.read({\n siteId: \"a:b\",\n user: \"c\",\n owner: OWNER,\n });\n expect(confusable.consented).toBe(\"no\");\n await s.done();\n });\n });\n}\n"],"mappings":";AAAA,SAAS,UAAU,QAAQ,UAAU;AAyDrC,IAAM,OAAO;AACb,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,UAAmB;AAAA,EACvB,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AACT;AAGO,SAAS,4BACd,MACA,SACM;AACN,WAAS,2BAAsB,IAAI,IAAI,MAAM;AAC3C,OAAG,kDAAkD,YAAY;AAI/D,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,QAAQ,KAAK;AAC3C,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,IAAI;AACpC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,2CAA2C,YAAY;AACxD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,KAAK;AACrC,aAAO,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;AAChD,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,6DAA6D,YAAY;AAI1E,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,EAAE,CAAC;AAC1D,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,KAAK;AACrC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,iDAAiD,YAAY;AAI9D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,EAAE,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC3C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,IAAI;AACpC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,2CAA2C,YAAY;AAMxD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,UAAI,CAAC,EAAE,OAAO;AACZ,cAAM,EAAE,KAAK;AACb;AAAA,MACF;AACA,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,EAAE,MAAM,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC1C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,QAAQ;AAexC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,+CAA+C,YAAY;AAC5D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,QAAQ,MAAM,EAAE,MAAM,KAAK;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,MAAM,SAAS,EAAE,KAAK,IAAI;AACjC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,6CAA6C,YAAY;AAC1D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,QAAQ,MAAM,EAAE,MAAM,KAAK;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,MAAM,SAAS,EAAE,KAAK,IAAI;AACjC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,gDAAgD,YAAY;AAI7D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,OAAO,MAAM,EAAE,MAAM,KAAK;AAAA,QAC9B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,YAAM,SAAS,MAAM,EAAE,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,KAAK,MAAM,EAAE,KAAK,IAAI;AAC7B,aAAO,OAAO,MAAM,EAAE,KAAK,KAAK;AAChC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,uDAAuD,YAAY;AAGpE,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,EAAE,aAAa,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AACjD,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,MAAM,EAAE,KAAK,KAAK;AAClC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,4CAA4C,YAAY;AAKzD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,YAAY,MAAM,EAAE,MAAM,KAAK;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,UAAU,MAAM,EAAE,KAAK,IAAI;AAClC,aAAO,UAAU,SAAS,EAAE,KAAK,IAAI;AAErC,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,SAAS,UAAU,CAAC,OAAO,EAAE,CAAC;AACpE,YAAM,eAAe,MAAM,EAAE,MAAM,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,aAAa,SAAS,EAAE,KAAK,KAAK;AACzC,aAAO,aAAa,MAAM,EAAE,KAAK,KAAK;AACtC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,mEAAmE,YAAY;AAKhF,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,OAAO,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,aAAa,MAAM,EAAE,MAAM,KAAK;AAAA,QACpC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,WAAW,SAAS,EAAE,KAAK,IAAI;AACtC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/store-contract.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport type { Mapping, PolicyStore } from \"./store.js\";\n\n/**\n * The policy store's behaviour, written once and run against every\n * implementation.\n *\n * The same reasoning as `@byollm/relay/store-contract`, and more of it. The\n * implementation that matters most is byollm.cloud's, in another repository\n * and not open — so this is the only way a reader can know that the hosted\n * product's store answers the same questions the same way the reference one\n * does. A contract only its author can run is a description.\n *\n * ## What a store has to do to be tested\n *\n * `PolicyStore` has one method, and it reads. Everything that *writes* —\n * consenting, mapping, adding somebody to a team — is the deployment's own\n * surface, and no two will agree on it. So the contract takes a small harness\n * that performs those acts however the implementation performs them, and\n * asserts only what `read` must then answer.\n *\n * That is the honest boundary: this proves the store's *answers*, not its\n * admin API. A store that passes has the semantics the engine relies on.\n */\nexport interface PolicyStoreContractOptions {\n /**\n * A fresh, empty store, its write surface, and how to dispose of it.\n *\n * Arrow-typed rather than methods, for the reason the routing store's\n * contract gives: a method signature lets `this` travel with a call read\n * off an options object, which is exactly where that goes wrong.\n */\n readonly make: () => Promise<{\n store: PolicyStore;\n consent: (input: {\n siteId: string;\n user: string;\n mappings?: readonly Mapping[];\n }) => Promise<void> | void;\n revoke: (input: { siteId: string; user: string }) => Promise<void> | void;\n /**\n * Pause a consent without withdrawing it.\n *\n * Optional because not every deployment offers pausing. A store that does\n * not is not asked to prove it — but one that does must prove a paused\n * consent reads as not-consented, which is the case below.\n */\n pause?: (input: { siteId: string; user: string }) => Promise<void> | void;\n addMember: (input: { owner: string; user: string }) => Promise<void> | void;\n removeMember: (input: {\n owner: string;\n user: string;\n }) => Promise<void> | void;\n done: () => Promise<void>;\n }>;\n /**\n * Whether this store accepts arbitrary strings as ids.\n *\n * True for a store that keys by concatenation, where two ids differing only\n * in where a separator falls is a real hazard and the case below is the\n * point. False for one with typed columns — a Postgres `uuid` cannot hold\n * `\"b:c\"`, so the confusion is structurally impossible and demanding the\n * case would only stop that store running the contract at all.\n *\n * Named rather than skipped silently: an implementation that opts out is\n * saying its ids are typed, which is a claim about its schema.\n */\n readonly opaqueIds?: boolean;\n}\n\n/**\n * Ids as **uuids**, so a real control plane can run this — byollm-review\n * 2026-08-27.\n *\n * These were `\"site_demo\"`, `\"bob\"` and `\"alice\"`: fine for a store that\n * treats an id as an opaque string, and rejected outright by the Postgres\n * `uuid` columns of the implementation this contract exists for. A suite the\n * hosted store could not execute is the \"contract only its author can run\"\n * this file's own header calls a description rather than a contract.\n *\n * A uuid is an opaque string too, so nothing is lost for stores that do not\n * care — and the names stay in the comments, where they are for people.\n */\nconst SITE = \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e01\";\n/** bob — the device owner. */\nconst OWNER = \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e02\";\n/** alice — whose work it is. */\nconst USER = \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e03\";\n/** carol — somebody else entirely, for the cases about keeping people apart. */\nconst CAROL = \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e05\";\nconst MAPPING: Mapping = {\n purpose: \"writing_assistant\",\n kind: \"llm.generate\",\n service: \"qwen\",\n owner: null,\n};\n\n/**\n * The same service name, on a teammate's machine.\n *\n * byollm-review 2026-08-27. Every case here used `owner: null`, so a store\n * that dropped `owner`, nulled it, or mis-joined it passed the whole suite —\n * while this contract claimed \"a store that passes has the semantics the\n * engine relies on\". It did not.\n *\n * The engine's wrong-machine check is `(mapped.owner ?? job.owner) !== owner`,\n * and it is the enforcement of the one substitution this design exists to\n * forbid: a mapping naming *carol's* qwen must not be satisfied by *bob's*\n * qwen. It depends entirely on a store round-tripping this field, and nothing\n * asked it to.\n */\nconst TEAMMATE_MAPPING: Mapping = {\n purpose: \"writing_assistant\",\n kind: \"llm.generate\",\n // Deliberately the same name as the mapper's own service above: a store\n // keyed on the name alone would pass a case that used a distinct one.\n service: \"qwen\",\n // carol — a second teammate, whose qwen is not bob's.\n owner: \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e04\",\n};\n\n/** Run the contract. Call inside a suite; it declares its own `describe`. */\nexport function describePolicyStoreContract(\n name: string,\n options: PolicyStoreContractOptions,\n): void {\n describe(`the policy store — ${name}`, () => {\n it(\"says a site nobody authorised is not consented\", async () => {\n // The ordinary answer, not an error. A person who has never heard of a\n // site is the common case, and a store that threw here would make the\n // engine treat \"no\" as \"broken\".\n const { store, done } = await options.make();\n const snapshot = await store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"no\");\n expect(snapshot.mappings).toEqual([]);\n await done();\n });\n\n it(\"returns the mapping the consent carried\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"yes\");\n expect([...snapshot.mappings]).toEqual([MAPPING]);\n await s.done();\n });\n\n it(\"round-trips whose service a mapping names\", async () => {\n /**\n * The field the anti-substitution check is made of.\n *\n * A store that answers `null` here — or the querying device's owner —\n * makes `(mapped.owner ?? job.owner) !== owner` pass for the wrong\n * machine, and a grant gets signed putting somebody's work on a device\n * they never chose. Device-side checks do not save it: the wrong\n * machine's service is team-scoped and the person is a legitimate\n * member, so admission succeeds.\n *\n * `null` and a real owner are both asserted, because the two are read\n * through the same column and a store that swapped them would satisfy\n * either case alone.\n */\n const s = await options.make();\n await s.consent({\n siteId: SITE,\n user: USER,\n mappings: [TEAMMATE_MAPPING],\n });\n await s.addMember({ owner: OWNER, user: USER });\n\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n\n expect(snapshot.mappings).toEqual([TEAMMATE_MAPPING]);\n expect(snapshot.mappings[0]?.owner).toBe(TEAMMATE_MAPPING.owner);\n await s.done();\n });\n\n it(\"keeps two same-named services apart by whose they are\", async () => {\n /**\n * The substitution itself, as a stored fact.\n *\n * Alice is on two teams that both run a `qwen`. Those are two different\n * machines and the consent screen shows them as two options; a store\n * that collapsed them would let either claim the work. Two mappings\n * under one consent, which no case here previously exercised either.\n */\n const s = await options.make();\n await s.consent({\n siteId: SITE,\n user: USER,\n mappings: [\n { ...MAPPING, purpose: \"mine\" },\n { ...TEAMMATE_MAPPING, purpose: \"theirs\" },\n ],\n });\n await s.addMember({ owner: OWNER, user: USER });\n\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n\n const byPurpose = new Map(\n snapshot.mappings.map((m) => [m.purpose, m.owner]),\n );\n expect(byPurpose.get(\"mine\")).toBeNull();\n expect(byPurpose.get(\"theirs\")).toBe(TEAMMATE_MAPPING.owner);\n await s.done();\n });\n\n it(\"distinguishes consented-and-unmapped from never-consented\", async () => {\n // A real signup state: every slot had two candidates, so nothing\n // auto-mapped and the person has not chosen. It is not the same as\n // never having authorised the site, and it sends them somewhere else.\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [] });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"yes\");\n expect(snapshot.mappings).toEqual([]);\n await s.done();\n });\n\n it(\"deletes the mapping when consent is withdrawn\", async () => {\n // Hole 2: the mapping *is* the consent, so un-consenting unmaps. A\n // store that kept the mapping would hold a resolution pointing at a\n // service its author no longer authorises anybody to reach.\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n await s.revoke({ siteId: SITE, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"no\");\n expect(snapshot.mappings).toEqual([]);\n await s.done();\n });\n\n it(\"reads a paused consent as not consented\", async () => {\n // Three states, one boolean, on purpose: never authorised, revoked and\n // paused are different to a person and identical to a grant. A relay's\n // projection can lag by seconds, so a store that reported a paused\n // consent as live would let a grant be authored for work its owner had\n // just stopped.\n const s = await options.make();\n if (!s.pause) {\n await s.done();\n return;\n }\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n await s.pause({ siteId: SITE, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.consented).toBe(\"paused\");\n\n /**\n * `\"paused\"`, not `\"no\"` — byollm-review 2026-08-27.\n *\n * The distinction is the whole reason this stopped being a boolean. A\n * store reporting a pause as \"no\" is not merely imprecise: the engine\n * declines `not-consented`, which is **permanent**, and the relay never\n * offers that job to that device again. The hosted product pauses a\n * consent the moment its author joins a team, so this is an everyday\n * path, and re-consenting minutes later did not bring the work back.\n *\n * A pause is somebody's own to lift, so the job has to still be there\n * when they do.\n */\n await s.done();\n });\n\n it(\"keeps one person's consent out of another's\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const other = await s.store.read({\n siteId: SITE,\n user: CAROL,\n owner: OWNER,\n });\n expect(other.consented).toBe(\"no\");\n await s.done();\n });\n\n it(\"keeps one site's consent out of another's\", async () => {\n const s = await options.make();\n await s.consent({ siteId: SITE, user: USER, mappings: [MAPPING] });\n const other = await s.store.read({\n siteId: \"site_other\",\n user: USER,\n owner: OWNER,\n });\n expect(other.consented).toBe(\"no\");\n await s.done();\n });\n\n it(\"reports membership per owner, not per person\", async () => {\n // A team is one owner's, and being on one confers nothing about\n // anybody else's devices. A store that kept a global \"alice is a team\n // member\" flag would share every owner's hardware with her.\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n const mine = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n const theirs = await s.store.read({\n siteId: SITE,\n user: USER,\n // carol — a second teammate, whose qwen is not bob's.\n owner: \"5cbc8f4c-96ab-4c1e-b5b6-9d4b2a1f0e04\",\n });\n expect(mine.member).toBe(true);\n expect(theirs.member).toBe(false);\n await s.done();\n });\n\n it(\"stops reporting membership once somebody is removed\", async () => {\n // Removal is the whole of it — there is no blocked-but-still-a-member,\n // because a team *is* access to its owner's devices (Amendment J).\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n await s.removeMember({ owner: OWNER, user: USER });\n const snapshot = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(snapshot.member).toBe(false);\n await s.done();\n });\n\n it(\"keeps membership and consent independent\", async () => {\n // Two different questions, and both have to be yes. A store that\n // conflated them would let a team member's work run on a site they\n // never authorised, or refuse a person their own site because nobody\n // had added them to a team.\n const s = await options.make();\n await s.addMember({ owner: OWNER, user: USER });\n const noConsent = await s.store.read({\n siteId: SITE,\n user: USER,\n owner: OWNER,\n });\n expect(noConsent.member).toBe(true);\n expect(noConsent.consented).toBe(\"no\");\n\n await s.consent({ siteId: SITE, user: CAROL, mappings: [MAPPING] });\n const noMembership = await s.store.read({\n siteId: SITE,\n user: CAROL,\n owner: OWNER,\n });\n expect(noMembership.consented).toBe(\"yes\");\n expect(noMembership.member).toBe(false);\n await s.done();\n });\n\n it.runIf(options.opaqueIds !== false)(\n \"does not let a site id and a user id be confused for each other\",\n async () => {\n // Composite keys are parsers waiting to meet an id containing their\n // separator. These two consents differ only in where the boundary\n // falls, and a store that joined them naively would answer one for\n // the other.\n const s = await options.make();\n await s.consent({ siteId: \"a\", user: \"b:c\", mappings: [MAPPING] });\n const confusable = await s.store.read({\n siteId: \"a:b\",\n user: \"c\",\n owner: OWNER,\n });\n expect(confusable.consented).toBe(\"no\");\n await s.done();\n },\n );\n });\n}\n"],"mappings":";AAAA,SAAS,UAAU,QAAQ,UAAU;AAmFrC,IAAM,OAAO;AAEb,IAAM,QAAQ;AAEd,IAAM,OAAO;AAEb,IAAM,QAAQ;AACd,IAAM,UAAmB;AAAA,EACvB,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AACT;AAgBA,IAAM,mBAA4B;AAAA,EAChC,SAAS;AAAA,EACT,MAAM;AAAA;AAAA;AAAA,EAGN,SAAS;AAAA;AAAA,EAET,OAAO;AACT;AAGO,SAAS,4BACd,MACA,SACM;AACN,WAAS,2BAAsB,IAAI,IAAI,MAAM;AAC3C,OAAG,kDAAkD,YAAY;AAI/D,YAAM,EAAE,OAAO,KAAK,IAAI,MAAM,QAAQ,KAAK;AAC3C,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,IAAI;AACpC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,KAAK;AAAA,IACb,CAAC;AAED,OAAG,2CAA2C,YAAY;AACxD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,KAAK;AACrC,aAAO,CAAC,GAAG,SAAS,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;AAChD,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,6CAA6C,YAAY;AAe1D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ;AAAA,QACd,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,UAAU,CAAC,gBAAgB;AAAA,MAC7B,CAAC;AACD,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAE9C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AAED,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC;AACpD,aAAO,SAAS,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,iBAAiB,KAAK;AAC/D,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,yDAAyD,YAAY;AAStE,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ;AAAA,QACd,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,UAAU;AAAA,UACR,EAAE,GAAG,SAAS,SAAS,OAAO;AAAA,UAC9B,EAAE,GAAG,kBAAkB,SAAS,SAAS;AAAA,QAC3C;AAAA,MACF,CAAC;AACD,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAE9C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AAED,YAAM,YAAY,IAAI;AAAA,QACpB,SAAS,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC;AAAA,MACnD;AACA,aAAO,UAAU,IAAI,MAAM,CAAC,EAAE,SAAS;AACvC,aAAO,UAAU,IAAI,QAAQ,CAAC,EAAE,KAAK,iBAAiB,KAAK;AAC3D,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,6DAA6D,YAAY;AAI1E,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,EAAE,CAAC;AAC1D,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,KAAK;AACrC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,iDAAiD,YAAY;AAI9D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,EAAE,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC3C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,IAAI;AACpC,aAAO,SAAS,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACpC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,2CAA2C,YAAY;AAMxD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,UAAI,CAAC,EAAE,OAAO;AACZ,cAAM,EAAE,KAAK;AACb;AAAA,MACF;AACA,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,EAAE,MAAM,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AAC1C,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,KAAK,QAAQ;AAexC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,+CAA+C,YAAY;AAC5D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,QAAQ,MAAM,EAAE,MAAM,KAAK;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,MAAM,SAAS,EAAE,KAAK,IAAI;AACjC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,6CAA6C,YAAY;AAC1D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,YAAM,QAAQ,MAAM,EAAE,MAAM,KAAK;AAAA,QAC/B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,MAAM,SAAS,EAAE,KAAK,IAAI;AACjC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,gDAAgD,YAAY;AAI7D,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,OAAO,MAAM,EAAE,MAAM,KAAK;AAAA,QAC9B,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,YAAM,SAAS,MAAM,EAAE,MAAM,KAAK;AAAA,QAChC,QAAQ;AAAA,QACR,MAAM;AAAA;AAAA,QAEN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,KAAK,MAAM,EAAE,KAAK,IAAI;AAC7B,aAAO,OAAO,MAAM,EAAE,KAAK,KAAK;AAChC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,uDAAuD,YAAY;AAGpE,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,EAAE,aAAa,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AACjD,YAAM,WAAW,MAAM,EAAE,MAAM,KAAK;AAAA,QAClC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,SAAS,MAAM,EAAE,KAAK,KAAK;AAClC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,4CAA4C,YAAY;AAKzD,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,YAAM,EAAE,UAAU,EAAE,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C,YAAM,YAAY,MAAM,EAAE,MAAM,KAAK;AAAA,QACnC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,UAAU,MAAM,EAAE,KAAK,IAAI;AAClC,aAAO,UAAU,SAAS,EAAE,KAAK,IAAI;AAErC,YAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM,MAAM,OAAO,UAAU,CAAC,OAAO,EAAE,CAAC;AAClE,YAAM,eAAe,MAAM,EAAE,MAAM,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD,aAAO,aAAa,SAAS,EAAE,KAAK,KAAK;AACzC,aAAO,aAAa,MAAM,EAAE,KAAK,KAAK;AACtC,YAAM,EAAE,KAAK;AAAA,IACf,CAAC;AAED,OAAG,MAAM,QAAQ,cAAc,KAAK;AAAA,MAClC;AAAA,MACA,YAAY;AAKV,cAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,cAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,OAAO,UAAU,CAAC,OAAO,EAAE,CAAC;AACjE,cAAM,aAAa,MAAM,EAAE,MAAM,KAAK;AAAA,UACpC,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,OAAO;AAAA,QACT,CAAC;AACD,eAAO,WAAW,SAAS,EAAE,KAAK,IAAI;AACtC,cAAM,EAAE,KAAK;AAAA,MACf;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byollm/control-plane",
3
- "version": "0.1.0-alpha.60",
3
+ "version": "0.1.0-alpha.61",
4
4
  "type": "module",
5
5
  "description": "The reference control plane: resolves a user's mapping and authors one signed grant per job, against a policy store it does not own.",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "README.md"
31
31
  ],
32
32
  "dependencies": {
33
- "@byollm/protocol": "0.1.0-alpha.60"
33
+ "@byollm/protocol": "0.1.0-alpha.61"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"