@poa-box/core 0.1.0 → 1.0.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.
- package/README.md +2 -0
- package/dist/abis/AuthorityRouter.d.ts +448 -0
- package/dist/abis/AuthorityRouter.js +584 -0
- package/dist/abis/CutoverVerifier.d.ts +194 -0
- package/dist/abis/CutoverVerifier.js +259 -0
- package/dist/abis/DirectDemocracyVotingNew.d.ts +168 -30
- package/dist/abis/DirectDemocracyVotingNew.js +214 -35
- package/dist/abis/EducationHubNew.d.ts +32 -75
- package/dist/abis/EducationHubNew.js +38 -94
- package/dist/abis/Executor.d.ts +56 -86
- package/dist/abis/Executor.js +68 -104
- package/dist/abis/HybridVotingNew.d.ts +279 -35
- package/dist/abis/HybridVotingNew.js +357 -40
- package/dist/abis/{EligibilityModuleNew.d.ts → MembershipAuthority.d.ts} +1437 -718
- package/dist/abis/{EligibilityModuleNew.js → MembershipAuthority.js} +1821 -899
- package/dist/abis/OrgDeployerNew.d.ts +193 -127
- package/dist/abis/OrgDeployerNew.js +244 -160
- package/dist/abis/OrgRegistry.d.ts +40 -0
- package/dist/abis/OrgRegistry.js +51 -0
- package/dist/abis/ParticipationToken.d.ts +32 -75
- package/dist/abis/ParticipationToken.js +38 -94
- package/dist/abis/PasskeyAccount.d.ts +28 -0
- package/dist/abis/PasskeyAccount.js +35 -0
- package/dist/abis/PaymasterHub.d.ts +527 -47
- package/dist/abis/PaymasterHub.js +750 -135
- package/dist/abis/QuickJoinNew.d.ts +59 -170
- package/dist/abis/QuickJoinNew.js +71 -215
- package/dist/abis/TaskManagerNew.d.ts +27 -44
- package/dist/abis/TaskManagerNew.js +31 -52
- package/dist/abis/UniversalAccountRegistry.d.ts +24 -0
- package/dist/abis/UniversalAccountRegistry.js +30 -0
- package/dist/abis/ZkEmailInvites.d.ts +44 -0
- package/dist/abis/ZkEmailInvites.js +55 -0
- package/dist/abis/index.d.ts +6 -4
- package/dist/abis/index.js +10 -7
- package/dist/chains.js +3 -3
- package/dist/error-catalog.js +125 -7
- package/dist/graph/documents/activity.d.ts +3 -1
- package/dist/graph/documents/activity.js +19 -14
- package/dist/graph/documents/org.d.ts +4 -4
- package/dist/graph/documents/org.js +10 -4
- package/dist/graph/documents/task.d.ts +10 -1
- package/dist/graph/documents/task.js +157 -3
- package/dist/graph/documents/user.d.ts +5 -30
- package/dist/graph/documents/user.js +2 -42
- package/dist/graph/documents/voting-classes.d.ts +23 -2
- package/dist/graph/documents/voting-classes.js +119 -10
- package/dist/metadata/index.d.ts +0 -1
- package/dist/metadata/index.js +1 -2
- package/dist/perms.d.ts +0 -15
- package/dist/perms.js +1 -21
- package/dist/preflight.d.ts +2 -2
- package/dist/preflight.js +14 -22
- package/dist/reads/authority.d.ts +33 -0
- package/dist/reads/authority.js +196 -0
- package/dist/reads/index.d.ts +1 -1
- package/dist/reads/index.js +2 -2
- package/dist/reads/org.d.ts +20 -20
- package/dist/reads/org.js +94 -28
- package/dist/reads/project.js +3 -3
- package/dist/reads/resolve.d.ts +1 -1
- package/dist/reads/resolve.js +12 -3
- package/dist/reads/task.d.ts +44 -22
- package/dist/reads/task.js +126 -67
- package/dist/reads/user.d.ts +8 -43
- package/dist/reads/user.js +27 -44
- package/dist/reads/vote.d.ts +26 -2
- package/dist/reads/vote.js +104 -23
- package/dist/tx/authority.d.ts +33 -0
- package/dist/tx/authority.js +62 -0
- package/dist/tx/governance.d.ts +6 -0
- package/dist/tx/governance.js +20 -2
- package/dist/tx/index.d.ts +1 -1
- package/dist/tx/index.js +2 -2
- package/dist/tx/org.d.ts +23 -18
- package/dist/tx/org.js +203 -67
- package/dist/tx/project.d.ts +3 -2
- package/dist/tx/project.js +28 -10
- package/dist/tx/task.d.ts +13 -24
- package/dist/tx/task.js +45 -85
- package/dist/tx/user.d.ts +2 -40
- package/dist/tx/user.js +2 -76
- package/dist/tx/vote.d.ts +5 -0
- package/dist/tx/vote.js +54 -43
- package/dist/version.d.ts +0 -5
- package/dist/version.js +1 -13
- package/package.json +1 -1
- package/dist/abis/ToggleModule.d.ts +0 -193
- package/dist/abis/ToggleModule.js +0 -255
- package/dist/metadata/role.d.ts +0 -38
- package/dist/metadata/role.js +0 -35
- package/dist/reads/eligibility.d.ts +0 -377
- package/dist/reads/eligibility.js +0 -687
- package/dist/tx/eligibility.d.ts +0 -511
- package/dist/tx/eligibility.js +0 -818
package/dist/reads/org.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* "nonexistent".
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.FETCH_MEMBERS = exports.LIST_ORGS_QUERY = void 0;
|
|
18
|
+
exports.USER_AUTHORITY_ORGS_QUERY = exports.FETCH_MEMBERS = exports.LIST_ORGS_QUERY = void 0;
|
|
19
19
|
exports.findOrgId = findOrgId;
|
|
20
20
|
exports.getOrganization = getOrganization;
|
|
21
21
|
exports.getOrgMetadataDoc = getOrgMetadataDoc;
|
|
@@ -33,13 +33,14 @@ const activity_1 = require("../graph/documents/activity");
|
|
|
33
33
|
const infrastructure_1 = require("../graph/documents/infrastructure");
|
|
34
34
|
const contracts_1 = require("../contracts");
|
|
35
35
|
const ipfs_1 = require("../ipfs");
|
|
36
|
+
const authority_1 = require("./authority");
|
|
36
37
|
// ---------------------------------------------------------------------------
|
|
37
38
|
// Inline documents ported from the org command files (not in src/queries)
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
40
|
/** Verbatim from src/commands/org/list.ts. */
|
|
40
41
|
exports.LIST_ORGS_QUERY = `
|
|
41
42
|
query ListOrgs($first: Int!) {
|
|
42
|
-
organizations(first: $first, orderBy: deployedAt, orderDirection: desc) {
|
|
43
|
+
organizations(where: { membershipAuthority_: { isRouterBound: true, cutoverAt_gt: "0" } }, first: $first, orderBy: deployedAt, orderDirection: desc) {
|
|
43
44
|
id
|
|
44
45
|
name
|
|
45
46
|
deployedAt
|
|
@@ -77,10 +78,12 @@ exports.FETCH_MEMBERS = `
|
|
|
77
78
|
* "not found" as data).
|
|
78
79
|
*/
|
|
79
80
|
async function findOrgId(client, orgIdOrName, chainId) {
|
|
80
|
-
if (orgIdOrName.startsWith('0x'))
|
|
81
|
-
|
|
81
|
+
if (orgIdOrName.startsWith('0x')) {
|
|
82
|
+
const result = await client.query(authority_1.FETCH_ORG_AUTHORITY, { id: orgIdOrName }, chainId);
|
|
83
|
+
return (0, authority_1.isAuthorityReady)(result.organization) ? result.organization.id : null;
|
|
84
|
+
}
|
|
82
85
|
const result = await client.query(org_1.GET_ORG_BY_NAME, { name: orgIdOrName }, chainId);
|
|
83
|
-
return result.organizations?.
|
|
86
|
+
return result.organizations?.find(authority_1.isAuthorityReady)?.id ?? null;
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
89
|
* Port of the `pop org view` read — src/commands/org/view.ts.
|
|
@@ -92,7 +95,21 @@ async function getOrganization(client, orgIdOrName, chainId) {
|
|
|
92
95
|
if (!orgId)
|
|
93
96
|
return null;
|
|
94
97
|
const result = await client.query(org_1.FETCH_ORG_FULL_DATA, { orgId }, chainId);
|
|
95
|
-
|
|
98
|
+
const org = result.organization;
|
|
99
|
+
if (!org || !(0, authority_1.isAuthorityReady)(org))
|
|
100
|
+
return null;
|
|
101
|
+
const [subjects, users] = await Promise.all([
|
|
102
|
+
(0, authority_1.readAuthorityRows)(client, orgId, authority_1.FETCH_AUTHORITY_SUBJECTS, 'subjects', chainId),
|
|
103
|
+
(0, authority_1.readAuthorityUsers)(client, orgId, org.users ?? [], chainId),
|
|
104
|
+
]);
|
|
105
|
+
org.users = users;
|
|
106
|
+
org.roles = subjects.filter(subject => subject.kind === 'Role').map(subject => {
|
|
107
|
+
const historical = org.roles?.find(role => role.hatId === subject.subjectId);
|
|
108
|
+
return { ...subject, id: historical?.id ?? `${orgId}-${subject.subjectId}`, hatId: subject.subjectId,
|
|
109
|
+
image: subject.imageURI, canVote: historical?.canVote ?? false,
|
|
110
|
+
canVoteSource: 'historical role metadata', isUserRole: historical?.isUserRole ?? false, hat: null };
|
|
111
|
+
});
|
|
112
|
+
return org;
|
|
96
113
|
}
|
|
97
114
|
/**
|
|
98
115
|
* The org's metadata document: the indexed projection when present, else the
|
|
@@ -165,35 +182,80 @@ async function listOrgsAllChains(client, first = 50) {
|
|
|
165
182
|
* Port of the `pop org list --member` read — src/commands/org/list.ts
|
|
166
183
|
* (FETCH_USER_ORGANIZATIONS): active memberships for an address.
|
|
167
184
|
*/
|
|
185
|
+
exports.USER_AUTHORITY_ORGS_QUERY = `query UserAuthorityOrganizations($userAddress: Bytes!, $lastId: String!) {
|
|
186
|
+
subjectMemberships(first: 1000, orderBy: id, where: {
|
|
187
|
+
user: $userAddress, isMember: true, id_gt: $lastId, subject_: { kind: Role }
|
|
188
|
+
}) {
|
|
189
|
+
id organization { id name metadataHash membershipAuthority { id isRouterBound cutoverAt } participationToken { symbol } }
|
|
190
|
+
userEntity { id participationTokenBalance totalTasksCompleted totalVotes }
|
|
191
|
+
}
|
|
192
|
+
}`;
|
|
168
193
|
async function listUserOrganizations(client, userAddress, chainId) {
|
|
169
|
-
const
|
|
170
|
-
|
|
194
|
+
const rows = new Map();
|
|
195
|
+
let lastId = '';
|
|
196
|
+
for (;;) {
|
|
197
|
+
const page = await client.query(exports.USER_AUTHORITY_ORGS_QUERY, { userAddress: userAddress.toLowerCase(), lastId }, chainId);
|
|
198
|
+
if (!Array.isArray(page.subjectMemberships))
|
|
199
|
+
throw new Error('MembershipAuthority index unavailable: missing subjectMemberships');
|
|
200
|
+
for (const membership of page.subjectMemberships) {
|
|
201
|
+
const org = membership.organization;
|
|
202
|
+
if (!(0, authority_1.isAuthorityReady)(org))
|
|
203
|
+
continue;
|
|
204
|
+
const historical = membership.userEntity;
|
|
205
|
+
rows.set(org.id, { id: historical?.id ?? `${org.id}-${userAddress.toLowerCase()}`,
|
|
206
|
+
membershipStatus: 'Active', historyIndexed: historical != null,
|
|
207
|
+
participationTokenBalance: historical?.participationTokenBalance ?? null,
|
|
208
|
+
totalTasksCompleted: historical?.totalTasksCompleted ?? null, totalVotes: historical?.totalVotes ?? null, organization: org });
|
|
209
|
+
}
|
|
210
|
+
if (page.subjectMemberships.length < 1000)
|
|
211
|
+
return [...rows.values()];
|
|
212
|
+
const next = page.subjectMemberships[page.subjectMemberships.length - 1].id;
|
|
213
|
+
if (next <= lastId)
|
|
214
|
+
throw new Error('MembershipAuthority pagination did not advance');
|
|
215
|
+
lastId = next;
|
|
216
|
+
}
|
|
171
217
|
}
|
|
172
218
|
/**
|
|
173
219
|
* Port of the `pop org members` read — src/commands/org/members.ts.
|
|
174
|
-
*
|
|
220
|
+
* Complete current member rows, plus totalSupply for
|
|
175
221
|
* share math. Null when the org is not indexed.
|
|
176
222
|
*/
|
|
177
223
|
async function listMembers(client, orgId, chainId) {
|
|
178
|
-
const
|
|
179
|
-
|
|
224
|
+
const ready = await client.query(authority_1.FETCH_ORG_AUTHORITY, { id: orgId }, chainId);
|
|
225
|
+
if (!(0, authority_1.isAuthorityReady)(ready.organization))
|
|
226
|
+
return null;
|
|
227
|
+
const [result, users] = await Promise.all([
|
|
228
|
+
client.query(exports.FETCH_MEMBERS, { orgId }, chainId),
|
|
229
|
+
(0, authority_1.readAuthorityUsers)(client, orgId, [], chainId),
|
|
230
|
+
]);
|
|
231
|
+
if (!result.organization)
|
|
232
|
+
return null;
|
|
233
|
+
return { ...result.organization, users: users.filter(user => user.membershipStatus === 'Active') };
|
|
180
234
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Port of the `pop org roles` read — src/commands/org/roles.ts.
|
|
183
|
-
*
|
|
184
|
-
* Tier 0 carries Role.hat.vouchConfig (present + populated on Gnosis and
|
|
185
|
-
* Arbitrum; a null vouchConfig means "never configured", verified equal to
|
|
186
|
-
* isVouchingEnabled()==false / quorum==0 on-chain). Tier 1 is the
|
|
187
|
-
* pre-vouchConfig schema; GraphQL validates the whole document, so the
|
|
188
|
-
* unknown-field fallback is what keeps older deployments working. When
|
|
189
|
-
* tierIndex === 1 serves, the CLI falls back to batched EligibilityModule
|
|
190
|
-
* reads through Multicall3 — that RPC path stays host-side.
|
|
191
|
-
*/
|
|
235
|
+
/** Current roles and membership are authority-owned; continuity fields retain their public names. */
|
|
192
236
|
async function listRoles(client, orgId, chainId) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
237
|
+
const result = await client.query(`query AuthorityRoleContext($orgId: Bytes!) {
|
|
238
|
+
organization(id: $orgId) {
|
|
239
|
+
membershipAuthority { id isRouterBound cutoverAt }
|
|
240
|
+
roles { hatId canVote isUserRole }
|
|
241
|
+
users(first: 1000) { address participationTokenBalance account { username } }
|
|
242
|
+
}
|
|
243
|
+
}`, { orgId }, chainId);
|
|
244
|
+
const org = result.organization;
|
|
245
|
+
if (!(0, authority_1.isAuthorityReady)(org))
|
|
246
|
+
return { data: { organization: null }, tierIndex: 0 };
|
|
247
|
+
const [subjects, users] = await Promise.all([
|
|
248
|
+
(0, authority_1.readAuthorityRows)(client, orgId, authority_1.FETCH_AUTHORITY_SUBJECTS, 'subjects', chainId),
|
|
249
|
+
(0, authority_1.readAuthorityUsers)(client, orgId, org.users, chainId),
|
|
250
|
+
]);
|
|
251
|
+
const roles = subjects.filter(s => s.kind === 'Role').map(subject => {
|
|
252
|
+
const continuity = org.roles.find((r) => r.hatId === subject.subjectId);
|
|
253
|
+
return { ...subject, id: orgId + '-' + subject.subjectId, hatId: subject.subjectId,
|
|
254
|
+
image: subject.imageURI, canVote: continuity?.canVote ?? false, isUserRole: continuity?.isUserRole ?? false,
|
|
255
|
+
hat: null,
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
return { data: { organization: { roles, users, membershipAuthority: org.membershipAuthority } }, tierIndex: 0 };
|
|
197
259
|
}
|
|
198
260
|
/**
|
|
199
261
|
* Port of the `pop org status` activity read — src/commands/org/status.ts
|
|
@@ -203,12 +265,16 @@ async function listRoles(client, orgId, chainId) {
|
|
|
203
265
|
* is separate infrastructure.
|
|
204
266
|
*/
|
|
205
267
|
async function getOrgActivity(client, modules, chainId) {
|
|
206
|
-
|
|
268
|
+
const result = await client.query(activity_1.FETCH_ORG_ACTIVITY, {
|
|
207
269
|
orgId: modules.orgId,
|
|
208
270
|
hybridVotingId: modules.hybridVotingAddress || '',
|
|
209
|
-
|
|
271
|
+
authorityId: modules.membershipAuthorityAddress || '',
|
|
210
272
|
tokenAddress: modules.participationTokenAddress || '',
|
|
211
273
|
}, chainId);
|
|
274
|
+
result.activeVouches = (0, activity_1.normalizeAuthorityVouches)(result.activeVouches ?? []);
|
|
275
|
+
if (result.organization)
|
|
276
|
+
result.organization.users = await (0, authority_1.readAuthorityUsers)(client, modules.orgId, result.organization.users ?? [], chainId);
|
|
277
|
+
return result;
|
|
212
278
|
}
|
|
213
279
|
/**
|
|
214
280
|
* Global infrastructure addresses (`FETCH_INFRASTRUCTURE_ADDRESSES`) — the
|
package/dist/reads/project.js
CHANGED
|
@@ -12,10 +12,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.listProjects = listProjects;
|
|
13
13
|
exports.resolveProjectInput = resolveProjectInput;
|
|
14
14
|
const ethers_1 = require("ethers");
|
|
15
|
-
const task_1 = require("../graph/documents/task");
|
|
16
15
|
const encoding_1 = require("../encoding");
|
|
17
16
|
const errors_1 = require("../errors");
|
|
18
17
|
const exit_codes_1 = require("../exit-codes");
|
|
18
|
+
const task_1 = require("./task");
|
|
19
19
|
/**
|
|
20
20
|
* List an org's (non-deleted) projects, tasks included.
|
|
21
21
|
* Port of the read at src/commands/project/list.ts:37 — a bare
|
|
@@ -23,7 +23,7 @@ const exit_codes_1 = require("../exit-codes");
|
|
|
23
23
|
* the deadline/release fields, so the base document always validates).
|
|
24
24
|
*/
|
|
25
25
|
async function listProjects(client, orgId, chainId) {
|
|
26
|
-
const result = await
|
|
26
|
+
const { data: result } = await (0, task_1.fetchProjectsData)(client, orgId, chainId);
|
|
27
27
|
return result.organization?.taskManager?.projects || [];
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
@@ -48,7 +48,7 @@ async function resolveProjectInput(client, input, orgId, chainId) {
|
|
|
48
48
|
// Title lookup via the subgraph (case-insensitive).
|
|
49
49
|
let projects = [];
|
|
50
50
|
try {
|
|
51
|
-
const result = await
|
|
51
|
+
const { data: result } = await (0, task_1.fetchProjectsData)(client, orgId, chainId);
|
|
52
52
|
projects = result.organization?.taskManager?.projects || [];
|
|
53
53
|
}
|
|
54
54
|
catch {
|
package/dist/reads/resolve.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface OrgModules {
|
|
|
19
19
|
educationHubAddress: string | null;
|
|
20
20
|
executorAddress: string | null;
|
|
21
21
|
quickJoinAddress: string | null;
|
|
22
|
-
|
|
22
|
+
membershipAuthorityAddress: string | null;
|
|
23
23
|
paymentManagerAddress: string | null;
|
|
24
24
|
/** ZkEmailInvites proxy — null for orgs deployed without the optional ZK Email module. */
|
|
25
25
|
zkEmailInvitesAddress: string | null;
|
package/dist/reads/resolve.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.resolveOrgId = resolveOrgId;
|
|
|
10
10
|
exports.resolveOrgModules = resolveOrgModules;
|
|
11
11
|
exports.requireModule = requireModule;
|
|
12
12
|
const org_1 = require("../graph/documents/org");
|
|
13
|
+
const authority_1 = require("./authority");
|
|
13
14
|
/**
|
|
14
15
|
* Resolve an org identifier (name or hex ID) to its bytes32 ID.
|
|
15
16
|
* Throws a helpful error if org is not provided.
|
|
@@ -19,13 +20,19 @@ async function resolveOrgId(client, orgIdOrName, chainId) {
|
|
|
19
20
|
throw new Error('Missing --org flag. Provide --org or set POP_DEFAULT_ORG in .env');
|
|
20
21
|
}
|
|
21
22
|
if (orgIdOrName.startsWith('0x')) {
|
|
22
|
-
|
|
23
|
+
const result = await client.query(authority_1.FETCH_ORG_AUTHORITY, { id: orgIdOrName.toLowerCase() }, chainId);
|
|
24
|
+
if (!(0, authority_1.isAuthorityReady)(result.organization))
|
|
25
|
+
throw new Error('Organization is retired or has not completed its MembershipAuthority cutover. Only authority-ready organizations are supported.');
|
|
26
|
+
return result.organization.id;
|
|
23
27
|
}
|
|
24
28
|
const result = await client.query(org_1.GET_ORG_BY_NAME, { name: orgIdOrName }, chainId);
|
|
25
29
|
if (!result.organizations?.length) {
|
|
26
30
|
throw new Error(`Organization "${orgIdOrName}" not found. Use the hex ID or check the org name.`);
|
|
27
31
|
}
|
|
28
|
-
|
|
32
|
+
const org = result.organizations.find(authority_1.isAuthorityReady);
|
|
33
|
+
if (!org)
|
|
34
|
+
throw new Error('Organization is retired or has not completed its MembershipAuthority cutover. Only authority-ready organizations are supported.');
|
|
35
|
+
return org.id;
|
|
29
36
|
}
|
|
30
37
|
/**
|
|
31
38
|
* Resolve an org's deployed module addresses via subgraph.
|
|
@@ -37,6 +44,8 @@ async function resolveOrgModules(client, orgIdOrName, chainId) {
|
|
|
37
44
|
throw new Error(`Organization ${orgId} not found on this chain`);
|
|
38
45
|
}
|
|
39
46
|
const org = result.organization;
|
|
47
|
+
if (!(0, authority_1.isAuthorityReady)(org))
|
|
48
|
+
throw new Error('Organization is retired or has not completed its MembershipAuthority cutover.');
|
|
40
49
|
return {
|
|
41
50
|
orgId: org.id,
|
|
42
51
|
taskManagerAddress: org.taskManager?.id || null,
|
|
@@ -46,7 +55,7 @@ async function resolveOrgModules(client, orgIdOrName, chainId) {
|
|
|
46
55
|
educationHubAddress: org.educationHub?.id || null,
|
|
47
56
|
executorAddress: org.executorContract?.id || null,
|
|
48
57
|
quickJoinAddress: org.quickJoin?.id || null,
|
|
49
|
-
|
|
58
|
+
membershipAuthorityAddress: org.membershipAuthority?.id || null,
|
|
50
59
|
paymentManagerAddress: org.paymentManager?.id || null,
|
|
51
60
|
zkEmailInvitesAddress: org.zkEmailInvites?.id || null,
|
|
52
61
|
};
|
package/dist/reads/task.d.ts
CHANGED
|
@@ -77,7 +77,46 @@ export interface ProjectsDataRead {
|
|
|
77
77
|
* The org's full projects → tasks tree with tier fallback.
|
|
78
78
|
* Port of the read at src/commands/task/list.ts:129 (also view.ts:95).
|
|
79
79
|
*/
|
|
80
|
-
export declare function fetchProjectsData(client: GraphClient, orgId: string, chainId?: number): Promise<ProjectsDataRead>;
|
|
80
|
+
export declare function fetchProjectsData(client: Pick<GraphClient, 'queryWithFieldFallback'>, orgId: string, chainId?: number): Promise<ProjectsDataRead>;
|
|
81
|
+
/** Canonical entity ID used by task-manager.ts; never cross an org boundary via a pasted ID. */
|
|
82
|
+
export declare function taskEntityId(taskManagerAddress: string, input: string | number): string;
|
|
83
|
+
/** Fetch one task without dropping old tasks or tasks in the 51st project. */
|
|
84
|
+
export declare function fetchTaskData(client: Pick<GraphClient, 'queryWithFieldFallback'>, taskManagerAddress: string, input: string | number, chainId?: number): Promise<{
|
|
85
|
+
task: SubgraphTask | null;
|
|
86
|
+
tierIndex: number;
|
|
87
|
+
}>;
|
|
88
|
+
export interface TaskSubmissionRecord {
|
|
89
|
+
id: string;
|
|
90
|
+
submissionHash: string;
|
|
91
|
+
submittedAt: string;
|
|
92
|
+
submittedAtBlock: string;
|
|
93
|
+
transactionHash: string;
|
|
94
|
+
metadata: {
|
|
95
|
+
submission?: string | null;
|
|
96
|
+
} | null;
|
|
97
|
+
}
|
|
98
|
+
export interface TaskReviewRecord {
|
|
99
|
+
id: string;
|
|
100
|
+
rejector: string;
|
|
101
|
+
rejectorUsername?: string | null;
|
|
102
|
+
rejectionHash: string;
|
|
103
|
+
rejectedAt: string;
|
|
104
|
+
rejectedAtBlock: string;
|
|
105
|
+
transactionHash: string;
|
|
106
|
+
metadata: {
|
|
107
|
+
rejection?: string | null;
|
|
108
|
+
} | null;
|
|
109
|
+
submission: TaskSubmissionRecord | null;
|
|
110
|
+
}
|
|
111
|
+
export interface TaskSubmissionHistory {
|
|
112
|
+
indexed: boolean;
|
|
113
|
+
latestSubmission: TaskSubmissionRecord | null;
|
|
114
|
+
latestRejection: string | null;
|
|
115
|
+
submissions: TaskSubmissionRecord[];
|
|
116
|
+
rejections: TaskReviewRecord[];
|
|
117
|
+
}
|
|
118
|
+
/** Full immutable submission/review history, with explicit old-deployment fallback. */
|
|
119
|
+
export declare function fetchTaskSubmissionHistory(client: Pick<GraphClient, 'queryWithFieldFallback'>, taskManagerAddress: string, input: string | number, chainId?: number): Promise<TaskSubmissionHistory>;
|
|
81
120
|
/** A task row flattened out of the projects tree, with its project attached. */
|
|
82
121
|
export interface TaskListRow extends SubgraphTask {
|
|
83
122
|
/** Composite project entity id the task belongs to. */
|
|
@@ -150,26 +189,14 @@ export interface TaskStatsRead {
|
|
|
150
189
|
* Port of the read at src/commands/task/stats.ts:62 (aggregation stays host-side).
|
|
151
190
|
*/
|
|
152
191
|
export declare function fetchTaskStats(client: GraphClient, orgId: string, chainId?: number): Promise<TaskStatsRead>;
|
|
153
|
-
/**
|
|
154
|
-
|
|
155
|
-
* per-project ProjectRolePermission masks.
|
|
156
|
-
* Port of PERMS_QUERY_FULL at src/commands/task/perms.ts:55. Masks are
|
|
157
|
-
* subgraph-only: they are NOT readable per-wearer on-chain.
|
|
158
|
-
*/
|
|
159
|
-
export declare const PERMS_QUERY_FULL = "\n query TaskPermsFull($orgId: Bytes!) {\n organization(id: $orgId) {\n id\n taskManager {\n id\n creatorHatIds\n organizerHatIds\n globalRolePermissions { hatId mask }\n projects(where: { deleted: false }, first: 50) {\n id\n title\n rolePermissions { hatId mask }\n }\n }\n }\n }\n";
|
|
160
|
-
/** Tier 1: older schema — no global perms/organizer hats; boolean per-project flags. */
|
|
161
|
-
export declare const PERMS_QUERY_LEGACY = "\n query TaskPermsLegacy($orgId: Bytes!) {\n organization(id: $orgId) {\n id\n taskManager {\n id\n creatorHatIds\n projects(where: { deleted: false }, first: 50) {\n id\n title\n rolePermissions { hatId canCreate canClaim canReview canAssign }\n }\n }\n }\n }\n";
|
|
162
|
-
/** Tier array for `queryWithFieldFallback`, matching the CLI's order. */
|
|
192
|
+
/** Historical project identity plus current authority readiness; no legacy permission rows. */
|
|
193
|
+
export declare const PERMS_QUERY_FULL = "query TaskPermsFull($orgId: Bytes!) {\n organization(id: $orgId) {\n id membershipAuthority { id isRouterBound cutoverAt }\n taskManager { id creatorHatIds organizerHatIds projects(where: { deleted: false }, first: 1000) { id title } }\n }\n}";
|
|
163
194
|
export declare function taskPermsTiers(orgId: string): FieldFallbackTier[];
|
|
164
195
|
export interface SubgraphRolePermission {
|
|
196
|
+
/** Stable compatibility label; values are authority subject ids (including adopted legacy ids). */
|
|
165
197
|
hatId: string;
|
|
166
|
-
|
|
198
|
+
subjectId?: string;
|
|
167
199
|
mask?: number | string;
|
|
168
|
-
/** Tier 1 (legacy) boolean fields. */
|
|
169
|
-
canCreate?: boolean;
|
|
170
|
-
canClaim?: boolean;
|
|
171
|
-
canReview?: boolean;
|
|
172
|
-
canAssign?: boolean;
|
|
173
200
|
}
|
|
174
201
|
export interface TaskPermsResult {
|
|
175
202
|
organization: {
|
|
@@ -196,11 +223,6 @@ export interface TaskPermsRead {
|
|
|
196
223
|
* src/commands/task/perms.ts:175 (`pop task perms show`).
|
|
197
224
|
*/
|
|
198
225
|
export declare function fetchTaskPerms(client: GraphClient, orgId: string, chainId?: number): Promise<TaskPermsRead>;
|
|
199
|
-
/**
|
|
200
|
-
* Rebuild a mask from tier-1 boolean flags (partial: only bits 1..8 indexable).
|
|
201
|
-
* Port of src/commands/task/perms.ts maskFromBooleans.
|
|
202
|
-
*/
|
|
203
|
-
export declare function maskFromBooleans(rp: SubgraphRolePermission): number;
|
|
204
226
|
/**
|
|
205
227
|
* Resolve --project-style input (bytes32 hex, subgraph composite ID, or title)
|
|
206
228
|
* to the on-chain bytes32 pid using an already-fetched projects list.
|
package/dist/reads/task.js
CHANGED
|
@@ -11,21 +11,24 @@
|
|
|
11
11
|
* verbatim, comments included — the live-verification notes are load-bearing.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.FOLDERS_QUERY_LEGACY = exports.FOLDERS_QUERY_FULL = exports.
|
|
14
|
+
exports.FOLDERS_QUERY_LEGACY = exports.FOLDERS_QUERY_FULL = exports.PERMS_QUERY_FULL = exports.FETCH_TASK_STATS_LEGACY = exports.FETCH_TASK_STATS = void 0;
|
|
15
15
|
exports.fetchProjectsData = fetchProjectsData;
|
|
16
|
+
exports.taskEntityId = taskEntityId;
|
|
17
|
+
exports.fetchTaskData = fetchTaskData;
|
|
18
|
+
exports.fetchTaskSubmissionHistory = fetchTaskSubmissionHistory;
|
|
16
19
|
exports.listTasks = listTasks;
|
|
17
20
|
exports.findSubgraphTask = findSubgraphTask;
|
|
18
21
|
exports.fetchTaskStats = fetchTaskStats;
|
|
19
22
|
exports.taskPermsTiers = taskPermsTiers;
|
|
20
23
|
exports.fetchTaskPerms = fetchTaskPerms;
|
|
21
|
-
exports.maskFromBooleans = maskFromBooleans;
|
|
22
24
|
exports.resolveProjectFromList = resolveProjectFromList;
|
|
23
25
|
exports.fetchOrgFolders = fetchOrgFolders;
|
|
24
26
|
exports.normalizeRoot = normalizeRoot;
|
|
25
27
|
exports.fetchTaskReleaseHistory = fetchTaskReleaseHistory;
|
|
26
28
|
const task_1 = require("../graph/documents/task");
|
|
27
29
|
const encoding_1 = require("../encoding");
|
|
28
|
-
const
|
|
30
|
+
const authority_1 = require("./authority");
|
|
31
|
+
const authority_2 = require("../tx/authority");
|
|
29
32
|
const errors_1 = require("../errors");
|
|
30
33
|
const exit_codes_1 = require("../exit-codes");
|
|
31
34
|
const ethers_1 = require("ethers");
|
|
@@ -34,14 +37,103 @@ const ethers_1 = require("ethers");
|
|
|
34
37
|
* Port of the read at src/commands/task/list.ts:129 (also view.ts:95).
|
|
35
38
|
*/
|
|
36
39
|
async function fetchProjectsData(client, orgId, chainId) {
|
|
37
|
-
const
|
|
40
|
+
const first = await client.queryWithFieldFallback((0, task_1.projectsDataTiers)(orgId), { chainId });
|
|
41
|
+
let tierIndex = first.tierIndex;
|
|
42
|
+
const data = first.data;
|
|
43
|
+
const manager = data.organization?.taskManager;
|
|
44
|
+
if (!manager)
|
|
45
|
+
return { data, tierIndex, hasDeadlineData: tierIndex <= 1, hasReleaseData: tierIndex === 0 };
|
|
46
|
+
const projects = [...(manager.projects ?? [])];
|
|
47
|
+
let page = manager.projects ?? [];
|
|
48
|
+
while (page.length === 50) {
|
|
49
|
+
const cursor = page[page.length - 1].id;
|
|
50
|
+
const next = await client.queryWithFieldFallback((0, task_1.projectsDataTiers)(orgId).map(tier => ({ ...tier, variables: { orgId, projectCursor: cursor } })), { chainId });
|
|
51
|
+
if (!next.data.organization?.taskManager?.projects)
|
|
52
|
+
throw new errors_1.CliError('Project pagination returned an incomplete response', exit_codes_1.EXIT.INFRA);
|
|
53
|
+
page = next.data.organization.taskManager.projects;
|
|
54
|
+
if (page.some(p => p.id <= cursor))
|
|
55
|
+
throw new errors_1.CliError('Project pagination did not advance', exit_codes_1.EXIT.INFRA);
|
|
56
|
+
projects.push(...page);
|
|
57
|
+
tierIndex = Math.max(tierIndex, next.tierIndex);
|
|
58
|
+
}
|
|
59
|
+
const completeProjects = await Promise.all(projects.map(async (project) => {
|
|
60
|
+
const tasks = [...(project.tasks ?? [])];
|
|
61
|
+
let taskPage = project.tasks ?? [];
|
|
62
|
+
while (taskPage.length === 1000) {
|
|
63
|
+
const cursor = taskPage[taskPage.length - 1].taskId;
|
|
64
|
+
const next = await client.queryWithFieldFallback(task_1.PROJECT_TASK_PAGE_TIERS.map(query => ({ query, variables: { projectId: project.id, taskCursor: cursor } })), { chainId });
|
|
65
|
+
if (!next.data.project?.tasks)
|
|
66
|
+
throw new errors_1.CliError('Task pagination returned an incomplete response', exit_codes_1.EXIT.INFRA);
|
|
67
|
+
taskPage = next.data.project.tasks;
|
|
68
|
+
if (taskPage.some(t => ethers_1.ethers.BigNumber.from(t.taskId).gte(cursor)))
|
|
69
|
+
throw new errors_1.CliError('Task pagination did not advance', exit_codes_1.EXIT.INFRA);
|
|
70
|
+
tasks.push(...taskPage);
|
|
71
|
+
tierIndex = Math.max(tierIndex, next.tierIndex);
|
|
72
|
+
}
|
|
73
|
+
return { ...project, tasks };
|
|
74
|
+
}));
|
|
38
75
|
return {
|
|
39
|
-
data,
|
|
76
|
+
data: { ...data, organization: { ...data.organization, taskManager: { ...manager, projects: completeProjects } } },
|
|
40
77
|
tierIndex,
|
|
41
78
|
hasDeadlineData: tierIndex <= 1,
|
|
42
79
|
hasReleaseData: tierIndex === 0,
|
|
43
80
|
};
|
|
44
81
|
}
|
|
82
|
+
/** Canonical entity ID used by task-manager.ts; never cross an org boundary via a pasted ID. */
|
|
83
|
+
function taskEntityId(taskManagerAddress, input) {
|
|
84
|
+
const address = ethers_1.ethers.utils.getAddress(taskManagerAddress).toLowerCase();
|
|
85
|
+
if (typeof input === 'number' && !Number.isSafeInteger(input))
|
|
86
|
+
throw new errors_1.CliError('Task ID must be an exact integer string', exit_codes_1.EXIT.USAGE);
|
|
87
|
+
const raw = String(input).trim();
|
|
88
|
+
if (raw.includes('-') && raw.split('-')[0].toLowerCase() !== address)
|
|
89
|
+
throw new errors_1.CliError('Task ID belongs to a different TaskManager', exit_codes_1.EXIT.USAGE);
|
|
90
|
+
const parsed = (0, encoding_1.parseTaskId)(raw);
|
|
91
|
+
if (!/^\d+$/.test(parsed) || raw.split('-').length > 2)
|
|
92
|
+
throw new errors_1.CliError('Task ID must be a non-negative integer or a TaskManager-taskId entity ID', exit_codes_1.EXIT.USAGE);
|
|
93
|
+
const number = ethers_1.ethers.BigNumber.from(parsed);
|
|
94
|
+
if (number.gt(ethers_1.ethers.constants.MaxUint256))
|
|
95
|
+
throw new errors_1.CliError('Task ID exceeds uint256', exit_codes_1.EXIT.USAGE);
|
|
96
|
+
return `${address}-${number.toString()}`;
|
|
97
|
+
}
|
|
98
|
+
/** Fetch one task without dropping old tasks or tasks in the 51st project. */
|
|
99
|
+
async function fetchTaskData(client, taskManagerAddress, input, chainId) {
|
|
100
|
+
const taskId = taskEntityId(taskManagerAddress, input);
|
|
101
|
+
const { data, tierIndex } = await client.queryWithFieldFallback([task_1.FETCH_TASK_DATA_WITH_RELEASES, task_1.FETCH_TASK_DATA, task_1.FETCH_TASK_DATA_LEGACY].map(query => ({ query, variables: { taskId } })), { chainId });
|
|
102
|
+
return { task: data.task ?? null, tierIndex };
|
|
103
|
+
}
|
|
104
|
+
/** Full immutable submission/review history, with explicit old-deployment fallback. */
|
|
105
|
+
async function fetchTaskSubmissionHistory(client, taskManagerAddress, input, chainId) {
|
|
106
|
+
const taskId = taskEntityId(taskManagerAddress, input);
|
|
107
|
+
const history = { indexed: false, latestSubmission: null, latestRejection: null, submissions: [], rejections: [] };
|
|
108
|
+
let submissionCursor = '0x';
|
|
109
|
+
let rejectionCursor = '0x';
|
|
110
|
+
while (true) {
|
|
111
|
+
const { data, tierIndex } = await client.queryWithFieldFallback([
|
|
112
|
+
{ query: task_1.FETCH_TASK_SUBMISSION_HISTORY, variables: { taskId, submissionCursor, rejectionCursor } },
|
|
113
|
+
{ query: task_1.FETCH_TASK_RELEASE_HISTORY_LEGACY, variables: { taskId } },
|
|
114
|
+
], { chainId });
|
|
115
|
+
if (tierIndex !== 0 || !data.task)
|
|
116
|
+
return history;
|
|
117
|
+
history.indexed = true;
|
|
118
|
+
history.latestSubmission = data.task.latestSubmission ?? null;
|
|
119
|
+
history.latestRejection = data.task.latestRejection?.id ?? null;
|
|
120
|
+
const submissions = data.task.submissions ?? [];
|
|
121
|
+
const rejections = data.task.rejections ?? [];
|
|
122
|
+
if (submissions.some(row => row.id <= submissionCursor) || rejections.some(row => row.id <= rejectionCursor))
|
|
123
|
+
throw new errors_1.CliError('Task history pagination did not advance', exit_codes_1.EXIT.INFRA);
|
|
124
|
+
history.submissions.push(...submissions);
|
|
125
|
+
history.rejections.push(...rejections);
|
|
126
|
+
if (submissions.length < 1000 && rejections.length < 1000)
|
|
127
|
+
break;
|
|
128
|
+
if (submissions.length)
|
|
129
|
+
submissionCursor = submissions[submissions.length - 1].id;
|
|
130
|
+
if (rejections.length)
|
|
131
|
+
rejectionCursor = rejections[rejections.length - 1].id;
|
|
132
|
+
}
|
|
133
|
+
history.submissions.sort((a, b) => Number(BigInt(b.submittedAtBlock) - BigInt(a.submittedAtBlock)));
|
|
134
|
+
history.rejections.sort((a, b) => Number(BigInt(b.rejectedAtBlock) - BigInt(a.rejectedAtBlock)));
|
|
135
|
+
return history;
|
|
136
|
+
}
|
|
45
137
|
/**
|
|
46
138
|
* Flat list of an org's tasks — the friendliest first read (`pop task list`'s
|
|
47
139
|
* data, sans rendering). Sugar over fetchProjectsData: same tiered query, the
|
|
@@ -132,76 +224,43 @@ async function fetchTaskStats(client, orgId, chainId) {
|
|
|
132
224
|
return { data, tierIndex, hasChurnCounters: tierIndex === 0 };
|
|
133
225
|
}
|
|
134
226
|
// ────────────────────── permission masks ──────────────────────
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*/
|
|
141
|
-
exports.PERMS_QUERY_FULL = `
|
|
142
|
-
query TaskPermsFull($orgId: Bytes!) {
|
|
143
|
-
organization(id: $orgId) {
|
|
144
|
-
id
|
|
145
|
-
taskManager {
|
|
146
|
-
id
|
|
147
|
-
creatorHatIds
|
|
148
|
-
organizerHatIds
|
|
149
|
-
globalRolePermissions { hatId mask }
|
|
150
|
-
projects(where: { deleted: false }, first: 50) {
|
|
151
|
-
id
|
|
152
|
-
title
|
|
153
|
-
rolePermissions { hatId mask }
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
227
|
+
/** Historical project identity plus current authority readiness; no legacy permission rows. */
|
|
228
|
+
exports.PERMS_QUERY_FULL = `query TaskPermsFull($orgId: Bytes!) {
|
|
229
|
+
organization(id: $orgId) {
|
|
230
|
+
id membershipAuthority { id isRouterBound cutoverAt }
|
|
231
|
+
taskManager { id creatorHatIds organizerHatIds projects(where: { deleted: false }, first: 1000) { id title } }
|
|
157
232
|
}
|
|
158
|
-
`;
|
|
159
|
-
/** Tier 1: older schema — no global perms/organizer hats; boolean per-project flags. */
|
|
160
|
-
exports.PERMS_QUERY_LEGACY = `
|
|
161
|
-
query TaskPermsLegacy($orgId: Bytes!) {
|
|
162
|
-
organization(id: $orgId) {
|
|
163
|
-
id
|
|
164
|
-
taskManager {
|
|
165
|
-
id
|
|
166
|
-
creatorHatIds
|
|
167
|
-
projects(where: { deleted: false }, first: 50) {
|
|
168
|
-
id
|
|
169
|
-
title
|
|
170
|
-
rolePermissions { hatId canCreate canClaim canReview canAssign }
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
`;
|
|
176
|
-
/** Tier array for `queryWithFieldFallback`, matching the CLI's order. */
|
|
233
|
+
}`;
|
|
177
234
|
function taskPermsTiers(orgId) {
|
|
178
|
-
return [
|
|
179
|
-
{ query: exports.PERMS_QUERY_FULL, variables: { orgId } },
|
|
180
|
-
{ query: exports.PERMS_QUERY_LEGACY, variables: { orgId } },
|
|
181
|
-
];
|
|
235
|
+
return [{ query: exports.PERMS_QUERY_FULL, variables: { orgId } }];
|
|
182
236
|
}
|
|
183
237
|
/**
|
|
184
238
|
* Global + per-project permission masks. Port of the read at
|
|
185
239
|
* src/commands/task/perms.ts:175 (`pop task perms show`).
|
|
186
240
|
*/
|
|
187
241
|
async function fetchTaskPerms(client, orgId, chainId) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
242
|
+
const data = await client.query(exports.PERMS_QUERY_FULL, { orgId }, chainId);
|
|
243
|
+
if (!(0, authority_1.isAuthorityReady)(data.organization))
|
|
244
|
+
return { data: { organization: null }, tierIndex: 0 };
|
|
245
|
+
const tm = data.organization.taskManager;
|
|
246
|
+
if (!tm)
|
|
247
|
+
return { data, tierIndex: 0 };
|
|
248
|
+
const rows = (await (0, authority_1.readAuthorityRows)(client, orgId, authority_1.FETCH_AUTHORITY_PERMS, 'permRows', chainId))
|
|
249
|
+
.filter(row => row.exists && row.permKey.toLowerCase() === authority_2.AUTHORITY_KEYS.TM_PERMS.toLowerCase());
|
|
250
|
+
const global = new Map(rows.filter(row => row.ctx.toLowerCase() === ethers_1.ethers.constants.HashZero).map(row => [row.subject.subjectId, row]));
|
|
251
|
+
// TaskManager consumes the low uint8 of the authority's OR-mask result.
|
|
252
|
+
const mask = (row) => row ? ethers_1.ethers.BigNumber.from(row.value).and(255).toNumber() : 0;
|
|
253
|
+
const projectRows = (pid) => {
|
|
254
|
+
const ctx = (0, authority_2.projectContext)((0, encoding_1.parseProjectId)(pid)).toLowerCase();
|
|
255
|
+
const scoped = new Map(rows.filter(row => row.ctx.toLowerCase() === ctx).map(row => [row.subject.subjectId, row]));
|
|
256
|
+
return [...new Set([...global.keys(), ...scoped.keys()])].map(subjectId => {
|
|
257
|
+
const row = scoped.get(subjectId);
|
|
258
|
+
return { hatId: subjectId, subjectId, mask: row ? mask(row) | (row.inheritGlobal ? mask(global.get(subjectId)) : 0) : mask(global.get(subjectId)) };
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
tm.globalRolePermissions = [...global.entries()].map(([subjectId, row]) => ({ hatId: subjectId, subjectId, mask: mask(row) }));
|
|
262
|
+
tm.projects = tm.projects.map((p) => ({ ...p, rolePermissions: projectRows(p.id) }));
|
|
263
|
+
return { data, tierIndex: 0 };
|
|
205
264
|
}
|
|
206
265
|
/**
|
|
207
266
|
* Resolve --project-style input (bytes32 hex, subgraph composite ID, or title)
|